diff --git a/app/Http/Controllers/DashboardController.php b/app/Http/Controllers/DashboardController.php index 0ab8be7..bb231d1 100644 --- a/app/Http/Controllers/DashboardController.php +++ b/app/Http/Controllers/DashboardController.php @@ -28,14 +28,14 @@ class DashboardController extends Controller { return view('dashboard.pages.home'); } - // View Model Data + // Page to View Model Data public function getView($model) { $model_class = Dashboard::getModel($model, 'view'); if ($model_class != null) { return view('dashboard.pages.view', [ - 'heading' => $model_class::getDashboardHeading($model), + 'heading' => $model_class->getDashboardHeading(), 'column_headings' => $model_class::getDashboardColumnData('headings'), 'model' => $model, 'rows' => $model_class::getDashboardData(), @@ -46,7 +46,7 @@ class DashboardController extends Controller { } } - // Edit List of Model Rows + // Page to Edit List of Model Rows public function getEditList($model) { $model_class = Dashboard::getModel($model, 'edit'); @@ -55,7 +55,7 @@ class DashboardController extends Controller { $data = $model_class::getDashboardData(true); return view('dashboard.pages.edit-list', [ - 'heading' => $model_class::getDashboardHeading($model), + 'heading' => $model_class->getDashboardHeading(), 'model' => $model, 'rows' => $data['rows'], 'paramdisplay' => $data['paramdisplay'], @@ -75,7 +75,7 @@ class DashboardController extends Controller { } } - // Create and Edit Model Item + // Page to Create and Edit Model Item public function getEditItem($model, $id = 'new') { $model_class = Dashboard::getModel($model, 'edit'); @@ -103,7 +103,7 @@ class DashboardController extends Controller { } return view('dashboard.pages.edit-item', [ - 'heading' => $model_class::getDashboardHeading($model), + 'heading' => $model_class->getDashboardHeading(), 'model' => $model, 'id' => $id, 'item' => $item, @@ -296,15 +296,14 @@ class DashboardController extends Controller { if (is_null($item)) { return 'record-access-fail'; - } else if (!$item->userCheck()) { - return 'permission-fail'; } else if ($request->hasFile('file')) { - $directory = base_path() . '/public/uploads/' . $request['model'] . '/img/'; - File::makeDirectory($directory, 0755, true, true); - $image = Image::make($request->file('file')); - $image->save($directory . $request['id'] . '-' . $request['name'] . '.jpg'); - $item->touch(); - return 'success'; + $save_result = $item->saveImage($request['name'], $request->file('file')); + + if ($save_result == 'success') { + $item->touch(); + } + + return $save_result; } else { return 'file-upload-fail'; } @@ -319,8 +318,7 @@ class DashboardController extends Controller { $this->validate($request, [ 'id' => 'required', 'model' => 'required', - 'name' => 'required', - 'ext' => 'required' + 'name' => 'required' ]); $model_class = Dashboard::getModel($request['model'], 'edit'); @@ -330,14 +328,14 @@ class DashboardController extends Controller { if (is_null($item)) { return 'record-access-fail'; - } else if (!$item->userCheck()) { - return 'permission-fail'; } else if ($request->hasFile('file')) { - $directory = base_path() . '/public/uploads/' . $request['model'] . '/files/'; - File::makeDirectory($directory, 0755, true, true); - $request->file('file')->move($directory, $request['id'] . '-' . $request['name'] . '.' . $request['ext']); - $item->touch(); - return 'success'; + $save_result = $item->saveFile($request['name'], $request->file('file')); + + if ($save_result == 'success') { + $item->touch(); + } + + return $save_result; } else { return 'file-upload-fail'; } @@ -365,26 +363,18 @@ class DashboardController extends Controller { return 'permission-fail'; } - // delete the row - $item->delete(); - // delete associated files if they exist foreach ($model_class::$dashboard_columns as $column) { if ($column['type'] == 'image') { - $image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $column['name'] . '.jpg'; - - if (file_exists($image) && !unlink($image)) { - return 'image-delete-fail'; - } + $item->deleteImage($column['name'], false); } else if ($column['type'] == 'file') { - $file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $column['name'] . '.' . $column['ext']; - - if (file_exists($file) && !unlink($file)) { - return 'file-delete-fail'; - } + $item->deleteFile($column['name'], false); } } + // delete the row + $item->delete(); + // update the order of the remaining rows if $dashboard_reorder is true if ($model_class::$dashboard_reorder) { foreach ($model_class::getDashboardData() as $index => $item) { @@ -412,20 +402,13 @@ class DashboardController extends Controller { $model_class = Dashboard::getModel($request['model'], 'edit'); if ($model_class != null) { - $image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $request['name'] . '.jpg'; $item = $model_class::find($request['id']); if (is_null($item)) { return 'record-access-fail'; - } else if (!$item->userCheck()) { - return 'permission-fail'; - } else if (!file_exists($image)) { - return 'image-not-exists-fail'; - } else if (!unlink($image)) { - return 'image-delete-fail'; } - return 'success'; + return $item->deleteImage($request['name'], true); } else { return 'model-access-fail'; } @@ -437,27 +420,19 @@ class DashboardController extends Controller { $this->validate($request, [ 'id' => 'required', 'model' => 'required', - 'name' => 'required', - 'ext' => 'required' + 'name' => 'required' ]); $model_class = Dashboard::getModel($request['model'], 'edit'); if ($model_class != null) { - $file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $request['name'] . '.' . $request['ext']; $item = $model_class::find($request['id']); if (is_null($item)) { return 'record-access-fail'; - } else if (!$item->userCheck()) { - return 'permission-fail'; - } else if (!file_exists($file)) { - return 'file-not-exists-fail'; - } else if (!unlink($file)) { - return 'file-delete-fail'; } - return 'success'; + return $item->deleteFile($request['name'], true); } else { return 'model-access-fail'; } diff --git a/app/Models/Blog.php b/app/Models/Blog.php index d305210..39e5033 100644 --- a/app/Models/Blog.php +++ b/app/Models/Blog.php @@ -49,8 +49,8 @@ class Blog extends DashboardModel $blog_entry['tags'] = $tags; // Add the header image if one exists - $header_image_path = '/uploads/blog/img/' . $blog_entry->id . '-header-image.jpg'; - $blog_entry['headerimage'] = file_exists(base_path() . '/public' . $header_image_path) ? $header_image_path . '?version=' . $blog_entry->timestamp() : ''; + $header_image_path = $blog_entry->getUploadsPath('image') . $blog_entry->id . '-header-image.jpg'; + $blog_entry['headerimage'] = file_exists(public_path($header_image_path)) ? $header_image_path . '?version=' . $blog_entry->timestamp() : ''; // Add the processed blog entry to the array array_push($blog_entries, $blog_entry); diff --git a/app/Models/DashboardModel.php b/app/Models/DashboardModel.php index e64a249..70cac68 100644 --- a/app/Models/DashboardModel.php +++ b/app/Models/DashboardModel.php @@ -4,6 +4,8 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; use Auth; +use File; +use Image; use App\Traits\Timestamp; class DashboardModel extends Model @@ -120,13 +122,316 @@ class DashboardModel extends Model * * @return string */ - public static function getDashboardHeading($model) + public function getDashboardHeading() { - return static::$dashboard_heading == null ? ucfirst($model) : static::$dashboard_heading; + return static::$dashboard_heading == null ? ucfirst($this->getTable()) : static::$dashboard_heading; } /** - * Returns an array of column 'headings' or 'names' + * Return the upload path for a given type + * + * @return boolean + */ + public function getUploadsPath($type) + { + if ($type == 'image') { + return '/uploads/' . $this->getTable() . '/img/'; + } else if ($type == 'file') { + return '/uploads/' . $this->getTable() . '/files/'; + } + } + + /** + * Save an image + * + * @return boolean + */ + public function saveImage($name, $file) + { + // Fail if the user doesn't have permission + if (!$this->userCheck()) { + return 'permission-fail'; + } + + $max_width = 0; + $max_height = 0; + $main_ext = 'jpg'; + + // Retrieve the column + $column = static::getColumn($name); + + // Return an error if no column is found + if ($column == null) { + return 'no-such-column-fail'; + } + + // Update the extension if it's been configured + if (array_key_exists('ext', $column)) { + $main_ext = $column['ext']; + } + + // Create the directory if it doesn't exist + $directory = public_path($this->getUploadsPath('image')); + File::makeDirectory($directory, 0755, true, true); + + // Set the base file path (including the file name but not the extension) + $base_filename = $directory . $this->id . '-' . $name . '.'; + + if ($main_ext == 'svg') { + // Save the image provided it's an SVG + if (gettype($file) == 'string') { + if (!preg_match('/\.svg$/i', $file)) { + return 'incorrect-format-fail'; + } + + copy($file, $base_filename . $main_ext); + } else { + if ($file->extension() != 'svg') { + return 'incorrect-format-fail'; + } + + $file->move($directory, $base_filename . $main_ext); + } + } else { + // Update the maximum width if it's been configured + if (array_key_exists('max_width', $column)) { + $max_width = $column['max_width']; + } + // Update the maximum height if it's been configured + if (array_key_exists('max_height', $column)) { + $max_height = $column['max_height']; + } + + $image = Image::make($file); + + if ($max_width > 0 || $max_height > 0) { + $width = $image->width(); + $height = $image->height(); + $new_width = null; + $new_height = null; + + if ($max_width > 0 && $max_height > 0) { + if ($width > $max_width || $height > $max_height) { + $new_width = $max_width; + $new_height = ($new_width / $width) * $height; + + if ($new_height > $max_height) { + $new_width = ($max_height / $height) * $width; + } + } + } else if ($max_width > 0) { + if ($width > $max_width) { + $new_width = $max_width; + } + } else if ($height > $max_height) { + $new_height = $max_height; + } + + if (!is_null($new_width) || !is_null($new_height)) { + $image->resize($new_width, $new_height, function($constraint) { + $constraint->aspectRatio(); + }); + } + } + + $image->save($base_filename . $main_ext); + $image->save($base_filename . 'webp'); + } + + return 'success'; + } + + /* + * Delete an image + * + * @return string + */ + public function deleteImage($name, $not_exist_fail) + { + // Fail if the user doesn't have permission + if (!$this->userCheck()) { + return 'permission-fail'; + } + + // Set up our variables + $main_ext = 'jpg'; + $extensions = []; + + // Retrieve the column + $column = static::getColumn($name); + + // Return an error if no column is found + if ($column == null) { + return 'no-such-column-fail'; + } + + // Update the extension if it's been configured + if (array_key_exists('ext', $column)) { + $main_ext = $column['ext']; + } + + // Build the set of extensions to delete + array_push($extensions, $main_ext); + + // If the image extension isn't svg also delete the webp + if ($main_ext != 'svg') { + array_push($extensions, 'webp'); + } + + // Delete each image + foreach ($extensions as $ext) { + // Get the full path of the image + $image = public_path($this->getUploadsPath('image') . $this->id . '-' . $name . '.' . $ext); + + // Try to delete the image + if (file_exists($image)) { + if (!unlink($image)) { + return 'image-delete-fail'; + } + } else if ($not_exist_fail) { + return 'image-not-exists-fail'; + } + } + + // Success + return 'success'; + } + + /** + * Save a file + * + * @return boolean + */ + public function saveFile($name, $file) + { + // Fail if the user doesn't have permission + if (!$this->userCheck()) { + return 'permission-fail'; + } + + // Retrieve the column + $column = static::getColumn($name); + + // Return an error if no column is found + if ($column == null) { + return 'no-such-column-fail'; + } + + // Fail if an ext hasn't been declared + if (!array_key_exists('ext', $column)) { + return 'no-configured-extension-fail'; + } + + // Store the extension + $ext = $column['ext']; + + // Create the directory if it doesn't exist + $directory = public_path($this->getUploadsPath('file')); + File::makeDirectory($directory, 0755, true, true); + + // Save the file provided it's the correct extension + if (gettype($file) == 'string') { + if (!preg_match("/\.$ext/i", $file)) { + return 'incorrect-format-fail'; + } + + copy($file, $base_filename . $main_ext); + } else { + if ($file->extension() != $ext) { + return 'incorrect-format-fail'; + } + + $file->move($directory, $this->id . '-' . $name . '.' . $ext); + } + + // Success + return 'success'; + } + + /* + * Delete a file + * + * @return string + */ + public function deleteFile($name, $not_exist_fail) + { + // Fail if the user doesn't have permission + if (!$this->userCheck()) { + return 'permission-fail'; + } + + // Retrieve the column + $column = static::getColumn($name); + + // Return an error if no column is found + if ($column == null) { + return 'no-such-column-fail'; + } + + // Fail if an ext hasn't been declared + if (!array_key_exists('ext', $column)) { + return 'no-configured-extension-fail'; + } + + // Store the extension + $ext = $column['ext']; + + // Get the full path of the file + $file = public_path($this->getUploadsPath('file') . $this->id . '-' . $name . '.' . $ext); + + // Try to delete the file + if (file_exists($file)) { + if (!unlink($file)) { + return 'file-delete-fail'; + } + } else if ($not_exist_fail) { + return 'file-not-exists-fail'; + } + + // Success + return 'success'; + } + + /** + * Determine whether a user column exists and whether it matches the current user if it does + * + * @return boolean + */ + public function userCheck() + { + $user_check = true; + + foreach (static::$dashboard_columns as $column) { + if (array_key_exists('type', $column) && $column['type'] == 'user') { + if ($this->{$column['name']} != Auth::id()) { + $user_check = false; + } + + break; + } + } + + return $user_check; + } + + /** + * Get the file extension for an image + * + * @return string + */ + public static function getColumn($name) + { + foreach (static::$dashboard_columns as $column) { + if ($column['name'] == $name) { + return $column; + } + } + + return null; + } + + /** + * Return an array of column 'headings' or 'names' * * @return array */ @@ -152,7 +457,7 @@ class DashboardModel extends Model } /** - * Performs a search against the columns in $dashboard_display + * Perform a search against the columns in $dashboard_display * * @return array */ @@ -192,7 +497,7 @@ class DashboardModel extends Model } /** - * Returns data for the dashboard + * Return data for the dashboard * * @return array */ @@ -250,7 +555,7 @@ class DashboardModel extends Model } /** - * Retrieves the current query string containing valid query parameters + * Retrieve the current query string containing valid query parameters * * @return string */ @@ -275,25 +580,4 @@ class DashboardModel extends Model return $string; } - - /** - * Determines whether a user column exists and whether it matches the current user if it does - * - * @return boolean - */ - public function userCheck() { - $user_check = true; - - foreach (static::$dashboard_columns as $column) { - if (array_key_exists('type', $column) && $column['type'] == 'user') { - if ($this->{$column['name']} != Auth::id()) { - $user_check = false; - } - - break; - } - } - - return $user_check; - } } diff --git a/gulpfile.js b/gulpfile.js index 15dde46..858ffdc 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -265,7 +265,7 @@ gulp.task("watch", () => { }); gulp.watch([ "app/**/*.php", "routes/**/*.php", "resources/views/**/*.blade.php" ], gulp.series(browserSyncReload)); - gulp.watch([ "resources/js/**/app.js", "resources/js/mixins/**/*.js", "resources/components/**/*.vue" ], gulp.series("js-public", browserSyncReload)); + gulp.watch([ "resources/js/**/app.js", "resources/js/mixins/**/*.js", "resources/js/imports/**/*.js", "resources/components/**/*.vue" ], gulp.series("js-public", browserSyncReload)); gulp.watch("resources/js/**/dashboard.js", gulp.series("js-dashboard", browserSyncReload)); gulp.watch("resources/sass/**/*.scss", gulp.parallel("sass-public", "sass-dashboard", "sass-error")); }); diff --git a/readme.md b/readme.md index 53e5fe2..0f6f66b 100644 --- a/readme.md +++ b/readme.md @@ -218,7 +218,9 @@ Models with their `$dashboard_type` set to `edit` also use: * `options` (required by `select`) Takes an array of options that are either strings or arrays containing the keys `title` (for what will display with the option) and `value` (for what will be recorded) * `name`: (required by `file` and `image`) Used along with the record id to determine the filename * `delete`: (optional for `file` and `image`) Enables a delete button for the upload when set to true -* `ext`: (required by `file`) Configures the file extension of the upload +* `ext`: (required by `file` and optional for `image`) Configures the file extension of the upload (`image` defaults to `jpg`) +* `max_width`: (optional for `image`) Configures the maximum width of an image upload (defaults to `0` which sets no maximum width) +* `max_height`: (optional for `image`) Configures the maximum height of an image upload (defaults to `0` which sets no maximum height) An example of the `$dashboard_columns` array in a model with its `$dashboard_type` set to `view`: @@ -238,7 +240,7 @@ An example of the `$dashboard_columns` array in a model with its `$dashboard_typ [ 'name' => 'created_at', 'title' => 'Date', 'type' => 'display' ], [ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ], [ 'name' => 'body', 'required' => true, 'type' => 'mkd' ], - [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true ], + [ 'name' => 'header-image', 'title' => 'Header Image', 'type' => 'image', 'delete' => true, 'ext' => 'jpg' ], [ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ] ]; ``` diff --git a/resources/components/pages/blog.vue b/resources/components/pages/blog.vue index d1326e7..d681175 100644 --- a/resources/components/pages/blog.vue +++ b/resources/components/pages/blog.vue @@ -12,7 +12,7 @@