* @param string $type
* @param Request $request
* @return \Illuminate\Http\JsonResponse
+ * @throws \Exception
*/
public function uploadByType($type, Request $request)
{
$this->validate($request, [
'file' => 'is_image'
]);
+ // TODO - Restrict & validate types
$imageUpload = $request->file('file');
try {
- $uploadedTo = $request->filled('uploaded_to') ? $request->get('uploaded_to') : 0;
+ $uploadedTo = $request->get('uploaded_to', 0);
$image = $this->imageRepo->saveNew($imageUpload, $type, $uploadedTo);
} catch (ImageUploadException $e) {
return response($e->getMessage(), 500);
return response()->json($image);
}
+ /**
+ * Upload a drawing to the system.
+ * @param Request $request
+ * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
+ */
+ public function uploadDrawing(Request $request)
+ {
+ $this->validate($request, [
+ 'image' => 'required|string',
+ 'uploaded_to' => 'required|integer'
+ ]);
+ $this->checkPermission('image-create-all');
+ $imageBase64Data = $request->get('image');
+
+ try {
+ $uploadedTo = $request->get('uploaded_to', 0);
+ $image = $this->imageRepo->saveDrawing($imageBase64Data, $uploadedTo);
+ } catch (ImageUploadException $e) {
+ return response($e->getMessage(), 500);
+ }
+
+ return response()->json($image);
+ }
+
+ /**
+ * Replace the data content of a drawing.
+ * @param string $id
+ * @param Request $request
+ * @return \Illuminate\Contracts\Routing\ResponseFactory|\Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response
+ */
+ public function replaceDrawing(string $id, Request $request)
+ {
+ $this->validate($request, [
+ 'image' => 'required|string'
+ ]);
+ $this->checkPermission('image-create-all');
+
+ $imageBase64Data = $request->get('image');
+ $image = $this->imageRepo->getById($id);
+ $this->checkOwnablePermission('image-update', $image);
+
+ try {
+ $image = $this->imageRepo->replaceDrawingContent($image, $imageBase64Data);
+ } catch (ImageUploadException $e) {
+ return response($e->getMessage(), 500);
+ }
+
+ return response()->json($image);
+ }
+
+ /**
+ * Get the content of an image based64 encoded.
+ * @param $id
+ * @return \Illuminate\Http\JsonResponse|mixed
+ */
+ public function getBase64Image($id)
+ {
+ $image = $this->imageRepo->getById($id);
+ $imageData = $this->imageRepo->getImageData($image);
+ if ($imageData === null) {
+ return $this->jsonError("Image data could not be found");
+ }
+ return response()->json([
+ 'content' => base64_encode($imageData)
+ ]);
+ }
+
/**
* Generate a sized thumbnail for an image.
* @param $id
* @param $height
* @param $crop
* @return \Illuminate\Http\JsonResponse
+ * @throws ImageUploadException
+ * @throws \Exception
*/
public function getThumbnail($id, $width, $height, $crop)
{
* @param integer $imageId
* @param Request $request
* @return \Illuminate\Http\JsonResponse
+ * @throws ImageUploadException
+ * @throws \Exception
*/
public function update($imageId, Request $request)
{
* @param string $type
* @param int $uploadedTo
* @return Image
+ * @throws \BookStack\Exceptions\ImageUploadException
+ * @throws \Exception
*/
public function saveNew(UploadedFile $uploadFile, $type, $uploadedTo = 0)
{
return $image;
}
+ /**
+ * Save a drawing the the database;
+ * @param string $base64Uri
+ * @param int $uploadedTo
+ * @return Image
+ * @throws \BookStack\Exceptions\ImageUploadException
+ */
+ public function saveDrawing(string $base64Uri, int $uploadedTo)
+ {
+ $name = 'Drawing-' . user()->getShortName(40) . '-' . strval(time()) . '.png';
+ $image = $this->imageService->saveNewFromBase64Uri($base64Uri, $name, 'drawing', $uploadedTo);
+ return $image;
+ }
+
+ /**
+ * Replace the image content of a drawing.
+ * @param Image $image
+ * @param string $base64Uri
+ * @return Image
+ * @throws \BookStack\Exceptions\ImageUploadException
+ */
+ public function replaceDrawingContent(Image $image, string $base64Uri)
+ {
+ return $this->imageService->replaceImageDataFromBase64Uri($image, $base64Uri);
+ }
+
/**
* Update the details of an image via an array of properties.
* @param Image $image
* @param array $updateDetails
* @return Image
+ * @throws \BookStack\Exceptions\ImageUploadException
+ * @throws \Exception
*/
public function updateImageDetails(Image $image, $updateDetails)
{
/**
* Load thumbnails onto an image object.
* @param Image $image
+ * @throws \BookStack\Exceptions\ImageUploadException
+ * @throws \Exception
*/
private function loadThumbs(Image $image)
{
* @param int $height
* @param bool $keepRatio
* @return string
+ * @throws \BookStack\Exceptions\ImageUploadException
+ * @throws \Exception
*/
public function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false)
{
}
}
+ /**
+ * Get the raw image data from an Image.
+ * @param Image $image
+ * @return null|string
+ */
+ public function getImageData(Image $image)
+ {
+ try {
+ return $this->imageService->getImageData($image);
+ } catch (\Exception $exception) {
+ return null;
+ }
+ }
+
}
\ No newline at end of file
return $this->saveNew($imageName, $imageData, $type, $uploadedTo);
}
+ /**
+ * Save a new image from a uri-encoded base64 string of data.
+ * @param string $base64Uri
+ * @param string $name
+ * @param string $type
+ * @param int $uploadedTo
+ * @return Image
+ * @throws ImageUploadException
+ */
+ public function saveNewFromBase64Uri(string $base64Uri, string $name, string $type, $uploadedTo = 0)
+ {
+ $splitData = explode(';base64,', $base64Uri);
+ if (count($splitData) < 2) {
+ throw new ImageUploadException("Invalid base64 image data provided");
+ }
+ $data = base64_decode($splitData[1]);
+ return $this->saveNew($name, $data, $type, $uploadedTo);
+ }
+
+ /**
+ * Replace the data for an image via a Base64 encoded string.
+ * @param Image $image
+ * @param string $base64Uri
+ * @return Image
+ * @throws ImageUploadException
+ */
+ public function replaceImageDataFromBase64Uri(Image $image, string $base64Uri)
+ {
+ $splitData = explode(';base64,', $base64Uri);
+ if (count($splitData) < 2) {
+ throw new ImageUploadException("Invalid base64 image data provided");
+ }
+ $data = base64_decode($splitData[1]);
+ $storage = $this->getStorage();
+
+ try {
+ $storage->put($image->path, $data);
+ } catch (Exception $e) {
+ throw new ImageUploadException(trans('errors.path_not_writable', ['filePath' => $image->path]));
+ }
+
+ return $image;
+ }
+
/**
* Gets an image from url and saves it to the database.
* @param $url
return $this->getPublicUrl($thumbFilePath);
}
+ /**
+ * Get the raw data content from an image.
+ * @param Image $image
+ * @return string
+ * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
+ */
+ public function getImageData(Image $image)
+ {
+ $imagePath = $this->getPath($image);
+ $storage = $this->getStorage();
+ return $storage->get($imagePath);
+ }
+
/**
* Destroys an Image object along with its files and thumbnails.
* @param Image $image
let formData = new FormData();
formData.append('file', file, remoteFilename);
- return window.$http.post('/images/gallery/upload', formData).then(resp => (resp.data));
+ return window.$http.post(window.baseUrl('/images/gallery/upload'), formData).then(resp => (resp.data));
}
function registerEditorShortcuts(editor) {
}
codePlugin();
+function drawIoPlugin() {
+
+ const drawIoUrl = 'https://www.draw.io/?embed=1&ui=atlas&spin=1&proto=json';
+ let iframe = null;
+ let pageEditor = null;
+ let currentNode = null;
+
+ function isDrawing(node) {
+ return node.hasAttribute('drawio-diagram');
+ }
+
+ function showDrawingEditor(mceEditor, selectedNode = null) {
+ pageEditor = mceEditor;
+ currentNode = selectedNode;
+ iframe = document.createElement('iframe');
+ iframe.setAttribute('frameborder', '0');
+ window.addEventListener('message', drawReceive);
+ iframe.setAttribute('src', drawIoUrl);
+ iframe.setAttribute('class', 'fullscreen');
+ iframe.style.backgroundColor = '#FFFFFF';
+ document.body.appendChild(iframe);
+ }
+
+ function drawReceive(event) {
+ if (!event.data || event.data.length < 1) return;
+ let message = JSON.parse(event.data);
+ if (message.event === 'init') {
+ drawEventInit();
+ } else if (message.event === 'exit') {
+ drawEventClose();
+ } else if (message.event === 'save') {
+ drawEventSave(message);
+ } else if (message.event === 'export') {
+ drawEventExport(message);
+ }
+ }
+
+ function updateContent(pngData) {
+ let id = "image-" + Math.random().toString(16).slice(2);
+ let loadingImage = window.baseUrl('/loading.gif');
+ let data = {
+ image: pngData,
+ uploaded_to: Number(document.getElementById('page-editor').getAttribute('page-id'))
+ };
+
+ // Handle updating an existing image
+ if (currentNode) {
+ console.log(currentNode);
+ drawEventClose();
+ let imgElem = currentNode.querySelector('img');
+ let drawingId = currentNode.getAttribute('drawio-diagram');
+ window.$http.put(window.baseUrl(`/images/drawing/upload/${drawingId}`), data).then(resp => {
+ pageEditor.dom.setAttrib(imgElem, 'src', `${resp.data.url}?updated=${Date.now()}`);
+ }).catch(err => {
+ window.$events.emit('error', trans('errors.image_upload_error'));
+ console.log(err);
+ });
+ return;
+ }
+
+ setTimeout(() => {
+ pageEditor.insertContent(`<div drawio-diagram contenteditable="false"><img src="${loadingImage}" id="${id}"></div>`);
+ drawEventClose();
+ window.$http.post(window.baseUrl('/images/drawing/upload'), data).then(resp => {
+ pageEditor.dom.setAttrib(id, 'src', resp.data.url);
+ pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', resp.data.id);
+ }).catch(err => {
+ pageEditor.dom.remove(id);
+ window.$events.emit('error', trans('errors.image_upload_error'));
+ console.log(err);
+ });
+ }, 5);
+ }
+
+ function drawEventExport(message) {
+ updateContent(message.data);
+ }
+
+ function drawEventSave(message) {
+ drawPostMessage({action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing'});
+ }
+
+ function drawEventInit() {
+ if (!currentNode) {
+ drawPostMessage({action: 'load', autosave: 1, xml: ''});
+ return;
+ }
+
+ let imgElem = currentNode.querySelector('img');
+ let drawingId = currentNode.getAttribute('drawio-diagram');
+ $http.get(window.baseUrl(`/images/base64/${drawingId}`)).then(resp => {
+ let xml = `data:image/png;base64,${resp.data.content}`;
+ drawPostMessage({action: 'load', autosave: 1, xml});
+ });
+ }
+
+ function drawEventClose() {
+ window.removeEventListener('message', drawReceive);
+ if (iframe) document.body.removeChild(iframe);
+ }
+
+ function drawPostMessage(data) {
+ iframe.contentWindow.postMessage(JSON.stringify(data), '*');
+ }
+
+ window.tinymce.PluginManager.add('drawio', function(editor, url) {
+
+ let $ = editor.$;
+
+ editor.addCommand('drawio', () => {
+ showDrawingEditor(editor);
+ });
+
+ editor.addButton('drawio', {
+ text: 'Drawing',
+ icon: false,
+ cmd: 'drawio'
+ });
+
+ editor.on('dblclick', event => {
+ let selectedNode = editor.selection.getNode();
+ if (!isDrawing(selectedNode)) return;
+ showDrawingEditor(editor, selectedNode);
+ });
+
+ editor.on('SetContent', function () {
+ let drawings = $('body > div[drawio-diagram]');
+ if (!drawings.length) return;
+
+ editor.undoManager.transact(function () {
+ drawings.each((index, elem) => {
+ elem.setAttribute('contenteditable', 'false');
+ });
+ });
+ });
+
+ });
+}
+drawIoPlugin();
+
window.tinymce.PluginManager.add('customhr', function (editor) {
editor.addCommand('InsertHorizontalRule', function () {
let hrElem = document.createElement('hr');
statusbar: false,
menubar: false,
paste_data_images: false,
- extended_valid_elements: 'pre[*]',
+ extended_valid_elements: 'pre[*],svg[*],div[drawio-diagram]',
automatic_uploads: false,
- valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre]",
- plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor",
+ valid_children: "-div[p|h1|h2|h3|h4|h5|h6|blockquote],+div[pre],+div[img]",
+ plugins: "image table textcolor paste link autolink fullscreen imagetools code customhr autosave lists codeeditor drawio",
imagetools_toolbar: 'imageoptions',
- toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen",
+ toolbar: "undo redo | styleselect | bold italic underline strikethrough superscript subscript | forecolor backcolor | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | table image-insert link hr | removeformat code fullscreen drawio",
content_style: "body {padding-left: 15px !important; padding-right: 15px !important; margin:0!important; margin-left:auto!important;margin-right:auto!important;}",
style_formats: [
{title: "Header Large", format: "h2"},
input {
width: 100%;
}
+}
+
+.fullscreen {
+ border:0;
+ position:fixed;
+ top:0;
+ left:0;
+ right:0;
+ bottom:0;
+ width:100%;
+ height:100%;
+ z-index: 150;
}
\ No newline at end of file
Route::get('/user/all/{page}', 'ImageController@getAllForUserType');
// Standard get, update and deletion for all types
Route::get('/thumb/{id}/{width}/{height}/{crop}', 'ImageController@getThumbnail');
+ Route::get('/base64/{id}', 'ImageController@getBase64Image');
Route::put('/update/{imageId}', 'ImageController@update');
+ Route::post('/drawing/upload', 'ImageController@uploadDrawing');
+ Route::put('/drawing/upload/{id}', 'ImageController@replaceDrawing');
Route::post('/{type}/upload', 'ImageController@uploadByType');
Route::get('/{type}/all', 'ImageController@getAllByType');
Route::get('/{type}/all/{page}', 'ImageController@getAllByType');
Route::get('/{type}/search/{page}', 'ImageController@searchByType');
Route::get('/gallery/{filter}/{page}', 'ImageController@getGalleryFiltered');
- Route::delete('/{imageId}', 'ImageController@destroy');
+ Route::delete('/{id}', 'ImageController@destroy');
});
// Attachments routes