diff --git a/.env.example b/.env.example index d3cf77234..6d9189b5d 100644 --- a/.env.example +++ b/.env.example @@ -14,12 +14,23 @@ CACHE_DRIVER=file SESSION_DRIVER=file QUEUE_DRIVER=sync +# Storage +STORAGE_TYPE=local +# Amazon S3 Config +STORAGE_S3_KEY=false +STORAGE_S3_SECRET=false +STORAGE_S3_REGION=false +STORAGE_S3_BUCKET=false +# Storage URL +# Used to prefix image urls for when using custom domains/cdns +STORAGE_URL=false + # Social Authentication information. Defaults as off. GITHUB_APP_ID=false GITHUB_APP_SECRET=false GOOGLE_APP_ID=false GOOGLE_APP_SECRET=false -# URL for social login redirects, NO TRAILING SLASH +# URL used for social login redirects, NO TRAILING SLASH APP_URL=http://bookstack.dev # Mail settings diff --git a/app/Http/Controllers/ImageController.php b/app/Http/Controllers/ImageController.php index fd7901570..7e7fedfde 100644 --- a/app/Http/Controllers/ImageController.php +++ b/app/Http/Controllers/ImageController.php @@ -2,6 +2,7 @@ namespace BookStack\Http\Controllers; +use BookStack\Repos\ImageRepo; use Illuminate\Filesystem\Filesystem as File; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -14,16 +15,19 @@ class ImageController extends Controller { protected $image; protected $file; + protected $imageRepo; /** * ImageController constructor. - * @param Image $image - * @param File $file + * @param Image $image + * @param File $file + * @param ImageRepo $imageRepo */ - public function __construct(Image $image, File $file) + public function __construct(Image $image, File $file, ImageRepo $imageRepo) { $this->image = $image; $this->file = $file; + $this->imageRepo = $imageRepo; parent::__construct(); } @@ -33,108 +37,31 @@ class ImageController extends Controller * @param int $page * @return \Illuminate\Http\JsonResponse */ - public function getAll($page = 0) + public function getAllGallery($page = 0) { - $pageSize = 30; - $images = $this->image->orderBy('created_at', 'desc') - ->skip($page * $pageSize)->take($pageSize)->get(); - foreach ($images as $image) { - $this->loadSizes($image); - } - $hasMore = $this->image->orderBy('created_at', 'desc') - ->skip(($page + 1) * $pageSize)->take($pageSize)->count() > 0; - return response()->json([ - 'images' => $images, - 'hasMore' => $hasMore - ]); + $imgData = $this->imageRepo->getAllGallery($page); + return response()->json($imgData); } - /** - * Loads the standard thumbnail sizes for an image. - * @param Image $image - */ - private function loadSizes(Image $image) - { - $image->thumbnail = $this->getThumbnail($image, 150, 150); - $image->display = $this->getThumbnail($image, 840, 0, true); - } - - /** - * Get the thumbnail for an image. - * If $keepRatio is true only the width will be used. - * @param $image - * @param int $width - * @param int $height - * @param bool $keepRatio - * @return string - */ - public function getThumbnail($image, $width = 220, $height = 220, $keepRatio = false) - { - $explodedPath = explode('/', $image->url); - $dirPrefix = $keepRatio ? 'scaled-' : 'thumbs-'; - array_splice($explodedPath, 4, 0, [$dirPrefix . $width . '-' . $height]); - $thumbPath = implode('/', $explodedPath); - $thumbFilePath = public_path() . $thumbPath; - - // Return the thumbnail url path if already exists - if (file_exists($thumbFilePath)) { - return $thumbPath; - } - - // Otherwise create the thumbnail - $thumb = ImageTool::make(public_path() . $image->url); - if($keepRatio) { - $thumb->resize($width, null, function ($constraint) { - $constraint->aspectRatio(); - $constraint->upsize(); - }); - } else { - $thumb->fit($width, $height); - } - - // Create thumbnail folder if it does not exist - if (!file_exists(dirname($thumbFilePath))) { - mkdir(dirname($thumbFilePath), 0775, true); - } - - //Save Thumbnail - $thumb->save($thumbFilePath); - return $thumbPath; - } /** * Handles image uploads for use on pages. * @param Request $request * @return \Illuminate\Http\JsonResponse */ - public function upload(Request $request) + public function uploadGallery(Request $request) { $this->checkPermission('image-create'); $this->validate($request, [ 'file' => 'image|mimes:jpeg,gif,png' ]); - $imageUpload = $request->file('file'); - $name = str_replace(' ', '-', $imageUpload->getClientOriginalName()); - $storageName = substr(sha1(time()), 0, 10) . '-' . $name; - $imagePath = '/uploads/images/' . Date('Y-m-M') . '/'; - $storagePath = public_path() . $imagePath; - $fullPath = $storagePath . $storageName; - while (file_exists($fullPath)) { - $storageName = substr(sha1(rand()), 0, 3) . $storageName; - $fullPath = $storagePath . $storageName; - } - $imageUpload->move($storagePath, $storageName); - // Create and save image object - $this->image->name = $name; - $this->image->url = $imagePath . $storageName; - $this->image->created_by = auth()->user()->id; - $this->image->updated_by = auth()->user()->id; - $this->image->save(); - $this->loadSizes($this->image); - return response()->json($this->image); + $imageUpload = $request->file('file'); + $image = $this->imageRepo->saveNew($imageUpload, 'gallery'); + return response()->json($image); } + /** * Update image details * @param $imageId @@ -147,13 +74,12 @@ class ImageController extends Controller $this->validate($request, [ 'name' => 'required|min:2|string' ]); - $image = $this->image->findOrFail($imageId); - $image->fill($request->all()); - $image->save(); - $this->loadSizes($image); - return response()->json($this->image); + $image = $this->imageRepo->getById($imageId); + $image = $this->imageRepo->updateImageDetails($image, $request->all()); + return response()->json($image); } + /** * Deletes an image and all thumbnail/image files * @param PageRepo $pageRepo @@ -164,41 +90,18 @@ class ImageController extends Controller public function destroy(PageRepo $pageRepo, Request $request, $id) { $this->checkPermission('image-delete'); - $image = $this->image->findOrFail($id); + $image = $this->imageRepo->getById($id); // Check if this image is used on any pages - $pageSearch = $pageRepo->searchForImage($image->url); $isForced = ($request->has('force') && ($request->get('force') === 'true') || $request->get('force') === true); - if ($pageSearch !== false && !$isForced) { - return response()->json($pageSearch, 400); - } - - // Delete files - $folder = public_path() . dirname($image->url); - $fileName = basename($image->url); - - // Delete thumbnails - foreach (glob($folder . '/*') as $file) { - if (is_dir($file)) { - $thumbName = $file . '/' . $fileName; - if (file_exists($file)) { - unlink($thumbName); - } - // Remove thumb folder if empty - if (count(glob($file . '/*')) === 0) { - rmdir($file); - } + if (!$isForced) { + $pageSearch = $pageRepo->searchForImage($image->url); + if ($pageSearch !== false) { + return response()->json($pageSearch, 400); } } - // Delete file and database entry - unlink($folder . '/' . $fileName); - $image->delete(); - - // Delete parent folder if empty - if (count(glob($folder . '/*')) === 0) { - rmdir($folder); - } + $this->imageRepo->destroyImage($image); return response()->json('Image Deleted'); } diff --git a/app/Http/routes.php b/app/Http/routes.php index e605dbee1..5b1e245a1 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -45,8 +45,6 @@ Route::group(['middleware' => 'auth'], function () { }); - // Uploads - Route::post('/upload/image', 'ImageController@upload'); // Users Route::get('/users', 'UserController@index'); @@ -58,10 +56,13 @@ Route::group(['middleware' => 'auth'], function () { Route::delete('/users/{id}', 'UserController@destroy'); // Image routes - Route::get('/images/all', 'ImageController@getAll'); - Route::put('/images/update/{imageId}', 'ImageController@update'); - Route::delete('/images/{imageId}', 'ImageController@destroy'); - Route::get('/images/all/{page}', 'ImageController@getAll'); + Route::group(['prefix' => 'images'], function() { + Route::get('/gallery/all', 'ImageController@getAllGallery'); + Route::get('/gallery/all/{page}', 'ImageController@getAllGallery'); + Route::post('/gallery/upload', 'ImageController@uploadGallery'); + Route::put('/update/{imageId}', 'ImageController@update'); + Route::delete('/{imageId}', 'ImageController@destroy'); + }); // Links Route::get('/link/{id}', 'PageController@redirectFromLink'); diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index 110426bbd..469fdb31d 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -77,6 +77,12 @@ class BookRepo return Views::getUserRecentlyViewed($count, $page, $this->book); } + /** + * Gets the most viewed books. + * @param int $count + * @param int $page + * @return mixed + */ public function getPopular($count = 10, $page = 0) { return Views::getPopular($count, $page, $this->book); diff --git a/app/Repos/ImageRepo.php b/app/Repos/ImageRepo.php new file mode 100644 index 000000000..3dce7e94b --- /dev/null +++ b/app/Repos/ImageRepo.php @@ -0,0 +1,265 @@ +image = $image; + $this->imageTool = $imageTool; + $this->fileSystem = $fileSystem; + $this->cache = $cache; + } + + + /** + * Get an image with the given id. + * @param $id + * @return mixed + */ + public function getById($id) + { + return $this->image->findOrFail($id); + } + + + /** + * Get all images for the standard gallery view that's used for + * adding images to shared content such as pages. + * @param int $page + * @param int $pageSize + * @return array + */ + public function getAllGallery($page = 0, $pageSize = 24) + { + $images = $this->image->where('type', '=', 'gallery') + ->orderBy('created_at', 'desc')->skip($pageSize * $page)->take($pageSize + 1)->get(); + $hasMore = count($images) > $pageSize; + + $returnImages = $images->take(24); + $returnImages->each(function ($image) { + $this->loadThumbs($image); + }); + + return [ + 'images' => $returnImages, + 'hasMore' => $hasMore + ]; + } + + /** + * Save a new image into storage and return the new image. + * @param UploadedFile $uploadFile + * @param string $type + * @return Image + */ + public function saveNew(UploadedFile $uploadFile, $type) + { + $storage = $this->getStorage(); + $secureUploads = Setting::get('app-secure-images'); + $imageName = str_replace(' ', '-', $uploadFile->getClientOriginalName()); + + if ($secureUploads) $imageName = str_random(16) . '-' . $imageName; + + $imagePath = '/uploads/images/' . $type . '/' . Date('Y-m-M') . '/'; + while ($storage->exists($imagePath . $imageName)) { + $imageName = str_random(3) . $imageName; + } + $fullPath = $imagePath . $imageName; + + $storage->put($fullPath, file_get_contents($uploadFile->getRealPath())); + + $userId = auth()->user()->id; + $image = $this->image->forceCreate([ + 'name' => $imageName, + 'path' => $fullPath, + 'url' => $this->getPublicUrl($fullPath), + 'type' => $type, + 'created_by' => $userId, + 'updated_by' => $userId + ]); + + $this->loadThumbs($image); + return $image; + } + + /** + * Update the details of an image via an array of properties. + * @param Image $image + * @param array $updateDetails + * @return Image + */ + public function updateImageDetails(Image $image, $updateDetails) + { + $image->fill($updateDetails); + $image->save(); + $this->loadThumbs($image); + return $image; + } + + + /** + * Destroys an Image object along with its files and thumbnails. + * @param Image $image + * @return bool + */ + public function destroyImage(Image $image) + { + $storage = $this->getStorage(); + + $imageFolder = dirname($image->path); + $imageFileName = basename($image->path); + $allImages = collect($storage->allFiles($imageFolder)); + + $imagesToDelete = $allImages->filter(function ($imagePath) use ($imageFileName) { + $expectedIndex = strlen($imagePath) - strlen($imageFileName); + return strpos($imagePath, $imageFileName) === $expectedIndex; + }); + + $storage->delete($imagesToDelete->all()); + + // Cleanup of empty folders + foreach ($storage->directories($imageFolder) as $directory) { + if ($this->isFolderEmpty($directory)) $storage->deleteDirectory($directory); + } + if ($this->isFolderEmpty($imageFolder)) $storage->deleteDirectory($imageFolder); + + $image->delete(); + return true; + } + + /** + * Check whether or not a folder is empty. + * @param $path + * @return int + */ + private function isFolderEmpty($path) + { + $files = $this->getStorage()->files($path); + $folders = $this->getStorage()->directories($path); + return count($files) === 0 && count($folders) === 0; + } + + /** + * Load thumbnails onto an image object. + * @param Image $image + */ + private function loadThumbs(Image $image) + { + $image->thumbs = [ + 'gallery' => $this->getThumbnail($image, 150, 150), + 'display' => $this->getThumbnail($image, 840, 0, true) + ]; + } + + /** + * Get the thumbnail for an image. + * If $keepRatio is true only the width will be used. + * Checks the cache then storage to avoid creating / accessing the filesystem on every check. + * + * @param Image $image + * @param int $width + * @param int $height + * @param bool $keepRatio + * @return string + */ + private function getThumbnail(Image $image, $width = 220, $height = 220, $keepRatio = false) + { + $thumbDirName = '/' . ($keepRatio ? 'scaled-' : 'thumbs-') . $width . '-' . $height . '/'; + $thumbFilePath = dirname($image->path) . $thumbDirName . basename($image->path); + + if ($this->cache->has('images-' . $image->id . '-' . $thumbFilePath) && $this->cache->get('images-' . $thumbFilePath)) { + return $this->getPublicUrl($thumbFilePath); + } + + $storage = $this->getStorage(); + + if ($storage->exists($thumbFilePath)) { + return $this->getPublicUrl($thumbFilePath); + } + + // Otherwise create the thumbnail + $thumb = $this->imageTool->make($storage->get($image->path)); + if ($keepRatio) { + $thumb->resize($width, null, function ($constraint) { + $constraint->aspectRatio(); + $constraint->upsize(); + }); + } else { + $thumb->fit($width, $height); + } + + $thumbData = (string)$thumb->encode(); + $storage->put($thumbFilePath, $thumbData); + $this->cache->put('images-' . $image->id . '-' . $thumbFilePath, $thumbFilePath, 60 * 72); + + return $this->getPublicUrl($thumbFilePath); + } + + /** + * Gets a public facing url for an image by checking relevant environment variables. + * @param $filePath + * @return string + */ + private function getPublicUrl($filePath) + { + if ($this->storageUrl === null) { + $storageUrl = env('STORAGE_URL'); + + // Get the standard public s3 url if s3 is set as storage type + if ($storageUrl == false && env('STORAGE_TYPE') === 's3') { + $storageDetails = config('filesystems.disks.s3'); + $storageUrl = 'https://s3-' . $storageDetails['region'] . '.amazonaws.com/' . $storageDetails['bucket'] . $filePath; + } + + $this->storageUrl = $storageUrl; + } + + return ($this->storageUrl == false ? '' : rtrim($this->storageUrl, '/')) . $filePath; + } + + + /** + * Get the storage that will be used for storing images. + * @return FileSystemInstance + */ + private function getStorage() + { + if ($this->storageInstance !== null) return $this->storageInstance; + + $storageType = env('STORAGE_TYPE'); + $this->storageInstance = $this->fileSystem->disk($storageType); + + return $this->storageInstance; + } + + +} \ No newline at end of file diff --git a/composer.json b/composer.json index 6096cc7e4..7865be636 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,8 @@ "intervention/image": "^2.3", "laravel/socialite": "^2.0", "barryvdh/laravel-ide-helper": "^2.1", - "barryvdh/laravel-debugbar": "^2.0" + "barryvdh/laravel-debugbar": "^2.0", + "league/flysystem-aws-s3-v3": "^1.0" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/composer.lock b/composer.lock index 595186a17..23d2d50b6 100644 --- a/composer.lock +++ b/composer.lock @@ -4,9 +4,87 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "c2067be65a036c540976c649dfd3763a", - "content-hash": "6581b2cca64df1f4d3df219552fbfd78", + "hash": "19725116631f01881caafa33052eecb9", + "content-hash": "f1dbd776f0ae13ec99e4e6d99510cd8e", "packages": [ + { + "name": "aws/aws-sdk-php", + "version": "3.11.4", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2", + "reference": "2524c78e0fa1ed049719b8b6b0696f0b6dfb1ca2", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "~5.3|~6.0.1|~6.1", + "guzzlehttp/promises": "~1.0", + "guzzlehttp/psr7": "~1.0", + "mtdowling/jmespath.php": "~2.2", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-json": "*", + "ext-openssl": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "ext-spl": "*", + "nette/neon": "^2.3", + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Aws\\": "src/" + }, + "files": [ + "src/functions.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "time": "2015-12-04 01:19:53" + }, { "name": "barryvdh/laravel-debugbar", "version": "v2.0.6", @@ -1074,6 +1152,53 @@ ], "time": "2015-09-30 22:26:59" }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "1.0.9", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/595e24678bf78f8107ebc9355d8376ae0eb712c6", + "reference": "595e24678bf78f8107ebc9355d8376ae0eb712c6", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.0.0", + "league/flysystem": "~1.0", + "php": ">=5.5.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "~1.0.1", + "phpspec/phpspec": "^2.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3v3\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Flysystem adapter for the AWS S3 SDK v3.x", + "time": "2015-11-19 08:44:16" + }, { "name": "league/oauth1-client", "version": "1.6.1", @@ -1314,6 +1439,61 @@ ], "time": "2015-01-11 23:07:46" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "a7d99d0c836e69d27b7bfca1d33ca2759fba3289" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a7d99d0c836e69d27b7bfca1d33ca2759fba3289", + "reference": "a7d99d0c836e69d27b7bfca1d33ca2759fba3289", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0-dev" + } + }, + "autoload": { + "psr-4": { + "JmesPath\\": "src/" + }, + "files": [ + "src/JmesPath.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "time": "2015-05-27 17:21:31" + }, { "name": "nesbot/carbon", "version": "1.21.0", diff --git a/config/filesystems.php b/config/filesystems.php index 3fffcf0a2..5fd3df6df 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -45,7 +45,7 @@ return [ 'local' => [ 'driver' => 'local', - 'root' => storage_path('app'), + 'root' => public_path(), ], 'ftp' => [ @@ -64,10 +64,10 @@ return [ 's3' => [ 'driver' => 's3', - 'key' => 'your-key', - 'secret' => 'your-secret', - 'region' => 'your-region', - 'bucket' => 'your-bucket', + 'key' => env('STORAGE_S3_KEY', 'your-key'), + 'secret' => env('STORAGE_S3_SECRET', 'your-secret'), + 'region' => env('STORAGE_S3_REGION', 'your-region'), + 'bucket' => env('STORAGE_S3_BUCKET', 'your-bucket'), ], 'rackspace' => [ diff --git a/database/migrations/2015_12_07_195238_add_image_upload_types.php b/database/migrations/2015_12_07_195238_add_image_upload_types.php new file mode 100644 index 000000000..eed937611 --- /dev/null +++ b/database/migrations/2015_12_07_195238_add_image_upload_types.php @@ -0,0 +1,41 @@ +string('path', 400); + $table->string('type')->index(); + }); + + Image::all()->each(function($image) { + $image->path = $image->url; + $image->type = 'gallery'; + $image->save(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('images', function (Blueprint $table) { + $table->dropColumn('type'); + $table->dropColumn('path'); + }); + + } +} diff --git a/resources/assets/js/components/image-manager.vue b/resources/assets/js/components/image-manager.vue index b1c805f8f..bb691760b 100644 --- a/resources/assets/js/components/image-manager.vue +++ b/resources/assets/js/components/image-manager.vue @@ -7,7 +7,7 @@
@@ -88,7 +88,7 @@ methods: { fetchData: function () { var _this = this; - this.$http.get('/images/all/' + _this.page, function (data) { + this.$http.get('/images/gallery/all/' + _this.page, function (data) { _this.images = _this.images.concat(data.images); _this.hasMore = data.hasMore; _this.page++; @@ -98,7 +98,7 @@ setupDropZone: function () { var _this = this; var dropZone = new Dropzone(_this.$els.dropZone, { - url: '/upload/image', + url: '/images/gallery/upload', init: function () { var dz = this; this.on("sending", function (file, xhr, data) { @@ -110,8 +110,8 @@ dz.removeFile(file); }); }); - this.on('error', function(file, errorMessage, xhr) { - if(errorMessage.file) { + this.on('error', function (file, errorMessage, xhr) { + if (errorMessage.file) { $(file.previewElement).find('[data-dz-errormessage]').text(errorMessage.file[0]); } console.log(errorMessage); diff --git a/resources/assets/js/pages/page-form.js b/resources/assets/js/pages/page-form.js index f6ded0063..2bf722196 100644 --- a/resources/assets/js/pages/page-form.js +++ b/resources/assets/js/pages/page-form.js @@ -100,7 +100,7 @@ module.exports = { onclick: function() { ImageManager.show(function(image) { var html = ''; - html += ''+image.name+''; + html += ''+image.name+''; html += ''; editor.execCommand('mceInsertContent', false, html); }); diff --git a/resources/views/settings/index.blade.php b/resources/views/settings/index.blade.php index eb59e0a5c..ebf538198 100644 --- a/resources/views/settings/index.blade.php +++ b/resources/views/settings/index.blade.php @@ -23,6 +23,11 @@ +
+ +

For performance reasons, all images are public by default, This option adds a random, hard-to-guess characters in front of image names. Ensure directory indexes are not enabled to prevent easy access.

+ +
@@ -57,7 +62,7 @@
- +

If domain restriction is used then email confirmation will be required and the below value will be ignored.