Simplify Media Library upload logic (#5026)
Based heavily off the work done by @alxy in #4314 May require https://github.com/rainlab/pages-plugin/pull/421 if testing with RainLab.Pages. Replaces #4314, related: #4311, Credit to @alxy for the initial work.
This commit is contained in:
parent
459677a168
commit
4f7e2995c7
|
|
@ -1,15 +1,15 @@
|
||||||
<?php namespace Backend\FormWidgets;
|
<?php namespace Backend\FormWidgets;
|
||||||
|
|
||||||
use App;
|
use App;
|
||||||
use Config;
|
|
||||||
use File;
|
use File;
|
||||||
use Event;
|
|
||||||
use Lang;
|
use Lang;
|
||||||
|
use Event;
|
||||||
|
use Config;
|
||||||
use Request;
|
use Request;
|
||||||
use Backend;
|
use Backend;
|
||||||
use BackendAuth;
|
use BackendAuth;
|
||||||
use Backend\Classes\FormWidgetBase;
|
|
||||||
use Backend\Models\EditorSetting;
|
use Backend\Models\EditorSetting;
|
||||||
|
use Backend\Classes\FormWidgetBase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rich Editor
|
* Rich Editor
|
||||||
|
|
@ -20,6 +20,8 @@ use Backend\Models\EditorSetting;
|
||||||
*/
|
*/
|
||||||
class RichEditor extends FormWidgetBase
|
class RichEditor extends FormWidgetBase
|
||||||
{
|
{
|
||||||
|
use \Backend\Traits\UploadableWidget;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Configurable properties
|
// Configurable properties
|
||||||
//
|
//
|
||||||
|
|
@ -39,6 +41,11 @@ class RichEditor extends FormWidgetBase
|
||||||
*/
|
*/
|
||||||
public $readOnly = false;
|
public $readOnly = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string|null Path in the Media Library where uploaded files should be stored. If null it will be pulled from Request::input('path');
|
||||||
|
*/
|
||||||
|
public $uploadPath = '/uploaded-files';
|
||||||
|
|
||||||
//
|
//
|
||||||
// Object properties
|
// Object properties
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -446,7 +446,6 @@ textarea.fr-code {display:none;width:100%;resize:none;-moz-resize:none;-webkit-r
|
||||||
.fr-view .fr-video.fr-dvi {display:inline-block}
|
.fr-view .fr-video.fr-dvi {display:inline-block}
|
||||||
.fr-view .fr-video.fr-dvi.fr-fvl {float:left}
|
.fr-view .fr-video.fr-dvi.fr-fvl {float:left}
|
||||||
.fr-view .fr-video.fr-dvi.fr-fvr {float:right}
|
.fr-view .fr-video.fr-dvi.fr-fvr {float:right}
|
||||||
.fr-view {}
|
|
||||||
.fr-view .oc-text-gray {color:#AAA !important}
|
.fr-view .oc-text-gray {color:#AAA !important}
|
||||||
.fr-view .oc-text-bordered {border-top:solid 1px #222;border-bottom:solid 1px #222;padding:10px 0}
|
.fr-view .oc-text-bordered {border-top:solid 1px #222;border-bottom:solid 1px #222;padding:10px 0}
|
||||||
.fr-view .oc-text-spaced {letter-spacing:1px}
|
.fr-view .oc-text-spaced {letter-spacing:1px}
|
||||||
|
|
@ -544,6 +543,11 @@ body .fr-popup .fr-checkbox span {border-color:#d1d6d9}
|
||||||
.field-richeditor.size-giant .fr-wrapper {height:350px}
|
.field-richeditor.size-giant .fr-wrapper {height:350px}
|
||||||
.field-richeditor.size-giant .fr-wrapper .fr-view {min-height:350px}
|
.field-richeditor.size-giant .fr-wrapper .fr-view {min-height:350px}
|
||||||
.field-richeditor.size-giant .height-indicator {height:350px;display:none}
|
.field-richeditor.size-giant .height-indicator {height:350px;display:none}
|
||||||
|
.field-richeditor.size-tiny.stretch {min-height:90px}
|
||||||
|
.field-richeditor.size-small.stretch {min-height:140px}
|
||||||
|
.field-richeditor.size-large.stretch {min-height:240px}
|
||||||
|
.field-richeditor.size-huge.stretch {min-height:290px}
|
||||||
|
.field-richeditor.size-giant.stretch {min-height:390px}
|
||||||
.fr-tooltip {z-index:9997 !important}
|
.fr-tooltip {z-index:9997 !important}
|
||||||
.fr-popup {z-index:9995 !important}
|
.fr-popup {z-index:9995 !important}
|
||||||
.fr-toolbar {z-index:11 !important}
|
.fr-toolbar {z-index:11 !important}
|
||||||
|
|
|
||||||
|
|
@ -157,11 +157,11 @@ Base.call(this)
|
||||||
this.init()}
|
this.init()}
|
||||||
RichEditor.prototype=Object.create(BaseProto)
|
RichEditor.prototype=Object.create(BaseProto)
|
||||||
RichEditor.prototype.constructor=RichEditor
|
RichEditor.prototype.constructor=RichEditor
|
||||||
RichEditor.DEFAULTS={linksHandler:null,stylesheet:null,fullpage:false,editorLang:'en',useMediaManager:false,toolbarButtons:null,allowEmptyTags:null,allowTags:null,noWrapTags:null,removeTags:null,lineBreakerTags:null,imageStyles:null,linkStyles:null,paragraphStyles:null,tableStyles:null,tableCellStyles:null,aceVendorPath:'/',readOnly:false}
|
RichEditor.DEFAULTS={linksHandler:null,uploadHandler:null,stylesheet:null,fullpage:false,editorLang:'en',useMediaManager:false,toolbarButtons:null,allowEmptyTags:null,allowTags:null,noWrapTags:null,removeTags:null,lineBreakerTags:null,imageStyles:null,linkStyles:null,paragraphStyles:null,tableStyles:null,tableCellStyles:null,aceVendorPath:'/',readOnly:false}
|
||||||
RichEditor.prototype.init=function(){var self=this;this.$el.one('dispose-control',this.proxy(this.dispose))
|
RichEditor.prototype.init=function(){var self=this;this.$el.one('dispose-control',this.proxy(this.dispose))
|
||||||
if(!this.$textarea.attr('id')){this.$textarea.attr('id','element-'+Math.random().toString(36).substring(7))}
|
if(!this.$textarea.attr('id')){this.$textarea.attr('id','element-'+Math.random().toString(36).substring(7))}
|
||||||
this.initFroala()}
|
this.initFroala()}
|
||||||
RichEditor.prototype.initFroala=function(){var froalaOptions={editorClass:'control-richeditor',language:this.options.editorLang,fullPage:this.options.fullpage,pageLinksHandler:this.options.linksHandler,aceEditorVendorPath:this.options.aceVendorPath,toolbarSticky:false}
|
RichEditor.prototype.initFroala=function(){var froalaOptions={editorClass:'control-richeditor',language:this.options.editorLang,fullPage:this.options.fullpage,pageLinksHandler:this.options.linksHandler,uploadHandler:this.options.uploadHandler,aceEditorVendorPath:this.options.aceVendorPath,toolbarSticky:false}
|
||||||
if(this.options.toolbarButtons){froalaOptions.toolbarButtons=this.options.toolbarButtons.split(',')}
|
if(this.options.toolbarButtons){froalaOptions.toolbarButtons=this.options.toolbarButtons.split(',')}
|
||||||
else{froalaOptions.toolbarButtons=$.oc.richEditorButtons}
|
else{froalaOptions.toolbarButtons=$.oc.richEditorButtons}
|
||||||
froalaOptions.imageStyles=this.options.imageStyles?this.options.imageStyles:{'oc-img-rounded':'Rounded','oc-img-bordered':'Bordered'}
|
froalaOptions.imageStyles=this.options.imageStyles?this.options.imageStyles:{'oc-img-rounded':'Rounded','oc-img-bordered':'Bordered'}
|
||||||
|
|
@ -178,9 +178,12 @@ froalaOptions.htmlDoNotWrapTags=this.options.noWrapTags?this.options.noWrapTags.
|
||||||
if(this.options.removeTags){froalaOptions.htmlRemoveTags=this.options.removeTags.split(/[\s,]+/)}
|
if(this.options.removeTags){froalaOptions.htmlRemoveTags=this.options.removeTags.split(/[\s,]+/)}
|
||||||
froalaOptions.lineBreakerTags=this.options.lineBreakerTags?this.options.lineBreakerTags.split(/[\s,]+/):['figure, table, hr, iframe, form, dl']
|
froalaOptions.lineBreakerTags=this.options.lineBreakerTags?this.options.lineBreakerTags.split(/[\s,]+/):['figure, table, hr, iframe, form, dl']
|
||||||
froalaOptions.shortcutsEnabled=['show','bold','italic','underline','indent','outdent','undo','redo']
|
froalaOptions.shortcutsEnabled=['show','bold','italic','underline','indent','outdent','undo','redo']
|
||||||
|
froalaOptions.requestHeaders={'X-CSRF-TOKEN':$('meta[name="csrf-token"]').attr('content'),'X-Requested-With':'XMLHttpRequest'}
|
||||||
|
var $form=this.$el.closest('form')
|
||||||
|
var formData={};if($form.length>0){$.each($form.serializeArray(),function(index,field){formData[field.name]=field.value;})}
|
||||||
froalaOptions.imageUploadURL=froalaOptions.fileUploadURL=window.location
|
froalaOptions.imageUploadURL=froalaOptions.fileUploadURL=window.location
|
||||||
froalaOptions.imageUploadParam=froalaOptions.fileUploadParam='file_data'
|
froalaOptions.imageUploadParam=froalaOptions.fileUploadParam='file_data'
|
||||||
froalaOptions.imageUploadParams=froalaOptions.fileUploadParams={X_OCTOBER_MEDIA_MANAGER_QUICK_UPLOAD:1,_token:$('meta[name="csrf-token"]').attr('content')}
|
froalaOptions.imageUploadParams=froalaOptions.fileUploadParams=$.extend(formData,{_handler:froalaOptions.uploadHandler,})
|
||||||
var placeholder=this.$textarea.attr('placeholder')
|
var placeholder=this.$textarea.attr('placeholder')
|
||||||
froalaOptions.placeholderText=placeholder?placeholder:''
|
froalaOptions.placeholderText=placeholder?placeholder:''
|
||||||
froalaOptions.height=this.$el.hasClass('stretch')?Infinity:$('.height-indicator',this.$el).height()
|
froalaOptions.height=this.$el.hasClass('stretch')?Infinity:$('.height-indicator',this.$el).height()
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@
|
||||||
|
|
||||||
RichEditor.DEFAULTS = {
|
RichEditor.DEFAULTS = {
|
||||||
linksHandler: null,
|
linksHandler: null,
|
||||||
|
uploadHandler: null,
|
||||||
stylesheet: null,
|
stylesheet: null,
|
||||||
fullpage: false,
|
fullpage: false,
|
||||||
editorLang: 'en',
|
editorLang: 'en',
|
||||||
|
|
@ -79,6 +80,7 @@
|
||||||
language: this.options.editorLang,
|
language: this.options.editorLang,
|
||||||
fullPage: this.options.fullpage,
|
fullPage: this.options.fullpage,
|
||||||
pageLinksHandler: this.options.linksHandler,
|
pageLinksHandler: this.options.linksHandler,
|
||||||
|
uploadHandler: this.options.uploadHandler,
|
||||||
aceEditorVendorPath: this.options.aceVendorPath,
|
aceEditorVendorPath: this.options.aceVendorPath,
|
||||||
toolbarSticky: false
|
toolbarSticky: false
|
||||||
}
|
}
|
||||||
|
|
@ -153,13 +155,27 @@
|
||||||
|
|
||||||
froalaOptions.shortcutsEnabled = ['show', 'bold', 'italic', 'underline', 'indent', 'outdent', 'undo', 'redo']
|
froalaOptions.shortcutsEnabled = ['show', 'bold', 'italic', 'underline', 'indent', 'outdent', 'undo', 'redo']
|
||||||
|
|
||||||
|
// Ensure that October recognizes AJAX requests from Froala
|
||||||
|
froalaOptions.requestHeaders = {
|
||||||
|
'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content'),
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the data from the parent form for including in the request
|
||||||
|
var $form = this.$el.closest('form')
|
||||||
|
var formData = {};
|
||||||
|
if ($form.length > 0) {
|
||||||
|
$.each($form.serializeArray(), function (index, field) {
|
||||||
|
formData[field.name] = field.value;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// File upload
|
// File upload
|
||||||
froalaOptions.imageUploadURL = froalaOptions.fileUploadURL = window.location
|
froalaOptions.imageUploadURL = froalaOptions.fileUploadURL = window.location
|
||||||
froalaOptions.imageUploadParam = froalaOptions.fileUploadParam = 'file_data'
|
froalaOptions.imageUploadParam = froalaOptions.fileUploadParam = 'file_data'
|
||||||
froalaOptions.imageUploadParams = froalaOptions.fileUploadParams = {
|
froalaOptions.imageUploadParams = froalaOptions.fileUploadParams = $.extend(formData, {
|
||||||
X_OCTOBER_MEDIA_MANAGER_QUICK_UPLOAD: 1,
|
_handler: froalaOptions.uploadHandler,
|
||||||
_token: $('meta[name="csrf-token"]').attr('content')
|
})
|
||||||
}
|
|
||||||
|
|
||||||
var placeholder = this.$textarea.attr('placeholder')
|
var placeholder = this.$textarea.attr('placeholder')
|
||||||
froalaOptions.placeholderText = placeholder ? placeholder : ''
|
froalaOptions.placeholderText = placeholder ? placeholder : ''
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,14 @@
|
||||||
&.size-large { .richeditor-set-height(@size-large); }
|
&.size-large { .richeditor-set-height(@size-large); }
|
||||||
&.size-huge { .richeditor-set-height(@size-huge); }
|
&.size-huge { .richeditor-set-height(@size-huge); }
|
||||||
&.size-giant { .richeditor-set-height(@size-giant); }
|
&.size-giant { .richeditor-set-height(@size-giant); }
|
||||||
|
|
||||||
|
&.stretch {
|
||||||
|
&.size-tiny { min-height: @size-tiny; }
|
||||||
|
&.size-small { min-height: @size-small; }
|
||||||
|
&.size-large { min-height: @size-large; }
|
||||||
|
&.size-huge { min-height: @size-huge; }
|
||||||
|
&.size-giant { min-height: @size-giant; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fr-tooltip {
|
.fr-tooltip {
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,11 @@
|
||||||
<?php if (isset($tableStyles)): ?>data-table-styles="<?= e(json_encode($tableStyles)) ?>"<?php endif ?>
|
<?php if (isset($tableStyles)): ?>data-table-styles="<?= e(json_encode($tableStyles)) ?>"<?php endif ?>
|
||||||
<?php if (isset($tableCellStyles)): ?>data-table-cell-styles="<?= e(json_encode($tableCellStyles)) ?>"<?php endif ?>
|
<?php if (isset($tableCellStyles)): ?>data-table-cell-styles="<?= e(json_encode($tableCellStyles)) ?>"<?php endif ?>
|
||||||
data-links-handler="<?= $this->getEventHandler('onLoadPageLinksForm') ?>"
|
data-links-handler="<?= $this->getEventHandler('onLoadPageLinksForm') ?>"
|
||||||
|
data-upload-handler="<?= $this->getEventHandler('onUpload') ?>"
|
||||||
data-ace-vendor-path="<?= Url::asset('/modules/backend/formwidgets/codeeditor/assets/vendor/ace') ?>"
|
data-ace-vendor-path="<?= Url::asset('/modules/backend/formwidgets/codeeditor/assets/vendor/ace') ?>"
|
||||||
data-control="richeditor"
|
data-control="richeditor"
|
||||||
<?= $field->getAttributes() ?>>
|
<?= $field->getAttributes() ?>
|
||||||
|
>
|
||||||
<textarea
|
<textarea
|
||||||
placeholder="<?= e(trans($field->placeholder)) ?>"
|
placeholder="<?= e(trans($field->placeholder)) ?>"
|
||||||
name="<?= $name ?>"
|
name="<?= $name ?>"
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
<?php namespace Backend\Traits;
|
||||||
|
|
||||||
|
use Str;
|
||||||
|
use File;
|
||||||
|
use Lang;
|
||||||
|
use Request;
|
||||||
|
use Response;
|
||||||
|
use ApplicationException;
|
||||||
|
use System\Classes\MediaLibrary;
|
||||||
|
use October\Rain\Filesystem\Definitions as FileDefinitions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploadable Widget Trait
|
||||||
|
* Adds media library upload features to back-end widgets
|
||||||
|
*
|
||||||
|
* @package october\backend
|
||||||
|
* @author Alexey Bobkov, Samuel Georges
|
||||||
|
*/
|
||||||
|
trait UploadableWidget
|
||||||
|
{
|
||||||
|
// /**
|
||||||
|
// * @var string Path in the Media Library where uploaded files should be stored. If empty it will be pulled from Request::input('path');
|
||||||
|
// */
|
||||||
|
// public $uploadPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process file uploads submitted via AJAX
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
* @throws ApplicationException If the file "file_data" wasn't detected in the request or if the file failed to pass validation / security checks
|
||||||
|
*/
|
||||||
|
public function onUpload()
|
||||||
|
{
|
||||||
|
if ($this->readOnly) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!Request::hasFile('file_data')) {
|
||||||
|
throw new ApplicationException('File missing from request');
|
||||||
|
}
|
||||||
|
|
||||||
|
$uploadedFile = Request::file('file_data');
|
||||||
|
|
||||||
|
$fileName = $uploadedFile->getClientOriginalName();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Convert uppcare case file extensions to lower case
|
||||||
|
*/
|
||||||
|
$extension = strtolower($uploadedFile->getClientOriginalExtension());
|
||||||
|
$fileName = File::name($fileName).'.'.$extension;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* File name contains non-latin characters, attempt to slug the value
|
||||||
|
*/
|
||||||
|
if (!$this->validateFileName($fileName)) {
|
||||||
|
$fileNameClean = $this->cleanFileName(File::name($fileName));
|
||||||
|
$fileName = $fileNameClean . '.' . $extension;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Check for unsafe file extensions
|
||||||
|
*/
|
||||||
|
if (!$this->validateFileType($fileName)) {
|
||||||
|
throw new ApplicationException(Lang::get('backend::lang.media.type_blocked'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* See mime type handling in the asset manager
|
||||||
|
*/
|
||||||
|
if (!$uploadedFile->isValid()) {
|
||||||
|
throw new ApplicationException($uploadedFile->getErrorMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the configured upload path unless it's null, in which case use the user-provided path
|
||||||
|
$path = !empty($this->uploadPath) ? $this->uploadPath : Request::input('path');
|
||||||
|
$path = MediaLibrary::validatePath($path);
|
||||||
|
$filePath = $path . '/' . $fileName;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* getRealPath() can be empty for some environments (IIS)
|
||||||
|
*/
|
||||||
|
$realPath = empty(trim($uploadedFile->getRealPath()))
|
||||||
|
? $uploadedFile->getPath() . DIRECTORY_SEPARATOR . $uploadedFile->getFileName()
|
||||||
|
: $uploadedFile->getRealPath();
|
||||||
|
|
||||||
|
MediaLibrary::instance()->put(
|
||||||
|
$filePath,
|
||||||
|
File::get($realPath)
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @event media.file.upload
|
||||||
|
* Called after a file is uploaded
|
||||||
|
*
|
||||||
|
* Example usage:
|
||||||
|
*
|
||||||
|
* Event::listen('media.file.upload', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) &$path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) {
|
||||||
|
* \Log::info($path . " was upoaded.");
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* Or
|
||||||
|
*
|
||||||
|
* $mediaWidget->bindEvent('file.upload', function ((string) &$path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) {
|
||||||
|
* \Log::info($path . " was uploaded");
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
$this->fireSystemEvent('media.file.upload', [&$filePath, $uploadedFile]);
|
||||||
|
|
||||||
|
$response = Response::make([
|
||||||
|
'link' => MediaLibrary::url($filePath),
|
||||||
|
'result' => 'success'
|
||||||
|
]);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$response = Response::make($ex->getMessage(), 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate a proposed media item file name.
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function validateFileName($name)
|
||||||
|
{
|
||||||
|
if (!preg_match('/^[\w@\.\s_\-]+$/iu', $name)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($name, '..') !== false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check for blocked / unsafe file extensions
|
||||||
|
*
|
||||||
|
* @param string
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
protected function validateFileType($name)
|
||||||
|
{
|
||||||
|
$extension = strtolower(File::extension($name));
|
||||||
|
|
||||||
|
$allowedFileTypes = FileDefinitions::get('defaultExtensions');
|
||||||
|
|
||||||
|
if (!in_array($extension, $allowedFileTypes)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a slug form the string. A modified version of Str::slug
|
||||||
|
* with the main difference that it accepts @-signs
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
protected function cleanFileName($name)
|
||||||
|
{
|
||||||
|
$title = Str::ascii($name);
|
||||||
|
|
||||||
|
// Convert all dashes/underscores into separator
|
||||||
|
$flip = $separator = '-';
|
||||||
|
$title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
|
||||||
|
|
||||||
|
// Remove all characters that are not the separator, letters, numbers, whitespace or @.
|
||||||
|
$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s@]+!u', '', mb_strtolower($title));
|
||||||
|
|
||||||
|
// Replace all separator characters and whitespace by a single separator
|
||||||
|
$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
|
||||||
|
|
||||||
|
return trim($title, $separator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -26,6 +26,8 @@ use Form as FormHelper;
|
||||||
*/
|
*/
|
||||||
class MediaManager extends WidgetBase
|
class MediaManager extends WidgetBase
|
||||||
{
|
{
|
||||||
|
use \Backend\Traits\UploadableWidget;
|
||||||
|
|
||||||
const FOLDER_ROOT = '/';
|
const FOLDER_ROOT = '/';
|
||||||
|
|
||||||
const VIEW_MODE_GRID = 'grid';
|
const VIEW_MODE_GRID = 'grid';
|
||||||
|
|
@ -67,8 +69,6 @@ class MediaManager extends WidgetBase
|
||||||
$this->readOnly = $readOnly;
|
$this->readOnly = $readOnly;
|
||||||
|
|
||||||
parent::__construct($controller, []);
|
parent::__construct($controller, []);
|
||||||
|
|
||||||
$this->checkUploadPostback();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -1497,170 +1497,6 @@ class MediaManager extends WidgetBase
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Detect the upload post flag
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
protected function checkUploadPostback()
|
|
||||||
{
|
|
||||||
if ($this->readOnly) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$fileName = null;
|
|
||||||
$quickMode = false;
|
|
||||||
|
|
||||||
if (
|
|
||||||
(!($uniqueId = Request::header('X-OCTOBER-FILEUPLOAD')) || $uniqueId != $this->getId()) &&
|
|
||||||
(!$quickMode = post('X_OCTOBER_MEDIA_MANAGER_QUICK_UPLOAD'))
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!Input::hasFile('file_data')) {
|
|
||||||
throw new ApplicationException('File missing from request');
|
|
||||||
}
|
|
||||||
|
|
||||||
$uploadedFile = Input::file('file_data');
|
|
||||||
|
|
||||||
$fileName = $uploadedFile->getClientOriginalName();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Convert uppcare case file extensions to lower case
|
|
||||||
*/
|
|
||||||
$extension = strtolower($uploadedFile->getClientOriginalExtension());
|
|
||||||
$fileName = File::name($fileName).'.'.$extension;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* File name contains non-latin characters, attempt to slug the value
|
|
||||||
*/
|
|
||||||
if (!$this->validateFileName($fileName)) {
|
|
||||||
$fileNameClean = $this->cleanFileName(File::name($fileName));
|
|
||||||
$fileName = $fileNameClean . '.' . $extension;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Check for unsafe file extensions
|
|
||||||
*/
|
|
||||||
if (!$this->validateFileType($fileName)) {
|
|
||||||
throw new ApplicationException(Lang::get('backend::lang.media.type_blocked'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* See mime type handling in the asset manager
|
|
||||||
*/
|
|
||||||
if (!$uploadedFile->isValid()) {
|
|
||||||
throw new ApplicationException($uploadedFile->getErrorMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
$path = $quickMode ? '/uploaded-files' : Input::get('path');
|
|
||||||
$path = MediaLibrary::validatePath($path);
|
|
||||||
$filePath = $path.'/'.$fileName;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* getRealPath() can be empty for some environments (IIS)
|
|
||||||
*/
|
|
||||||
$realPath = empty(trim($uploadedFile->getRealPath()))
|
|
||||||
? $uploadedFile->getPath() . DIRECTORY_SEPARATOR . $uploadedFile->getFileName()
|
|
||||||
: $uploadedFile->getRealPath();
|
|
||||||
|
|
||||||
MediaLibrary::instance()->put(
|
|
||||||
$filePath,
|
|
||||||
File::get($realPath)
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @event media.file.upload
|
|
||||||
* Called after a file is uploaded
|
|
||||||
*
|
|
||||||
* Example usage:
|
|
||||||
*
|
|
||||||
* Event::listen('media.file.upload', function ((\Backend\Widgets\MediaManager) $mediaWidget, (string) &$path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) {
|
|
||||||
* \Log::info($path . " was upoaded.");
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* Or
|
|
||||||
*
|
|
||||||
* $mediaWidget->bindEvent('file.upload', function ((string) &$path, (\Symfony\Component\HttpFoundation\File\UploadedFile) $uploadedFile) {
|
|
||||||
* \Log::info($path . " was uploaded");
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
$this->fireSystemEvent('media.file.upload', [&$filePath, $uploadedFile]);
|
|
||||||
|
|
||||||
$response = Response::make([
|
|
||||||
'link' => MediaLibrary::url($filePath),
|
|
||||||
'result' => 'success'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
catch (Exception $ex) {
|
|
||||||
$response = Response::make($ex->getMessage(), 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override the controller response
|
|
||||||
$this->controller->setResponse($response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate a proposed media item file name.
|
|
||||||
* @param string
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function validateFileName($name)
|
|
||||||
{
|
|
||||||
if (!preg_match('/^[\w@\.\s_\-]+$/iu', $name)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strpos($name, '..') !== false) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check for blocked / unsafe file extensions
|
|
||||||
* @param string
|
|
||||||
* @return bool
|
|
||||||
*/
|
|
||||||
protected function validateFileType($name)
|
|
||||||
{
|
|
||||||
$extension = strtolower(File::extension($name));
|
|
||||||
|
|
||||||
$allowedFileTypes = FileDefinitions::get('defaultExtensions');
|
|
||||||
|
|
||||||
if (!in_array($extension, $allowedFileTypes)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a slug form the string. A modified version of Str::slug
|
|
||||||
* with the main difference that it accepts @-signs
|
|
||||||
* @param string $name
|
|
||||||
* @return string
|
|
||||||
*/
|
|
||||||
protected function cleanFileName($name)
|
|
||||||
{
|
|
||||||
$title = Str::ascii($name);
|
|
||||||
|
|
||||||
// Convert all dashes/underscores into separator
|
|
||||||
$flip = $separator = '-';
|
|
||||||
$title = preg_replace('!['.preg_quote($flip).']+!u', $separator, $title);
|
|
||||||
|
|
||||||
// Remove all characters that are not the separator, letters, numbers, whitespace or @.
|
|
||||||
$title = preg_replace('![^'.preg_quote($separator).'\pL\pN\s@]+!u', '', mb_strtolower($title));
|
|
||||||
|
|
||||||
// Replace all separator characters and whitespace by a single separator
|
|
||||||
$title = preg_replace('!['.preg_quote($separator).'\s]+!u', $separator, $title);
|
|
||||||
|
|
||||||
return trim($title, $separator);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Cropping
|
// Cropping
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -277,7 +277,6 @@ MediaManager.prototype.doObjectsCollide=function(aTop,aLeft,aWidth,aHeight,bTop,
|
||||||
MediaManager.prototype.initUploader=function(){if(!this.itemListElement||this.options.readOnly)
|
MediaManager.prototype.initUploader=function(){if(!this.itemListElement||this.options.readOnly)
|
||||||
return
|
return
|
||||||
var uploaderOptions={clickable:this.$el.find('[data-control="upload"]').get(0),url:this.options.url,paramName:'file_data',timeout:0,headers:{},createImageThumbnails:false}
|
var uploaderOptions={clickable:this.$el.find('[data-control="upload"]').get(0),url:this.options.url,paramName:'file_data',timeout:0,headers:{},createImageThumbnails:false}
|
||||||
if(this.options.uniqueId){uploaderOptions.headers['X-OCTOBER-FILEUPLOAD']=this.options.uniqueId}
|
|
||||||
var token=$('meta[name="csrf-token"]').attr('content')
|
var token=$('meta[name="csrf-token"]').attr('content')
|
||||||
if(token){uploaderOptions.headers['X-CSRF-TOKEN']=token}
|
if(token){uploaderOptions.headers['X-CSRF-TOKEN']=token}
|
||||||
this.dropzone=new Dropzone(this.$el.get(0),uploaderOptions)
|
this.dropzone=new Dropzone(this.$el.get(0),uploaderOptions)
|
||||||
|
|
@ -307,7 +306,8 @@ progressBar.setAttribute('class','progress-bar')}
|
||||||
MediaManager.prototype.uploadQueueComplete=function(){this.$el.find('[data-command="cancel-uploading"]').addClass('hide')
|
MediaManager.prototype.uploadQueueComplete=function(){this.$el.find('[data-command="cancel-uploading"]').addClass('hide')
|
||||||
this.$el.find('[data-command="close-uploader"]').removeClass('hide')
|
this.$el.find('[data-command="close-uploader"]').removeClass('hide')
|
||||||
this.refresh()}
|
this.refresh()}
|
||||||
MediaManager.prototype.uploadSending=function(file,xhr,formData){formData.append('path',this.$el.find('[data-type="current-folder"]').val())}
|
MediaManager.prototype.uploadSending=function(file,xhr,formData){formData.append('path',this.$el.find('[data-type="current-folder"]').val())
|
||||||
|
xhr.setRequestHeader('X-OCTOBER-REQUEST-HANDLER',this.options.uploadHandler)}
|
||||||
MediaManager.prototype.uploadCancelAll=function(){this.dropzone.removeAllFiles(true)
|
MediaManager.prototype.uploadCancelAll=function(){this.dropzone.removeAllFiles(true)
|
||||||
this.hideUploadUi()}
|
this.hideUploadUi()}
|
||||||
MediaManager.prototype.updateUploadBar=function(templateName,classNames){var fileNumberLabel=this.$el.get(0).querySelector('[data-label="file-number-and-progress"]'),successTemplate=fileNumberLabel.getAttribute('data-'+templateName+'-template'),progressBar=this.$el.get(0).querySelector('[data-control="upload-progress-bar"]')
|
MediaManager.prototype.updateUploadBar=function(templateName,classNames){var fileNumberLabel=this.$el.get(0).querySelector('[data-label="file-number-and-progress"]'),successTemplate=fileNumberLabel.getAttribute('data-'+templateName+'-template'),progressBar=this.$el.get(0).querySelector('[data-control="upload-progress-bar"]')
|
||||||
|
|
@ -465,7 +465,7 @@ eventHandled=true
|
||||||
break;}
|
break;}
|
||||||
if(eventHandled){ev.preventDefault()
|
if(eventHandled){ev.preventDefault()
|
||||||
ev.stopPropagation()}}
|
ev.stopPropagation()}}
|
||||||
MediaManager.DEFAULTS={url:window.location,alias:'',uniqueId:null,deleteEmpty:'Please select files to delete.',deleteConfirm:'Delete the selected file(s)?',moveEmpty:'Please select files to move.',selectSingleImage:'Please select a single image.',selectionNotImage:'The selected item is not an image.',bottomToolbar:false,cropAndInsertButton:false}
|
MediaManager.DEFAULTS={url:window.location,uploadHandler:null,alias:'',deleteEmpty:'Please select files to delete.',deleteConfirm:'Delete the selected file(s)?',moveEmpty:'Please select files to move.',selectSingleImage:'Please select a single image.',selectionNotImage:'The selected item is not an image.',bottomToolbar:false,cropAndInsertButton:false}
|
||||||
var old=$.fn.mediaManager
|
var old=$.fn.mediaManager
|
||||||
$.fn.mediaManager=function(option){var args=Array.prototype.slice.call(arguments,1),result=undefined
|
$.fn.mediaManager=function(option){var args=Array.prototype.slice.call(arguments,1),result=undefined
|
||||||
this.each(function(){var $this=$(this)
|
this.each(function(){var $this=$(this)
|
||||||
|
|
|
||||||
|
|
@ -721,10 +721,6 @@
|
||||||
// fallback: implement method that would set a flag that the uploader is not supported by the browser
|
// fallback: implement method that would set a flag that the uploader is not supported by the browser
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.options.uniqueId) {
|
|
||||||
uploaderOptions.headers['X-OCTOBER-FILEUPLOAD'] = this.options.uniqueId
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add CSRF token to headers
|
* Add CSRF token to headers
|
||||||
*/
|
*/
|
||||||
|
|
@ -800,6 +796,7 @@
|
||||||
|
|
||||||
MediaManager.prototype.uploadSending = function(file, xhr, formData) {
|
MediaManager.prototype.uploadSending = function(file, xhr, formData) {
|
||||||
formData.append('path', this.$el.find('[data-type="current-folder"]').val())
|
formData.append('path', this.$el.find('[data-type="current-folder"]').val())
|
||||||
|
xhr.setRequestHeader('X-OCTOBER-REQUEST-HANDLER', this.options.uploadHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaManager.prototype.uploadCancelAll = function() {
|
MediaManager.prototype.uploadCancelAll = function() {
|
||||||
|
|
@ -1284,8 +1281,8 @@
|
||||||
|
|
||||||
MediaManager.DEFAULTS = {
|
MediaManager.DEFAULTS = {
|
||||||
url: window.location,
|
url: window.location,
|
||||||
|
uploadHandler: null,
|
||||||
alias: '',
|
alias: '',
|
||||||
uniqueId: null,
|
|
||||||
deleteEmpty: 'Please select files to delete.',
|
deleteEmpty: 'Please select files to delete.',
|
||||||
deleteConfirm: 'Delete the selected file(s)?',
|
deleteConfirm: 'Delete the selected file(s)?',
|
||||||
moveEmpty: 'Please select files to move.',
|
moveEmpty: 'Please select files to move.',
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
data-control="media-manager"
|
data-control="media-manager"
|
||||||
class="layout"
|
class="layout"
|
||||||
data-alias="<?= $this->alias ?>"
|
data-alias="<?= $this->alias ?>"
|
||||||
data-unique-id="<?= $this->getId() ?>"
|
data-upload-handler="<?= $this->getEventHandler('onUpload') ?>"
|
||||||
data-delete-empty="<?= e(trans('backend::lang.media.delete_empty')) ?>"
|
data-delete-empty="<?= e(trans('backend::lang.media.delete_empty')) ?>"
|
||||||
data-delete-confirm="<?= e(trans('backend::lang.media.delete_confirm')) ?>"
|
data-delete-confirm="<?= e(trans('backend::lang.media.delete_confirm')) ?>"
|
||||||
data-move-empty="<?= e(trans('backend::lang.media.move_empty')) ?>"
|
data-move-empty="<?= e(trans('backend::lang.media.move_empty')) ?>"
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,7 @@
|
||||||
<div data-control="toolbar">
|
<div data-control="toolbar">
|
||||||
<?php if (!$this->readOnly) : ?>
|
<?php if (!$this->readOnly) : ?>
|
||||||
<div class="btn-group offset-right">
|
<div class="btn-group offset-right">
|
||||||
<button type="button" class="btn btn-primary oc-icon-upload" data-control="upload"
|
<button type="button" class="btn btn-primary oc-icon-upload" data-control="upload"><?= e(trans('backend::lang.media.upload')) ?></button>
|
||||||
><?= e(trans('backend::lang.media.upload')) ?></button>
|
|
||||||
<button type="button" class="btn btn-primary oc-icon-folder" data-command="create-folder"><?= e(trans('backend::lang.media.add_folder')) ?></button>
|
<button type="button" class="btn btn-primary oc-icon-folder" data-command="create-folder"><?= e(trans('backend::lang.media.add_folder')) ?></button>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
@ -14,10 +13,8 @@
|
||||||
|
|
||||||
<?php if (!$this->readOnly) : ?>
|
<?php if (!$this->readOnly) : ?>
|
||||||
<div class="btn-group offset-right">
|
<div class="btn-group offset-right">
|
||||||
<button type="button" class="btn btn-default oc-icon-reply-all" data-command="move"
|
<button type="button" class="btn btn-default oc-icon-reply-all" data-command="move"><?= e(trans('backend::lang.media.move')) ?></button>
|
||||||
><?= e(trans('backend::lang.media.move')) ?></button>
|
<button type="button" class="btn btn-default oc-icon-trash" data-command="delete"><?= e(trans('backend::lang.media.delete')) ?></button>
|
||||||
<button type="button" class="btn btn-default oc-icon-trash" data-command="delete"
|
|
||||||
><?= e(trans('backend::lang.media.delete')) ?></button>
|
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue