├── .gitignore ├── Module.php ├── README.md ├── assets ├── BaseUploadAsset.php ├── DisplayWidgetAsset.php ├── FileAPIAsset.php ├── JCropAsset.php ├── UploadAsset.php ├── css │ ├── fp_display.css │ └── fp_upload.css ├── img │ ├── file-icon.png │ ├── rotate.png │ ├── userpic.gif │ └── webcam.png └── js │ ├── fp_base.js │ ├── fp_multi_upload.js │ └── fp_single_upload.js ├── behaviours └── ConnectFileSequence.php ├── components ├── ImageColumn.php └── WatermarkFilter.php ├── composer.json ├── controllers └── BaseController.php ├── helpers ├── AccessControl.php ├── FileHelper.php └── VariationHelper.php ├── messages ├── en │ └── fp.php ├── ru │ └── fp.php └── tr_TR │ └── fp.php ├── migrations ├── m140516_113603_create_file_storage_table.php └── m150111_173823_add_index.php ├── models └── Uploads.php ├── variations_default.php ├── vendor ├── FileAPI.php ├── MimeTypeExtensions.php └── assets │ ├── Sortable │ ├── .gitignore │ ├── Gruntfile.js │ ├── README.md │ ├── Sortable.js │ ├── Sortable.min.js │ ├── bower.json │ ├── component.json │ ├── index.html │ ├── package.json │ └── st │ │ └── logo.png │ └── jquery.fileapi │ ├── .DS_Store │ ├── statics │ └── .DS_Store │ └── tests │ ├── .DS_Store │ └── grunt-task │ └── .DS_Store ├── views └── base │ └── index.php └── widgets ├── BaseUploadWidget.php ├── DisplayWidget.php ├── MultiUploadWidget.php ├── SingleUploadWidget.php └── views ├── display_widget.php ├── multi_upload_widget.php ├── single_upload_widget.php └── single_upload_widget_simple.php /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # composer itself is not needed 16 | composer.phar 17 | 18 | # Mac DS_Store Files 19 | .DS_Store 20 | 21 | # phpunit itself is not needed 22 | phpunit.phar 23 | # local phpunit config 24 | /phpunit.xml 25 | -------------------------------------------------------------------------------- /Module.php: -------------------------------------------------------------------------------- 1 | variations_config = ArrayHelper::merge($this->variations_config, $default); 39 | 40 | $this->registerTranslations(); 41 | } 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function beforeAction($action) 47 | { 48 | if (!parent::beforeAction($action)) { 49 | return false; 50 | } 51 | 52 | return true; 53 | } 54 | 55 | /** 56 | * Register widget translations. 57 | */ 58 | public function registerTranslations() 59 | { 60 | Yii::$app->i18n->translations['deanar/fileProcessor*'] = [ 61 | 'class' => 'yii\i18n\PhpMessageSource', 62 | // 'sourceLanguage' => 'en-US', 63 | 'basePath' => '@deanar/fileProcessor/messages', 64 | 'fileMap' => [ 65 | 'deanar/fileProcessor' => 'fp.php', 66 | ], 67 | 'forceTranslation' => true 68 | ]; 69 | } 70 | 71 | /** 72 | * Translate shortcut 73 | * 74 | * @param $message 75 | * @param array $params 76 | * @param null $language 77 | * 78 | * @return string 79 | */ 80 | public static function t($message, $params = [], $language = null) 81 | { 82 | return \Yii::t('deanar/fileProcessor', $message, $params, $language); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # File Processor (Yii2 Extension) 2 | 3 | Upload and process files and images. 4 | 5 | Based on jquery.fileapi [Link to github](https://github.com/RubaXa/jquery.fileapi) 6 | 7 | ## Installation 8 | 9 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 10 | 11 | Either run 12 | 13 | ``` 14 | php composer.phar require --prefer-dist deanar/yii2-file-processor:"0.1.*" 15 | ``` 16 | 17 | or add 18 | 19 | ``` 20 | "deanar/yii2-file-processor": "0.1.*" 21 | ``` 22 | 23 | to the require section of your `composer.json` file and update composer dependencies; 24 | 25 | If installation fails, try to use minimum stability: dev. 26 | 27 | Then run migrations 28 | 29 | ```bash 30 | ./yii migrate/up --migrationPath=@deanar/fileProcessor/migrations 31 | ``` 32 | 33 | Include module into your web config 34 | 35 | ```php 36 | 'modules' => [ 37 | 'fp' => [ 38 | 'class' => 'deanar\fileProcessor\Module', 39 | //'image_driver' => \deanar\fileProcessor\models\Uploads::IMAGE_DRIVER_GD, 40 | 'variations_config' => require(__DIR__ . '/file_processor_variations.php'), 41 | //'root_path' => '@frontend/web', // default: @webroot 42 | //'root_url' => 'http://front.example.com', // default: current host (Yii::$app->request->getHostInfo()) 43 | 'upload_dir' => 'uploads', 44 | //'default_quality' => 95, 45 | //'default_resize_mod' => 'outbound', 46 | //'unlink_files' => true, 47 | //'debug' => true, // FileAPI debug. false by default 48 | ], 49 | ] 50 | ``` 51 | 52 | Attach behavior to your model 53 | 54 | ```php 55 | public function behaviors() 56 | { 57 | return [ 58 | 'fileSequence' => [ 59 | 'class' => \deanar\fileProcessor\behaviours\ConnectFileSequence::className(), 60 | 'defaultType' => 'projects', 61 | 'registeredTypes' => ['projects', 'files'], // or 'projects, files' as string 62 | ] 63 | ]; 64 | } 65 | ``` 66 | 67 | Create file `file_processor_variations.php` in config directory and configure image variations like: 68 | 69 | ```php 70 | use deanar\fileProcessor\components\WatermarkFilter; 71 | 72 | return [ 73 | 'projects' => [ 74 | '_original' => false, 75 | 'thumb' => [200, 150, 'inset'], 76 | 'small' => [300, 200, 'outbound', 75], 77 | 'big' => [ 78 | 'width' => 600, 79 | 'height' => 350, 80 | 'mode' => 'outbound', 81 | 'quality' => 75, 82 | 'watermark' => [ 83 | 'path' => 'watermark.png', 84 | 'position' => WatermarkFilter::WM_POSITION_BOTTOM_RIGHT, 85 | 'margin' => 10, 86 | ] 87 | ], 88 | ], 89 | 'article_header' => [ 90 | '_original' => true, 91 | 'thumb' => [200, 150, 'inset'], 92 | ], 93 | 'avatar_picture' => [ 94 | '_original' => true, 95 | 'preview' => [200, 200, 'outbound'], 96 | 97 | // For single file uploads. Automatically will be updated 'avatar' attribute in 'Project' model 98 | // with of currently uploaded file 99 | '_insert' => ['app\models\Project' => 'avatar'], 100 | 101 | // variants of access control definitions 102 | '_acl' => '*', // * - all users, like without _acl 103 | '_acl' => '@', // @ - authenticated users only 104 | '_acl' => ['users' => ['admin', 'user1']], // defined list of users 105 | '_acl' => ['app\models\Project' => 'user_id'], // if current user id equals to `user_id` attribute of model `app\models\Project` 106 | '_acl' => function ($type_id, $user_id) { // callable check 107 | return \app\models\Project::findOne($type_id)->user_id == $user_id; 108 | }, 109 | 110 | ], 111 | 112 | // Used if no variation with specified name found 113 | '_default' => [ ], 114 | 115 | // Mixin for all variations. Used by merging arrays. 116 | '_all' => [ ], 117 | ]; 118 | ``` 119 | 120 | **NB!** Don't forget to disable php execution in your upload dir. 121 | For example: If you use Apache web server, you can create `.htaccess` file in the root of upload directory with the following code inside: 122 | 123 | ``` 124 | RemoveHandler .php 125 | AddType text/html .php 126 | ``` 127 | 128 | ## Upgrade instruction 129 | 130 | Run migrations 131 | 132 | ```bash 133 | ./yii migrate/up --migrationPath=@deanar/fileProcessor/migrations 134 | ``` 135 | 136 | In ConnectFileSequence behaviour replace `deleteTypes` property to `registeredTypes`. 137 | 138 | ## Usage 139 | 140 | Once the extension is installed, simply use it in your form by adding widget code to view: 141 | 142 | Multi upload widget: 143 | 144 | ```php 145 | 'projects', 147 | 'type_id' => $model->id, 148 | 149 | 'options' => [ 150 | 'autoUpload' => true, 151 | 'multiple' => true, 152 | 'accept' => 'image/*,application/zip', 153 | 'duplicate' => false, 154 | 'maxSize' => '2M', // you can use 'M', 'K', 'G' or simple size in bytes 155 | 'maxFiles' => 3, 156 | 'imageSize' => [ 157 | 'minWidth' => 150, 158 | 'maxWidth' => 2000, 159 | 'minHeight' => 150, 160 | 'maxHeight' => 2000, 161 | ], 162 | ], 163 | 164 | 'htmlOptions' => [ 165 | 'class' => 'additional-class', 166 | 'data-attribute' => 'value', 167 | ], 168 | 169 | ]) ?> 170 | ``` 171 | 172 | Single upload widget: 173 | 174 | ```php 175 | 'projects', 177 | 'type_id' => $model->id, 178 | 179 | 'crop' => true, 180 | 'preview' => true, 181 | 'previewSize' => [200,200], 182 | 183 | 'options' => [ 184 | 'accept' => 'image/*', 185 | 'maxSize' => '2M', // you can use 'M', 'K', 'G' or simple size in bytes 186 | 'imageSize' => [ 187 | 'minWidth' => 150, 188 | 'maxWidth' => 2000, 189 | 'minHeight' => 150, 190 | 'maxHeight' => 2000, 191 | ], 192 | ], 193 | 194 | 'htmlOptions' => [ 195 | 'class' => 'additional-class', 196 | 'data-attribute' => 'value', 197 | ], 198 | 199 | ]) ?> 200 | ``` 201 | 202 | If `preview` is set to `false`, `crop` automatically set to `false` and will be very simple upload widget. 203 | If crop set to `true`, `accept` option automatically set to `'image/*'`. 204 | For single upload without crop, `autoUpload` automatically set to `true`. 205 | 206 | To setup size of window and minimum size of crop area use `previewSize` property. Default is `[200,200]`. 207 | 208 | `imageAutoOrientation` option is set to `false` by default 209 | 210 | --- 211 | 212 | You can access your images\files by: 213 | 214 | ```php 215 | $model = ExampleModel::findOne(1); 216 | $uploads = $model->getFiles(); 217 | 218 | foreach($uploads as $u){ 219 | echo $u->imgTag('thumb2', true,['style'=>'border:1px solid red;']); 220 | //or just url (for files/download links) 221 | echo \yii\helpers\Html::a($u->original, $u->getPublicFileUrl('original', true)); 222 | } 223 | ``` 224 | 225 | You can filter files like this: 226 | ```php 227 | $uploads = $model->imagesOnly()->getFiles(); 228 | // or 229 | $uploads = $model->filesOnly()->getFiles(); 230 | ``` 231 | 232 | You can fetch first file in the row: 233 | ```php 234 | $uploads = $model->getFirstFile(); 235 | ``` 236 | 237 | You can display your images\files in the `GridView`. 238 | 239 | Add in the column list: 240 | 241 | ```php 242 | [ 243 | 'class' => 'deanar\fileProcessor\components\ImageColumn', 244 | 'header' => 'Image', // optional 245 | 'empty' => 'No Image', // optional 246 | 'type' => 'projects', // optional, default value goes from behavior options 247 | 'variation' => '_thumb', 248 | 'htmlOptions' => [], // optional 249 | ], 250 | ``` 251 | 252 | You can display list of your images\files anywhere else via `DisplayWidget`, e.g. in `DetailView` widget or just in the view. 253 | 254 | Case with `DetailView`: 255 | 256 | ```php 257 | 'attributes' => [ 258 | 'id', 259 | 'title', 260 | ... 261 | [ 262 | 'attribute'=>'Images', 263 | 'value'=>\deanar\fileProcessor\widgets\DisplayWidget::widget(['type'=>'projects','type_id'=>$model->id,'variation'=>'_thumb']), 264 | 'format'=>'raw', 265 | ], 266 | ... 267 | 'text', 268 | ], 269 | ``` 270 | 271 | All properties of DisplayWidget are required. 272 | 273 | 274 | ## TODOs and progress 275 | 276 | - Special widget for single file uploads [*****] 277 | - Access control system [*****] 278 | - Internationalization (EN + RU) [*****] 279 | - More customization [**---] 280 | - Crop and other features of jquery.fileapi [****-] 281 | - API for upload files by url or by path [-----] 282 | - Console commands for generating new image variations [-----] 283 | - Admin interface for viewing and editing all uploaded files [-----] 284 | - Mode for generating image variations on the fly (by request) [-----] 285 | - Mode for generating image variations in background [-----] 286 | - Advanced variation features: watermarks, cropping, rotation etc. [***--] 287 | - Beautiful alerts (e.g. http://rubaxa.github.io/Ply/) [-----] 288 | - Refactoring [*----] 289 | 290 | 291 | ## Changelog 292 | 293 | ======= 294 | ### 0.1.4 (2016-06-05) 295 | * Turkish language support (Thanks to https://github.com/fg) 296 | 297 | ### 0.1.3 (2016-05-19) 298 | * Possibility to save models in console 299 | 300 | ### 0.1.2 (2015-08-18) 301 | * Bug: Several single-upload widgets with crop 302 | * Bug: Removing image preview in single-upload widget on delete 303 | 304 | ### 0.1.1 (2015-07-16) 305 | * `imageAutoOrientation` option is set to `false` by default 306 | 307 | ### 0.1.0 (2015-03-10) 308 | * First tagged version. 309 | -------------------------------------------------------------------------------- /assets/BaseUploadAsset.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | 7 | var file_processor = file_processor || { 8 | languageMessages: {}, 9 | 10 | showValidationErrors: function(evt, data){ 11 | setTimeout(function () { // don't remember why i use timeout, maybe error does not raise without it 12 | $(data.all).each(function (i, file) { 13 | if (file.$el === undefined) { 14 | file_processor.showValidationErrorsByFile(file); 15 | } else { 16 | file.$el.removeClass('js-sort'); 17 | } 18 | }); 19 | }, 300); 20 | }, 21 | 22 | showValidationErrorsByFile: function (file) { 23 | var errors = file.errors; 24 | 25 | if (errors === undefined) return true; 26 | 27 | // count and size 28 | if (errors.maxFiles) this.raiseError(file_processor.getMessage('MAX_FILES', {filename: file.name})); 29 | if (errors.maxSize) this.raiseError(file_processor.getMessage('MAX_SIZE', {filename: file.name, maxSize: this.bytesToSize(errors.maxSize) })); 30 | 31 | // min dimension 32 | if (errors.minWidth) this.raiseError(file_processor.getMessage('MIN_WIDTH', {filename: file.name, minWidth: errors.minWidth})); 33 | if (errors.minHeight) this.raiseError(file_processor.getMessage('MIN_HEIGHT', {filename: file.name, minHeight: errors.minHeight})); 34 | 35 | // max dimension 36 | if (errors.maxWidth) this.raiseError(file_processor.getMessage('MAX_WIDTH', {filename: file.name, maxWidth: errors.maxWidth})); 37 | if (errors.maxHeight) this.raiseError(file_processor.getMessage('MAX_HEIGHT', {filename: file.name, maxHeight: errors.maxHeight})); 38 | }, 39 | 40 | raiseError: function (msg) { 41 | //console.log(msg); 42 | alert(msg); 43 | }, 44 | 45 | // used decimal, not binary 46 | bytesToSize: function (bytes) { 47 | if (bytes == 0) return '0 Byte'; 48 | var k = 1000; 49 | var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; 50 | var i = Math.floor(Math.log(bytes) / Math.log(k)); 51 | return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i]; 52 | }, 53 | 54 | addMessages: function(message_array){ 55 | jQuery.extend(file_processor.languageMessages, message_array); 56 | }, 57 | 58 | getMessage: function(key, options){ 59 | if(file_processor.languageMessages){ 60 | var message = file_processor.languageMessages[key] || key; 61 | if(options){ 62 | for( var arg in options ) { 63 | message = message.replace("{" + arg + "}", options[arg]); 64 | } 65 | } 66 | return message; 67 | } 68 | return key; 69 | } 70 | }; 71 | 72 | -------------------------------------------------------------------------------- /assets/js/fp_multi_upload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by deanar on 24/12/14. 3 | * @author Mikhail Razumovskiy 4 | */ 5 | 6 | 7 | file_processor.multi_upload = function (settings) { 8 | 9 | /* 10 | settings = { 11 | 'identifier' => '', 12 | 'uploadUrl' => '', 13 | 'removeUrl' => '', 14 | 'sortUrl' => '', 15 | 'additionalData' => {}, 16 | 'alreadyUploadedFiles' => [], 17 | 'options' => {}, 18 | } 19 | */ 20 | 21 | var uploadContainer = $('#' + settings.identifier); 22 | 23 | var fileapi_options = { 24 | url: settings.uploadUrl, 25 | data: settings.additionalData, 26 | 27 | // Restores the list of files uploaded earlier. 28 | files: settings.alreadyUploadedFiles, 29 | 30 | // Events 31 | onSelect: function (evt, data) { 32 | file_processor.showValidationErrors(evt, data); 33 | }, 34 | 35 | // Remove a file from the upload queue 36 | onFileRemove: function (evt, file) { 37 | if (!confirm(file_processor.getMessage('REMOVE_FROM_QUEUE_CONFIRM'))) { 38 | // Cancel remove 39 | evt.preventDefault(); 40 | } 41 | }, 42 | 43 | onFileComplete: function (evt, uiEvt) { 44 | var file = uiEvt.file; 45 | var images = uiEvt.result.images; 46 | 47 | if (images === undefined || images.length < 1) { 48 | if(FileAPI.debug){ 49 | if(uiEvt.result.errors === undefined){ 50 | file_processor.raiseError(file_processor.getMessage('UPLOAD_ERROR_DETAILED', {errors: uiEvt.error})); 51 | }else { 52 | file_processor.raiseError(file_processor.getMessage('UPLOAD_ERROR_DETAILED', {errors: uiEvt.result.errors.join(', ')})); 53 | } 54 | }else{ 55 | file_processor.raiseError(file_processor.getMessage('UPLOAD_ERROR')); 56 | } 57 | uploadContainer.fileapi("remove", file); 58 | } else { 59 | var filedata = images.filedata; 60 | 61 | file.data = { 62 | id: filedata.id, 63 | type: filedata.type, 64 | type_id: filedata.type_id 65 | }; 66 | 67 | file.$el.addClass('js-sort'); 68 | } 69 | }, 70 | 71 | 72 | onFileRemoveCompleted: function (evt, file) { 73 | evt.preventDefault(); 74 | 75 | file.$el 76 | .attr('disabled', true) 77 | .addClass('my_disabled') 78 | ; 79 | 80 | if (confirm(file_processor.getMessage('REMOVE_FILE_WITH_NAME_CONFIRM', {filename: file.name}))) { 81 | $.post(settings.removeUrl, file.data) 82 | .done(function (data) { 83 | //TODO indication 84 | uploadContainer.fileapi("remove", file); 85 | }) 86 | .fail(function (data) { 87 | if (FileAPI.debug) { 88 | file_processor.raiseError(file_processor.getMessage('REMOVE_FILE_ERROR_DETAILED', {errors: data.responseText})); 89 | } else { 90 | file_processor.raiseError(file_processor.getMessage('REMOVE_FILE_ERROR')); 91 | } 92 | }); 93 | } else { 94 | file.$el 95 | .attr('disabled', false) 96 | .removeClass('my_disabled') 97 | ; 98 | } 99 | }, 100 | 101 | elements: { 102 | ctrl: {upload: '.js-upload'}, 103 | empty: {show: '.b-upload__hint'}, 104 | emptyQueue: {hide: '.js-upload', show: '.fp-dragndrop-hint' }, 105 | list: '.js-files', 106 | file: { 107 | tpl: '.js-file-tpl', 108 | preview: { 109 | el: '.b-thumb__preview', 110 | width: 80, 111 | height: 80 112 | }, 113 | upload: {show: '.progress-upload', hide: '.b-thumb__rotate'}, 114 | complete: {hide: '.progress-upload', show: '.b-thumb__del'}, 115 | progress: '.progress-upload .bar' 116 | }, 117 | dnd: { 118 | el: '.b-upload__dnd', 119 | hover: 'b-upload__dnd_hover', 120 | fallback: '.b-upload__dnd-not-supported' 121 | } 122 | } 123 | 124 | }; 125 | 126 | $.extend(fileapi_options, settings.options); 127 | uploadContainer.fileapi(fileapi_options); 128 | 129 | /* 130 | File sorting 131 | */ 132 | 133 | var drag_list_container = uploadContainer.find('ul').get(0); 134 | 135 | var sort = new Sortable(drag_list_container, { 136 | handle: ".b-thumb__preview", // Restricts sort start click/touch to the specified element 137 | draggable: ".js-sort", // Specifies which items inside the element should be sortable 138 | ghostClass: "sortable-ghost", 139 | onUpdate: function (evt) { 140 | var sort = []; 141 | 142 | uploadContainer.find('li').each(function (i, el) { 143 | var filedata_id = $(el).data('id'); 144 | var file_data = uploadContainer.fileapi('_getFile', filedata_id).data; 145 | if (file_data !== undefined) { 146 | sort.push(file_data.id); 147 | } 148 | }); 149 | 150 | $.ajax({ 151 | url: settings.sortUrl, 152 | data: {sort: sort}, 153 | type: "POST", 154 | error: function (data, status, e) { 155 | file_processor.raiseError(file_processor.getMessage('ORDER_SAVE_ERROR')); 156 | } 157 | }); 158 | 159 | //var item = evt.item; // the current dragged HTMLElement 160 | } 161 | }); 162 | 163 | }; -------------------------------------------------------------------------------- /assets/js/fp_single_upload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by deanar on 25/12/14. 3 | * @author Mikhail Razumovskiy 4 | */ 5 | 6 | 7 | file_processor.single_upload = function (settings) { 8 | 9 | /* 10 | settings = { 11 | 'identifier' => '', 12 | 'uploadUrl' => '', 13 | 'removeUrl' => '', 14 | 'additionalData' => {}, 15 | 'alreadyUploadedFiles' => [], 16 | 'options' => {}, 17 | 'crop' => true, 18 | 'preview' => true 19 | } 20 | */ 21 | 22 | var uploadContainer = $('#' + settings.identifier); 23 | 24 | uploadContainer.width(settings.previewSize[0]); 25 | uploadContainer.height(settings.previewSize[1]); 26 | 27 | uploadContainer.find('.js-controls').css('margin-top', Math.min(parseInt(settings.previewSize[1] * (167 / 200) - 17), settings.previewSize[1] - 40)); 28 | 29 | uploadContainer.find('div.js-delete').on('click', function(){ 30 | 31 | if (!confirm(file_processor.getMessage('REMOVE_FILE_CONFIRM'))) return; 32 | 33 | var delete_array = []; 34 | if( typeof uploadContainer.fileapi('widget').files !== "undefined"){ 35 | 36 | var files_length = uploadContainer.fileapi('widget').files.length-1; 37 | for (var file=files_length; file >= 0; file--){ 38 | delete_array.push([uploadContainer.fileapi('widget').files[file]]); 39 | } 40 | 41 | for (i in delete_array) { 42 | $.post(settings.removeUrl, delete_array[i][0].data) 43 | .done(function (data) { 44 | //TODO indication 45 | uploadContainer.fileapi('remove', delete_array[i][0]); 46 | uploadContainer.find('.js-preview').empty(); 47 | }) 48 | .fail(function (data) { 49 | if(FileAPI.debug) { 50 | file_processor.raiseError(file_processor.getMessage('REMOVE_FILE_ERROR_DETAILED', {errors: data.responseText})); 51 | }else{ 52 | file_processor.raiseError(file_processor.getMessage('REMOVE_FILE_ERROR')); 53 | } 54 | }); 55 | } 56 | } 57 | }); 58 | 59 | var fileapi_options = { 60 | url: settings.uploadUrl, 61 | data: settings.additionalData, 62 | 63 | // Restores the list of files uploaded earlier. 64 | files: settings.alreadyUploadedFiles, 65 | 66 | elements: { 67 | //active: { show: '.js-upload', hide: '.js-browse' }, 68 | active: { show: '.js-upload' }, 69 | //complete: { hide: '.js-browse', show: '.js-delete'}, 70 | empty: { 71 | show: '.js-browse', 72 | hide: '.js-delete' 73 | }, 74 | 75 | preview: { 76 | el: '.js-preview', 77 | width: settings.previewSize[0], 78 | height: settings.previewSize[1] 79 | }, 80 | progress: '.js-progress' 81 | }, 82 | onSelect: function (evt, ui){ 83 | 84 | file_processor.showValidationErrors(evt, ui); 85 | 86 | var file = ui.files[0]; 87 | 88 | if( !FileAPI.support.transform ) { 89 | file_processor.raiseError(file_processor.getMessage('FLASH_NOT_SUPPORTED')); 90 | } 91 | else if( file ){ 92 | 93 | var bootstrap = true; 94 | if (bootstrap) { 95 | 96 | var modal_html = $('#fp_single_upload_modal_bs_' + settings.identifier).html(); 97 | var modal_selector = '#' + settings.identifier + '_modal'; 98 | 99 | $(modal_selector).remove(); 100 | $(modal_html).appendTo('body'); 101 | $(modal_selector).on('show.bs.modal', function (event) { 102 | 103 | var modal = $(this); 104 | 105 | modal.find('.js-upload').on('click', function () { 106 | modal.modal('hide'); 107 | uploadContainer.fileapi('upload'); 108 | }); 109 | 110 | $('.js-img', modal).cropper({ 111 | file: file, 112 | bgColor: '#fff', 113 | maxSize: [$(window).width() - 100, $(window).height() - 100], 114 | minSize: settings.previewSize, 115 | selection: '90%', 116 | onSelect: function (coords) { 117 | uploadContainer.fileapi('crop', file, coords); 118 | } 119 | }); 120 | 121 | }) 122 | .modal({keyboard: false, backdrop: 'static'}) 123 | .modal('show'); 124 | 125 | } else { 126 | 127 | $('#popup').modal({ 128 | closeOnEsc: true, 129 | closeOnOverlayClick: false, 130 | onOpen: function (overlay) { 131 | 132 | console.log(overlay); 133 | 134 | $(overlay).on('click', '.js-upload', function () { 135 | $.modal().close(); 136 | uploadContainer.fileapi('upload'); 137 | }); 138 | 139 | $('.js-img', overlay).cropper({ 140 | file: file, 141 | bgColor: '#fff', 142 | maxSize: [$(window).width() - 100, $(window).height() - 100], 143 | minSize: [200, 200], 144 | selection: '90%', 145 | onSelect: function (coords) { 146 | uploadContainer.fileapi('crop', file, coords); 147 | } 148 | }); 149 | } 150 | }).open(); 151 | } // no bootstrap 152 | } 153 | }, 154 | onFileComplete: function (evt, uiEvt) { 155 | var file = uiEvt.file; 156 | var images = uiEvt.result.images; 157 | 158 | if (images === undefined || images.length < 1) { 159 | if(FileAPI.debug){ 160 | if(uiEvt.result.errors === undefined){ 161 | file_processor.raiseError(file_processor.getMessage('UPLOAD_ERROR_DETAILED', {errors: uiEvt.error})); 162 | }else { 163 | file_processor.raiseError(file_processor.getMessage('UPLOAD_ERROR_DETAILED', {errors: uiEvt.result.errors.join(', ')})); 164 | } 165 | }else{ 166 | file_processor.raiseError(file_processor.getMessage('UPLOAD_ERROR')); 167 | } 168 | uploadContainer.fileapi("remove", file); 169 | uploadContainer.find('.js-preview').empty(); 170 | } else { 171 | var json = images.filedata; 172 | 173 | file.data = { 174 | id: json.id, 175 | type: json.type, 176 | type_id: json.type_id 177 | }; 178 | 179 | uploadContainer.find('div.js-delete input').val(json.id); 180 | } 181 | } 182 | 183 | }; 184 | 185 | $.extend(fileapi_options, settings.options); 186 | 187 | if(!settings.crop) 188 | fileapi_options.onSelect = function(evt, ui){ 189 | file_processor.showValidationErrors(evt, ui); 190 | }; 191 | 192 | if(!settings.preview) 193 | fileapi_options.elements = { 194 | //ctrl: { upload: '.js-send', reset: '.js-reset' }, // maybe later 195 | name: '.js-name', 196 | size: '.js-size', 197 | empty: { show: '.js-browse', hide: '.js-info, .js-delete' } 198 | }; 199 | 200 | uploadContainer.fileapi(fileapi_options); 201 | }; -------------------------------------------------------------------------------- /behaviours/ConnectFileSequence.php: -------------------------------------------------------------------------------- 1 | registeredTypes)){ //TODO for back compatibility. Remove in next release. 31 | $this->registeredTypes = $this->deleteTypes; 32 | } 33 | 34 | if( is_string($this->registeredTypes) ){ 35 | $this->registeredTypes = empty($this->registeredTypes) ? [] : array_filter(explode(',', str_replace(' ', '', $this->registeredTypes))); 36 | } 37 | } 38 | 39 | public function events() 40 | { 41 | return [ 42 | ActiveRecord::EVENT_AFTER_UPDATE => 'updateFileIdInOwnerModel', 43 | ActiveRecord::EVENT_AFTER_INSERT => 'updateSequence', 44 | ActiveRecord::EVENT_BEFORE_DELETE => 'deleteSequence', 45 | ]; 46 | } 47 | 48 | public function deleteSequence($event) 49 | { 50 | $type_id = $this->owner->getPrimaryKey(); 51 | $types = $this->registeredTypes; 52 | 53 | $files = Uploads::find()->where([ 54 | 'type' => $types, // Array of types or single type as string 55 | 'type_id' => $type_id, 56 | ] 57 | )->all(); 58 | 59 | foreach($files as $file){ 60 | /** @var Uploads $file */ 61 | $file->removeFile(); 62 | } 63 | } 64 | 65 | public function updateSequence($event){ 66 | if(Yii::$app->request->isConsoleRequest) return; 67 | 68 | $type_id = $this->owner->getPrimaryKey(); 69 | $hashes = Yii::$app->request->post('fp_hash', false); 70 | 71 | if ($hashes != false && is_array($hashes)) { 72 | foreach($hashes as $hash){ 73 | // fetch one record to determine `type` of upload 74 | $uploadExample = Uploads::find()->select(['type'])->where(['hash' => $hash])->one(); 75 | if(count($uploadExample) > 0) { 76 | $type = $uploadExample->getAttribute('type'); 77 | $acl = VariationHelper::getAclOfType($type); 78 | 79 | if(AccessControl::checkAccess($acl, $type_id)) { 80 | // all right, attach uploads 81 | Uploads::updateAll(['type_id' => $type_id], 'hash=:hash', [':hash' => $hash]); 82 | }else{ 83 | // no access, delete uploaded files 84 | Uploads::deleteAll('hash=:hash', [':hash' => $hash]); 85 | } 86 | } 87 | } 88 | } 89 | 90 | $this->updateFileIdInOwnerModel($event); 91 | } 92 | 93 | public function updateFileIdInOwnerModel($event){ 94 | $type_id = $this->owner->getPrimaryKey(); 95 | 96 | $configs = VariationHelper::getRawConfig(); 97 | foreach($configs as $type => $config){ 98 | if( ! in_array($type, $this->registeredTypes) ) continue; 99 | 100 | if (isset($config['_insert'])) { 101 | $attribute = array_shift($config['_insert']); 102 | 103 | $files = Uploads::findByReference($type, $type_id); 104 | 105 | if(!is_null($files)){ 106 | $file = array_shift($files); 107 | 108 | //TODO replace with ActiveRecord::updateAttributes() to avoid loops 109 | if($this->owner->getAttribute($attribute) !== $file->id) { 110 | $this->owner->setAttribute($attribute, $file->id); 111 | $this->owner->save(); // one more loop 112 | } 113 | } 114 | } 115 | } 116 | 117 | } 118 | 119 | /* 120 | * Access methods 121 | */ 122 | 123 | public function imagesOnly(){ 124 | $this->selectFileType = self::SELECT_IMAGES; 125 | return $this; 126 | } 127 | 128 | public function filesOnly(){ 129 | $this->selectFileType = self::SELECT_FILES; 130 | return $this; 131 | } 132 | 133 | 134 | public function getFiles($type=null,$selectFileType=null) 135 | { 136 | if($type === null) $type = $this->defaultType; 137 | 138 | if(!is_null($selectFileType)) $this->selectFileType = $selectFileType; 139 | 140 | switch ($this->selectFileType) { 141 | case self::SELECT_IMAGES: 142 | $condition = 'width IS NOT NULL'; 143 | break; 144 | case self::SELECT_FILES: 145 | $condition = 'width IS NULL'; 146 | break; 147 | default: 148 | $condition = ''; 149 | } 150 | 151 | return $this->owner->hasMany( Uploads::className(), ['type_id' => $this->owner->primaryKey()[0]] ) 152 | ->andOnCondition('type =:type',[':type' => $type]) 153 | ->where($condition) 154 | ->orderBy('ord')->all(); 155 | } 156 | 157 | public function getFirstFile($type=null){ 158 | return isset($this->getFiles($type)[0]) ? $this->getFiles($type)[0] : new Uploads(); 159 | } 160 | } -------------------------------------------------------------------------------- /components/ImageColumn.php: -------------------------------------------------------------------------------- 1 | [ 20 | * // ... 21 | * [ 22 | * 'class' => 'deanar\fileProcessor\components\ImageColumn', 23 | * 'variation' => '_thumb', 24 | * // you may configure additional properties here 25 | * 'header' => 'Image', 26 | * 'empty' => 'No Image', 27 | * 'type' => 'projects', 28 | * ], 29 | * ] 30 | * ``` 31 | * 32 | * @author deanar 33 | */ 34 | class ImageColumn extends Column 35 | { 36 | //TODO lightbox with full preview 37 | 38 | public $header = '#'; //TODO add header value to locales 39 | public $empty = 'No image'; //TODO add default value and add to locales 40 | public $type = null; 41 | public $variation = null; // Maybe set default variation to '_thumb' 42 | public $htmlOptions = []; 43 | 44 | /** 45 | * @inheritdoc 46 | */ 47 | protected function renderDataCellContent($model, $key, $index) 48 | { 49 | $imageTag = $model->getFirstFile($this->type)->imgTag($this->variation, true, $this->htmlOptions); 50 | return $imageTag ? $imageTag : $this->empty; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /components/WatermarkFilter.php: -------------------------------------------------------------------------------- 1 | open('testimage.jpg'); 11 | * 12 | * $image = $image->thumbnail(new Box(320, 320), ImageInterface::THUMBNAIL_INSET); 13 | * 14 | * $filter = new WatermarkFilter($imagine, 'wm.png', WatermarkFilter::WM_POSITION_BOTTOM_RIGHT, 5); 15 | * $image = $filter->apply($image); 16 | * 17 | * $image->save('testimage2.jpg'); 18 | * 19 | * Class WatermarkFilter 20 | * @package deanar\fileProcessor\components 21 | */ 22 | 23 | class WatermarkFilter implements \Imagine\Filter\FilterInterface 24 | { 25 | const WM_POSITION_TOP_LEFT = 'tl'; 26 | const WM_POSITION_TOP_RIGHT = 'tr'; 27 | const WM_POSITION_BOTTOM_LEFT = 'bl'; 28 | const WM_POSITION_BOTTOM_RIGHT = 'br'; 29 | const WM_POSITION_CENTER = 'center'; 30 | 31 | private $imagine; 32 | private $wm_position; 33 | private $wm_margin; 34 | private $wm_path; 35 | 36 | private $pos_vertical = 0; 37 | private $pos_horizontal = 0; 38 | 39 | public function __construct(\Imagine\Image\ImagineInterface $imagine, $wm_path, $wm_position=self::WM_POSITION_BOTTOM_RIGHT, $wm_margin=5) 40 | { 41 | $this->imagine = $imagine; 42 | 43 | $this->wm_path = $wm_path; 44 | $this->wm_position = $wm_position; 45 | $this->wm_margin = $wm_margin >= 0 ? $wm_margin : 0; 46 | } 47 | 48 | /** 49 | * @inheritdoc 50 | * @param \Imagine\Image\ImageInterface $image 51 | * @return \Imagine\Image\ImageInterface|\Imagine\Image\ManipulatorInterface 52 | */ 53 | public function apply(\Imagine\Image\ImageInterface $image) 54 | { 55 | if(!file_exists($this->wm_path)) return $image; 56 | \Yii::warning('Watermark does not exists: '. $this->wm_path); 57 | 58 | $watermark = $this->imagine->open($this->wm_path); 59 | 60 | $size = $image->getSize(); 61 | $wm_size = $watermark->getSize(); 62 | 63 | // Horizontal position 64 | switch ($this->wm_position) { 65 | case self::WM_POSITION_TOP_LEFT: 66 | case self::WM_POSITION_BOTTOM_LEFT: 67 | $this->pos_horizontal = $this->wm_margin; 68 | break; 69 | case self::WM_POSITION_TOP_RIGHT: 70 | case self::WM_POSITION_BOTTOM_RIGHT: 71 | $this->pos_horizontal = $size->getWidth() - $wm_size->getWidth() - $this->wm_margin; 72 | break; 73 | case self::WM_POSITION_CENTER: 74 | $this->pos_horizontal = ceil(($size->getWidth() - $wm_size->getWidth()) / 2); 75 | break; 76 | } 77 | 78 | // Vertical position 79 | switch ($this->wm_position) { 80 | case self::WM_POSITION_TOP_LEFT: 81 | case self::WM_POSITION_TOP_RIGHT: 82 | $this->pos_vertical = $this->wm_margin; 83 | break; 84 | case self::WM_POSITION_BOTTOM_LEFT: 85 | case self::WM_POSITION_BOTTOM_RIGHT: 86 | $this->pos_vertical = $size->getHeight() - $wm_size->getHeight() - $this->wm_margin; 87 | break; 88 | case self::WM_POSITION_CENTER: 89 | $this->pos_vertical = ceil(($size->getHeight() - $wm_size->getHeight()) / 2); 90 | break; 91 | } 92 | 93 | if($this->pos_horizontal <= 0) $this->pos_horizontal = 0; 94 | if($this->pos_vertical <= 0) $this->pos_vertical = 0; 95 | 96 | $wm_position_point = new \Imagine\Image\Point($this->pos_horizontal, $this->pos_vertical); 97 | 98 | try { 99 | $image = $image->paste($watermark, $wm_position_point); 100 | } catch (\Imagine\Exception\OutOfBoundsException $e) { 101 | \Yii::warning($e->getMessage()); 102 | } 103 | 104 | return $image; 105 | } 106 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "deanar/yii2-file-processor", 3 | "description": "Upload and process files and images for Yii2", 4 | "type": "yii2-extension", 5 | "keywords": ["yii2","extension","upload","files","images","crop","resize","thumbnails"], 6 | "license": "BSD-4-Clause", 7 | "version" : "0.1.4", 8 | "authors": [ 9 | { 10 | "name": "Mikhail Razumovskiy", 11 | "email": "rdeanar@gmail.com" 12 | } 13 | ], 14 | "support": { 15 | "issues": "https://github.com/rdeanar/yii2-file-processor/issues", 16 | "source": "https://github.com/rdeanar/yii2-file-processor" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "deanar\\fileProcessor\\": "" 21 | } 22 | }, 23 | "require": { 24 | "php": ">=5.4.0", 25 | "yiisoft/yii2": "*", 26 | "rubaxa/fileapi": "*", 27 | "bower-asset/jcrop": "*", 28 | "yiisoft/yii2-imagine": "*" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | [ 38 | 'class' => VerbFilter::className(), 39 | 'actions' => [ 40 | 'upload' => ['post', 'options'], 41 | 'remove' => ['post', 'delete'], 42 | 'sort' => ['post'], 43 | ], 44 | ], 45 | ]; 46 | } 47 | 48 | public function actionIndex() 49 | { 50 | $imagine = new Imagine(); 51 | $image = $imagine->open('testimage.jpg'); 52 | 53 | $image = $image->thumbnail(new Box(320, 320), ImageInterface::THUMBNAIL_INSET); 54 | 55 | $path = Yii::getAlias('@webroot'.DIRECTORY_SEPARATOR.'wm.png'); 56 | 57 | $filter = new WatermarkFilter($imagine, $path, WatermarkFilter::WM_POSITION_CENTER, 5); 58 | $image = $filter->apply($image); 59 | 60 | $image->save('testimage2.jpg'); 61 | 62 | return Html::img('/testimage2.jpg'); 63 | return $this->render('index'); 64 | return ''; 65 | } 66 | 67 | public function actionRemove() 68 | { 69 | $id = Yii::$app->request->post('id', null); 70 | $type = Yii::$app->request->post('type', null); 71 | $type_id = Yii::$app->request->post('type_id', null); 72 | 73 | foreach(['id','type','type_id'] as $param){ 74 | if(is_null($$param)) throw new BadRequestHttpException('Missing required parameter: ' . $param); 75 | } 76 | 77 | $acl = VariationHelper::getAclOfType($type); 78 | if (!AccessControl::checkAccess($acl, $type_id)) { 79 | Yii::warning('Someone trying to delete file with no access.', 'file-processor'); 80 | throw new ForbiddenHttpException('You have no access to delete current file'); 81 | } 82 | 83 | $success = Uploads::staticRemoveFile($id, compact('type', 'type_id')); 84 | 85 | if ($success) { 86 | return 'File with id: ' . $id . ' removed successfully'; 87 | } else { 88 | return 'Fail to remove file with id: ' . $id; 89 | } 90 | } 91 | 92 | public function actionUpload() 93 | { 94 | if (!empty($_SERVER['HTTP_ORIGIN'])) { 95 | // Enable CORS 96 | header('Access-Control-Allow-Origin: ' . $_SERVER['HTTP_ORIGIN']); 97 | header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); 98 | header('Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type'); 99 | } 100 | 101 | if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') { 102 | exit; 103 | } 104 | 105 | if (strtoupper($_SERVER['REQUEST_METHOD']) == 'POST') { 106 | $files = FileAPI::getFiles(); // Retrieve File List 107 | $images = array(); 108 | 109 | // Fetch all image-info from files list 110 | $errors = $this->fetchFiles($files, $images); 111 | 112 | // JSONP callback name 113 | $jsonp = isset($_REQUEST['callback']) ? trim($_REQUEST['callback']) : null; 114 | 115 | // JSON-data for server response 116 | $json = array( 117 | 'images' => $images, 118 | 'errors' => array_unique($errors), 119 | ); 120 | 121 | FileAPI::makeResponse(array( 122 | 'status' => empty($errors) ? FileAPI::OK : FileAPI::ERROR, 123 | 'statusText' => empty($errors) ? 'OK' : 'ERROR', 124 | 'body' => $json, 125 | ), $jsonp); 126 | Yii::$app->end(); 127 | } 128 | 129 | } // end of actionUpload 130 | 131 | private function fetchFiles($files, &$images, $name = 'file') 132 | { 133 | $errors = []; 134 | 135 | $type = Yii::$app->request->post('type'); 136 | $type_id = Yii::$app->request->post('type_id'); 137 | $hash = Yii::$app->request->post('hash'); 138 | 139 | // Check access. if `$type_id` is null, then access check must be only in ConnectFileSequence behaviour 140 | if (!is_null($type_id)) { 141 | $acl = VariationHelper::getAclOfType($type); 142 | if (!AccessControl::checkAccess($acl, $type_id)) { 143 | Yii::warning('Someone trying to upload file with no access.','file-processor'); 144 | return ['You have no access to perform this upload']; 145 | } 146 | } 147 | 148 | if (isset($files['tmp_name'])) { 149 | 150 | $file_temp_name = $files['tmp_name']; 151 | $file_real_name = basename($files['name']); 152 | 153 | if (is_uploaded_file($file_temp_name)) { 154 | 155 | $mime = FileHelper::getMimeType($file_temp_name); 156 | 157 | if( is_null($mime)){ 158 | $mime = FileHelper::getMimeTypeByExtension($file_real_name); 159 | } 160 | 161 | if (strpos($mime, 'image') !== false) { 162 | $file_dimensions = getimagesize($file_temp_name); 163 | } else { 164 | $file_dimensions = [null, null]; 165 | } 166 | 167 | // insert into db 168 | $model = new Uploads(); 169 | $model->type = $type; 170 | $model->type_id = $type_id; 171 | $model->hash = $hash; 172 | $model->ord = Uploads::getMaxOrderValue($type, $type_id, $hash) + 1; 173 | $model->filename = Uploads::generateBaseFileName($file_real_name); 174 | $model->original = $file_real_name; 175 | $model->mime = $mime; 176 | $model->size = filesize($file_temp_name); 177 | $model->width = $file_dimensions[0]; 178 | $model->height = $file_dimensions[1]; 179 | 180 | // save model, save file and fill response array 181 | if ($model->save()) { 182 | 183 | // load configuration 184 | $config = VariationHelper::getConfigOfType($model->type); 185 | 186 | $errors = array_merge( 187 | $errors, 188 | // upload and process variations 189 | $model->process($file_temp_name, $config) 190 | ); 191 | 192 | // insert id of uploaded file into attribute in model (if needed) 193 | Uploads::updateConnectedModelAttribute($config, $model->type_id, $model->id); 194 | 195 | if(empty($errors)) { 196 | $images[$name] = [ 197 | 'width' => $model->width, 198 | 'height' => $model->height, 199 | 'mime' => $model->mime, 200 | 'size' => $model->size, 201 | 'id' => $model->id, 202 | 'type' => $model->type, 203 | 'type_id' => $model->type_id, 204 | 'hash' => $model->hash, 205 | 'errors' => null, 206 | ]; 207 | }else{ 208 | $model->removeFile(); 209 | } 210 | 211 | } else { 212 | Yii::warning('file was unable to be saved. Errors: ' . VarDumper::dumpAsString($model->getErrors()), 'file-processor'); 213 | array_push($errors, 'File was unable to be saved.'); 214 | } 215 | }else{ 216 | array_push($errors, 'File was unable to be uploaded.'); 217 | } 218 | 219 | } else { 220 | foreach ($files as $name => $file) { 221 | $errors = array_merge($errors, $this->fetchFiles($file, $images, $name)); 222 | } 223 | } 224 | 225 | return $errors; 226 | } 227 | 228 | public function actionSort(){ 229 | $sort = Yii::$app->request->post('sort',[]); 230 | if( !is_array($sort)) return false; 231 | 232 | foreach ($sort as $k => $v) { 233 | $file = Uploads::findOne($v); 234 | if(is_null($file)) continue; 235 | $file->ord = $k; 236 | $file->save(); 237 | } 238 | return ''; 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /helpers/AccessControl.php: -------------------------------------------------------------------------------- 1 | user->isGuest; 24 | 25 | $user_id = Yii::$app->user->identity->getId(); 26 | $username = Yii::$app->user->identity->username; 27 | 28 | if(is_array($acl) && ArrayHelper::isAssociative($acl)){ 29 | if(self::checkAccess('@')) { 30 | 31 | // 3) List of users 32 | if (array_key_exists('users', $acl) && is_array($acl['users'])) { 33 | return in_array($username, $acl['users']); 34 | } else { 35 | 36 | // 4) Current user id equals to specified attribute of model 37 | $keys = array_keys($acl); 38 | $className = array_shift($keys); 39 | $attribute = array_shift($acl); 40 | 41 | if (class_exists($className)) { 42 | if (is_null($reference)) return false; 43 | 44 | if($reference instanceof $className){ 45 | return $reference->getAttribute($attribute) == $user_id; 46 | }else { 47 | try{ 48 | $whereCondition = [$className::primaryKey()[0] => $reference, $attribute => $user_id]; 49 | return $className::find()->where($whereCondition)->count() > 0; 50 | }catch (Exception $e){ 51 | Yii::warning('Invalid configuration: ' . $e->getMessage(), 'file-processor'); 52 | return false; 53 | } 54 | } 55 | } else { 56 | // throw new Exception; // maybe 57 | return false; 58 | } 59 | 60 | } 61 | } 62 | } 63 | 64 | // 5) Defined function 65 | if(is_callable($acl) ){ 66 | return call_user_func($acl, $reference, $user_id); 67 | } 68 | 69 | return false; 70 | } 71 | } -------------------------------------------------------------------------------- /helpers/FileHelper.php: -------------------------------------------------------------------------------- 1 | getModule('fp')->default_resize_mod; 21 | } 22 | 23 | public static function default_quality(){ 24 | return Yii::$app->getModule('fp')->default_quality; 25 | } 26 | 27 | public static function getRawConfig(){ 28 | return Yii::$app->getModule('fp')->variations_config; 29 | } 30 | 31 | public static function getConfigOfType($type) 32 | { 33 | // TODO set default variation instead of '_original' 34 | $config = self::getRawConfig(); 35 | 36 | if (!array_key_exists($type, $config)) { 37 | $return = isset($config['_default']) ? $config['_default'] : array(); 38 | } else { 39 | $return = $config[$type]; 40 | } 41 | 42 | $all = isset($config['_all']) ? $config['_all'] : array(); 43 | 44 | return ArrayHelper::merge($all,$return); 45 | } 46 | 47 | /** 48 | * Get Access Control settings for given type from variation config 49 | * @param $type 50 | * @return null 51 | */ 52 | public static function getAclOfType($type){ 53 | $config = self::getRawConfig(); 54 | 55 | if (!array_key_exists($type, $config)) { 56 | return null; 57 | } else { 58 | $config_of_type = $config[$type]; 59 | } 60 | 61 | if(array_key_exists('_acl',$config_of_type)){ 62 | return $config_of_type['_acl']; 63 | } 64 | } 65 | 66 | /** 67 | * @param array $variationConfig 68 | * @return array 69 | */ 70 | public static function normalizeVariationConfig($variationConfig){ 71 | $config = array(); 72 | $arrayIndexed = ArrayHelper::isIndexed($variationConfig); 73 | $argumentCount = count($variationConfig); 74 | $defaultResizeMode = self::default_resize_mod(); 75 | 76 | if ($arrayIndexed) { 77 | $config['width'] = $variationConfig[0]; 78 | $config['height'] = $variationConfig[1]; 79 | if ($argumentCount > 2) { 80 | $config['mode'] = in_array($variationConfig[2], array('inset', 'outbound')) ? $variationConfig[2] : $defaultResizeMode; 81 | } 82 | if ($argumentCount > 3) { 83 | $config['quality'] = is_numeric($variationConfig[3]) ? $variationConfig[3] : self::default_quality(); 84 | } 85 | 86 | } else { 87 | $config['width'] = $variationConfig['width']; 88 | $config['height'] = $variationConfig['height']; 89 | $config['mode'] = in_array($variationConfig['mode'], array('inset', 'outbound')) ? $variationConfig['mode'] : $defaultResizeMode; 90 | if( isset($variationConfig['quality']) ) 91 | $config['quality'] = is_numeric($variationConfig['quality']) ? $variationConfig['quality'] : self::default_quality(); 92 | 93 | if (isset($variationConfig['watermark'])) { 94 | 95 | $default_watermark_config = array( 96 | 'position' => WatermarkFilter::WM_POSITION_BOTTOM_RIGHT, 97 | 'margin' => 5, 98 | ); 99 | 100 | if (is_array($variationConfig['watermark'])) { 101 | if (isset($variationConfig['watermark']['path'])) { 102 | $config['watermark'] = ArrayHelper::merge( 103 | $default_watermark_config, 104 | $variationConfig['watermark'] 105 | ); 106 | } 107 | } else { 108 | $config['watermark'] = ArrayHelper::merge( 109 | $default_watermark_config, 110 | ['path' => $variationConfig['watermark']] 111 | ); 112 | } 113 | } 114 | 115 | // fill color for resize mode fill in (inset variation) 116 | // crop 117 | // rotate 118 | // etc 119 | } 120 | 121 | if (!isset($config['mode'])) $config['mode'] = $defaultResizeMode; 122 | if (!isset($config['quality'])) $config['quality'] = self::default_quality(); 123 | 124 | return $config; 125 | } 126 | 127 | 128 | } -------------------------------------------------------------------------------- /messages/en/fp.php: -------------------------------------------------------------------------------- 1 | 'Your browser does not support Flash :(', 10 | 11 | // multi upload 12 | 'ADD_FILES_TO_QUEUE_TIP' => 'Add files to upload queue', 13 | 'ADD_FILES_BUTTON' => 'Add files', 14 | 'OR_DRAG_N_DROP_HERE' => 'or drag-n-drop it', 15 | 'UPLOAD_BUTTON' => 'Upload', 16 | 17 | // single upload 18 | 'CHOOSE_FILE' => 'Choose', 19 | 'UPLOADING_PROCESS' => 'Uploading', 20 | 'MODAL_CROP_TITLE' => 'Crop image', 21 | 'MODAL_CROP_AND_UPLOAD' => 'Crop & upload', 22 | 'REMOVE_FILE' => 'Remove file', 23 | 24 | // js 25 | 'UPLOAD_ERROR' => 'Error while uploading', 26 | 'UPLOAD_ERROR_DETAILED' => 'Error while uploading with following errors: {errors}', 27 | 'REMOVE_FILE_CONFIRM' => 'Are you sure you want to delete file?', 28 | 'REMOVE_FILE_WITH_NAME_CONFIRM' => 'Are you sure you want to delete file "{filename}"?', 29 | 'REMOVE_FILE_FROM_QUEUE_CONFIRM' => 'Are you sure you want to delete file from queue?', 30 | 'REMOVE_FILE_ERROR' => 'Error while removing file', 31 | 'REMOVE_FILE_ERROR_DETAILED' => 'Error while removing file: {errors}', 32 | 'ORDER_SAVE_ERROR' => 'Error while saving order', 33 | 34 | // validation errors 35 | 'MAX_FILES' => 'Can not add file "{filename}". Too much files.', 36 | 'MAX_SIZE' => 'Can not add file "{filename}". File bigger that need by {maxSize}.', 37 | 'MIN_WIDTH' => 'Can not add file "{filename}". File thinner than need by {minWidth} pixels.', 38 | 'MIN_HEIGHT' => 'Can not add file "{filename}". File lower than need by {minHeight} pixels.', 39 | 'MAX_WIDTH' => 'Can not add file "{filename}". File wider than need by {maxWidth} pixels.', 40 | 'MAX_HEIGHT' => 'Can not add file "{filename}". File higher than need by {maxHeight} pixels.', 41 | ]; -------------------------------------------------------------------------------- /messages/ru/fp.php: -------------------------------------------------------------------------------- 1 | 'Ваш браузер не поддерживает Flash :(', 10 | 11 | // multi upload 12 | 'ADD_FILES_TO_QUEUE_TIP' => 'Добавьте файлы в очередь загрузки', 13 | 'ADD_FILES_BUTTON' => 'Выберите файлы', 14 | 'OR_DRAG_N_DROP_HERE' => 'или перетащите их', 15 | 'UPLOAD_BUTTON' => 'Загрузить', 16 | 17 | // single upload 18 | 'CHOOSE_FILE' => 'Выбрать файл', 19 | 'UPLOADING_PROCESS' => 'Загрузка', 20 | 'MODAL_CROP_TITLE' => 'Выберите область изображения', 21 | 'MODAL_CROP_AND_UPLOAD' => 'Обрезать и загрузить', 22 | 'REMOVE_FILE' => 'Удалить файл', 23 | 24 | // js 25 | 'UPLOAD_ERROR' => 'Ошибка во время загрузки файлов', 26 | 'UPLOAD_ERROR_DETAILED' => 'Ошибка во время загрузки файлов: {errors}', 27 | 'REMOVE_FILE_CONFIRM' => 'Вы уверены, что хотите удалить файл?', 28 | 'REMOVE_FILE_WITH_NAME_CONFIRM' => 'Вы уверены, что хотите удалить файл "{filename}"?', 29 | 'REMOVE_FILE_FROM_QUEUE_CONFIRM' => 'Вы уверены, что хотите удалить файл из очереди?', 30 | 'REMOVE_FILE_ERROR' => 'Ошибка удаления файла', 31 | 'REMOVE_FILE_ERROR_DETAILED' => 'Ошибка удаления файла: {errors}', 32 | 'ORDER_SAVE_ERROR' => 'Ошибка во время сохранения изменения порядка отображения', 33 | 34 | // validation errors 35 | 'MAX_FILES' => 'Невозможно добавить файл "{filename}". Слишком много файлов.', 36 | 'MAX_SIZE' => 'Невозможно добавить файл "{filename}". Файл больше чем нужно на {maxSize}.', 37 | 'MIN_WIDTH' => 'Невозможно добавить файл "{filename}". Файл по ширине меньше чем нужно на {minWidth} пикселей.', 38 | 'MIN_HEIGHT' => 'Невозможно добавить файл "{filename}". Файл по высоте меньше чем нужно на {minHeight} пикселей.', 39 | 'MAX_WIDTH' => 'Невозможно добавить файл "{filename}". Файл по ширине больше чем нужно на {maxWidth} пикселей.', 40 | 'MAX_HEIGHT' => 'Невозможно добавить файл "{filename}". Файл по высоте больше чем нужно на {maxHeight} пикселей.', 41 | ]; -------------------------------------------------------------------------------- /messages/tr_TR/fp.php: -------------------------------------------------------------------------------- 1 | 'Browser\'ınız flash desteklemiyor :(', 6 | 7 | // multi upload 8 | 'ADD_FILES_TO_QUEUE_TIP' => 'Yükleme sırasına ekle', 9 | 'ADD_FILES_BUTTON' => 'Dosya ekle', 10 | 'OR_DRAG_N_DROP_HERE' => 'veya sürükle & bırak', 11 | 'UPLOAD_BUTTON' => 'Yükle', 12 | 13 | // single upload 14 | 'CHOOSE_FILE' => 'Seç', 15 | 'UPLOADING_PROCESS' => 'Yükleniyor', 16 | 'MODAL_CROP_TITLE' => 'Resmi Kırp', 17 | 'MODAL_CROP_AND_UPLOAD' => 'Resmi kırp & yükle', 18 | 'REMOVE_FILE' => 'Dosyayı Sil', 19 | 20 | // js 21 | 'UPLOAD_ERROR' => 'Yüklenirken bir hata oluştu', 22 | 'UPLOAD_ERROR_DETAILED' => 'Yüklenirken bir hata oluştu. Hata: {errors}', 23 | 'REMOVE_FILE_CONFIRM' => 'Dosyayı silmek istediğinize emin misiniz?', 24 | 'REMOVE_FILE_WITH_NAME_CONFIRM' => '"{filename}" dosyasını silmek istediğinize emin misiniz?', 25 | 'REMOVE_FILE_FROM_QUEUE_CONFIRM' => 'Dosyayı yükleme kuyruğundan çıkarmak istediğinize emin misiniz?', 26 | 'REMOVE_FILE_ERROR' => 'Dosya silinirken bir hata oluştu', 27 | 'REMOVE_FILE_ERROR_DETAILED' => 'Dosya silinirken bir hata oluştu. Hata: {errors}', 28 | 'ORDER_SAVE_ERROR' => 'Sıralama işlemi sırasında bir hata oluştu', 29 | 30 | // validation errors 31 | 'MAX_FILES' => '"{filename}" dosya eklenemedi. Maksimum dosya sayısı.', 32 | 'MAX_SIZE' => '"{filename}" dosya eklenemedi. Dosya boyutu maksimum {maxSize} olmalı.', 33 | 'MIN_WIDTH' => '"{filename}" dosya eklenemedi. Dosyanın genişliği minimum {minWidth}px olmalı.', 34 | 'MIN_HEIGHT' => '"{filename}" dosya eklenemedi. Dosyanın boyu minimum {minHeight}px olmalı.', 35 | 'MAX_WIDTH' => '"{filename}" dosya eklenemedi. Dosyanın genişliği maksimum {maxWidth}px olmalı.', 36 | 'MAX_HEIGHT' => '"{filename}" dosya eklenemedi. Dosyanın boyu maksimum {maxHeight} px olmalı.', 37 | ]; -------------------------------------------------------------------------------- /migrations/m140516_113603_create_file_storage_table.php: -------------------------------------------------------------------------------- 1 | createTable('{{%fp_uploads}}', [ 10 | 'id' => 'pk', 11 | 'timestamp' => Schema::TYPE_TIMESTAMP . ' NOT NULL DEFAULT CURRENT_TIMESTAMP', 12 | 'type' => Schema::TYPE_STRING . ' DEFAULT NULL', 13 | 'type_id' => Schema::TYPE_INTEGER . ' DEFAULT NULL', 14 | 'hash' => Schema::TYPE_STRING . ' DEFAULT NULL', 15 | 'ord' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT "0"', 16 | 'filename' => Schema::TYPE_STRING . ' NOT NULL', 17 | 'original' => Schema::TYPE_STRING . ' NOT NULL', 18 | 'mime' => Schema::TYPE_STRING . ' NOT NULL DEFAULT ""', 19 | 'size' => Schema::TYPE_INTEGER . ' NOT NULL', 20 | 'width' => Schema::TYPE_INTEGER . ' DEFAULT NULL', 21 | 'height' => Schema::TYPE_INTEGER . ' DEFAULT NULL', 22 | ]); 23 | } 24 | 25 | public function down() 26 | { 27 | $this->dropTable('fp_uploads'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /migrations/m150111_173823_add_index.php: -------------------------------------------------------------------------------- 1 | createIndex('type_type_id', '{{%fp_uploads}}', ['type', 'type_id']); 10 | $this->createIndex('type_hash', '{{%fp_uploads}}', ['type', 'hash']); 11 | } 12 | 13 | public function down() 14 | { 15 | $this->dropIndex('type_type_id', '{{%fp_uploads}}'); 16 | $this->dropIndex('type_hash', '{{%fp_uploads}}'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /models/Uploads.php: -------------------------------------------------------------------------------- 1 | upload_dir = Yii::$app->getModule('fp')->upload_dir; 53 | $this->root_path = Yii::$app->getModule('fp')->root_path; 54 | $this->root_url = Yii::$app->getModule('fp')->root_url; 55 | 56 | $this->unlink_files = Yii::$app->getModule('fp')->unlink_files; 57 | $this->image_driver = Yii::$app->getModule('fp')->image_driver; 58 | parent::init(); 59 | } 60 | 61 | /** 62 | * @inheritdoc 63 | */ 64 | public static function tableName() 65 | { 66 | return '{{%fp_uploads}}'; 67 | } 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function rules() 73 | { 74 | return [ 75 | [['filename', 'original', 'size'], 'required'], 76 | [['timestamp'], 'safe'], 77 | [['type_id', 'ord', 'size', 'width', 'height'], 'integer'], 78 | [['type', 'hash', 'filename', 'original', 'mime'], 'string', 'max' => 255] 79 | ]; 80 | } 81 | 82 | /** 83 | * @inheritdoc 84 | */ 85 | public function attributeLabels() 86 | { 87 | return [ 88 | 'id' => Yii::t('app', 'ID'), 89 | 'timestamp' => Yii::t('app', 'Время загрузки'), 90 | 'type' => Yii::t('app', 'Тип'), 91 | 'type_id' => Yii::t('app', 'ID Типа'), 92 | 'hash' => Yii::t('app', 'HASH'), 93 | 'ord' => Yii::t('app', 'Порядок отображения'), 94 | 'filename' => Yii::t('app', 'Имя файла'), 95 | 'original' => Yii::t('app', 'Оригинальное имя файла'), 96 | 'mime' => Yii::t('app', 'Тип файла'), 97 | 'size' => Yii::t('app', 'Размер файла'), 98 | 'width' => Yii::t('app', 'Ширина'), 99 | 'height' => Yii::t('app', 'Высота'), 100 | ]; 101 | } 102 | 103 | /* 104 | * File upload and process methods 105 | * 106 | */ 107 | 108 | public function isImage(){ 109 | return !is_null($this->width); 110 | } 111 | 112 | public static function findByReference($type, $type_id){ 113 | if (is_null($type_id)) return null; 114 | 115 | return self::find() 116 | ->where(['type' => $type, 'type_id' => $type_id]) 117 | ->orderBy('ord') 118 | ->all(); 119 | } 120 | 121 | /** 122 | * @param $id 123 | * @param $check 124 | * @return bool 125 | * 126 | * Static call function removeFile 127 | */ 128 | public static function staticRemoveFile($id, $check){ 129 | $file = self::findOne($id); 130 | if(is_null($file)) return false; 131 | if( 132 | $check['type'] == $file->type && 133 | $check['type_id'] == $file->type_id 134 | ){ 135 | return $file->removeFile(); 136 | } 137 | return false; 138 | } 139 | 140 | /** 141 | * @return bool 142 | * 143 | * Remove file from file system and database 144 | */ 145 | public function removeFile(){ 146 | $config = VariationHelper::getConfigOfType($this->type); 147 | $error = false; 148 | 149 | if ($this->unlink_files) { 150 | foreach ($config as $variation_name => $variation_config) { 151 | 152 | if ($variation_name == '_original') { 153 | if (!$variation_config) continue; 154 | $variation_name = 'original'; 155 | } 156 | 157 | if (substr($variation_name, 0, 1) !== '_' || $variation_name == '_thumb') { 158 | // delete file 159 | $file = $this->getUploadFilePath($variation_name); 160 | if (file_exists($file)) { 161 | if (!@unlink($file)) { 162 | Yii::warning('Can not unlink file: ' . $file, 'file-processor'); 163 | $error = true; 164 | } else { 165 | Yii::trace('Unlinked file: ' . $file, 'file-processor'); 166 | } 167 | } 168 | } 169 | } 170 | } 171 | 172 | // clear attribute value in model (if needed) 173 | Uploads::updateConnectedModelAttribute($config, $this->type_id, null); 174 | 175 | if(!$error){ 176 | return $this->delete() ? true : false; 177 | } 178 | return false; 179 | } 180 | 181 | /** 182 | * Update attribute value of model (if needed). 183 | * Used for insertion of uploaded file in case of single file upload 184 | * @param $config array 185 | * @param $model_pk integer 186 | * @param $value integer 187 | */ 188 | public static function updateConnectedModelAttribute($config, $model_pk, $value){ 189 | if (isset($config['_insert'])) { 190 | $keys = array_keys($config['_insert']); 191 | $className = array_shift($keys); 192 | $attribute = array_shift($config['_insert']); 193 | if( class_exists($className)) { 194 | $className::updateAll([$attribute => $value], [$className::primaryKey()[0] => $model_pk]); 195 | } 196 | } 197 | } 198 | 199 | public static function getMaxOrderValue($type, $type_id, $hash) 200 | { 201 | if (is_null($type) || is_null($type_id)){ 202 | $where = ['hash' => $hash]; 203 | }else{ 204 | $where = ['type' => $type, 'type_id' => $type_id]; 205 | } 206 | 207 | $find = self::find() 208 | ->select('MAX(ord) as ord') 209 | ->where($where) 210 | ->one(); 211 | 212 | return is_null($find) ? 0 : $find->ord; 213 | } 214 | 215 | 216 | 217 | 218 | public function process($file_temp_name, $config=null){ 219 | $errors = []; 220 | 221 | if( is_null($config) ) $config = VariationHelper::getConfigOfType($this->type); 222 | 223 | $is_image = $this->isImage(); 224 | 225 | if ( !$is_image || ( isset($config['_original']) && $config['_original'] === true ) ){ 226 | $upload_dir = $this->getUploadDir($this->type); 227 | 228 | if(!is_dir($upload_dir) ) mkdir($upload_dir, 0777, true); 229 | 230 | $upload_full_path = $upload_dir . DIRECTORY_SEPARATOR . $this->filename; 231 | 232 | if (!move_uploaded_file($file_temp_name, $upload_full_path)) { 233 | array_push($errors, 'Can not move uploaded file.'); 234 | } 235 | }else{ 236 | $upload_full_path = $file_temp_name; 237 | } 238 | 239 | if(!$is_image) return $errors; 240 | 241 | 242 | try { 243 | switch($this->image_driver){ 244 | case self::IMAGE_DRIVER_IMAGICK: 245 | $imagine = new \Imagine\Imagick\Imagine(); 246 | break; 247 | case self::IMAGE_DRIVER_GMAGICK: 248 | $imagine = new \Imagine\Gmagick\Imagine(); 249 | break; 250 | default: //case self::IMAGE_DRIVER_GD: 251 | $imagine = new \Imagine\Gd\Imagine(); 252 | break; 253 | } 254 | 255 | $image = $imagine->open($upload_full_path); 256 | 257 | foreach ($config as $variation_name => $variation_config) { 258 | if (substr($variation_name, 0, 1) !== '_' || $variation_name == '_thumb') { 259 | $errors = array_merge( 260 | $errors, 261 | $this->makeVariation($imagine, $image, $variation_name, $variation_config) 262 | ); 263 | } 264 | } 265 | } catch (\Imagine\Exception\Exception $e) { 266 | // handle the exception 267 | array_push($errors, $e->getMessage()); 268 | } 269 | 270 | return $errors; 271 | } // end of process 272 | 273 | 274 | 275 | /** 276 | * @param $image 277 | * @param $variationName 278 | * @param $variationConfig 279 | * @return array 280 | * 281 | * Resize images by variation config 282 | */ 283 | public function makeVariation($imagine, $image, $variationName, $variationConfig){ 284 | $errors = []; 285 | if( !is_array($variationConfig)) return ['Variation config must be an array']; 286 | 287 | $config = VariationHelper::normalizeVariationConfig($variationConfig); 288 | 289 | // here because in normalizeVariationConfig we don't process variation name 290 | if($variationName == '_thumb'){ 291 | $config['mode'] = 'outbound'; 292 | } 293 | 294 | if($config['mode'] == 'inset'){ 295 | $mode = ImageInterface::THUMBNAIL_INSET; 296 | }else{ 297 | $mode = ImageInterface::THUMBNAIL_OUTBOUND; 298 | } 299 | 300 | $image = $image->thumbnail(new Box($config['width'], $config['height']), $mode); 301 | 302 | // TODO order of watermark applying (before or after thumbnailing) 303 | if(isset($config['watermark'])){ 304 | $filter = new WatermarkFilter($imagine, $config['watermark']['path'], $config['watermark']['position'], $config['watermark']['margin']); 305 | $image = $filter->apply($image); 306 | } 307 | 308 | $options = array( 309 | 'quality' => $config['quality'], 310 | ); 311 | 312 | try { 313 | if (!$image->save($this->getUploadFilePath($variationName), $options)) 314 | array_push($errors, 'Can not save generated image.'); 315 | }catch (ErrorException $e){ 316 | array_push($errors, $e->getMessage()); 317 | } 318 | 319 | return $errors; 320 | } 321 | 322 | /** 323 | * @param $type 324 | * @return bool|string 325 | * 326 | * Get upload dir 327 | */ 328 | public function getUploadDir($type){ 329 | return Yii::getAlias($this->root_path) . DIRECTORY_SEPARATOR . $this->upload_dir . DIRECTORY_SEPARATOR . $type; 330 | } 331 | 332 | 333 | /** 334 | * @param string $variation 335 | * @return string 336 | * 337 | * Get upload path to file 338 | */ 339 | public function getUploadFilePath($variation = 'original'){ 340 | return $this->getUploadDir($this->type) . DIRECTORY_SEPARATOR . $this->getFilenameByVariation($variation); 341 | } 342 | 343 | 344 | /** 345 | * @param string $variation 346 | * @param boolean $absolute 347 | * @return string 348 | * 349 | * Get Public file url 350 | */ 351 | public function getPublicFileUrl($variation = 'original', $absolute = false){ 352 | $requestHost = Yii::$app->request->getHostInfo(); 353 | $currentHost = ''; 354 | 355 | if ($absolute) { 356 | $currentHost = is_null($this->root_url) ? Url::base($absolute) : $this->root_url; 357 | } else { 358 | if (!is_null($this->root_url) && $this->root_url != $requestHost) { 359 | $currentHost = $this->root_url; 360 | } 361 | } 362 | return $currentHost . '/' . $this->upload_dir . '/' . $this->type . '/' . $this->getFilenameByVariation($variation); 363 | } 364 | 365 | /** 366 | * @param string $variation 367 | * @return string 368 | * 369 | * Get variation filename 370 | */ 371 | public function getFilenameByVariation($variation='original'){ 372 | if(empty($this->filename)) return ''; 373 | //TODO make file name template 374 | if($variation == 'original'){ 375 | return $this->filename; 376 | }else{ 377 | return $variation . $this->filename_separator . $this->filename; 378 | } 379 | } 380 | 381 | 382 | /** 383 | * @param $filename 384 | * @return string 385 | * 386 | * Generate unique filename by uniqid() and original extension 387 | */ 388 | public static function generateBaseFileName($filename){ 389 | //TODO perhaps check extension and mime type compatibility 390 | return uniqid() . '.' . FileHelper::extractExtensionName($filename); 391 | } 392 | 393 | 394 | /** 395 | * Returns tag as string by variation name 396 | * 397 | * @param string $variation 398 | * @param bool $absolute 399 | * @param array $options 400 | * @return string 401 | */ 402 | public function imgTag($variation='original', $absolute=false,$options=array()){ 403 | //TODO return 'empty' value if no image available 404 | if( empty($this->filename) ) return ''; 405 | $src = $this->getPublicFileUrl($variation,$absolute); 406 | $attributes = ['src' => $src]; 407 | return Html::tag('img', '', ArrayHelper::merge($options, $attributes)); 408 | } 409 | 410 | 411 | } 412 | -------------------------------------------------------------------------------- /variations_default.php: -------------------------------------------------------------------------------- 1 | [ 5 | ], 6 | '_all' => [ 7 | '_original' => true, 8 | '_thumb' => [80,80], 9 | ] 10 | ]; 11 | -------------------------------------------------------------------------------- /vendor/FileAPI.php: -------------------------------------------------------------------------------- 1 | $mixedValue) { 15 | self::rRestructuringFilesArray($arrayForFill[$currentKey], 16 | $nameKey, 17 | $mixedValue, 18 | $fileDescriptionParam); 19 | } 20 | } else { 21 | $arrayForFill[$currentKey][$fileDescriptionParam] = $currentMixedValue; 22 | } 23 | } 24 | 25 | 26 | private static function determineMimeType(&$file) 27 | { 28 | if (function_exists('mime_content_type')) { 29 | if (isset($file['tmp_name']) && is_string($file['tmp_name'])) { 30 | if ($file['type'] == 'application/octet-stream') { 31 | $mime = mime_content_type($file['tmp_name']); 32 | if (!empty($mime)) { 33 | $file['type'] = $mime; 34 | } 35 | } 36 | } else if (is_array($file)) { 37 | foreach ($file as &$entry) { 38 | self::determineMimeType($entry); 39 | } 40 | } 41 | } 42 | } 43 | 44 | 45 | /** 46 | * Enable CORS -- http://enable-cors.org/ 47 | * @param array [$options] 48 | */ 49 | public static function enableCORS($options = null) 50 | { 51 | if (is_null($options)) { 52 | $options = array(); 53 | } 54 | 55 | if (!isset($options['origin'])) { 56 | $options['origin'] = $_SERVER['HTTP_ORIGIN']; 57 | } 58 | 59 | if (!isset($options['methods'])) { 60 | $options['methods'] = 'POST, GET'; 61 | } 62 | 63 | if (!isset($options['headers'])) { 64 | $options['headers'] = array(); 65 | } 66 | 67 | header('Access-Control-Allow-Origin: ' . $options['origin']); 68 | header('Access-Control-Allow-Methods: ' . $options['methods']); 69 | header('Access-Control-Allow-Headers: ' . implode(', ', array_merge($options['headers'], array('X-Requested-With', 'Content-Range', 'Content-Disposition')))); 70 | 71 | if (!isset($options['cookie']) || $options['cookie']) { 72 | header('Access-Control-Allow-Credentials: true'); 73 | } 74 | } 75 | 76 | 77 | /** 78 | * Request header 79 | * @return array 80 | */ 81 | public static function getRequestHeaders() 82 | { 83 | $headers = array(); 84 | 85 | foreach ($_SERVER as $key => $value) { 86 | if (substr($key, 0, 5) == 'HTTP_') { 87 | $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5))))); 88 | $headers[$header] = $value; 89 | } 90 | } 91 | 92 | return $headers; 93 | } 94 | 95 | 96 | /** 97 | * Retrieve File List 98 | * @return array 99 | */ 100 | public static function getFiles() 101 | { 102 | $files = array(); 103 | 104 | // http://www.php.net/manual/ru/reserved.variables.files.php#106558 105 | foreach ($_FILES as $firstNameKey => $arFileDescriptions) { 106 | foreach ($arFileDescriptions as $fileDescriptionParam => $mixedValue) { 107 | self::rRestructuringFilesArray($files, $firstNameKey, $_FILES[$firstNameKey][$fileDescriptionParam], $fileDescriptionParam); 108 | } 109 | } 110 | 111 | self::determineMimeType($files); 112 | 113 | return $files; 114 | } 115 | 116 | 117 | /** 118 | * Make server response 119 | * @param array $res 120 | * @param string [$jsonp] 121 | */ 122 | public static function makeResponse(array $res, $jsonp = null) 123 | { 124 | $body = $res['body']; 125 | $json = is_array($body) ? json_encode($body) : $body; 126 | 127 | $httpStatus = isset($res['status']) ? $res['status'] : self::OK; 128 | $httpStatusText = addslashes(isset($res['statusText']) ? $res['statusText'] : 'OK'); 129 | $httpHeaders = isset($res['headers']) ? $res['headers'] : array(); 130 | 131 | if (empty($jsonp)) { 132 | header("HTTP/1.1 $httpStatus $httpStatusText"); 133 | $httpHeaders['Content-Type'] = 'application/json'; 134 | foreach ($httpHeaders as $header => $value) { 135 | header("$header: $value"); 136 | } 137 | echo $json; 138 | } else { 139 | $json = addslashes($json); 140 | 141 | echo << 143 | (function (ctx, jsonp){ 144 | 'use strict'; 145 | var status = $httpStatus, statusText = "$httpStatusText", response = "$json"; 146 | try { 147 | ctx[jsonp](status, statusText, response); 148 | } catch (e){ 149 | var data = "{\"id\":\"$jsonp\",\"status\":"+status+",\"statusText\":\""+statusText+"\",\"response\":\""+response.replace(/\"/g, '\\\\\"')+"\"}"; 150 | try { 151 | ctx.postMessage(data, document.referrer); 152 | } catch (e){} 153 | } 154 | })(window.parent, '$jsonp'); 155 | 156 | END; 157 | } 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | mock.png 3 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt){ 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | version: { 8 | src: ['<%= pkg.exportName %>.js', 'bower.json'] 9 | }, 10 | 11 | uglify: { 12 | options: { 13 | banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %> */\n' 14 | }, 15 | dist: { 16 | files: { 17 | '<%= pkg.exportName %>.min.js': ['<%= pkg.exportName %>.js'] 18 | } 19 | } 20 | } 21 | }); 22 | 23 | 24 | // These plugins provide necessary tasks. 25 | grunt.loadNpmTasks('grunt-version'); 26 | grunt.loadNpmTasks('grunt-contrib-uglify'); 27 | 28 | 29 | // Default task. 30 | grunt.registerTask('default', ['version', 'uglify']); 31 | }; 32 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/README.md: -------------------------------------------------------------------------------- 1 | # Sortable 2 | 3 | 4 | ## Features 5 | * Support touch devices and [modern](http://caniuse.com/#search=drag) browsers 6 | * Built using native HTML5 drag and drop API 7 | * Simple API 8 | * Lightweight, 2KB gzipped 9 | * No jQuery 10 | 11 | 12 | ### Usage 13 | ```html 14 |
    15 |
  • item 1
  • 16 |
  • item 2
  • 17 |
  • item 3
  • 18 |
19 | ``` 20 | 21 | ```js 22 | new Sortable(items); 23 | ``` 24 | 25 | 26 | ### Options 27 | ```js 28 | new Sortable(elem, { 29 | group: "name", 30 | handle: ".my-handle", // Restricts sort start click/touch to the specified element 31 | draggable: ".item", // Specifies which items inside the element should be sortable 32 | ghostClass: "sortable-ghost", 33 | 34 | onAdd: function (evt){ 35 | var itemEl = evt.item; 36 | }, 37 | 38 | onUpdate: function (evt){ 39 | var itemEl = evt.item; // the current dragged HTMLElement 40 | }, 41 | 42 | onRemove: function (evt){ 43 | var itemEl = evt.item; 44 | } 45 | }); 46 | ``` 47 | 48 | 49 | --- 50 | 51 | 52 | 53 | ### Sortable.utils 54 | * on(el`:HTMLElement`, event`:String`, fn`:Function`) — attach an event handler function 55 | * off(el`:HTMLElement`, event`:String`, fn`:Function`) — remove an event handler 56 | * css(el`:HTMLElement`)`:Object` — get the values of all the CSS properties 57 | * css(el`:HTMLElement`, prop`:String`)`:Mixed` — get the value of style properties 58 | * css(el`:HTMLElement`, prop`:String`, value`:String`) — set one CSS properties 59 | * css(el`:HTMLElement`, props`:Object`) — set more CSS properties 60 | * find(ctx`:HTMLElement`, tagName`:String`[, iterator`:Function`])`:Array` — get elements by tag name 61 | * bind(ctx`:Mixed`, fn`:Function`)`:Function` — Takes a function and returns a new one that will always have a particular context 62 | * closest(el`:HTMLElement`, selector`:String`[, ctx`:HTMLElement`])`:HTMLElement|Null` — for each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree 63 | * toggleClass(el`:HTMLElement`, name`:String`, state`:Boolean`) — add or remove one classes from each element 64 | 65 | 66 | [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/RubaXa/sortable/trend.png)](https://bitdeli.com/free "Bitdeli Badge") 67 | 68 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/Sortable.js: -------------------------------------------------------------------------------- 1 | /**! 2 | * Sortable 3 | * @author RubaXa 4 | * @license MIT 5 | */ 6 | 7 | 8 | (function (factory){ 9 | "use strict"; 10 | 11 | if( typeof define === "function" && define.amd ){ 12 | define(factory); 13 | } 14 | else if( typeof module != "undefined" && typeof module.exports != "undefined" ){ 15 | module.exports = factory(); 16 | } 17 | else { 18 | window["Sortable"] = factory(); 19 | } 20 | })(function (){ 21 | "use strict"; 22 | 23 | var 24 | dragEl 25 | , ghostEl 26 | , rootEl 27 | , nextEl 28 | 29 | , lastEl 30 | , lastCSS 31 | , lastRect 32 | 33 | , activeGroup 34 | 35 | , tapEvt 36 | , touchEvt 37 | 38 | , expando = 'Sortable' + (new Date).getTime() 39 | 40 | , win = window 41 | , document = win.document 42 | , parseInt = win.parseInt 43 | , supportIEdnd = !!document.createElement('div').dragDrop 44 | 45 | , _silent = false 46 | 47 | , _createEvent = function (event/**String*/, item/**HTMLElement*/){ 48 | var evt = document.createEvent('Event'); 49 | evt.initEvent(event, true, true); 50 | evt.item = item; 51 | return evt; 52 | } 53 | 54 | , noop = function (){} 55 | , slice = [].slice 56 | 57 | , touchDragOverListeners = [] 58 | ; 59 | 60 | 61 | /** 62 | * @class Sortable 63 | * @param {HTMLElement} el 64 | * @param {Object} [options] 65 | * @constructor 66 | */ 67 | function Sortable(el, options){ 68 | this.el = el; // root element 69 | this.options = options = (options || {}); 70 | 71 | 72 | // Defaults 73 | options.group = options.group || Math.random(); 74 | options.handle = options.handle || null; 75 | options.draggable = options.draggable || el.children[0] && el.children[0].nodeName || (/[uo]l/i.test(el.nodeName) ? 'li' : '*'); 76 | options.ghostClass = options.ghostClass || 'sortable-ghost'; 77 | 78 | options.onAdd = _bind(this, options.onAdd || noop); 79 | options.onUpdate = _bind(this, options.onUpdate || noop); 80 | options.onRemove = _bind(this, options.onRemove || noop); 81 | 82 | 83 | // Export group name 84 | el[expando] = options.group; 85 | 86 | 87 | // Bind all private methods 88 | for( var fn in this ){ 89 | if( fn.charAt(0) === '_' ){ 90 | this[fn] = _bind(this, this[fn]); 91 | } 92 | } 93 | 94 | 95 | // Bind events 96 | _on(el, 'add', options.onAdd); 97 | _on(el, 'update', options.onUpdate); 98 | _on(el, 'remove', options.onRemove); 99 | 100 | _on(el, 'mousedown', this._onTapStart); 101 | _on(el, 'touchstart', this._onTapStart); 102 | supportIEdnd && _on(el, 'selectstart', this._onTapStart); 103 | 104 | _on(el, 'dragover', this._onDragOver); 105 | _on(el, 'dragenter', this._onDragOver); 106 | 107 | touchDragOverListeners.push(this._onDragOver); 108 | } 109 | 110 | 111 | Sortable.prototype = { 112 | constructor: Sortable, 113 | 114 | 115 | _applyEffects: function (){ 116 | _toggleClass(dragEl, this.options.ghostClass, true); 117 | }, 118 | 119 | 120 | _onTapStart: function (evt/**Event|TouchEvent*/){ 121 | var 122 | touch = evt.touches && evt.touches[0] 123 | , target = (touch || evt).target 124 | , options = this.options 125 | , el = this.el 126 | ; 127 | 128 | if( options.handle ){ 129 | target = _closest(target, options.handle, el); 130 | } 131 | 132 | target = _closest(target, options.draggable, el); 133 | 134 | // IE 9 Support 135 | if( target && evt.type == 'selectstart' ){ 136 | if( target.tagName != 'A' && target.tagName != 'IMG'){ 137 | target.dragDrop(); 138 | } 139 | } 140 | 141 | if( target && !dragEl && (target.parentNode === el) ){ 142 | tapEvt = evt; 143 | target.draggable = true; 144 | 145 | 146 | // Disable "draggable" 147 | _find(target, 'a', _disableDraggable); 148 | _find(target, 'img', _disableDraggable); 149 | 150 | 151 | if( touch ){ 152 | // Touch device support 153 | tapEvt = { 154 | target: target 155 | , clientX: touch.clientX 156 | , clientY: touch.clientY 157 | }; 158 | this._onDragStart(tapEvt, true); 159 | evt.preventDefault(); 160 | } 161 | 162 | 163 | _on(this.el, 'dragstart', this._onDragStart); 164 | _on(this.el, 'dragend', this._onDrop); 165 | _on(document, 'dragover', _globalDragOver); 166 | 167 | 168 | try { 169 | if( document.selection ){ 170 | document.selection.empty(); 171 | } else { 172 | window.getSelection().removeAllRanges() 173 | } 174 | } catch (err){ } 175 | } 176 | }, 177 | 178 | 179 | _emulateDragOver: function (){ 180 | if( touchEvt ){ 181 | _css(ghostEl, 'display', 'none'); 182 | 183 | var 184 | target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY) 185 | , parent = target 186 | , group = this.options.group 187 | , i = touchDragOverListeners.length 188 | ; 189 | 190 | if( parent ){ 191 | do { 192 | if( parent[expando] === group ){ 193 | while( i-- ){ 194 | touchDragOverListeners[i]({ 195 | clientX: touchEvt.clientX, 196 | clientY: touchEvt.clientY, 197 | target: target, 198 | rootEl: parent 199 | }); 200 | } 201 | break; 202 | } 203 | 204 | target = parent; // store last element 205 | } 206 | while( parent = parent.parentNode ); 207 | } 208 | 209 | _css(ghostEl, 'display', ''); 210 | } 211 | }, 212 | 213 | 214 | _onTouchMove: function (evt/**TouchEvent*/){ 215 | if( tapEvt ){ 216 | var 217 | touch = evt.touches[0] 218 | , dx = touch.clientX - tapEvt.clientX 219 | , dy = touch.clientY - tapEvt.clientY 220 | ; 221 | 222 | touchEvt = touch; 223 | _css(ghostEl, 'webkitTransform', 'translate3d('+dx+'px,'+dy+'px,0)'); 224 | } 225 | }, 226 | 227 | 228 | _onDragStart: function (evt/**Event*/, isTouch/**Boolean*/){ 229 | var 230 | target = evt.target 231 | , dataTransfer = evt.dataTransfer 232 | ; 233 | 234 | rootEl = this.el; 235 | dragEl = target; 236 | nextEl = target.nextSibling; 237 | activeGroup = this.options.group; 238 | 239 | if( isTouch ){ 240 | var 241 | rect = target.getBoundingClientRect() 242 | , css = _css(target) 243 | , ghostRect 244 | ; 245 | 246 | ghostEl = target.cloneNode(true); 247 | 248 | _css(ghostEl, 'top', rect.top - parseInt(css.marginTop, 10)); 249 | _css(ghostEl, 'left', rect.left - parseInt(css.marginLeft, 10)); 250 | _css(ghostEl, 'width', rect.width); 251 | _css(ghostEl, 'height', rect.height); 252 | _css(ghostEl, 'opacity', '0.8'); 253 | _css(ghostEl, 'position', 'fixed'); 254 | _css(ghostEl, 'zIndex', '100000'); 255 | 256 | rootEl.appendChild(ghostEl); 257 | 258 | // Fixing dimensions. 259 | ghostRect = ghostEl.getBoundingClientRect(); 260 | _css(ghostEl, 'width', rect.width*2 - ghostRect.width); 261 | _css(ghostEl, 'height', rect.height*2 - ghostRect.height); 262 | 263 | // Bind touch events 264 | _on(document, 'touchmove', this._onTouchMove); 265 | _on(document, 'touchend', this._onDrop); 266 | 267 | this._loopId = setInterval(this._emulateDragOver, 150); 268 | } 269 | else { 270 | dataTransfer.effectAllowed = 'move'; 271 | dataTransfer.setData('Text', target.textContent); 272 | 273 | _on(document, 'drop', this._onDrop); 274 | } 275 | 276 | setTimeout(this._applyEffects); 277 | }, 278 | 279 | 280 | _onDragOver: function (evt/**Event*/){ 281 | if( !_silent && (activeGroup === this.options.group) && (evt.rootEl === void 0 || evt.rootEl === this.el) ){ 282 | var 283 | el = this.el 284 | , target = _closest(evt.target, this.options.draggable, el) 285 | ; 286 | 287 | if( el.children.length === 0 || el.children[0] === ghostEl || (el === evt.target) && _ghostInBottom(el, evt) ){ 288 | el.appendChild(dragEl); 289 | } 290 | else if( target && target !== dragEl && (target.parentNode[expando] !== void 0) ){ 291 | if( lastEl !== target ){ 292 | lastEl = target; 293 | lastCSS = _css(target); 294 | lastRect = target.getBoundingClientRect(); 295 | } 296 | 297 | 298 | var 299 | rect = lastRect 300 | , width = rect.right - rect.left 301 | , height = rect.bottom - rect.top 302 | , floating = /left|right|inline/.test(lastCSS.cssFloat + lastCSS.display) 303 | , skew = (floating ? (evt.clientX - rect.left)/width : (evt.clientY - rect.top)/height) > .5 304 | , isWide = (target.offsetWidth > dragEl.offsetWidth) 305 | , isLong = (target.offsetHeight > dragEl.offsetHeight) 306 | , nextSibling = target.nextSibling 307 | , after 308 | ; 309 | 310 | _silent = true; 311 | setTimeout(_unsilent, 30); 312 | 313 | if( floating ){ 314 | after = (target.previousElementSibling === dragEl) && !isWide || (skew > .5) && isWide 315 | } else { 316 | after = (target.nextElementSibling !== dragEl) && !isLong || (skew > .5) && isLong; 317 | } 318 | 319 | if( after && !nextSibling ){ 320 | el.appendChild(dragEl); 321 | } else { 322 | target.parentNode.insertBefore(dragEl, after ? nextSibling : target); 323 | } 324 | } 325 | } 326 | }, 327 | 328 | 329 | _onDrop: function (evt/**Event*/){ 330 | clearInterval(this._loopId); 331 | 332 | // Unbind events 333 | _off(document, 'drop', this._onDrop); 334 | _off(document, 'dragover', _globalDragOver); 335 | 336 | _off(this.el, 'dragend', this._onDrop); 337 | _off(this.el, 'dragstart', this._onDragStart); 338 | _off(this.el, 'selectstart', this._onTapStart); 339 | 340 | _off(document, 'touchmove', this._onTouchMove); 341 | _off(document, 'touchend', this._onDrop); 342 | 343 | 344 | if( evt ){ 345 | evt.preventDefault(); 346 | evt.stopPropagation(); 347 | 348 | if( ghostEl ){ 349 | ghostEl.parentNode.removeChild(ghostEl); 350 | } 351 | 352 | if( dragEl ){ 353 | _disableDraggable(dragEl); 354 | _toggleClass(dragEl, this.options.ghostClass, false); 355 | 356 | if( !rootEl.contains(dragEl) ){ 357 | // Remove event 358 | rootEl.dispatchEvent(_createEvent('remove', dragEl)); 359 | 360 | // Add event 361 | dragEl.dispatchEvent(_createEvent('add', dragEl)); 362 | } 363 | else if( dragEl.nextSibling !== nextEl ){ 364 | // Update event 365 | dragEl.dispatchEvent(_createEvent('update', dragEl)); 366 | } 367 | } 368 | 369 | // Set NULL 370 | rootEl = 371 | dragEl = 372 | ghostEl = 373 | nextEl = 374 | 375 | tapEvt = 376 | touchEvt = 377 | 378 | lastEl = 379 | lastCSS = 380 | 381 | activeGroup = null; 382 | } 383 | }, 384 | 385 | 386 | destroy: function (){ 387 | var el = this.el, options = this.options; 388 | 389 | _off(el, 'add', options.onAdd); 390 | _off(el, 'update', options.onUpdate); 391 | _off(el, 'remove', options.onRemove); 392 | 393 | _off(el, 'mousedown', this._onTapStart); 394 | _off(el, 'touchstart', this._onTapStart); 395 | _off(el, 'selectstart', this._onTapStart); 396 | 397 | _off(el, 'dragover', this._onDragOver); 398 | _off(el, 'dragenter', this._onDragOver); 399 | 400 | //remove draggable attributes 401 | Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function(el) { 402 | el.removeAttribute('draggable'); 403 | }); 404 | 405 | touchDragOverListeners.splice(touchDragOverListeners.indexOf(this._onDragOver), 1); 406 | 407 | this._onDrop(); 408 | 409 | this.el = null; 410 | } 411 | }; 412 | 413 | 414 | function _bind(ctx, fn){ 415 | var args = slice.call(arguments, 2); 416 | return fn.bind ? fn.bind.apply(fn, [ctx].concat(args)) : function (){ 417 | return fn.apply(ctx, args.concat(slice.call(arguments))); 418 | }; 419 | } 420 | 421 | 422 | function _closest(el, selector, ctx){ 423 | if( selector === '*' ){ 424 | return el; 425 | } 426 | else if( el ){ 427 | ctx = ctx || document; 428 | selector = selector.split('.'); 429 | 430 | var 431 | tag = selector.shift().toUpperCase() 432 | , re = new RegExp('\\s('+selector.join('|')+')\\s', 'g') 433 | ; 434 | 435 | do { 436 | if( 437 | (tag === '' || el.nodeName == tag) 438 | && (!selector.length || ((' '+el.className+' ').match(re) || []).length == selector.length) 439 | ){ 440 | return el; 441 | } 442 | } 443 | while( el !== ctx && (el = el.parentNode) ); 444 | } 445 | 446 | return null; 447 | } 448 | 449 | 450 | function _globalDragOver(evt){ 451 | evt.dataTransfer.dropEffect = 'move'; 452 | evt.preventDefault(); 453 | } 454 | 455 | 456 | function _on(el, event, fn){ 457 | el.addEventListener(event, fn, false); 458 | } 459 | 460 | 461 | function _off(el, event, fn){ 462 | el.removeEventListener(event, fn, false); 463 | } 464 | 465 | 466 | function _toggleClass(el, name, state){ 467 | if( el ){ 468 | if( el.classList ){ 469 | el.classList[state ? 'add' : 'remove'](name); 470 | } 471 | else { 472 | var className = (' '+el.className+' ').replace(/\s+/g, ' ').replace(' '+name+' ', ''); 473 | el.className = className + (state ? ' '+name : '') 474 | } 475 | } 476 | } 477 | 478 | 479 | function _css(el, prop, val){ 480 | if( el && el.style ){ 481 | if( val === void 0 ){ 482 | if( document.defaultView && document.defaultView.getComputedStyle ){ 483 | val = document.defaultView.getComputedStyle(el, ''); 484 | } 485 | else if( el.currentStyle ){ 486 | val = el.currentStyle; 487 | } 488 | return prop === void 0 ? val : val[prop]; 489 | } else { 490 | el.style[prop] = val + (typeof val === 'string' ? '' : 'px'); 491 | } 492 | } 493 | } 494 | 495 | 496 | function _find(ctx, tagName, iterator){ 497 | if( ctx ){ 498 | var list = ctx.getElementsByTagName(tagName), i = 0, n = list.length; 499 | if( iterator ){ 500 | for( ; i < n; i++ ){ 501 | iterator(list[i], i); 502 | } 503 | } 504 | return list; 505 | } 506 | return []; 507 | } 508 | 509 | 510 | function _disableDraggable(el){ 511 | return el.draggable = false; 512 | } 513 | 514 | 515 | function _unsilent(){ 516 | _silent = false; 517 | } 518 | 519 | 520 | function _ghostInBottom(el, evt){ 521 | var last = el.lastElementChild.getBoundingClientRect(); 522 | return evt.clientY - (last.top + last.height) > 5; // min delta 523 | } 524 | 525 | 526 | 527 | // Export utils 528 | Sortable.utils = { 529 | on: _on, 530 | off: _off, 531 | css: _css, 532 | find: _find, 533 | bind: _bind, 534 | closest: _closest, 535 | toggleClass: _toggleClass 536 | }; 537 | 538 | 539 | Sortable.version = '0.1.9'; 540 | 541 | // Export 542 | return Sortable; 543 | }); 544 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/Sortable.min.js: -------------------------------------------------------------------------------- 1 | /*! Sortable 0.1.9 - MIT | git://github.com/rubaxa/Sortable.git */ 2 | !function(a){"use strict";"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&"undefined"!=typeof module.exports?module.exports=a():window.Sortable=a()}(function(){"use strict";function a(a,c){this.el=a,this.options=c=c||{},c.group=c.group||Math.random(),c.handle=c.handle||null,c.draggable=c.draggable||a.children[0]&&a.children[0].nodeName||(/[uo]l/i.test(a.nodeName)?"li":"*"),c.ghostClass=c.ghostClass||"sortable-ghost",c.onAdd=b(this,c.onAdd||D),c.onUpdate=b(this,c.onUpdate||D),c.onRemove=b(this,c.onRemove||D),a[w]=c.group;for(var d in this)"_"===d.charAt(0)&&(this[d]=b(this,this[d]));e(a,"add",c.onAdd),e(a,"update",c.onUpdate),e(a,"remove",c.onRemove),e(a,"mousedown",this._onTapStart),e(a,"touchstart",this._onTapStart),A&&e(a,"selectstart",this._onTapStart),e(a,"dragover",this._onDragOver),e(a,"dragenter",this._onDragOver),F.push(this._onDragOver)}function b(a,b){var c=E.call(arguments,2);return b.bind?b.bind.apply(b,[a].concat(c)):function(){return b.apply(a,c.concat(E.call(arguments)))}}function c(a,b,c){if("*"===b)return a;if(a){c=c||y,b=b.split(".");var d=b.shift().toUpperCase(),e=new RegExp("\\s("+b.join("|")+")\\s","g");do if(!(""!==d&&a.nodeName!=d||b.length&&((" "+a.className+" ").match(e)||[]).length!=b.length))return a;while(a!==c&&(a=a.parentNode))}return null}function d(a){a.dataTransfer.dropEffect="move",a.preventDefault()}function e(a,b,c){a.addEventListener(b,c,!1)}function f(a,b,c){a.removeEventListener(b,c,!1)}function g(a,b,c){if(a)if(a.classList)a.classList[c?"add":"remove"](b);else{var d=(" "+a.className+" ").replace(/\s+/g," ").replace(" "+b+" ","");a.className=d+(c?" "+b:"")}}function h(a,b,c){if(a&&a.style){if(void 0===c)return y.defaultView&&y.defaultView.getComputedStyle?c=y.defaultView.getComputedStyle(a,""):a.currentStyle&&(c=a.currentStyle),void 0===b?c:c[b];a.style[b]=c+("string"==typeof c?"":"px")}}function i(a,b,c){if(a){var d=a.getElementsByTagName(b),e=0,f=d.length;if(c)for(;f>e;e++)c(d[e],e);return d}return[]}function j(a){return a.draggable=!1}function k(){B=!1}function l(a,b){var c=a.lastElementChild.getBoundingClientRect();return b.clientY-(c.top+c.height)>5}var m,n,o,p,q,r,s,t,u,v,w="Sortable"+(new Date).getTime(),x=window,y=x.document,z=x.parseInt,A=!!y.createElement("div").dragDrop,B=!1,C=function(a,b){var c=y.createEvent("Event");return c.initEvent(a,!0,!0),c.item=b,c},D=function(){},E=[].slice,F=[];return a.prototype={constructor:a,_applyEffects:function(){g(m,this.options.ghostClass,!0)},_onTapStart:function(a){var b=a.touches&&a.touches[0],f=(b||a).target,g=this.options,h=this.el;if(g.handle&&(f=c(f,g.handle,h)),f=c(f,g.draggable,h),f&&"selectstart"==a.type&&"A"!=f.tagName&&"IMG"!=f.tagName&&f.dragDrop(),f&&!m&&f.parentNode===h){u=a,f.draggable=!0,i(f,"a",j),i(f,"img",j),b&&(u={target:f,clientX:b.clientX,clientY:b.clientY},this._onDragStart(u,!0),a.preventDefault()),e(this.el,"dragstart",this._onDragStart),e(this.el,"dragend",this._onDrop),e(y,"dragover",d);try{y.selection?y.selection.empty():window.getSelection().removeAllRanges()}catch(k){}}},_emulateDragOver:function(){if(v){h(n,"display","none");var a=y.elementFromPoint(v.clientX,v.clientY),b=a,c=this.options.group,d=F.length;if(b)do{if(b[w]===c){for(;d--;)F[d]({clientX:v.clientX,clientY:v.clientY,target:a,rootEl:b});break}a=b}while(b=b.parentNode);h(n,"display","")}},_onTouchMove:function(a){if(u){var b=a.touches[0],c=b.clientX-u.clientX,d=b.clientY-u.clientY;v=b,h(n,"webkitTransform","translate3d("+c+"px,"+d+"px,0)")}},_onDragStart:function(a,b){var c=a.target,d=a.dataTransfer;if(o=this.el,m=c,p=c.nextSibling,t=this.options.group,b){var f,g=c.getBoundingClientRect(),i=h(c);n=c.cloneNode(!0),h(n,"top",g.top-z(i.marginTop,10)),h(n,"left",g.left-z(i.marginLeft,10)),h(n,"width",g.width),h(n,"height",g.height),h(n,"opacity","0.8"),h(n,"position","fixed"),h(n,"zIndex","100000"),o.appendChild(n),f=n.getBoundingClientRect(),h(n,"width",2*g.width-f.width),h(n,"height",2*g.height-f.height),e(y,"touchmove",this._onTouchMove),e(y,"touchend",this._onDrop),this._loopId=setInterval(this._emulateDragOver,150)}else d.effectAllowed="move",d.setData("Text",c.textContent),e(y,"drop",this._onDrop);setTimeout(this._applyEffects)},_onDragOver:function(a){if(!B&&t===this.options.group&&(void 0===a.rootEl||a.rootEl===this.el)){var b=this.el,d=c(a.target,this.options.draggable,b);if(0===b.children.length||b.children[0]===n||b===a.target&&l(b,a))b.appendChild(m);else if(d&&d!==m&&void 0!==d.parentNode[w]){q!==d&&(q=d,r=h(d),s=d.getBoundingClientRect());var e,f=s,g=f.right-f.left,i=f.bottom-f.top,j=/left|right|inline/.test(r.cssFloat+r.display),o=(j?(a.clientX-f.left)/g:(a.clientY-f.top)/i)>.5,p=d.offsetWidth>m.offsetWidth,u=d.offsetHeight>m.offsetHeight,v=d.nextSibling;B=!0,setTimeout(k,30),e=j?d.previousElementSibling===m&&!p||o>.5&&p:d.nextElementSibling!==m&&!u||o>.5&&u,e&&!v?b.appendChild(m):d.parentNode.insertBefore(m,e?v:d)}}},_onDrop:function(a){clearInterval(this._loopId),f(y,"drop",this._onDrop),f(y,"dragover",d),f(this.el,"dragend",this._onDrop),f(this.el,"dragstart",this._onDragStart),f(this.el,"selectstart",this._onTapStart),f(y,"touchmove",this._onTouchMove),f(y,"touchend",this._onDrop),a&&(a.preventDefault(),a.stopPropagation(),n&&n.parentNode.removeChild(n),m&&(j(m),g(m,this.options.ghostClass,!1),o.contains(m)?m.nextSibling!==p&&m.dispatchEvent(C("update",m)):(o.dispatchEvent(C("remove",m)),m.dispatchEvent(C("add",m)))),o=m=n=p=u=v=q=r=t=null)},destroy:function(){var a=this.el,b=this.options;f(a,"add",b.onAdd),f(a,"update",b.onUpdate),f(a,"remove",b.onRemove),f(a,"mousedown",this._onTapStart),f(a,"touchstart",this._onTapStart),f(a,"selectstart",this._onTapStart),f(a,"dragover",this._onDragOver),f(a,"dragenter",this._onDragOver),Array.prototype.forEach.call(a.querySelectorAll("[draggable]"),function(a){a.removeAttribute("draggable")}),F.splice(F.indexOf(this._onDragOver),1),this._onDrop(),this.el=null}},a.utils={on:e,off:f,css:h,find:i,bind:b,closest:c,toggleClass:g},a.version="0.1.9",a}); -------------------------------------------------------------------------------- /vendor/assets/Sortable/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sortable", 3 | "main": "Sortable.js", 4 | "version": "0.1.9", 5 | "homepage": "http://rubaxa.github.io/Sortable/", 6 | "authors": [ 7 | "RubaXa " 8 | ], 9 | "description": "Sortable is a minimalist JavaScript library for modern browsers and touch devices. No jQuery.", 10 | "keywords": [ 11 | "sortable", 12 | "reorder", 13 | "list", 14 | "html5", 15 | "drag", 16 | "and", 17 | "drop", 18 | "dnd" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "node_modules", 23 | "bower_components", 24 | "test", 25 | "tests" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sortable", 3 | "main": "Sortable.js", 4 | "version": "0.1.9", 5 | "homepage": "http://rubaxa.github.io/Sortable/", 6 | "repo": "RubaXa/Sortable", 7 | "authors": [ 8 | "RubaXa " 9 | ], 10 | "description": "Sortable is a minimalist JavaScript library for modern browsers and touch devices. No jQuery.", 11 | "keywords": [ 12 | "sortable", 13 | "reorder", 14 | "list", 15 | "html5", 16 | "drag", 17 | "and", 18 | "drop", 19 | "dnd" 20 | ], 21 | "license": "MIT", 22 | "ignore": [ 23 | "node_modules", 24 | "bower_components", 25 | "test", 26 | "tests" 27 | ], 28 | 29 | "scripts": [ 30 | "Sortable.js" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sortable. No jQuery. 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 181 | 182 | 183 | 184 | Fork me on GitHub 185 | 186 |
187 |
188 | 189 |

The JavaScript library for modern browsers and touch devices. No jQuery.

190 |
191 |
192 | 193 | 194 |
195 |
196 |
List A
197 |
    198 |
  • бегемот
  • 199 |
  • корм
  • 200 |
  • антон
  • 201 |
  • сало
  • 202 |
  • железосталь
  • 203 |
  • валик
  • 204 |
  • кровать
  • 205 |
  • краб
  • 206 |
207 |
208 | 209 |
210 |
List B
211 |
    212 |
  • казнить
  • 213 |
  • ,
  • 214 |
  • нельзя
  • 215 |
  • помиловать
  • 216 |
217 |
218 |
219 | 220 | 221 |
222 |
223 |
Multi
224 | 225 |
226 |
Group A
227 |
228 | 232 |
233 |
234 | 235 |
236 |
Group B
237 |
238 | 241 |
242 |
243 | 244 |
245 |
Group C
246 |
247 | 249 |
250 |
251 | 252 |
253 |
254 | 255 | 256 |
257 |
258 |
Code example
259 |
// Simple list
260 | var list = document.getElementById("my-ui-list");
261 | new Sortable(list); // That's all.
262 | 
263 | 
264 | // Grouping
265 | var foo = document.getElementById("foo");
266 | new Sortable(foo, { group: "omega" });
267 | 
268 | var bar = document.getElementById("bar");
269 | new Sortable(bar, { group: "omega" });
270 | 
271 | 
272 | // Or
273 | var container = document.getElementById("multi");
274 | var sort = new Sortable(container, {
275 |   handle: ".tile__title", // Restricts sort start click/touch to the specified element
276 |   draggable: ".tile", // Specifies which items inside the element should be sortable
277 |   onUpdate: function (evt/**Event*/){
278 |      var item = evt.item; // the current dragged HTMLElement
279 |   }
280 | });
281 | 
282 | // ..
283 | sort.destroy();
284 | 
285 |
286 | 287 |
288 |
289 |
See also
290 |
Loading…
291 | 292 |
293 |
294 | 295 |
296 | 297 | 298 | 299 | 300 | 301 | 363 | 364 | 365 | 366 | 367 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 474 | 475 | 476 | 477 | 478 | 487 | 488 | 489 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sortable", 3 | "exportName": "Sortable", 4 | "version": "0.1.9", 5 | "devDependencies": { 6 | "grunt": "*", 7 | "grunt-version": "*", 8 | "grunt-contrib-uglify": "*" 9 | }, 10 | "description": "Sortable is a minimalist JavaScript library for modern browsers and touch devices. No jQuery.", 11 | "main": "Sortable.js", 12 | "scripts": { 13 | "test": "grunt" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git://github.com/rubaxa/Sortable.git" 18 | }, 19 | "keywords": [ 20 | "sortable", 21 | "reorder", 22 | "drag" 23 | ], 24 | "author": "Konstantin Lebedev ", 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /vendor/assets/Sortable/st/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdeanar/yii2-file-processor/ab20489ff146757d0fbfd60bfeddd171df96845a/vendor/assets/Sortable/st/logo.png -------------------------------------------------------------------------------- /vendor/assets/jquery.fileapi/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdeanar/yii2-file-processor/ab20489ff146757d0fbfd60bfeddd171df96845a/vendor/assets/jquery.fileapi/.DS_Store -------------------------------------------------------------------------------- /vendor/assets/jquery.fileapi/statics/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdeanar/yii2-file-processor/ab20489ff146757d0fbfd60bfeddd171df96845a/vendor/assets/jquery.fileapi/statics/.DS_Store -------------------------------------------------------------------------------- /vendor/assets/jquery.fileapi/tests/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdeanar/yii2-file-processor/ab20489ff146757d0fbfd60bfeddd171df96845a/vendor/assets/jquery.fileapi/tests/.DS_Store -------------------------------------------------------------------------------- /vendor/assets/jquery.fileapi/tests/grunt-task/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rdeanar/yii2-file-processor/ab20489ff146757d0fbfd60bfeddd171df96845a/vendor/assets/jquery.fileapi/tests/grunt-task/.DS_Store -------------------------------------------------------------------------------- /views/base/index.php: -------------------------------------------------------------------------------- 1 | 6 |

base/index

7 | 8 |

9 | You may change the content of this page by modifying 10 | the file . 11 |

12 | -------------------------------------------------------------------------------- /widgets/BaseUploadWidget.php: -------------------------------------------------------------------------------- 1 | hash = rand(111111, 999999); 50 | $this->uploadUrl = Url::toRoute('/fp/base/upload', true); 51 | $this->removeUrl = Url::toRoute('/fp/base/remove', true); 52 | $this->identifier .= '-' . $this->hash; 53 | $this->htmlOptions['id'] = $this->identifier; 54 | 55 | $this->debug = Yii::$app->getModule('fp')->debug ? 'true' : 'false'; 56 | 57 | $this->language_keys = array_merge($this->language_keys, $this->language_keys_default); 58 | if (count($this->language_keys) > 0){ 59 | $language_array = []; 60 | foreach($this->language_keys as $key){ 61 | $language_array[$key] = Module::t($key); 62 | } 63 | $language = 'file_processor.addMessages('.json_encode($language_array).');'; 64 | $this->getView()->registerJs($language); 65 | } 66 | } 67 | 68 | /** 69 | * Return array of already uploaded files. Used for display uploads in update form. 70 | * @param $type 71 | * @param $type_id 72 | * @param string $variation 73 | * @return array 74 | */ 75 | public function getAlreadyUploadedByReference($type, $type_id, $variation = '_thumb') 76 | { 77 | if (is_null($type_id)) return []; 78 | 79 | $uploads = []; 80 | 81 | $array = Uploads::findByReference($type, $type_id); 82 | 83 | if(!is_null($array)) { 84 | foreach ($array as $item) { 85 | /** 86 | * @var $item Uploads 87 | */ 88 | array_push($uploads, 89 | array( 90 | 'src' => $item->getPublicFileUrl($variation), 91 | 'type' => $item->mime, 92 | 'name' => $item->original, 93 | 'size' => $item->size, 94 | 'data' => array( 95 | 'id' => $item->id, 96 | 'type' => $item->type, 97 | 'type_id' => $item->type_id, 98 | ) 99 | )); 100 | } 101 | } 102 | 103 | return $uploads; 104 | } 105 | 106 | public function getHtmlOptionsWithBaseClasses($base_class_names){ 107 | if(empty($this->htmlOptions['class'])){ 108 | $this->htmlOptions['class'] = implode(' ', $base_class_names); 109 | }else { 110 | $this->htmlOptions['class'] = str_replace(' ', ' ', 111 | implode(' ', 112 | array_unique( 113 | array_merge( 114 | $base_class_names, 115 | explode(' ', $this->htmlOptions['class']) 116 | ) 117 | ) 118 | ) 119 | ); 120 | } 121 | 122 | return $this->htmlOptions; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /widgets/DisplayWidget.php: -------------------------------------------------------------------------------- 1 | 'img-thumbnail']; 21 | 22 | public function init() 23 | { 24 | parent::init(); 25 | } 26 | 27 | /** 28 | * Renders the widget. 29 | */ 30 | public function run() 31 | { 32 | if( is_null($this->variation) ) return ''; 33 | 34 | $asset = DisplayWidgetAsset::register($this->getView()); 35 | 36 | $uploads = Uploads::findByReference($this->type, $this->type_id); 37 | return $this->render('display_widget', [ 38 | 'uploads' => $uploads, 39 | 'variation' => $this->variation, 40 | 'htmlOptions' => $this->htmlOptions, 41 | ]); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /widgets/MultiUploadWidget.php: -------------------------------------------------------------------------------- 1 | sortUrl = Url::toRoute('/fp/base/sort', true); 38 | } 39 | 40 | /** 41 | * Return options array for fileapi 42 | * @return array 43 | */ 44 | private function generateOptionsArray(){ 45 | if (empty($this->options)) return []; 46 | $return = []; 47 | 48 | $return['imageAutoOrientation'] = false; 49 | 50 | if(!isset($this->options['multiple'])) $this->options['multiple'] = true; 51 | if($this->options['multiple'] === false){ 52 | $this->options['maxFiles'] = 1; 53 | $this->options['multiple'] = true; // hack, because if false you can add many files, but uploaded will be only the last one. 54 | } 55 | $this->multiple = $this->options['multiple']; 56 | 57 | foreach($this->options as $option_name => $option_value){ 58 | if( !in_array($option_name, $this->options_allowed)) continue; 59 | 60 | if($option_name == 'maxSize'){ 61 | $option_value = FileHelper::sizeToBytes($option_value); 62 | } 63 | 64 | $return[$option_name] = $option_value; 65 | } 66 | return $return; 67 | } 68 | 69 | /** 70 | * Renders the widget. 71 | */ 72 | public function run() 73 | { 74 | $upload_asset = UploadAsset::register($this->getView()); 75 | $fileapi_asset = FileAPIAsset::register($this->getView()); 76 | 77 | $additionalData = array( 78 | 'type' => $this->type, 79 | 'type_id' => $this->type_id, 80 | 'hash' => $this->hash, 81 | Yii::$app->request->csrfParam => Yii::$app->request->getCsrfToken(), 82 | ); 83 | 84 | $settingsJson = Json::encode([ 85 | 'identifier' => $this->identifier, 86 | 'uploadUrl' => $this->uploadUrl, 87 | 'removeUrl' => $this->removeUrl, 88 | 'sortUrl' => $this->sortUrl, 89 | 'additionalData' => $additionalData, 90 | 'alreadyUploadedFiles' => $this->getAlreadyUploadedByReference($this->type, $this->type_id), 91 | 'options' => $this->generateOptionsArray(), 92 | ]); 93 | 94 | $fileApiInitSettings = <<debug, media: true, staticPath: '$fileapi_asset->baseUrl/FileAPI/', 'url' : '$this->uploadUrl' 97 | }; 98 | EOF; 99 | 100 | $fileApiRun = <<getView()->registerJs($fileApiInitSettings, View::POS_HEAD); 105 | $this->getView()->registerJs($fileApiRun); 106 | 107 | return $this->render('multi_upload_widget', array( 108 | 'hash' => $this->hash, 109 | 'identifier' => $this->identifier, 110 | 'uploadUrl' => $this->uploadUrl, 111 | 'multiple' => $this->multiple, 112 | 'htmlOptions' => $this->getHtmlOptionsWithBaseClasses(['b-upload', 'fp_multi_upload']), 113 | )); 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /widgets/SingleUploadWidget.php: -------------------------------------------------------------------------------- 1 | crop = (bool)$this->crop; 39 | $this->preview = (bool)$this->preview; 40 | 41 | if($this->crop) { 42 | $this->preview = true; 43 | $this->options['accept'] = 'image/*'; 44 | }else{ 45 | // without crop control 46 | $this->options['autoUpload'] = true; 47 | } 48 | 49 | if(is_array($this->previewSize) && count($this->previewSize) >= 2){ 50 | $normalizePreviewSize = function($value) { 51 | $value = intval($value); 52 | if($value < 50 OR $value > 500){ 53 | $value = 200; 54 | } 55 | return $value; 56 | }; 57 | $this->previewSize = array_map($normalizePreviewSize, $this->previewSize); 58 | }else{ 59 | $this->previewSize = [200, 200]; 60 | } 61 | 62 | } 63 | 64 | private function generateOptionsArray(){ 65 | if (empty($this->options)) return []; 66 | $return = []; 67 | 68 | $this->options['maxFiles'] = 1; 69 | $this->multiple = false; 70 | $return['imageAutoOrientation'] = false; 71 | 72 | foreach($this->options as $option_name => $option_value){ 73 | if( !in_array($option_name, $this->options_allowed)) continue; 74 | 75 | if($option_name == 'maxSize'){ 76 | $option_value = FileHelper::sizeToBytes($option_value); 77 | } 78 | 79 | $return[$option_name] = $option_value; 80 | } 81 | return $return; 82 | } 83 | 84 | /** 85 | * Renders the widget. 86 | */ 87 | public function run() 88 | { 89 | $upload_asset = UploadAsset::register($this->getView()); 90 | $fileapi_asset = FileAPIAsset::register($this->getView()); 91 | 92 | $additionalData = array( 93 | 'type' => $this->type, 94 | 'type_id' => $this->type_id, 95 | 'hash' => $this->hash, 96 | Yii::$app->request->csrfParam => Yii::$app->request->getCsrfToken(), 97 | ); 98 | 99 | $settingsJson = Json::encode([ 100 | 'identifier' => $this->identifier, 101 | 'uploadUrl' => $this->uploadUrl, 102 | 'removeUrl' => $this->removeUrl, 103 | 'additionalData' => $additionalData, 104 | 'alreadyUploadedFiles' => $this->getAlreadyUploadedByReference($this->type, $this->type_id, 'original'), 105 | 'options' => $this->generateOptionsArray(), 106 | 'crop' => $this->crop, 107 | 'preview' => $this->preview, 108 | 'previewSize' => $this->previewSize, 109 | ]); 110 | 111 | $fileApiInitSettings = <<debug, media: true, staticPath: '$fileapi_asset->baseUrl/FileAPI/', 'url' : '$this->uploadUrl' 114 | }; 115 | EOF; 116 | 117 | $fileApiRun = <<getView()->registerJs($fileApiInitSettings, View::POS_HEAD); 122 | $this->getView()->registerJs($fileApiRun); 123 | 124 | $params = array( 125 | 'hash' => $this->hash, 126 | 127 | 'identifier' => $this->identifier, 128 | 'uploadUrl' => $this->uploadUrl, 129 | 'multiple' => $this->multiple, 130 | 'crop' => $this->crop, 131 | 'preview' => $this->preview, 132 | 'htmlOptions' => $this->getHtmlOptionsWithBaseClasses($this->isSimple() ? ['fp_single_simple_upload'] : ['fp_single_upload']), 133 | ); 134 | 135 | if($this->isSimple()) { 136 | return $this->render('single_upload_widget_simple', $params); 137 | }else{ 138 | return $this->render('single_upload_widget', $params); 139 | } 140 | } 141 | 142 | /** 143 | * @return bool 144 | * With or without preview (simple) 145 | */ 146 | public function isSimple(){ 147 | return $this->preview === false; 148 | } 149 | 150 | } 151 | -------------------------------------------------------------------------------- /widgets/views/display_widget.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | ?> 6 | 7 |
8 | original; 15 | if ($upload->isImage()) { 16 | echo $upload->imgTag($variation, true, $htmlOptions); 17 | } else { 18 | $htmlOptions['href'] = $upload->getPublicFileUrl('original', true); 19 | $htmlOptions['target'] = '_blank'; 20 | echo \yii\helpers\Html::tag('a', $upload->original, $htmlOptions); 21 | } 22 | } 23 | } 24 | ?> 25 |
26 | -------------------------------------------------------------------------------- /widgets/views/multi_upload_widget.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | use \deanar\fileProcessor\Module; 7 | 8 | ?> 9 | 10 | 11 | 12 |
13 |
14 | 15 |
    16 |
  • 17 |
    18 |
    19 |
    20 |
    21 | <% if( /^image/.test(type) ){ %> 22 |
    23 | <% } %> 24 |
    25 |
    26 |
    27 |
    <%-name%>
    28 |
  • 29 |
30 | 31 |
32 | 33 | /> 34 |
35 |
36 | 37 |
38 |
39 | 40 | -------------------------------------------------------------------------------- /widgets/views/single_upload_widget.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | use \deanar\fileProcessor\Module; 7 | ?> 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 22 | 28 | 29 | 30 | 31 |
32 | 33 | 34 | 51 | 52 | 62 | 63 | -------------------------------------------------------------------------------- /widgets/views/single_upload_widget_simple.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | ?> 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | Browse 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 27 | 28 | 29 | */ 30 | ?> 31 | 32 | 33 | 34 |
35 | --------------------------------------------------------------------------------