mirror of
https://github.com/prurigro/hypothetical.git
synced 2024-11-21 23:52:31 -05:00
Use a non-static function for getDashboardHeader so we can use getTable and not have to worry about sending the model, pull file and image saving and deletion into the dashboard model so it can be used by seeders and other things, allow filetype to be specified for images so more than just jpg is possible, if the image filetype isn't svg also save a webp version of a given image, add a dashboard model method to get the uploads path so we aren't hard-coding that to one degree or another in a whole bunch of places, allow a max width and/or height to be specified for image uploads to avoid giant images, don't send unnecessary parameters from the back-end to the front-end and through the api back to the back-end, add the ability to show error text to the dashboard alert model, show an error if a file of an incorrect type is uploaded, add webp suport detection logic to the vue-based front end as well as a method that selects the appropriate format of image (falling back on the provided format)
This commit is contained in:
parent
2a5d046e2c
commit
2a615b75a7
12 changed files with 464 additions and 97 deletions
|
@ -28,14 +28,14 @@ class DashboardController extends Controller {
|
||||||
return view('dashboard.pages.home');
|
return view('dashboard.pages.home');
|
||||||
}
|
}
|
||||||
|
|
||||||
// View Model Data
|
// Page to View Model Data
|
||||||
public function getView($model)
|
public function getView($model)
|
||||||
{
|
{
|
||||||
$model_class = Dashboard::getModel($model, 'view');
|
$model_class = Dashboard::getModel($model, 'view');
|
||||||
|
|
||||||
if ($model_class != null) {
|
if ($model_class != null) {
|
||||||
return view('dashboard.pages.view', [
|
return view('dashboard.pages.view', [
|
||||||
'heading' => $model_class::getDashboardHeading($model),
|
'heading' => $model_class->getDashboardHeading(),
|
||||||
'column_headings' => $model_class::getDashboardColumnData('headings'),
|
'column_headings' => $model_class::getDashboardColumnData('headings'),
|
||||||
'model' => $model,
|
'model' => $model,
|
||||||
'rows' => $model_class::getDashboardData(),
|
'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)
|
public function getEditList($model)
|
||||||
{
|
{
|
||||||
$model_class = Dashboard::getModel($model, 'edit');
|
$model_class = Dashboard::getModel($model, 'edit');
|
||||||
|
@ -55,7 +55,7 @@ class DashboardController extends Controller {
|
||||||
$data = $model_class::getDashboardData(true);
|
$data = $model_class::getDashboardData(true);
|
||||||
|
|
||||||
return view('dashboard.pages.edit-list', [
|
return view('dashboard.pages.edit-list', [
|
||||||
'heading' => $model_class::getDashboardHeading($model),
|
'heading' => $model_class->getDashboardHeading(),
|
||||||
'model' => $model,
|
'model' => $model,
|
||||||
'rows' => $data['rows'],
|
'rows' => $data['rows'],
|
||||||
'paramdisplay' => $data['paramdisplay'],
|
'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')
|
public function getEditItem($model, $id = 'new')
|
||||||
{
|
{
|
||||||
$model_class = Dashboard::getModel($model, 'edit');
|
$model_class = Dashboard::getModel($model, 'edit');
|
||||||
|
@ -103,7 +103,7 @@ class DashboardController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
return view('dashboard.pages.edit-item', [
|
return view('dashboard.pages.edit-item', [
|
||||||
'heading' => $model_class::getDashboardHeading($model),
|
'heading' => $model_class->getDashboardHeading(),
|
||||||
'model' => $model,
|
'model' => $model,
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'item' => $item,
|
'item' => $item,
|
||||||
|
@ -296,15 +296,14 @@ class DashboardController extends Controller {
|
||||||
|
|
||||||
if (is_null($item)) {
|
if (is_null($item)) {
|
||||||
return 'record-access-fail';
|
return 'record-access-fail';
|
||||||
} else if (!$item->userCheck()) {
|
|
||||||
return 'permission-fail';
|
|
||||||
} else if ($request->hasFile('file')) {
|
} else if ($request->hasFile('file')) {
|
||||||
$directory = base_path() . '/public/uploads/' . $request['model'] . '/img/';
|
$save_result = $item->saveImage($request['name'], $request->file('file'));
|
||||||
File::makeDirectory($directory, 0755, true, true);
|
|
||||||
$image = Image::make($request->file('file'));
|
if ($save_result == 'success') {
|
||||||
$image->save($directory . $request['id'] . '-' . $request['name'] . '.jpg');
|
$item->touch();
|
||||||
$item->touch();
|
}
|
||||||
return 'success';
|
|
||||||
|
return $save_result;
|
||||||
} else {
|
} else {
|
||||||
return 'file-upload-fail';
|
return 'file-upload-fail';
|
||||||
}
|
}
|
||||||
|
@ -319,8 +318,7 @@ class DashboardController extends Controller {
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required',
|
'id' => 'required',
|
||||||
'model' => 'required',
|
'model' => 'required',
|
||||||
'name' => 'required',
|
'name' => 'required'
|
||||||
'ext' => 'required'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$model_class = Dashboard::getModel($request['model'], 'edit');
|
$model_class = Dashboard::getModel($request['model'], 'edit');
|
||||||
|
@ -330,14 +328,14 @@ class DashboardController extends Controller {
|
||||||
|
|
||||||
if (is_null($item)) {
|
if (is_null($item)) {
|
||||||
return 'record-access-fail';
|
return 'record-access-fail';
|
||||||
} else if (!$item->userCheck()) {
|
|
||||||
return 'permission-fail';
|
|
||||||
} else if ($request->hasFile('file')) {
|
} else if ($request->hasFile('file')) {
|
||||||
$directory = base_path() . '/public/uploads/' . $request['model'] . '/files/';
|
$save_result = $item->saveFile($request['name'], $request->file('file'));
|
||||||
File::makeDirectory($directory, 0755, true, true);
|
|
||||||
$request->file('file')->move($directory, $request['id'] . '-' . $request['name'] . '.' . $request['ext']);
|
if ($save_result == 'success') {
|
||||||
$item->touch();
|
$item->touch();
|
||||||
return 'success';
|
}
|
||||||
|
|
||||||
|
return $save_result;
|
||||||
} else {
|
} else {
|
||||||
return 'file-upload-fail';
|
return 'file-upload-fail';
|
||||||
}
|
}
|
||||||
|
@ -365,26 +363,18 @@ class DashboardController extends Controller {
|
||||||
return 'permission-fail';
|
return 'permission-fail';
|
||||||
}
|
}
|
||||||
|
|
||||||
// delete the row
|
|
||||||
$item->delete();
|
|
||||||
|
|
||||||
// delete associated files if they exist
|
// delete associated files if they exist
|
||||||
foreach ($model_class::$dashboard_columns as $column) {
|
foreach ($model_class::$dashboard_columns as $column) {
|
||||||
if ($column['type'] == 'image') {
|
if ($column['type'] == 'image') {
|
||||||
$image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $column['name'] . '.jpg';
|
$item->deleteImage($column['name'], false);
|
||||||
|
|
||||||
if (file_exists($image) && !unlink($image)) {
|
|
||||||
return 'image-delete-fail';
|
|
||||||
}
|
|
||||||
} else if ($column['type'] == 'file') {
|
} else if ($column['type'] == 'file') {
|
||||||
$file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $column['name'] . '.' . $column['ext'];
|
$item->deleteFile($column['name'], false);
|
||||||
|
|
||||||
if (file_exists($file) && !unlink($file)) {
|
|
||||||
return 'file-delete-fail';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete the row
|
||||||
|
$item->delete();
|
||||||
|
|
||||||
// update the order of the remaining rows if $dashboard_reorder is true
|
// update the order of the remaining rows if $dashboard_reorder is true
|
||||||
if ($model_class::$dashboard_reorder) {
|
if ($model_class::$dashboard_reorder) {
|
||||||
foreach ($model_class::getDashboardData() as $index => $item) {
|
foreach ($model_class::getDashboardData() as $index => $item) {
|
||||||
|
@ -412,20 +402,13 @@ class DashboardController extends Controller {
|
||||||
$model_class = Dashboard::getModel($request['model'], 'edit');
|
$model_class = Dashboard::getModel($request['model'], 'edit');
|
||||||
|
|
||||||
if ($model_class != null) {
|
if ($model_class != null) {
|
||||||
$image = base_path() . '/public/uploads/' . $request['model'] . '/img/' . $request['id'] . '-' . $request['name'] . '.jpg';
|
|
||||||
$item = $model_class::find($request['id']);
|
$item = $model_class::find($request['id']);
|
||||||
|
|
||||||
if (is_null($item)) {
|
if (is_null($item)) {
|
||||||
return 'record-access-fail';
|
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 {
|
} else {
|
||||||
return 'model-access-fail';
|
return 'model-access-fail';
|
||||||
}
|
}
|
||||||
|
@ -437,27 +420,19 @@ class DashboardController extends Controller {
|
||||||
$this->validate($request, [
|
$this->validate($request, [
|
||||||
'id' => 'required',
|
'id' => 'required',
|
||||||
'model' => 'required',
|
'model' => 'required',
|
||||||
'name' => 'required',
|
'name' => 'required'
|
||||||
'ext' => 'required'
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$model_class = Dashboard::getModel($request['model'], 'edit');
|
$model_class = Dashboard::getModel($request['model'], 'edit');
|
||||||
|
|
||||||
if ($model_class != null) {
|
if ($model_class != null) {
|
||||||
$file = base_path() . '/public/uploads/' . $request['model'] . '/files/' . $request['id'] . '-' . $request['name'] . '.' . $request['ext'];
|
|
||||||
$item = $model_class::find($request['id']);
|
$item = $model_class::find($request['id']);
|
||||||
|
|
||||||
if (is_null($item)) {
|
if (is_null($item)) {
|
||||||
return 'record-access-fail';
|
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 {
|
} else {
|
||||||
return 'model-access-fail';
|
return 'model-access-fail';
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,8 +49,8 @@ class Blog extends DashboardModel
|
||||||
$blog_entry['tags'] = $tags;
|
$blog_entry['tags'] = $tags;
|
||||||
|
|
||||||
// Add the header image if one exists
|
// Add the header image if one exists
|
||||||
$header_image_path = '/uploads/blog/img/' . $blog_entry->id . '-header-image.jpg';
|
$header_image_path = $blog_entry->getUploadsPath('image') . $blog_entry->id . '-header-image.jpg';
|
||||||
$blog_entry['headerimage'] = file_exists(base_path() . '/public' . $header_image_path) ? $header_image_path . '?version=' . $blog_entry->timestamp() : '';
|
$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
|
// Add the processed blog entry to the array
|
||||||
array_push($blog_entries, $blog_entry);
|
array_push($blog_entries, $blog_entry);
|
||||||
|
|
|
@ -4,6 +4,8 @@ namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Auth;
|
use Auth;
|
||||||
|
use File;
|
||||||
|
use Image;
|
||||||
use App\Traits\Timestamp;
|
use App\Traits\Timestamp;
|
||||||
|
|
||||||
class DashboardModel extends Model
|
class DashboardModel extends Model
|
||||||
|
@ -120,13 +122,316 @@ class DashboardModel extends Model
|
||||||
*
|
*
|
||||||
* @return string
|
* @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
|
* @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
|
* @return array
|
||||||
*/
|
*/
|
||||||
|
@ -192,7 +497,7 @@ class DashboardModel extends Model
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns data for the dashboard
|
* Return data for the dashboard
|
||||||
*
|
*
|
||||||
* @return array
|
* @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
|
* @return string
|
||||||
*/
|
*/
|
||||||
|
@ -275,25 +580,4 @@ class DashboardModel extends Model
|
||||||
|
|
||||||
return $string;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
2
gulpfile.js
vendored
2
gulpfile.js
vendored
|
@ -265,7 +265,7 @@ gulp.task("watch", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
gulp.watch([ "app/**/*.php", "routes/**/*.php", "resources/views/**/*.blade.php" ], gulp.series(browserSyncReload));
|
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/js/**/dashboard.js", gulp.series("js-dashboard", browserSyncReload));
|
||||||
gulp.watch("resources/sass/**/*.scss", gulp.parallel("sass-public", "sass-dashboard", "sass-error"));
|
gulp.watch("resources/sass/**/*.scss", gulp.parallel("sass-public", "sass-dashboard", "sass-error"));
|
||||||
});
|
});
|
||||||
|
|
|
@ -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)
|
* `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
|
* `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
|
* `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`:
|
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' => 'created_at', 'title' => 'Date', 'type' => 'display' ],
|
||||||
[ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ],
|
[ 'name' => 'title', 'required' => true, 'unique' => true, 'type' => 'string' ],
|
||||||
[ 'name' => 'body', 'required' => true, 'type' => 'mkd' ],
|
[ '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' ]
|
[ 'name' => 'tags', 'type' => 'list', 'model' => 'BlogTags', 'columns' => [ 'name' ], 'foreign' => 'blog_id', 'sort' => 'order' ]
|
||||||
];
|
];
|
||||||
```
|
```
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<div
|
<div
|
||||||
v-if="entry.headerimage !== ''"
|
v-if="entry.headerimage !== ''"
|
||||||
class="blog-entry-header-image"
|
class="blog-entry-header-image"
|
||||||
:style="{ backgroundImage: 'url(' + entry.headerimage + ')' }">
|
:style="{ backgroundImage: 'url(' + imageType(entry.headerimage) + ')' }">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blog-entry-content">
|
<div class="blog-entry-content">
|
||||||
|
|
23
resources/js/app.js
vendored
23
resources/js/app.js
vendored
|
@ -15,6 +15,15 @@ Vue.use(Vuex);
|
||||||
// CSRF prevention header
|
// CSRF prevention header
|
||||||
Vue.http.headers.common["X-CSRF-TOKEN"] = env.csrfToken;
|
Vue.http.headers.common["X-CSRF-TOKEN"] = env.csrfToken;
|
||||||
|
|
||||||
|
// Import local javascript
|
||||||
|
import SupportsWebP from "imports/supports-webp.js";
|
||||||
|
|
||||||
|
// Import global mixins
|
||||||
|
import ImageType from "mixins/image-type.js";
|
||||||
|
|
||||||
|
// Register global mixins
|
||||||
|
Vue.mixin(ImageType);
|
||||||
|
|
||||||
// Import global components
|
// Import global components
|
||||||
import NavSection from "sections/nav.vue";
|
import NavSection from "sections/nav.vue";
|
||||||
import FooterSection from "sections/footer.vue";
|
import FooterSection from "sections/footer.vue";
|
||||||
|
@ -61,7 +70,8 @@ const store = new Vuex.Store({
|
||||||
appLang: env.appLang,
|
appLang: env.appLang,
|
||||||
appDefaultLang: env.appDefaultLang,
|
appDefaultLang: env.appDefaultLang,
|
||||||
firstLoad: true,
|
firstLoad: true,
|
||||||
lastPath: ""
|
lastPath: "",
|
||||||
|
supportsWebP: null
|
||||||
},
|
},
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -83,6 +93,10 @@ const store = new Vuex.Store({
|
||||||
|
|
||||||
getLastPath: state => {
|
getLastPath: state => {
|
||||||
return state.lastPath;
|
return state.lastPath;
|
||||||
|
},
|
||||||
|
|
||||||
|
getSupportsWebP: state => {
|
||||||
|
return state.supportsWebP;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -98,6 +112,10 @@ const store = new Vuex.Store({
|
||||||
|
|
||||||
setLastPath(state, value) {
|
setLastPath(state, value) {
|
||||||
state.lastPath = value;
|
state.lastPath = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
setSupportsWebP(state, value) {
|
||||||
|
state.supportsWebP = value;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -106,6 +124,9 @@ const store = new Vuex.Store({
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Detect webp support
|
||||||
|
SupportsWebP.detect(store);
|
||||||
|
|
||||||
// Sync vue-router-sync with vuex store
|
// Sync vue-router-sync with vuex store
|
||||||
sync(store, router);
|
sync(store, router);
|
||||||
|
|
||||||
|
|
18
resources/js/dashboard.js
vendored
18
resources/js/dashboard.js
vendored
|
@ -4,6 +4,19 @@ const fadeTime = 250;
|
||||||
// declare a reverse function for jquery
|
// declare a reverse function for jquery
|
||||||
jQuery.fn.reverse = [].reverse;
|
jQuery.fn.reverse = [].reverse;
|
||||||
|
|
||||||
|
// extends an error message with additional text
|
||||||
|
function getErrorText(message, response) {
|
||||||
|
let errorText = message;
|
||||||
|
|
||||||
|
switch (response) {
|
||||||
|
case "incorrect-format-fail":
|
||||||
|
errorText += ": Incorrect file format";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorText;
|
||||||
|
}
|
||||||
|
|
||||||
// show or hide the loading modal
|
// show or hide the loading modal
|
||||||
function loadingModal(action) {
|
function loadingModal(action) {
|
||||||
const $loadingModal = $("#loading-modal");
|
const $loadingModal = $("#loading-modal");
|
||||||
|
@ -429,7 +442,6 @@ function editItemInit() {
|
||||||
file.append("name", $(fileUpload).attr("name"));
|
file.append("name", $(fileUpload).attr("name"));
|
||||||
file.append("id", row_id);
|
file.append("id", row_id);
|
||||||
file.append("model", model);
|
file.append("model", model);
|
||||||
file.append("ext", $(fileUpload).data("ext"));
|
|
||||||
|
|
||||||
$.ajax({
|
$.ajax({
|
||||||
type: "POST",
|
type: "POST",
|
||||||
|
@ -444,7 +456,7 @@ function editItemInit() {
|
||||||
} else {
|
} else {
|
||||||
loadingModal("hide");
|
loadingModal("hide");
|
||||||
|
|
||||||
showAlert("Failed to upload file", function() {
|
showAlert(getErrorText("Failed to upload file", response), function() {
|
||||||
submitting = false;
|
submitting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -491,7 +503,7 @@ function editItemInit() {
|
||||||
} else {
|
} else {
|
||||||
loadingModal("hide");
|
loadingModal("hide");
|
||||||
|
|
||||||
showAlert("Failed to upload image", function() {
|
showAlert(getErrorText("Failed to upload image", response), function() {
|
||||||
submitting = false;
|
submitting = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
48
resources/js/imports/supports-webp.js
vendored
Normal file
48
resources/js/imports/supports-webp.js
vendored
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
export default {
|
||||||
|
detect: (store) => {
|
||||||
|
const webpTestImages = {
|
||||||
|
lossy: "UklGRiIAAABXRUJQVlA4IBYAAAAwAQCdASoBAAEADsD+JaQAA3AAAAAA",
|
||||||
|
lossless: "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==",
|
||||||
|
alpha: "UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA=="
|
||||||
|
};
|
||||||
|
|
||||||
|
const results = {
|
||||||
|
lossy: null,
|
||||||
|
lossless: null,
|
||||||
|
alpha: null
|
||||||
|
};
|
||||||
|
|
||||||
|
const getResultsValues = () => {
|
||||||
|
return Object.keys(results).map((feature) => {
|
||||||
|
return results[feature];
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const callback = (feature, result) => {
|
||||||
|
results[feature] = result;
|
||||||
|
|
||||||
|
if (getResultsValues().indexOf(null) === -1) {
|
||||||
|
store.commit("setSupportsWebP", getResultsValues().indexOf(false) === -1);
|
||||||
|
console.log(store.getters.getSupportsWebP);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkFeature = (feature) => {
|
||||||
|
const img = new Image();
|
||||||
|
|
||||||
|
img.onload = function() {
|
||||||
|
callback(feature, img.width > 0 && img.height > 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.onerror = function() {
|
||||||
|
callback(feature, false);
|
||||||
|
};
|
||||||
|
|
||||||
|
img.src = "data:image/webp;base64," + webpTestImages[feature];
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(webpTestImages).forEach((feature) => {
|
||||||
|
checkFeature(feature);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
13
resources/js/mixins/image-type.js
vendored
Normal file
13
resources/js/mixins/image-type.js
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
imageType(image) {
|
||||||
|
if (this.$store.getters.getSupportsWebP === true) {
|
||||||
|
return image.replace(/\.(png|jpg)/, ".webp");
|
||||||
|
} else if (this.$store.getters.getSupportsWebP === false) {
|
||||||
|
return image;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -21,6 +21,7 @@
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@set('value', $item !== null ? $item[$column['name']] : '')
|
@set('value', $item !== null ? $item[$column['name']] : '')
|
||||||
@set('type', $id == 'new' && array_key_exists('type-new', $column) ? $column['type-new'] : $column['type'])
|
@set('type', $id == 'new' && array_key_exists('type-new', $column) ? $column['type-new'] : $column['type'])
|
||||||
|
@set('ext', array_key_exists('ext', $column) ? $column['ext'] : 'jpg')
|
||||||
|
|
||||||
@if($type == 'hidden')
|
@if($type == 'hidden')
|
||||||
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value }}" />
|
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ $value }}" />
|
||||||
|
@ -28,7 +29,17 @@
|
||||||
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ Auth::id() }}" />
|
<input class="text-input" type="hidden" name="{{ $column['name'] }}" id="{{ $column['name'] }}" value="{{ Auth::id() }}" />
|
||||||
@elseif($type != 'display' || $id != 'new')
|
@elseif($type != 'display' || $id != 'new')
|
||||||
<div class="col-12 col-md-4 col-lg-3">
|
<div class="col-12 col-md-4 col-lg-3">
|
||||||
<label for="{{ $column['name'] }}">{{ array_key_exists('title', $column) ? $column['title'] : ucfirst($column['name']) }}:</label>
|
<label for="{{ $column['name'] }}">
|
||||||
|
{{ array_key_exists('title', $column) ? $column['title'] : ucfirst($column['name']) }}
|
||||||
|
|
||||||
|
@if($column['type'] == 'image')
|
||||||
|
@if($ext == 'svg')
|
||||||
|
(SVG)
|
||||||
|
@endif
|
||||||
|
@elseif($column['type'] == 'file')
|
||||||
|
({{ strtoupper($ext) }})
|
||||||
|
@endif
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-12 col-md-8 col-lg-9">
|
<div class="col-12 col-md-8 col-lg-9">
|
||||||
|
@ -100,7 +111,7 @@
|
||||||
<button class="list-add-button" type="button">Add</button>
|
<button class="list-add-button" type="button">Add</button>
|
||||||
</div>
|
</div>
|
||||||
@elseif($type == 'image')
|
@elseif($type == 'image')
|
||||||
@set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.jpg')
|
@set('current_image', "/uploads/$model/img/$id-" . $column['name'] . '.' . $ext)
|
||||||
<input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
|
<input class="image-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
|
||||||
|
|
||||||
@if(file_exists(base_path() . '/public' . $current_image))
|
@if(file_exists(base_path() . '/public' . $current_image))
|
||||||
|
@ -116,14 +127,14 @@
|
||||||
@endif
|
@endif
|
||||||
@elseif($type == 'file')
|
@elseif($type == 'file')
|
||||||
@set('current_file', "/uploads/$model/files/$id-" . $column['name'] . '.' . $column['ext'])
|
@set('current_file', "/uploads/$model/files/$id-" . $column['name'] . '.' . $column['ext'])
|
||||||
<input class="file-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" data-ext="{{ $column['ext'] }}" />
|
<input class="file-upload" type="file" name="{{ $column['name'] }}" id="{{ $column['name'] }}" />
|
||||||
|
|
||||||
@if(file_exists(base_path() . '/public' . $current_file))
|
@if(file_exists(base_path() . '/public' . $current_file))
|
||||||
<div id="current-file-{{ $column['name'] }}">
|
<div id="current-file-{{ $column['name'] }}">
|
||||||
<a class="edit-button view" href="{{ $current_file }}?version={{ $item->timestamp() }}" target="_blank">View {{ strtoupper($column['ext']) }}</a>
|
<a class="edit-button view" href="{{ $current_file }}?version={{ $item->timestamp() }}" target="_blank">View {{ strtoupper($column['ext']) }}</a>
|
||||||
|
|
||||||
@if(array_key_exists('delete', $column) && $column['delete'])
|
@if(array_key_exists('delete', $column) && $column['delete'])
|
||||||
<span class="edit-button delete file" data-name="{{ $column['name'] }}" data-ext="{{ $column['ext'] }}">
|
<span class="edit-button delete file" data-name="{{ $column['name'] }}">
|
||||||
Delete {{ strtoupper($column['ext']) }}
|
Delete {{ strtoupper($column['ext']) }}
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
@ -6,11 +6,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var env = {
|
var env = {
|
||||||
appName: "{{ env('APP_NAME') }}",
|
appName: "{!! env('APP_NAME') !!}",
|
||||||
appDesc: "{{ env('APP_DESC') }}",
|
appDesc: "{!! env('APP_DESC') !!}",
|
||||||
appLang: "{{ Language::getSessionLanguage() }}",
|
appLang: "{{ Language::getSessionLanguage() }}",
|
||||||
appDefaultLang: "{{ env('DEFAULT_LANGUAGE') }}",
|
appDefaultLang: "{{ env('DEFAULT_LANGUAGE') }}",
|
||||||
apiToken: "{{ Auth::check() ? '?api_token=' . Auth::user()->api_token : '' }}",
|
apiToken: "{{ Auth::check() ? '?api_token=' . Auth::user()->api_token : '' }}",
|
||||||
|
currentUrl: "{{ Request::url() }}",
|
||||||
csrfToken: "{{ csrf_token() }}",
|
csrfToken: "{{ csrf_token() }}",
|
||||||
debug: {{ Config::get('app.debug') ? 'true' : 'false' }}
|
debug: {{ Config::get('app.debug') ? 'true' : 'false' }}
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue