├── .editorconfig
├── .env.dist
├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── LICENSE.md
├── README.md
├── api
└── README.md
├── composer.json
├── composer.lock
├── docker-compose.yml
├── docker
└── mysql
│ └── my.cnf
├── guide
└── README.md
├── phpcs.xml
├── phpdoc.xml
├── phpunit.xml.dist
├── src
├── FileManager.php
├── actions
│ └── UploadAction.php
├── behaviors
│ ├── FileBehavior.php
│ └── FileBind.php
└── messages
│ └── ru
│ └── filemanager-yii2.php
└── tests
├── BaseTest.php
├── MultipleUploadTest.php
├── README.md
├── ResizeTest.php
├── RulesTest.php
├── SingleUploadTest.php
├── bootstrap.php
├── config
├── console.php
└── local
│ └── .gitignore
├── data
├── behavior.php
├── controllers
│ └── Controller.php
├── files.php
├── files
│ ├── 100x100.png
│ ├── 300x300-without-extension
│ ├── 300x300.png
│ └── 500x500.png
├── fixtures
│ └── file.php
├── models
│ ├── File.php
│ ├── News.php
│ └── User.php
└── views
│ └── gallery-item.php
├── migrations
├── m141230_075228_create_file.php
├── m141230_075229_create_news.php
└── m161026_103037_create_news_files.php
└── yii
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 4
6 |
7 | [*.{html,js,css}]
8 | indent_size = 2
9 |
--------------------------------------------------------------------------------
/.env.dist:
--------------------------------------------------------------------------------
1 | COMPOSE_PROJECT_NAME=filemanager_yii2
2 | MYSQL_ROOT_PASSWORD=
3 | MYSQL_USER=
4 | MYSQL_DATABASE=
5 | MYSQL_PASSWORD=
6 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | Thumbs.db
3 | *.log
4 | .env
5 |
6 | vendor/
7 | tests/tmp/
8 | tests/data/files/tmp/
9 | build-api/
10 |
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | tools:
2 | php_sim: false
3 | php_cpd: false
4 | php_pdepend: true
5 | php_analyzer: true
6 | external_code_coverage:
7 | timeout: 600 # Timeout in seconds.
8 | filter:
9 | excluded_paths:
10 | - 'tests/*'
11 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.5
5 | - 5.6
6 | - 7.0
7 |
8 | cache:
9 | directories:
10 | - $HOME/.composer/cache
11 |
12 | before_install:
13 | - pip install --user codecov
14 |
15 | install:
16 | - travis_retry composer self-update && composer --version
17 | - export PATH="$HOME/.composer/vendor/bin:$PATH"
18 | - travis_retry composer install --prefer-dist --no-interaction
19 |
20 | before_script:
21 | - mysql -e 'create database filemanager_yii2_tests;'
22 | - php ./tests/yii migrate --migrationPath=./src/migrations --interactive=0
23 | - php ./tests/yii migrate --migrationPath=./tests/migrations --interactive=0
24 |
25 | script:
26 | - vendor/bin/phpunit --coverage-clover=coverage.clover
27 |
28 | after_script:
29 | - wget https://scrutinizer-ci.com/ocular.phar
30 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
31 |
32 | after_success:
33 | - codecov
34 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015, Igor Romanov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FileManager for Yii2
2 |
3 | [](https://travis-ci.org/rkit/filemanager-yii2)
4 | [](https://scrutinizer-ci.com/g/rkit/filemanager-yii2/?branch=master)
5 | [](http://codecov.io/github/rkit/filemanager-yii2?branch=master)
6 | [](https://scrutinizer-ci.com/g/rkit/filemanager-yii2/?branch=master)
7 |
8 | ## Features
9 |
10 | - Use [flysystem](https://flysystem.thephpleague.com/)
11 | - Multiple file uploads
12 | - Presets (resize, crop) for files
13 | - Create thumbnails on the fly or after upload
14 | - The ability to specify extra fields for files
15 | - The ability to use any library for manipulating files (resize, crop, etc)
16 |
17 | ## Installation
18 |
19 | ```
20 | composer require rkit/filemanager-yii2
21 | ```
22 |
23 | ## Documentation
24 |
25 | - [Guide](/guide)
26 | - [API Reference](/api)
27 |
28 | ## Development
29 |
30 | ### Tests
31 |
32 | - [See docs](/tests/#tests)
33 |
34 | ### Coding Standard
35 |
36 | - PHP Code Sniffer — [phpcs.xml](./phpcs.xml)
37 |
--------------------------------------------------------------------------------
/api/README.md:
--------------------------------------------------------------------------------
1 | # FileManager for Yii2
2 |
3 | ## Table of Contents
4 |
5 | * [FileBehavior](#filebehavior)
6 | * [fileRelation](#filerelation)
7 | * [fileOption](#fileoption)
8 | * [fileStorage](#filestorage)
9 | * [filePath](#filepath)
10 | * [fileUrl](#fileurl)
11 | * [fileExtraFields](#fileextrafields)
12 | * [files](#files)
13 | * [file](#file)
14 | * [fileRules](#filerules)
15 | * [fileState](#filestate)
16 | * [filePresetAfterUpload](#filepresetafterupload)
17 | * [thumbUrl](#thumburl)
18 | * [thumbPath](#thumbpath)
19 | * [createFile](#createfile)
20 |
21 | ## FileBehavior
22 |
23 |
24 |
25 |
26 |
27 | * Full name: \rkit\filemanager\behaviors\FileBehavior
28 | * Parent class:
29 |
30 |
31 | ### fileRelation
32 |
33 | Get relation
34 |
35 | ```php
36 | FileBehavior::fileRelation( string $attribute ): \ActiveQuery
37 | ```
38 |
39 |
40 |
41 |
42 | **Parameters:**
43 |
44 | | Parameter | Type | Description |
45 | |-----------|------|-------------|
46 | | `$attribute` | **string** | The attribute name |
47 |
48 |
49 |
50 |
51 | ---
52 |
53 | ### fileOption
54 |
55 | Get file option
56 |
57 | ```php
58 | FileBehavior::fileOption( string $attribute, string $option, mixed $defaultValue = null ): mixed
59 | ```
60 |
61 |
62 |
63 |
64 | **Parameters:**
65 |
66 | | Parameter | Type | Description |
67 | |-----------|------|-------------|
68 | | `$attribute` | **string** | The attribute name |
69 | | `$option` | **string** | Option name |
70 | | `$defaultValue` | **mixed** | Default value |
71 |
72 |
73 |
74 |
75 | ---
76 |
77 | ### fileStorage
78 |
79 | Get file storage
80 |
81 | ```php
82 | FileBehavior::fileStorage( string $attribute ): \Flysystem
83 | ```
84 |
85 |
86 |
87 |
88 | **Parameters:**
89 |
90 | | Parameter | Type | Description |
91 | |-----------|------|-------------|
92 | | `$attribute` | **string** | The attribute name |
93 |
94 |
95 |
96 |
97 | ---
98 |
99 | ### filePath
100 |
101 | Get file path
102 |
103 | ```php
104 | FileBehavior::filePath( string $attribute, \yii\db\ActiveRecord $file = null ): string
105 | ```
106 |
107 |
108 |
109 |
110 | **Parameters:**
111 |
112 | | Parameter | Type | Description |
113 | |-----------|------|-------------|
114 | | `$attribute` | **string** | The attribute name |
115 | | `$file` | **\yii\db\ActiveRecord** | Use this file model |
116 |
117 |
118 | **Return Value:**
119 |
120 | The file path
121 |
122 |
123 |
124 | ---
125 |
126 | ### fileUrl
127 |
128 | Get file url
129 |
130 | ```php
131 | FileBehavior::fileUrl( string $attribute, \yii\db\ActiveRecord $file = null ): string
132 | ```
133 |
134 |
135 |
136 |
137 | **Parameters:**
138 |
139 | | Parameter | Type | Description |
140 | |-----------|------|-------------|
141 | | `$attribute` | **string** | The attribute name |
142 | | `$file` | **\yii\db\ActiveRecord** | Use this file model |
143 |
144 |
145 | **Return Value:**
146 |
147 | The file url
148 |
149 |
150 |
151 | ---
152 |
153 | ### fileExtraFields
154 |
155 | Get extra fields of file
156 |
157 | ```php
158 | FileBehavior::fileExtraFields( string $attribute ): array
159 | ```
160 |
161 |
162 |
163 |
164 | **Parameters:**
165 |
166 | | Parameter | Type | Description |
167 | |-----------|------|-------------|
168 | | `$attribute` | **string** | The attribute name |
169 |
170 |
171 |
172 |
173 | ---
174 |
175 | ### files
176 |
177 | Get files
178 |
179 | ```php
180 | FileBehavior::files( string $attribute ): array<mixed,\ActiveRecord>
181 | ```
182 |
183 |
184 |
185 |
186 | **Parameters:**
187 |
188 | | Parameter | Type | Description |
189 | |-----------|------|-------------|
190 | | `$attribute` | **string** | The attribute name |
191 |
192 |
193 | **Return Value:**
194 |
195 | The file models
196 |
197 |
198 |
199 | ---
200 |
201 | ### file
202 |
203 | Get the file
204 |
205 | ```php
206 | FileBehavior::file( string $attribute ): \ActiveRecord
207 | ```
208 |
209 |
210 |
211 |
212 | **Parameters:**
213 |
214 | | Parameter | Type | Description |
215 | |-----------|------|-------------|
216 | | `$attribute` | **string** | The attribute name |
217 |
218 |
219 | **Return Value:**
220 |
221 | The file model
222 |
223 |
224 |
225 | ---
226 |
227 | ### fileRules
228 |
229 | Get rules
230 |
231 | ```php
232 | FileBehavior::fileRules( string $attribute, boolean $onlyCoreValidators = false ): array
233 | ```
234 |
235 |
236 |
237 |
238 | **Parameters:**
239 |
240 | | Parameter | Type | Description |
241 | |-----------|------|-------------|
242 | | `$attribute` | **string** | The attribute name |
243 | | `$onlyCoreValidators` | **boolean** | Only core validators |
244 |
245 |
246 |
247 |
248 | ---
249 |
250 | ### fileState
251 |
252 | Get file state
253 |
254 | ```php
255 | FileBehavior::fileState( string $attribute ): array
256 | ```
257 |
258 |
259 |
260 |
261 | **Parameters:**
262 |
263 | | Parameter | Type | Description |
264 | |-----------|------|-------------|
265 | | `$attribute` | **string** | The attribute name |
266 |
267 |
268 |
269 |
270 | ---
271 |
272 | ### filePresetAfterUpload
273 |
274 | Get the presets of the file for apply after upload
275 |
276 | ```php
277 | FileBehavior::filePresetAfterUpload( string $attribute ): array
278 | ```
279 |
280 |
281 |
282 |
283 | **Parameters:**
284 |
285 | | Parameter | Type | Description |
286 | |-----------|------|-------------|
287 | | `$attribute` | **string** | The attribute name |
288 |
289 |
290 |
291 |
292 | ---
293 |
294 | ### thumbUrl
295 |
296 | Create a thumb and return url
297 |
298 | ```php
299 | FileBehavior::thumbUrl( string $attribute, string $preset, \yii\db\ActiveRecord $file = null ): string
300 | ```
301 |
302 |
303 |
304 |
305 | **Parameters:**
306 |
307 | | Parameter | Type | Description |
308 | |-----------|------|-------------|
309 | | `$attribute` | **string** | The attribute name |
310 | | `$preset` | **string** | The preset name |
311 | | `$file` | **\yii\db\ActiveRecord** | Use this file model |
312 |
313 |
314 | **Return Value:**
315 |
316 | The file url
317 |
318 |
319 |
320 | ---
321 |
322 | ### thumbPath
323 |
324 | Create a thumb and return full path
325 |
326 | ```php
327 | FileBehavior::thumbPath( string $attribute, string $preset, \yii\db\ActiveRecord $file = null ): string
328 | ```
329 |
330 |
331 |
332 |
333 | **Parameters:**
334 |
335 | | Parameter | Type | Description |
336 | |-----------|------|-------------|
337 | | `$attribute` | **string** | The attribute name |
338 | | `$preset` | **string** | The preset name |
339 | | `$file` | **\yii\db\ActiveRecord** | Use this file model |
340 |
341 |
342 | **Return Value:**
343 |
344 | The file path
345 |
346 |
347 |
348 | ---
349 |
350 | ### createFile
351 |
352 | Create a file
353 |
354 | ```php
355 | FileBehavior::createFile( string $attribute, string $path, string $name ): \ActiveRecord
356 | ```
357 |
358 |
359 |
360 |
361 | **Parameters:**
362 |
363 | | Parameter | Type | Description |
364 | |-----------|------|-------------|
365 | | `$attribute` | **string** | The attribute name |
366 | | `$path` | **string** | The file path |
367 | | `$name` | **string** | The file name |
368 |
369 |
370 | **Return Value:**
371 |
372 | The file model
373 |
374 |
375 |
376 | ---
377 |
378 |
379 |
380 | --------
381 | > This document was automatically generated from source code comments on 2016-11-03 using [phpDocumentor](http://www.phpdoc.org/) and [cvuorinen/phpdoc-markdown-public](https://github.com/cvuorinen/phpdoc-markdown-public)
382 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "rkit/filemanager-yii2",
3 | "description": "FileManager for Yii2",
4 | "keywords": ["yii2", "extension", "file manager", "manager", "upload", "resize", "files"],
5 | "homepage": "https://github.com/rkit/filemanager-yii2",
6 | "type": "yii2-extension",
7 | "license": "MIT",
8 | "authors": [
9 | {
10 | "name": "Igor Romanov",
11 | "email": "rkit.ru@gmail.com"
12 | }
13 | ],
14 | "support": {
15 | "issues": "https://github.com/rkit/filemanager-yii2/issues?state=open",
16 | "source": "https://github.com/rkit/filemanager-yii2"
17 | },
18 | "require": {
19 | "yiisoft/yii2": "^2.0"
20 | },
21 | "scripts": {
22 | "phpdoc": "phpdoc",
23 | "test-prepare": [
24 | "php tests/yii migrate --migrationPath=@tests/migrations/ --interactive=0"
25 | ],
26 | "test": "phpunit --colors=always",
27 | "test-coverage": "phpunit --coverage-html=tests/tmp/coverage.html --colors=always",
28 | "test-coverage-open": "open tests/tmp/coverage.html/index.html"
29 | },
30 | "autoload": {
31 | "psr-4": {
32 | "rkit\\filemanager\\": "src"
33 | }
34 | },
35 | "require-dev": {
36 | "intervention/image": "^2.3.2",
37 | "phpunit/phpunit": "^4.8",
38 | "phpunit/dbunit": "^1.4",
39 | "phpdocumentor/phpdocumentor": "^2.8.0",
40 | "cvuorinen/phpdoc-markdown-public": "^0.1.2",
41 | "league/flysystem": "^1.0",
42 | "creocoder/yii2-flysystem": "^0.8.1",
43 | "squizlabs/php_codesniffer": "3.0.0RC1"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | mysql:
5 | image: mysql:5.7
6 | ports:
7 | - "3306:3306"
8 | volumes:
9 | - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
10 | - data-mysql-5.7:/var/lib/mysql
11 | restart: always
12 | env_file: .env
13 |
14 | volumes:
15 | data-mysql-5.7:
16 | driver: local
17 |
--------------------------------------------------------------------------------
/docker/mysql/my.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 |
3 | default_time_zone = '+00:00'
4 | sql_mode = STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE
5 |
--------------------------------------------------------------------------------
/guide/README.md:
--------------------------------------------------------------------------------
1 | # Guide
2 |
3 | This extension does not contain any widget for ajax upload.
4 | You can to use any widget, for example: [fileapi-widget-yii2](https://github.com/rkit/fileapi-widget-yii2)
5 | But what is important, is that **the input value should contain the id of a file or an array of ids.**
6 |
7 | ## Usage
8 |
9 | For example, we have a model `News`, and we want to add the ability to upload files.
10 | Let's do it.
11 |
12 | 1. **Configuring components**
13 |
14 | ```php
15 | 'fileManager' => [
16 | 'class' => 'rkit\filemanager\FileManager',
17 | // 'sessionName' => 'filemanager.uploads',
18 | ],
19 | // any flysystem component for storage, for example https://github.com/creocoder/yii2-flysystem
20 | 'localFs' => [
21 | 'class' => 'creocoder\flysystem\LocalFilesystem',
22 | 'path' => '@webroot/uploads',
23 | ],
24 | ```
25 |
26 | 2. **Creating migrations**
27 |
28 | Migration for a model `File` (and of course you need to create the model)
29 |
30 | ```php
31 | php yii migrate/create create_file_table --fields="title:string:notNull:defaultValue(''),name:string:notNull:defaultValue(''),date_create:timestamp,date_update:timestamp"
32 | ```
33 | > You can add any extra fields, such as `user_id`, `ip`, `size`, `extension`
34 |
35 | Migration for a join table
36 |
37 | ```php
38 | php yii migrate/create create_news_files_table --fields="news_id:integer:notNull:defaultValue(0),file_id:integer:notNull:defaultValue(0)"
39 | ```
40 | > You can add any extra fields, such as `type` to divide files by type or `position` to set sort order
41 |
42 | 3. **Applying Migrations**
43 |
44 | ```php
45 | php yii migrate
46 | ```
47 |
48 | 4. **Declaring a relation**
49 |
50 | ```php
51 | public function getPreviewFile()
52 | {
53 | return $this
54 | ->hasMany(File::className(), ['id' => 'file_id'])
55 | ->viaTable('news_files', ['news_id' => 'id']);
56 | }
57 | ```
58 |
59 | 5. **Adding upload action**
60 |
61 | ```php
62 | public function behaviors()
63 | {
64 | return [
65 | 'verbs' => [
66 | 'class' => VerbFilter::className(),
67 | 'actions' => [
68 | 'preview-upload' => ['post']
69 | ],
70 | ],
71 | ];
72 | }
73 |
74 | public function actions()
75 | {
76 | return [
77 | 'preview-upload' => [
78 | 'class' => 'rkit\filemanager\actions\UploadAction',
79 | 'modelClass' => 'app\models\News',
80 | 'attribute' => 'preview',
81 | 'inputName' => 'file',
82 | ],
83 | ];
84 | }
85 | ```
86 |
87 | 6. **Adding behavior**
88 |
89 | ```php
90 | public function behaviors()
91 | {
92 | return [
93 | [
94 | 'class' => 'rkit\filemanager\behaviors\FileBehavior',
95 | 'attributes' => [
96 | 'preview' => [
97 | // any flysystem component for storage
98 | 'storage' => 'localFs',
99 | // the base URL for files
100 | 'baseUrl' => '@web/uploads',
101 | // file type (default `image`)
102 | 'type' => 'image',
103 | // relation name
104 | 'relation' => 'previewFile',
105 | // save file path in attribute (default `false`)
106 | 'saveFilePathInAttribute' => true,
107 | // a callback for generating file path
108 | // `function(ActiveRecord $file): string`
109 | 'templatePath' => function ($file) {
110 | $date = new \DateTime(is_object($file->date_create) ? null : $file->date_create);
111 | return '/' . $date->format('Ym') . '/' . $file->id . '/' . $file->name;
112 | },
113 | // a callback for creating `File` model
114 | // `function(string $path, string $name): ActiveRecord`
115 | 'createFile' => function ($path, $name) {
116 | $file = new File();
117 | $file->title = $name;
118 | $file->generateName(pathinfo($name, PATHINFO_EXTENSION));
119 | $file->save();
120 | return $file;
121 | },
122 | // core validators
123 | 'rules' => [
124 | 'imageSize' => ['minWidth' => 300, 'minHeight' => 300],
125 | 'mimeTypes' => ['image/png', 'image/jpg', 'image/jpeg'],
126 | 'extensions' => ['jpg', 'jpeg', 'png'],
127 | 'maxSize' => 1024 * 1024 * 1, // 1 MB
128 | 'maxFiles' => 1,
129 | 'tooBig' => Yii::t('app', 'File size must not exceed') . ' 1Mb'
130 | ]),
131 | // the names[] of presets with callbacks
132 | // `function(string $realPath, string $publicPath, string $thumbPath)`
133 | 'preset' => [
134 | '220x220' => function ($realPath, $publicPath, $thumbPath) {
135 | // you can to use any library for manipulating with files
136 | Image::make($realPath . $publicPath)
137 | ->fit(220, 220)
138 | ->save($realPath . $thumbPath, 100);
139 | },
140 | ]),
141 | // the names[] of presets or `*` to apply all
142 | 'applyPresetAfterUpload' => ['220x220']
143 | ],
144 | ]
145 | ]
146 | ];
147 | }
148 | ```
149 |
150 | ## Behavior settings
151 |
152 | ```php
153 | // any flysystem component for storage
154 | 'storage' => 'localFs',
155 | // the base URL for files
156 | 'baseUrl' => '@web/uploads',
157 | // file type (default `image`)
158 | 'type' => 'image',
159 | // multiple files (default `false`)
160 | 'multiple' => false,
161 | // path to template for upload response (the default is an array with id and path of file)
162 | 'template' => null,
163 | // a relation name
164 | 'relation' => 'previewFile',
165 | // save file path in attribute (default `false`)
166 | 'saveFilePathInAttribute' => true,
167 | // save file id in attribute (default `false`)
168 | 'saveFileIdInAttribute' => false,
169 | // a callback for generating file path
170 | // `function(ActiveRecord $file): string`
171 | 'templatePath' => function ($file) {
172 | $date = new \DateTime(is_object($file->date_create) ? null : $file->date_create);
173 | return '/' . $date->format('Ym') . '/' . $file->id . '/' . $file->name;
174 | },
175 | // a callback for creating `File` model
176 | // `function(string $path, string $name): ActiveRecord`
177 | 'createFile' => function ($path, $name) {
178 | $file = new File();
179 | $file->title = $name;
180 | $file->generateName(pathinfo($name, PATHINFO_EXTENSION));
181 | $file->save();
182 | return $file;
183 | },
184 | // a callback for updating `File` model, triggered every time after saving model
185 | // important: return model without saving
186 | // `function(ActiveRecord $file): ActiveRecord`
187 | 'updateFile' => function ($file) {
188 | // you can modify attributes (without saving)
189 | return $file;
190 | },
191 | // a callback for filling extra fields, triggered every time after saving model
192 | // `function(ActiveRecord $file, array $fields): array new extra fields`
193 | 'extraFields' => function ($file, $fields) {
194 | return [
195 | 'type' => 1, if you want to divide files by type
196 | 'position' => $positions[$file->id], // if you want to set sort order
197 | ];
198 | },
199 | // core validators
200 | 'rules' => [
201 | 'imageSize' => ['minWidth' => 300, 'minHeight' => 300],
202 | 'mimeTypes' => ['image/png', 'image/jpg', 'image/jpeg'],
203 | 'extensions' => ['jpg', 'jpeg', 'png'],
204 | 'maxSize' => 1024 * 1024 * 1, // 1 MB
205 | 'maxFiles' => 1,
206 | 'tooBig' => Yii::t('app', 'File size must not exceed') . ' 1Mb'
207 | ]),
208 | // the names[] of presets with callbacks
209 | // `function(string $realPath, string $publicPath, string $thumbPath)`
210 | 'preset' => [
211 | '220x220' => function ($realPath, $publicPath, $thumbPath) {
212 | // you can to use any library for manipulating with files
213 | Image::make($realPath . $publicPath)
214 | ->fit(220, 220)
215 | ->save($realPath . $thumbPath, 100);
216 | },
217 | ]),
218 | // the names[] of presets or `*` to apply all
219 | 'applyPresetAfterUpload' => ['220x220']
220 | ```
221 |
222 | ### API
223 |
224 | To get file
225 |
226 | ```php
227 | $model->file('preview');
228 | ```
229 |
230 | or by relation name
231 |
232 | ```php
233 | $model->previewFile;
234 | ```
235 |
236 | If multiple files
237 |
238 | ```php
239 | $model->files('gallery');
240 | ```
241 |
242 | To get file full path
243 |
244 | ```php
245 | $model->filePath('preview');
246 | ```
247 |
248 | To get file url
249 |
250 | ```php
251 | $model->fileUrl('gallery');
252 | ```
253 |
254 | To create a file manually
255 |
256 | ```php
257 | $model->createFile('preview', '/path/to/file', 'title');
258 | ```
259 |
260 | To create thumbnail and return url
261 |
262 | ```php
263 | $model->thumbUrl('preview', '200x200');
264 | ```
265 |
266 | To create thumbnail and return full path
267 |
268 | ```php
269 | $model->thumbPath('preview', '200x200');
270 | ```
271 |
272 | To get extra fields
273 |
274 | ```php
275 | $model->fileExtraFields('preview');
276 | ```
277 |
278 | > [See more in API](../api)
279 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Based on PSR-2
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | *.md
12 | */views/*
13 |
14 |
15 |
16 |
17 |
18 | 0
19 |
20 |
21 |
22 |
23 | */migrations/*
24 |
25 |
26 |
27 |
28 | */migrations/*
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/phpdoc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | FileManager for Yii2
4 |
5 | build-api
6 |
7 |
8 | api
9 |
10 |
11 |
12 |
13 |
14 | src
15 | src/FileManager.php
16 | src/behaviors/FileBind.php
17 | tests/*
18 | migrations/*
19 | actions/*
20 |
21 |
22 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | tests
6 |
7 |
8 |
9 |
10 | src
11 |
12 | src/messages
13 | src/migrations
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/FileManager.php:
--------------------------------------------------------------------------------
1 |
18 | */
19 | class FileManager extends Component
20 | {
21 | /**
22 | * @var string Session variable name
23 | */
24 | public $sessionName = 'filemanager.uploads';
25 |
26 | /**
27 | * @internal
28 | */
29 | public function init()
30 | {
31 | parent::init();
32 | $this->registerTranslations();
33 | }
34 |
35 | /**
36 | * Registers translator
37 | * @internal
38 | *
39 | * @return void
40 | */
41 | public function registerTranslations()
42 | {
43 | if (!isset(\Yii::$app->i18n->translations['filemanager-yii2'])) {
44 | \Yii::$app->i18n->translations['filemanager-yii2'] = [
45 | 'class' => 'yii\i18n\PhpMessageSource',
46 | 'basePath' => '@vendor/rkit/filemanager-yii2/src/messages',
47 | 'sourceLanguage' => 'en',
48 | ];
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/actions/UploadAction.php:
--------------------------------------------------------------------------------
1 | modelClass === null && $this->modelObject === null) {
56 | throw new InvalidParamException(
57 | get_class($this) . '::$modelClass or ' .get_class($this) . '::$modelObject must be set'
58 | );
59 | }
60 |
61 | $this->model = $this->modelClass ? new $this->modelClass : $this->modelObject;
62 | }
63 |
64 | public function run()
65 | {
66 | $file = UploadedFile::getInstanceByName($this->inputName);
67 |
68 | if (!$file) {
69 | return $this->response(
70 | ['error' => Yii::t('filemanager-yii2', 'An error occured, try again later…')]
71 | );
72 | }
73 |
74 | $rules = $this->model->fileRules($this->attribute, true);
75 | $type = $this->model->fileOption($this->attribute, 'type', 'image');
76 |
77 | $model = new DynamicModel(compact('file'));
78 |
79 | $maxFiles = ArrayHelper::getValue($rules, 'maxFiles');
80 | if ($maxFiles !== null && $maxFiles > 1) {
81 | $model->file = [$model->file];
82 | }
83 |
84 | $model->addRule('file', $type, $rules)->validate();
85 | if ($model->hasErrors()) {
86 | return $this->response(['error' => $model->getFirstError('file')]);
87 | }
88 |
89 | if (is_array($model->file)) {
90 | $model->file = $model->file[0];
91 | }
92 |
93 | return $this->upload($model->file);
94 | }
95 |
96 | /**
97 | * Upload
98 | *
99 | * @param UploadedFile $file
100 | * @return string JSON
101 | * @SuppressWarnings(PHPMD.ElseExpression)
102 | */
103 | private function upload($file)
104 | {
105 | $file = $this->model->createFile($this->attribute, $file->tempName, $file->name);
106 | if ($file) {
107 | if ($this->saveAfterUpload) {
108 | $this->model->save(false);
109 | }
110 | $presetAfterUpload = $this->model->filePresetAfterUpload($this->attribute);
111 | if (count($presetAfterUpload)) {
112 | $this->applyPreset($presetAfterUpload, $file);
113 | }
114 | $template = $this->model->fileOption($this->attribute, 'template');
115 | if ($template) {
116 | return $this->response(
117 | $this->controller->renderFile(Yii::getAlias($template), [
118 | 'file' => $file,
119 | 'model' => $this->model,
120 | 'attribute' => $this->attribute
121 | ])
122 | );
123 | }
124 | return $this->response([
125 | $this->resultFieldId => $file->getPrimaryKey(),
126 | $this->resultFieldPath => $this->model->fileUrl($this->attribute, $file),
127 | ]);
128 | }
129 | return $this->response(['error' => Yii::t('filemanager-yii2', 'Error saving file')]); // @codeCoverageIgnore
130 | }
131 |
132 | /**
133 | * Apply preset for file
134 | *
135 | * @param array $presetAfterUpload
136 | * @param ActiveRecord $file The file model
137 | * @return void
138 | */
139 | private function applyPreset($presetAfterUpload, $file)
140 | {
141 | foreach ($presetAfterUpload as $preset) {
142 | $this->model->thumbUrl($this->attribute, $preset, $file);
143 | }
144 | }
145 |
146 | /**
147 | * JSON Response
148 | *
149 | * @param mixed $data
150 | * @return string JSON Only for yii\web\Application, for console app returns `mixed`
151 | */
152 | private function response($data)
153 | {
154 | // @codeCoverageIgnoreStart
155 | if (!Yii::$app instanceof \yii\console\Application) {
156 | \Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
157 | }
158 | // @codeCoverageIgnoreEnd
159 | return $data;
160 | }
161 | }
162 |
--------------------------------------------------------------------------------
/src/behaviors/FileBehavior.php:
--------------------------------------------------------------------------------
1 | fileBind = new FileBind();
39 |
40 | Yii::$app->fileManager->registerTranslations();
41 | }
42 |
43 | /**
44 | * @inheritdoc
45 | * @internal
46 | */
47 | public function events()
48 | {
49 | return [
50 | ActiveRecord::EVENT_AFTER_INSERT => 'afterSave',
51 | ActiveRecord::EVENT_AFTER_UPDATE => 'afterSave',
52 | ActiveRecord::EVENT_BEFORE_INSERT => 'beforeSave',
53 | ActiveRecord::EVENT_BEFORE_UPDATE => 'beforeSave',
54 | ActiveRecord::EVENT_BEFORE_DELETE => 'beforeDelete',
55 | ];
56 | }
57 |
58 | /**
59 | * @internal
60 | * @SuppressWarnings(PHPMD.UnusedFormalParameter)
61 | * @SuppressWarnings(PHPMD.UnusedLocalVariable)
62 | */
63 | public function beforeSave($insert)
64 | {
65 | foreach ($this->attributes as $attribute => $options) {
66 | $oldValue = $this->owner->isNewRecord ? null : $this->owner->getOldAttribute($attribute);
67 | $isAttributeChanged = $oldValue === null ? true : $this->owner->isAttributeChanged($attribute);
68 |
69 | $this->attributes[$attribute]['isAttributeChanged'] = $isAttributeChanged;
70 | $this->attributes[$attribute]['oldValue'] = $oldValue;
71 | }
72 | }
73 |
74 | /**
75 | * @internal
76 | */
77 | public function afterSave()
78 | {
79 | foreach ($this->attributes as $attribute => $options) {
80 | $files = $this->owner->{$attribute};
81 |
82 | $isAttributeNotChanged = $options['isAttributeChanged'] === false || $files === null;
83 | if ($isAttributeNotChanged) {
84 | continue;
85 | }
86 |
87 | if (is_numeric($files)) {
88 | $files = [$files];
89 | }
90 |
91 | if (is_array($files)) {
92 | $files = array_filter($files);
93 | }
94 |
95 | if ($files === [] || $files === '') {
96 | $this->fileBind->delete($this->owner, $attribute, $this->files($attribute));
97 | continue;
98 | }
99 |
100 | $maxFiles = ArrayHelper::getValue($this->fileRules($attribute, true), 'maxFiles');
101 | if (is_array($files) && $maxFiles !== null) {
102 | $files = array_slice($files, 0, $maxFiles, true);
103 | }
104 |
105 | $files = $this->fileBind->bind($this->owner, $attribute, $files);
106 | if (is_array($files)) {
107 | $files = array_shift($files);
108 | }
109 |
110 | $this->clearState($attribute);
111 | $this->setValue($attribute, $files, $options['oldValue']);
112 | }
113 | }
114 |
115 | /**
116 | * @internal
117 | * @SuppressWarnings(PHPMD.UnusedLocalVariable)
118 | */
119 | public function beforeDelete()
120 | {
121 | foreach ($this->attributes as $attribute => $options) {
122 | $this->fileBind->delete($this->owner, $attribute, $this->files($attribute));
123 | }
124 | }
125 |
126 | private function clearState($attribute)
127 | {
128 | $state = Yii::$app->session->get(Yii::$app->fileManager->sessionName);
129 | unset($state[$attribute]);
130 | Yii::$app->session->set(Yii::$app->fileManager->sessionName, $state);
131 | }
132 |
133 | private function setState($attribute, $file)
134 | {
135 | $state = Yii::$app->session->get(Yii::$app->fileManager->sessionName);
136 | if (!is_array($state)) {
137 | $state = [];
138 | }
139 | $state[$attribute][] = $file->getPrimaryKey();
140 | Yii::$app->session->set(Yii::$app->fileManager->sessionName, $state);
141 | }
142 |
143 | private function setValue($attribute, $file, $defaultValue)
144 | {
145 | $saveFilePath = $this->fileOption($attribute, 'saveFilePathInAttribute');
146 | $saveFileId = $this->fileOption($attribute, 'saveFileIdInAttribute');
147 |
148 | if ($saveFilePath || $saveFileId) {
149 | if (!$file) {
150 | $value = $defaultValue;
151 | } elseif ($saveFilePath) {
152 | $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
153 | $value = Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $handlerTemplatePath($file);
154 | } elseif ($saveFileId) {
155 | $value = $file->getPrimaryKey();
156 | }
157 | $this->owner->{$attribute} = $value;
158 | $this->owner->updateAttributes([$attribute => $value]);
159 | }
160 | }
161 |
162 | /**
163 | * Generate a thumb
164 | *
165 | * @param string $attribute The attribute name
166 | * @param string $preset The preset name
167 | * @param string $path The file path
168 | * @return string The thumb path
169 | */
170 | private function generateThumb($attribute, $preset, $path)
171 | {
172 | $thumbPath = pathinfo($path, PATHINFO_FILENAME);
173 | $thumbPath = str_replace($thumbPath, $preset . '_' . $thumbPath, $path);
174 | $realPath = $this->fileStorage($attribute)->path;
175 |
176 | if (!file_exists($realPath . $thumbPath) && file_exists($realPath . $path)) {
177 | $handlerPreset = $this->fileOption($attribute, 'preset.'.$preset);
178 | $handlerPreset($realPath, $path, $thumbPath);
179 | }
180 |
181 | return $thumbPath;
182 | }
183 |
184 | /**
185 | * Generate file path by template
186 | *
187 | * @param string $attribute The attribute name
188 | * @param ActiveRecord $file The file model
189 | * @return string The file path
190 | */
191 | private function templatePath($attribute, $file = null)
192 | {
193 | $value = $this->owner->{$attribute};
194 |
195 | $saveFilePath = $this->fileOption($attribute, 'saveFilePathInAttribute');
196 | $isFilledPath = $saveFilePath && !empty($value);
197 |
198 | $saveFileId = $this->fileOption($attribute, 'saveFileIdInAttribute');
199 | $isFilledId = $saveFileId && is_numeric($value) && $value;
200 |
201 | if (($isFilledPath || $isFilledId) && $file === null) {
202 | $file = $this->file($attribute);
203 | }
204 |
205 | if ($file !== null) {
206 | $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
207 | return $handlerTemplatePath($file);
208 | }
209 | return $value;
210 | }
211 |
212 | /**
213 | * Get relation
214 | *
215 | * @param string $attribute The attribute name
216 | * @return \ActiveQuery
217 | */
218 | public function fileRelation($attribute)
219 | {
220 | if ($this->relation === null) {
221 | $this->relation = $this->owner->getRelation($this->fileOption($attribute, 'relation'));
222 | }
223 | return $this->relation;
224 | }
225 |
226 | /**
227 | * Get file option
228 | *
229 | * @param string $attribute The attribute name
230 | * @param string $option Option name
231 | * @param mixed $defaultValue Default value
232 | * @return mixed
233 | */
234 | public function fileOption($attribute, $option, $defaultValue = null)
235 | {
236 | return ArrayHelper::getValue($this->attributes[$attribute], $option, $defaultValue);
237 | }
238 |
239 | /**
240 | * Get file storage
241 | *
242 | * @param string $attribute The attribute name
243 | * @return \Flysystem
244 | */
245 | public function fileStorage($attribute)
246 | {
247 | return Yii::$app->get($this->fileOption($attribute, 'storage'));
248 | }
249 |
250 | /**
251 | * Get file path
252 | *
253 | * @param string $attribute The attribute name
254 | * @param ActiveRecord $file Use this file model
255 | * @return string The file path
256 | */
257 | public function filePath($attribute, $file = null)
258 | {
259 | $path = $this->templatePath($attribute, $file);
260 | return $this->fileStorage($attribute)->path . $path;
261 | }
262 |
263 | /**
264 | * Get file url
265 | *
266 | * @param string $attribute The attribute name
267 | * @param ActiveRecord $file Use this file model
268 | * @return string The file url
269 | */
270 | public function fileUrl($attribute, $file = null)
271 | {
272 | $path = $this->templatePath($attribute, $file);
273 | return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $path;
274 | }
275 |
276 | /**
277 | * Get extra fields of file
278 | *
279 | * @param string $attribute The attribute name
280 | * @return array
281 | */
282 | public function fileExtraFields($attribute)
283 | {
284 | $fields = $this->fileBind->relations($this->owner, $attribute);
285 | if (!$this->fileOption($attribute, 'multiple')) {
286 | return array_shift($fields);
287 | }
288 | return $fields;
289 | }
290 |
291 | /**
292 | * Get files
293 | *
294 | * @param string $attribute The attribute name
295 | * @return \ActiveRecord[] The file models
296 | */
297 | public function files($attribute)
298 | {
299 | return $this->fileBind->files($this->owner, $attribute);
300 | }
301 |
302 | /**
303 | * Get the file
304 | *
305 | * @param string $attribute The attribute name
306 | * @return \ActiveRecord The file model
307 | */
308 | public function file($attribute)
309 | {
310 | return $this->fileBind->file($this->owner, $attribute);
311 | }
312 |
313 | /**
314 | * Get rules
315 | *
316 | * @param string $attribute The attribute name
317 | * @param bool $onlyCoreValidators Only core validators
318 | * @return array
319 | */
320 | public function fileRules($attribute, $onlyCoreValidators = false)
321 | {
322 | $rules = $this->fileOption($attribute, 'rules', []);
323 | if ($onlyCoreValidators && isset($rules['imageSize'])) {
324 | $rules = array_merge($rules, $rules['imageSize']);
325 | unset($rules['imageSize']);
326 | }
327 | return $rules;
328 | }
329 |
330 | /**
331 | * Get file state
332 | *
333 | * @param string $attribute The attribute name
334 | * @return array
335 | */
336 | public function fileState($attribute)
337 | {
338 | $state = Yii::$app->session->get(Yii::$app->fileManager->sessionName);
339 | return ArrayHelper::getValue($state === null ? [] : $state, $attribute, []);
340 | }
341 |
342 | /**
343 | * Get the presets of the file for apply after upload
344 | *
345 | * @param string $attribute The attribute name
346 | * @return array
347 | */
348 | public function filePresetAfterUpload($attribute)
349 | {
350 | $preset = $this->fileOption($attribute, 'applyPresetAfterUpload', []);
351 | if (is_string($preset) && $preset === '*') {
352 | return array_keys($this->fileOption($attribute, 'preset', []));
353 | }
354 | return $preset;
355 | }
356 |
357 | /**
358 | * Create a thumb and return url
359 | *
360 | * @param string $attribute The attribute name
361 | * @param string $preset The preset name
362 | * @param ActiveRecord $file Use this file model
363 | * @return string The file url
364 | */
365 | public function thumbUrl($attribute, $preset, $file = null)
366 | {
367 | $path = $this->templatePath($attribute, $file);
368 | $thumbPath = $this->generateThumb($attribute, $preset, $path);
369 |
370 | return Yii::getAlias($this->fileOption($attribute, 'baseUrl')) . $thumbPath;
371 | }
372 |
373 | /**
374 | * Create a thumb and return full path
375 | *
376 | * @param string $attribute The attribute name
377 | * @param string $preset The preset name
378 | * @param ActiveRecord $file Use this file model
379 | * @return string The file path
380 | */
381 | public function thumbPath($attribute, $preset, $file = null)
382 | {
383 | $path = $this->templatePath($attribute, $file);
384 | $thumbPath = $this->generateThumb($attribute, $preset, $path);
385 |
386 | return $this->fileStorage($attribute)->path . $thumbPath;
387 | }
388 |
389 | /**
390 | * Create a file
391 | *
392 | * @param string $attribute The attribute name
393 | * @param string $path The file path
394 | * @param string $name The file name
395 | * @return \ActiveRecord The file model
396 | */
397 | public function createFile($attribute, $path, $name)
398 | {
399 | $handlerCreateFile = $this->fileOption($attribute, 'createFile');
400 | $file = $handlerCreateFile($path, $name);
401 | if ($file) {
402 | $storage = $this->fileStorage($attribute);
403 | $contents = file_get_contents($path);
404 | $handlerTemplatePath = $this->fileOption($attribute, 'templatePath');
405 | if ($storage->write($handlerTemplatePath($file), $contents)) {
406 | $this->setState($attribute, $file);
407 | $this->owner->{$attribute} = $file->id;
408 | return $file;
409 | }
410 | } // @codeCoverageIgnore
411 | return false; // @codeCoverageIgnore
412 | }
413 | }
414 |
--------------------------------------------------------------------------------
/src/behaviors/FileBind.php:
--------------------------------------------------------------------------------
1 | newFiles($model, $attribute, $files);
20 | if (!count($newFiles)) {
21 | return false;
22 | }
23 |
24 | $currentFiles = $this->files($model, $attribute);
25 | $currentRelations = $this->relations($model, $attribute);
26 | $insertData = $this->prepareInsertRelations($model, $attribute, $newFiles);
27 | $updateData = $this->prepareUpdateRelations($model, $attribute, $newFiles, $currentRelations);
28 |
29 | $resultFiles = $insertData['files'] + $updateData['files'];
30 | if (!count($resultFiles)) {
31 | return false;
32 | }
33 |
34 | $this->delete($model, $attribute, array_diff_key($currentFiles, $resultFiles));
35 | $this->save($model, $attribute, $insertData, $updateData, $resultFiles);
36 |
37 | return $resultFiles;
38 | }
39 |
40 | private function save($model, $attribute, $insertData, $updateData, $resultFiles)
41 | {
42 | if (count($insertData['rows'])) {
43 | $this->insertRelations($model, $attribute, $insertData['rows'], $insertData['columns']);
44 | }
45 | if (count($updateData['rows'])) {
46 | $this->updateRelations($model, $attribute, $updateData['rows']);
47 | }
48 | if (count($resultFiles)) {
49 | $this->updateFiles($model, $attribute, $resultFiles);
50 | }
51 | }
52 |
53 | private function newFiles($model, $attribute, $fileIds)
54 | {
55 | $relation = $model->fileRelation($attribute);
56 | $fileModel = $relation->modelClass;
57 |
58 | return $fileModel::find()
59 | ->where([key($relation->link) => $fileIds])
60 | ->indexBy(key($relation->link))
61 | ->all();
62 | }
63 |
64 | private function prepareInsertRelations($model, $attribute, $newFiles)
65 | {
66 | $ownerId = $model->getPrimaryKey();
67 | $relation = $model->fileRelation($attribute);
68 | $uploadedFiles = $model->fileState($attribute);
69 | $handlerExtraFields = $model->fileOption($attribute, 'extraFields');
70 |
71 | $files = [];
72 | $rows = [];
73 | $extraFields = [];
74 | foreach ($uploadedFiles as $fileId) {
75 | if (isset($newFiles[$fileId])) {
76 | $file = $newFiles[$fileId];
77 | $row = [$ownerId, $fileId];
78 | if ($handlerExtraFields) {
79 | $fields = [
80 | key($relation->via->link) => $ownerId,
81 | current($relation->link) => $fileId,
82 | ];
83 | $extraFields = $handlerExtraFields($file, $fields);
84 | $row = array_merge($row, array_values($extraFields));
85 | }
86 | $rows[] = $row;
87 | $files[$file->getPrimaryKey()] = $file;
88 | }
89 | }
90 |
91 | $columns = [key($relation->via->link), current($relation->link)];
92 | $columns = array_merge($columns, array_keys($extraFields));
93 |
94 | return ['rows' => $rows, 'files' => $files, 'columns' => $columns];
95 | }
96 |
97 | private function prepareUpdateRelations($model, $attribute, $newFiles, $currentRelations)
98 | {
99 | $relation = $model->fileRelation($attribute);
100 | $handlerExtraFields = $model->fileOption($attribute, 'extraFields');
101 |
102 | $files = [];
103 | $rows = [];
104 | foreach ($currentRelations as $fields) {
105 | if (isset($newFiles[$fields[current($relation->link)]])) {
106 | $file = $newFiles[$fields[current($relation->link)]];
107 | if ($handlerExtraFields) {
108 | $extraFields = $handlerExtraFields($file, $fields);
109 | $fieldChanged = (bool)count(array_diff_assoc($extraFields, $fields));
110 | if ($fieldChanged) {
111 | $rows[$file->getPrimaryKey()] = $extraFields;
112 | }
113 | }
114 | $files[$file->getPrimaryKey()] = $file;
115 | }
116 | }
117 | return ['rows' => $rows, 'files' => $files];
118 | }
119 |
120 | private function insertRelations($model, $attribute, $rows, $columns)
121 | {
122 | $relation = $model->fileRelation($attribute);
123 | Yii::$app->getDb()->createCommand()
124 | ->batchInsert($relation->via->from[0], $columns, $rows)
125 | ->execute();
126 | }
127 |
128 | private function updateRelations($model, $attribute, $rows)
129 | {
130 | $relation = $model->fileRelation($attribute);
131 | $ownerId = $model->getPrimaryKey();
132 | $db = Yii::$app->getDb()->createCommand();
133 |
134 | foreach ($rows as $fileId => $row) {
135 | $db->update($relation->via->from[0], $row, [
136 | key($relation->via->link) => $ownerId,
137 | current($relation->link) => $fileId
138 | ])->execute();
139 | }
140 | }
141 |
142 | private function updateFiles($model, $attribute, $files)
143 | {
144 | $handlerUpdateFile = $model->fileOption($attribute, 'updateFile');
145 | $relation = $model->fileOption($attribute, 'relation');
146 | $isMultiple = $model->fileOption($attribute, 'multiple');
147 |
148 | $relationValue = [];
149 | foreach ($files as $file) {
150 | $relationValue[] = $file;
151 | if ($handlerUpdateFile) {
152 | $fileUpd = $handlerUpdateFile($file);
153 | $dirtyAttributes = $fileUpd->getDirtyAttributes();
154 | if (count($dirtyAttributes)) {
155 | $fileUpd->updateAttributes($dirtyAttributes);
156 | }
157 | }
158 | }
159 |
160 | if (!$isMultiple) {
161 | $relationValue = array_shift($relationValue);
162 | }
163 | $model->populateRelation($relation, $relationValue);
164 | }
165 |
166 | public function delete($model, $attribute, $files)
167 | {
168 | $relation = $model->fileRelation($attribute);
169 | $storage = $model->fileStorage($attribute);
170 | $presets = array_keys($model->fileOption($attribute, 'preset', []));
171 | $handlerTemplatePath = $model->fileOption($attribute, 'templatePath');
172 |
173 | $db = Yii::$app->getDb()->createCommand();
174 | foreach ($files as $file) {
175 | foreach ($presets as $preset) {
176 | $thumbPath = $model->thumbPath($attribute, $preset, $file);
177 | $filePath = str_replace($storage->path, '', $thumbPath);
178 | if ($storage->has($filePath)) {
179 | $storage->delete($filePath);
180 | }
181 | }
182 |
183 | if ($file->delete()) {
184 | $db->delete($relation->via->from[0], [
185 | current($relation->link) => $file->getPrimaryKey()
186 | ])->execute();
187 | $filePath = $handlerTemplatePath($file);
188 | if ($storage->has($filePath)) {
189 | $storage->delete($filePath);
190 | }
191 | }
192 | }
193 | }
194 |
195 | public function relations($model, $attribute)
196 | {
197 | $relation = $model->fileRelation($attribute);
198 |
199 | return (new Query())
200 | ->from($relation->via->from[0])
201 | ->andWhere([key($relation->via->link) => $model->getPrimaryKey()])
202 | ->indexBy(current($relation->link))
203 | ->all();
204 | }
205 |
206 | public function files($model, $attribute)
207 | {
208 | $relation = $model->fileRelation($attribute);
209 | $relationName = $model->fileOption($attribute, 'relation');
210 |
211 | $query = call_user_func([$model, 'get' . $relationName]);
212 | return $query->indexBy(key($relation->link))->all();
213 | }
214 |
215 | public function file($model, $attribute)
216 | {
217 | $relation = $model->fileOption($attribute, 'relation');
218 | return $model->$relation;
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/messages/ru/filemanager-yii2.php:
--------------------------------------------------------------------------------
1 | 'Произошла ошибка, попробуйте позже…',
5 | 'Error saving file' => 'Ошибка сохранения файла',
6 | ];
7 |
--------------------------------------------------------------------------------
/tests/BaseTest.php:
--------------------------------------------------------------------------------
1 | getDb()->open();
24 | return $this->createDefaultDBConnection(\Yii::$app->getDb()->pdo);
25 | }
26 |
27 | /**
28 | * @inheritdoc
29 | */
30 | public function getDataSet()
31 | {
32 | $data = require Yii::getAlias('@tests/data/fixtures/file.php');
33 | return new \PHPUnit_Extensions_Database_DataSet_ArrayDataSet($data);
34 | }
35 |
36 | /**
37 | * @SuppressWarnings(PHPMD.Superglobals)
38 | */
39 | public static function setUpBeforeClass()
40 | {
41 | FileHelper::createDirectory(Yii::getAlias('@tests/data/files/tmp'));
42 | $_FILES = require Yii::getAlias('@tests/data/files.php');
43 | }
44 |
45 | /**
46 | * @SuppressWarnings(PHPMD.Superglobals)
47 | */
48 | public static function tearDownAfterClass()
49 | {
50 | FileHelper::removeDirectory(Yii::getAlias('@tests/tmp/public'));
51 | FileHelper::removeDirectory(Yii::getAlias('@tests/data/files/tmp'));
52 |
53 | unset($_FILES);
54 | }
55 |
56 | /**
57 | * @SuppressWarnings(PHPMD.Superglobals)
58 | */
59 | public function tearDown()
60 | {
61 | $db = Yii::$app->getDb()->createCommand();
62 | $db->truncateTable('news')->execute();
63 | $db->truncateTable('news_files')->execute();
64 | }
65 |
66 | /**
67 | * @SuppressWarnings(PHPMD.Superglobals)
68 | */
69 | public function createTmpFile($fileName)
70 | {
71 | if (!isset($_FILES[$fileName])) {
72 | return false;
73 | }
74 |
75 | $file = $_FILES[$fileName];
76 | copy(Yii::getAlias('@tests/data/files/' . $file['name']), $file['tmp_name']);
77 |
78 | return $file['tmp_name'];
79 | }
80 |
81 | /**
82 | * Runs the upload action
83 | *
84 | * @param array $config
85 | * @return mixed The result of the action
86 | */
87 | public function runUploadAction($config)
88 | {
89 | $this->createTmpFile($config['inputName']);
90 |
91 | $action = new UploadAction('upload', new Controller('test', Yii::$app), $config);
92 | return $action->run();
93 | }
94 |
95 | public function createObject($modelClass, $options = [], $id = null)
96 | {
97 | $options;
98 | $behavior = require Yii::getAlias('@tests/data/behavior.php');
99 |
100 | $model = $id ? $modelClass::findOne($id) : new $modelClass;
101 | $model->attachBehavior('fileManager', $behavior);
102 |
103 | return $model;
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/tests/MultipleUploadTest.php:
--------------------------------------------------------------------------------
1 | createObject($this->modelClass);
26 | $response = $this->runUploadAction([
27 | 'modelObject' => $model,
28 | 'attribute' => 'gallery',
29 | 'inputName' => 'file-300',
30 | ]);
31 |
32 | $this->assertCount(2, $response);
33 | $this->assertArrayHasKey('id', $response);
34 | $this->assertArrayHasKey('path', $response);
35 |
36 | $file = File::findOne($response['id']);
37 | $this->assertInstanceOf(File::className(), $file);
38 |
39 | $path = $model->filePath('gallery', $file);
40 | $this->assertFileExists($path);
41 | }
42 |
43 | public function testUploadWithTemplate()
44 | {
45 | $model = $this->createObject($this->modelClass, [
46 | 'template' => '@tests/data/views/gallery-item.php',
47 | ]);
48 |
49 | $response = $this->runUploadAction([
50 | 'modelObject' => $model,
51 | 'attribute' => 'gallery',
52 | 'inputName' => 'file-300',
53 | ]);
54 |
55 | preg_match('/value=\"(.*?)\"/', $response, $matches);
56 |
57 | $this->assertTrue(is_string($response));
58 | $this->assertCount(2, $matches);
59 | $this->assertTrue(is_numeric($matches[1]));
60 | }
61 |
62 | public function testUploadAndBind()
63 | {
64 | $model = $this->createObject($this->modelClass, [
65 | 'rules.maxFiles' => 2,
66 | ]);
67 |
68 | $response1 = $this->runUploadAction([
69 | 'modelObject' => $model,
70 | 'attribute' => 'gallery',
71 | 'inputName' => 'file-300',
72 | ]);
73 |
74 | $response2 = $this->runUploadAction([
75 | 'modelObject' => $model,
76 | 'attribute' => 'gallery',
77 | 'inputName' => 'file-300',
78 | ]);
79 |
80 | $model->gallery = [$response1['id'], $response2['id']];
81 | $model->save();
82 |
83 | $files = $model->files('gallery');
84 | $this->assertCount(2, $files);
85 |
86 | foreach ($files as $file) {
87 | $this->assertInstanceOf(File::className(), $file);
88 | $this->assertFileExists($model->filePath('gallery', $file));
89 | }
90 | }
91 |
92 | public function testAddFile()
93 | {
94 | $model = $this->createObject($this->modelClass, [
95 | 'rules.maxFiles' => 2,
96 | ]);
97 |
98 | $response1 = $this->runUploadAction([
99 | 'modelObject' => $model,
100 | 'attribute' => 'gallery',
101 | 'inputName' => 'file-300',
102 | ]);
103 |
104 | $model->gallery = [$response1['id']];
105 | $model->save();
106 |
107 | $response2 = $this->runUploadAction([
108 | 'modelObject' => $model,
109 | 'attribute' => 'gallery',
110 | 'inputName' => 'file-300',
111 | ]);
112 |
113 | $model->gallery = [$response1['id'], $response2['id']];
114 | $model->save();
115 |
116 | $files = $model->files('gallery');
117 | $this->assertCount(2, $files);
118 |
119 | foreach ($files as $file) {
120 | $this->assertInstanceOf(File::className(), $file);
121 | $this->assertFileExists($model->filePath('gallery', $file));
122 | }
123 | }
124 |
125 | public function testUpdateLinks()
126 | {
127 | $model = $this->createObject($this->modelClass, [
128 | 'extraFields' => function () {
129 | return [
130 | 'type' => 2,
131 | 'position' => 1,
132 | ];
133 | }
134 | ]);
135 |
136 | $response = $this->runUploadAction([
137 | 'modelObject' => $model,
138 | 'attribute' => 'gallery',
139 | 'inputName' => 'file-300'
140 | ]);
141 |
142 | $model->gallery = [$response['id']];
143 | $model->save();
144 |
145 | $fields = $model->fileExtraFields('gallery');
146 | $this->assertEquals($fields[$response['id']]['position'], 1);
147 |
148 | $model = $this->createObject($this->modelClass, [
149 | 'extraFields' => function () {
150 | return [
151 | 'type' => 2,
152 | 'position' => 2,
153 | ];
154 | }
155 | ], $model->id);
156 |
157 | $model->gallery = [$response['id']];
158 | $model->title = 'tester';
159 | $model->save();
160 |
161 | $fields = $model->fileExtraFields('gallery');
162 | $this->assertEquals($fields[$response['id']]['position'], 2);
163 | }
164 |
165 | public function testMaxFiles()
166 | {
167 | $model = $this->createObject($this->modelClass, [
168 | 'rules.maxFiles' => 1,
169 | ]);
170 |
171 | $response1 = $this->runUploadAction([
172 | 'modelObject' => $model,
173 | 'attribute' => 'gallery',
174 | 'inputName' => 'file-300',
175 | ]);
176 | $response2 = $this->runUploadAction([
177 | 'modelObject' => $model,
178 | 'attribute' => 'gallery',
179 | 'inputName' => 'file-300',
180 | ]);
181 |
182 | $model->gallery = [$response1['id'], $response2['id']];
183 | $model->save();
184 |
185 | $files = $model->files('gallery');
186 | $this->assertCount(1, $files);
187 | }
188 |
189 | public function testClearFiles()
190 | {
191 | $model = $this->createObject($this->modelClass, [
192 | 'rules.maxFiles' => 2,
193 | ]);
194 |
195 | $response1 = $this->runUploadAction([
196 | 'modelObject' => $model,
197 | 'attribute' => 'gallery',
198 | 'inputName' => 'file-300',
199 | ]);
200 | $response2 = $this->runUploadAction([
201 | 'modelObject' => $model,
202 | 'attribute' => 'gallery',
203 | 'inputName' => 'file-300',
204 | ]);
205 |
206 | $model->gallery = [$response1['id'], $response2['id']];
207 | $model->save();
208 |
209 | $files = $model->files('gallery');
210 | $this->assertCount(2, $files);
211 |
212 | $model->gallery = [];
213 | $model->save();
214 |
215 | $this->assertCount(0, $model->files('gallery'));
216 | }
217 |
218 | public function testDeleteWrongItem()
219 | {
220 | $model = $this->createObject($this->modelClass);
221 | $response = $this->runUploadAction([
222 | 'modelObject' => $model,
223 | 'attribute' => 'gallery',
224 | 'inputName' => 'file-300',
225 | ]);
226 |
227 | $model->gallery = [$response['id'], 9999];
228 | $model->save();
229 |
230 | $this->assertCount(1, $model->files('gallery'));
231 | }
232 |
233 | public function testDeleteWrongGallery()
234 | {
235 | $model = $this->createObject($this->modelClass);
236 |
237 | $model->gallery = [9999];
238 | $model->save();
239 |
240 | $this->assertCount(0, $model->files('gallery'));
241 | }
242 | }
243 |
--------------------------------------------------------------------------------
/tests/README.md:
--------------------------------------------------------------------------------
1 | # Tests
2 |
3 | ## Preparation
4 |
5 | Create `filemanager_yii2_tests` database and run
6 |
7 | ```
8 | composer test-prepare
9 | ```
10 |
11 | ## Commands
12 |
13 | - Run tests
14 | ```
15 | composer test
16 | ```
17 |
18 | - Run tests with coverage
19 | ```
20 | composer test-coverage
21 | ```
22 |
23 | - Show coverage dashboard
24 | ```
25 | composer test-coverage-open
26 | ```
27 |
--------------------------------------------------------------------------------
/tests/ResizeTest.php:
--------------------------------------------------------------------------------
1 | assertTrue($imgWidth === $width);
32 | $this->assertTrue($imgHeight === $height);
33 | }
34 |
35 | public function testResize()
36 | {
37 | $model = $this->createObject($this->modelClass);
38 | $response = $this->runUploadAction([
39 | 'modelObject' => $model,
40 | 'attribute' => 'image',
41 | 'inputName' => 'file-300'
42 | ]);
43 |
44 | $model->image = $response['id'];
45 | $model->save();
46 |
47 | $thumb = $model->thumbPath('image', '200x200');
48 | $this->assertContains('200x200', $thumb);
49 | $this->assertFileExists($thumb);
50 | $this->checkImageSize($thumb, 200, 200);
51 |
52 | // for check cache thumb
53 | $model->thumbUrl('image', '200x200');
54 | }
55 |
56 | public function testResizeAndApplyPresetAfterUpload()
57 | {
58 | $model = $this->createObject($this->modelClass);
59 | $response = $this->runUploadAction([
60 | 'modelObject' => $model,
61 | 'attribute' => 'image',
62 | 'inputName' => 'file-300'
63 | ]);
64 |
65 | $model->image = $response['id'];
66 | $model->save();
67 |
68 | $path = $model->filePath('image');
69 | $fileName = pathinfo($path, PATHINFO_FILENAME);
70 | $thumb220 = str_replace($fileName, '220x220_' . $fileName, $path);
71 |
72 | $this->assertFileExists($thumb220);
73 | $this->checkImageSize($thumb220, 220, 220);
74 | }
75 |
76 | public function testResizeAndApplyAllPresetAfterUpload()
77 | {
78 | $model = $this->createObject($this->modelClass, [
79 | 'applyPresetAfterUpload' => '*',
80 | ]);
81 |
82 | $response = $this->runUploadAction([
83 | 'modelObject' => $model,
84 | 'attribute' => 'image',
85 | 'inputName' => 'file-300'
86 | ]);
87 |
88 | $model->image = $response['id'];
89 | $model->save();
90 |
91 | $path = $model->filePath('image');
92 | $fileName = pathinfo($path, PATHINFO_FILENAME);
93 |
94 | $thumb200 = str_replace($fileName, '200x200_' . $fileName, $path);
95 | $thumb220 = str_replace($fileName, '220x220_' . $fileName, $path);
96 |
97 | $this->assertFileExists($thumb200);
98 | $this->checkImageSize($thumb200, 200, 200);
99 |
100 | $this->assertFileExists($thumb220);
101 | $this->checkImageSize($thumb220, 220, 220);
102 | }
103 |
104 | public function testResizeAndReplace()
105 | {
106 | $model = $this->createObject($this->modelClass);
107 | $response = $this->runUploadAction([
108 | 'modelObject' => $model,
109 | 'attribute' => 'image',
110 | 'inputName' => 'file-500'
111 | ]);
112 |
113 | $model->image = $response['id'];
114 | $model->save();
115 |
116 | $thumb = $model->thumbPath('image', '400x400');
117 | $this->assertContains('400x400', $thumb);
118 | $this->assertFileNotExists($thumb);
119 | $this->checkImageSize($model->filePath('image'), 400, 400);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/tests/RulesTest.php:
--------------------------------------------------------------------------------
1 | createObject($this->modelClass);
20 | $rules = $model->fileRules('image');
21 |
22 | $this->assertArrayHasKey('imageSize', $rules);
23 | $this->assertArrayHasKey('mimeTypes', $rules);
24 | $this->assertArrayHasKey('extensions', $rules);
25 | $this->assertArrayHasKey('maxSize', $rules);
26 | $this->assertArrayHasKey('tooBig', $rules);
27 | }
28 |
29 | public function testRulesWithOnlyCoreValidators()
30 | {
31 | $model = $this->createObject($this->modelClass);
32 | $rules = $model->fileRules('image', true);
33 |
34 | $this->assertArrayNotHasKey('imageSize', $rules);
35 | $this->assertArrayHasKey('mimeTypes', $rules);
36 | $this->assertArrayHasKey('extensions', $rules);
37 | $this->assertArrayHasKey('maxSize', $rules);
38 | $this->assertArrayHasKey('tooBig', $rules);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/tests/SingleUploadTest.php:
--------------------------------------------------------------------------------
1 | createObject($this->modelClass);
25 | $response = $this->runUploadAction([
26 | 'modelObject' => $model,
27 | 'attribute' => 'image',
28 | 'inputName' => 'file-300',
29 | ]);
30 |
31 | $this->assertCount(2, $response);
32 | $this->assertArrayHasKey('id', $response);
33 | $this->assertArrayHasKey('path', $response);
34 |
35 | $file = File::findOne($response['id']);
36 | $this->assertInstanceOf(File::className(), $file);
37 |
38 | $path = $model->filePath('image', $file);
39 | $this->assertFileExists($path);
40 | }
41 |
42 | public function testUploadAndBind()
43 | {
44 | $model = $this->createObject($this->modelClass);
45 | $response = $this->runUploadAction([
46 | 'modelObject' => $model,
47 | 'attribute' => 'image',
48 | 'inputName' => 'file-300',
49 | ]);
50 |
51 | $model->image = $response['id'];
52 | $model->save();
53 |
54 | $file = $model->file('image');
55 |
56 | $this->assertInstanceOf(File::className(), $file);
57 | $this->assertFileExists($model->filePath('image'));
58 | $this->assertContains($model->image, $model->filePath('image'));
59 | $this->assertEquals($file->id, $response['id']);
60 | }
61 |
62 | public function testSaveFilePath()
63 | {
64 | $model = $this->createObject($this->modelClass);
65 | $response = $this->runUploadAction([
66 | 'modelObject' => $model,
67 | 'attribute' => 'image',
68 | 'inputName' => 'file-300',
69 | ]);
70 |
71 | $model->image = $response['id'];
72 | $model->save();
73 |
74 | $this->assertContains($model->image, $model->filePath('image'));
75 | }
76 |
77 | public function testSaveFileId()
78 | {
79 | $model = $this->createObject($this->modelClass, [
80 | 'saveFilePathInAttribute' => false,
81 | 'saveFileIdInAttribute' => true,
82 | ]);
83 |
84 | $response = $this->runUploadAction([
85 | 'modelObject' => $model,
86 | 'attribute' => 'image',
87 | 'inputName' => 'file-300',
88 | ]);
89 |
90 | $model->image = $response['id'];
91 | $model->save();
92 |
93 | $this->assertEquals($model->image, $model->file('image')->id);
94 | }
95 |
96 | public function testReUpload()
97 | {
98 | $model = $this->createObject($this->modelClass);
99 | $response1 = $this->runUploadAction([
100 | 'modelObject' => $model,
101 | 'attribute' => 'image',
102 | 'inputName' => 'file-300',
103 | ]);
104 |
105 | $model->image = $response1['id'];
106 | $model->save();
107 |
108 | $file1 = $model->file('image');
109 | $file1Path = $model->filePath('image');
110 |
111 | $this->assertEquals($file1->id, $response1['id']);
112 |
113 | $response2 = $this->runUploadAction([
114 | 'modelObject' => $model,
115 | 'attribute' => 'image',
116 | 'inputName' => 'file-500',
117 | ]);
118 |
119 | $model->image = $response2['id'];
120 | $model->save();
121 |
122 | $file2 = $model->file('image');
123 | $file2Path = $model->filePath('image');
124 |
125 | $this->assertEquals($file2->id, $response2['id']);
126 | $this->assertFileExists($file2Path);
127 | $this->assertFileNotExists($file1Path);
128 | $this->assertNull(File::findOne($response1['id']));
129 | }
130 |
131 | public function testUpdateLinks()
132 | {
133 | $model = $this->createObject($this->modelClass, [
134 | 'extraFields' => function () {
135 | return [
136 | 'type' => 1,
137 | 'position' => 1,
138 | ];
139 | }
140 | ]);
141 |
142 | $response = $this->runUploadAction([
143 | 'modelObject' => $model,
144 | 'attribute' => 'image',
145 | 'inputName' => 'file-300'
146 | ]);
147 |
148 | $model->image = $response['id'];
149 | $model->save();
150 |
151 | $fields = $model->fileExtraFields('image');
152 | $this->assertEquals($fields['position'], 1);
153 |
154 | $model = $this->createObject($this->modelClass, [
155 | 'extraFields' => function () {
156 | return [
157 | 'type' => 1,
158 | 'position' => 2,
159 | ];
160 | }
161 | ], $model->id);
162 |
163 | $model->image = $response['id'];
164 | $model->title = 'tester';
165 | $model->save();
166 |
167 | $fields = $model->fileExtraFields('image');
168 | $this->assertEquals($fields['position'], 2);
169 | }
170 |
171 | public function testUpdateFiles()
172 | {
173 | $model = $this->createObject($this->modelClass, [
174 | 'updateFile' => function ($file) {
175 | $file->title = 'test';
176 | return $file;
177 | }
178 | ]);
179 |
180 | $response = $this->runUploadAction([
181 | 'modelObject' => $model,
182 | 'attribute' => 'image',
183 | 'inputName' => 'file-300'
184 | ]);
185 |
186 | $model->image = $response['id'];
187 | $model->save();
188 |
189 | $file = File::findOne($response['id']);
190 |
191 | $this->assertInstanceOf(File::className(), $file);
192 | $this->assertEquals($file->title, 'test');
193 | }
194 |
195 | public function testDeleteModel()
196 | {
197 | $model = $this->createObject($this->modelClass);
198 | $response = $this->runUploadAction([
199 | 'modelObject' => $model,
200 | 'attribute' => 'image',
201 | 'inputName' => 'file-300'
202 | ]);
203 |
204 | $model->image = $response['id'];
205 | $model->save();
206 |
207 | $this->assertFileExists($model->filePath('image'));
208 |
209 | $model->delete();
210 |
211 | $this->assertFileNotExists($model->filePath('image'));
212 | }
213 |
214 | public function testEmptyFilePath()
215 | {
216 | $model = $this->createObject($this->modelClass);
217 | $model->image = '';
218 | $model->save();
219 |
220 | $this->assertNull($model->file('image'));
221 | $this->assertEmpty($model->image);
222 | }
223 |
224 | public function testClearField()
225 | {
226 | $model = $this->createObject($this->modelClass);
227 | $response = $this->runUploadAction([
228 | 'modelObject' => $model,
229 | 'attribute' => 'image',
230 | 'inputName' => 'file-300'
231 | ]);
232 |
233 | $model->image = $response['id'];
234 | $model->save();
235 |
236 | $model->image = '';
237 | $model->save();
238 |
239 | $this->assertNull(File::findOne($response['id']));
240 | $this->assertEmpty($model->image);
241 | }
242 |
243 | public function testNotChanged()
244 | {
245 | $model = $this->createObject($this->modelClass);
246 | $response = $this->runUploadAction([
247 | 'modelObject' => $model,
248 | 'attribute' => 'image',
249 | 'inputName' => 'file-300'
250 | ]);
251 |
252 | $model->image = $response['id'];
253 | $model->save();
254 |
255 | $path = $model->image;
256 |
257 | // repeat
258 | $model->save();
259 |
260 | $file = File::findOne($response['id']);
261 |
262 | $this->assertInstanceOf(File::className(), $file);
263 | $this->assertEquals($response['id'], $model->file('image')->id);
264 | $this->assertEquals($path, $model->image);
265 | }
266 |
267 | public function testWrongFilePath()
268 | {
269 | $model = $this->createObject($this->modelClass);
270 | $response = $this->runUploadAction([
271 | 'modelObject' => $model,
272 | 'attribute' => 'image',
273 | 'inputName' => 'file-300'
274 | ]);
275 |
276 | $model->image = $response['id'];
277 | $model->save();
278 |
279 | $model->image = 'test';
280 | $model->save();
281 |
282 | $file = File::findOne($response['id']);
283 |
284 | $this->assertInstanceOf(File::className(), $file);
285 | $this->assertEquals($response['id'], $model->file('image')->id);
286 | }
287 |
288 | public function testCustomResultFieldId()
289 | {
290 | $model = $this->createObject($this->modelClass);
291 | $response = $this->runUploadAction([
292 | 'modelObject' => $model,
293 | 'attribute' => 'image',
294 | 'inputName' => 'file-300',
295 | 'resultFieldId' => 'customFieldId',
296 | ]);
297 |
298 | $this->assertArrayHasKey('customFieldId', $response);
299 | }
300 |
301 | public function testCustomResultFieldPath()
302 | {
303 | $model = $this->createObject($this->modelClass);
304 | $response = $this->runUploadAction([
305 | 'modelObject' => $model,
306 | 'attribute' => 'image',
307 | 'inputName' => 'file-300',
308 | 'resultFieldPath' => 'customFieldPath',
309 | ]);
310 |
311 | $this->assertArrayHasKey('customFieldPath', $response);
312 | }
313 |
314 | /**
315 | * @expectedException Exception
316 | * @expectedExceptionMessage rkit\filemanager\actions\UploadAction::$modelClass or
317 | */
318 | public function testEmptyModelClass()
319 | {
320 | $this->runUploadAction([
321 | 'attribute' => 'image',
322 | 'inputName' => 'file-300'
323 | ]);
324 | }
325 |
326 | public function testWrongInputName()
327 | {
328 | $model = $this->createObject($this->modelClass);
329 | $response = $this->runUploadAction([
330 | 'modelObject' => $model,
331 | 'attribute' => 'image',
332 | 'inputName' => 'fail'
333 | ]);
334 |
335 | $this->assertCount(1, $response);
336 | $this->assertArrayHasKey('error', $response);
337 | }
338 |
339 | public function testWrongImageSize()
340 | {
341 | $model = $this->createObject($this->modelClass);
342 | $response = $this->runUploadAction([
343 | 'modelObject' => $model,
344 | 'attribute' => 'image',
345 | 'inputName' => 'file-100'
346 | ]);
347 |
348 | $this->assertCount(1, $response);
349 | $this->assertArrayHasKey('error', $response);
350 | $this->assertContains('The image "100x100.png" is too small', $response['error']);
351 | }
352 | }
353 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | 'unit',
7 | 'basePath' => Yii::getAlias('@tests'),
8 | 'components' => [
9 | 'user' => [
10 | 'class' => 'tests\data\models\User',
11 | 'id' => 0
12 | ],
13 | 'db' => [
14 | 'class' => 'yii\db\Connection',
15 | 'dsn' => 'mysql:host=127.0.0.1;dbname=filemanager_yii2_tests',
16 | 'username' => 'root',
17 | 'password' => '',
18 | 'emulatePrepare' => true,
19 | 'charset' => 'utf8',
20 | 'enableSchemaCache' => false
21 | ],
22 | 'session' => [
23 | 'class' => 'yii\web\Session',
24 | ],
25 | 'fileManager' => [
26 | 'class' => 'rkit\filemanager\FileManager',
27 | // 'sessionName' => 'filemanager.uploads',
28 | ],
29 | // any flysystem component for storage
30 | 'localFs' => [
31 | 'class' => 'creocoder\flysystem\LocalFilesystem',
32 | 'path' => '@tests/tmp/public',
33 | ],
34 | ]
35 | ];
36 |
37 | if (file_exists(__DIR__ . '/local/config.php')) {
38 | require_once __DIR__ . '/local/config.php';
39 | }
40 |
41 | return $config;
42 |
--------------------------------------------------------------------------------
/tests/config/local/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/data/behavior.php:
--------------------------------------------------------------------------------
1 | 'rkit\filemanager\behaviors\FileBehavior',
9 | 'attributes' => [
10 | 'image' => [
11 | 'storage' => 'localFs',
12 | 'baseUrl' => '@tests/tmp/public',
13 | 'type' => 'image',
14 | 'multiple' => false,
15 | 'relation' => 'imageFile',
16 | 'saveFilePathInAttribute' => ArrayHelper::getValue($options, 'saveFilePathInAttribute', true),
17 | 'saveFileIdInAttribute' => ArrayHelper::getValue($options, 'saveFileIdInAttribute', false),
18 | 'templatePath' => function ($file) {
19 | $date = new \DateTime(is_object($file->date_create) ? null : $file->date_create);
20 | return '/' . $date->format('Ym') . '/' . $file->id . '/' . $file->name;
21 | },
22 | 'createFile' => function ($path, $name) {
23 | $file = new File();
24 | $file->title = $name;
25 | $file->generateName(pathinfo($name, PATHINFO_EXTENSION));
26 | $file->save();
27 | return $file;
28 | },
29 | 'updateFile' => ArrayHelper::getValue($options, 'updateFile', function ($file) {
30 | return $file;
31 | }),
32 | 'extraFields' => ArrayHelper::getValue($options, 'extraFields', function () {
33 | return [
34 | 'type' => 1,
35 | ];
36 | }),
37 | 'rules' => ArrayHelper::getValue($options, 'rules', [
38 | 'imageSize' => ['minWidth' => 300, 'minHeight' => 300],
39 | 'mimeTypes' => ['image/png', 'image/jpg', 'image/jpeg'],
40 | 'extensions' => ['jpg', 'jpeg', 'png'],
41 | 'maxSize' => 1024 * 1024 * 1, // 1 MB
42 | 'maxFiles' => ArrayHelper::getValue($options, 'rules.maxFiles', 1),
43 | 'tooBig' => Yii::t('app', 'File size must not exceed') . ' 1Mb'
44 | ]),
45 | 'preset' => ArrayHelper::getValue($options, 'preset', [
46 | '200x200' => function ($realPath, $publicPath, $thumbPath) {
47 | Image::make($realPath . $publicPath)
48 | ->fit(200, 200)
49 | ->save($realPath . $thumbPath, 100);
50 | },
51 | '220x220' => function ($realPath, $publicPath, $thumbPath) {
52 | Image::make($realPath . $publicPath)
53 | ->fit(220, 220)
54 | ->save($realPath . $thumbPath, 100);
55 | },
56 | '400x400' => function ($realPath, $publicPath, $thumbPath) {
57 | Image::make($realPath . $publicPath)
58 | ->fit(400, 400)
59 | ->save(null, 100);
60 | },
61 | ]),
62 | 'applyPresetAfterUpload' => ArrayHelper::getValue($options, 'applyPresetAfterUpload', ['220x220'])
63 | ],
64 | 'gallery' => [
65 | 'storage' => 'localFs',
66 | 'baseUrl' => '@tests/tmp/public',
67 | 'type' => 'image',
68 | 'multiple' => true,
69 | 'template' => ArrayHelper::getValue($options, 'template', null),
70 | 'relation' => 'galleryFiles',
71 | 'saveFilePathInAttribute' => ArrayHelper::getValue($options, 'saveFilePathInAttribute', true),
72 | 'saveFileIdInAttribute' => ArrayHelper::getValue($options, 'saveFileIdInAttribute', false),
73 | 'templatePath' => function ($file) {
74 | $date = new \DateTime(is_object($file->date_create) ? null : $file->date_create);
75 | return '/' . $date->format('Ym') . '/' . $file->id . '/' . $file->name;
76 | },
77 | 'createFile' => function ($path, $name) {
78 | $file = new File();
79 | $file->title = $name;
80 | $file->generateName(pathinfo($name, PATHINFO_EXTENSION));
81 | $file->save();
82 | return $file;
83 | },
84 | 'updateFile' => ArrayHelper::getValue($options, 'updateFile', function ($file) {
85 | return $file;
86 | }),
87 | 'extraFields' => ArrayHelper::getValue($options, 'extraFields', function () {
88 | return [
89 | 'type' => 2,
90 | ];
91 | }),
92 | 'rules' => ArrayHelper::getValue($options, 'rules', [
93 | 'imageSize' => ['minWidth' => 300, 'minHeight' => 300],
94 | 'mimeTypes' => ['image/png', 'image/jpg', 'image/jpeg'],
95 | 'extensions' => ['jpg', 'jpeg', 'png'],
96 | 'maxSize' => 1024 * 1024 * 1, // 1 MB
97 | 'maxFiles' => ArrayHelper::getValue($options, 'rules.maxFiles', 1),
98 | 'tooBig' => Yii::t('app', 'File size must not exceed') . ' 1Mb'
99 | ]),
100 | 'preset' => ArrayHelper::getValue($options, 'preset', [
101 | '200x200' => function ($realPath, $publicPath, $thumbPath) {
102 | Image::make($realPath . $publicPath)
103 | ->fit(200, 200)
104 | ->save($realPath . $thumbPath, 100);
105 | },
106 | '220x220' => function ($realPath, $publicPath, $thumbPath) {
107 | Image::make($realPath . $publicPath)
108 | ->fit(220, 220)
109 | ->save($realPath . $thumbPath, 100);
110 | },
111 | '400x400' => function ($realPath, $publicPath, $thumbPath) {
112 | Image::make($realPath . $publicPath)
113 | ->fit(400, 400)
114 | ->save(null, 100);
115 | },
116 | ]),
117 | 'applyPresetAfterUpload' => ArrayHelper::getValue($options, 'applyPresetAfterUpload', ['220x220'])
118 | ],
119 | ]
120 | ];
121 |
--------------------------------------------------------------------------------
/tests/data/controllers/Controller.php:
--------------------------------------------------------------------------------
1 | [
5 | 'name' => '100x100.png',
6 | 'size' => 293,
7 | 'type' => 'image/png',
8 | 'ext' => 'png',
9 | 'error' => 0,
10 | 'tmp_name' => Yii::getAlias('@tests/data/files/tmp/100x100.png'),
11 | ],
12 | 'file-300' => [
13 | 'name' => '300x300.png',
14 | 'size' => 1299,
15 | 'type' => 'image/png',
16 | 'ext' => 'png',
17 | 'error' => 0,
18 | 'tmp_name' => Yii::getAlias('@tests/data/files/tmp/300x300.png'),
19 | ],
20 | 'file-500' => [
21 | 'name' => '500x500.png',
22 | 'size' => 1543,
23 | 'type' => 'image/png',
24 | 'ext' => 'png',
25 | 'error' => 0,
26 | 'tmp_name' => Yii::getAlias('@tests/data/files/tmp/500x500.png'),
27 | ],
28 | 'file-300-without-extension' => [
29 | 'name' => '300x300-without-extension',
30 | 'size' => 1299,
31 | 'type' => 'image/png',
32 | 'ext' => 'png',
33 | 'error' => 0,
34 | 'tmp_name' => Yii::getAlias('@tests/data/files/tmp/300x300-without-extension'),
35 | ],
36 | ];
37 |
--------------------------------------------------------------------------------
/tests/data/files/100x100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rkit/filemanager-yii2/02c7e772710690163f1f1b8a5c5822d18ffaf9f6/tests/data/files/100x100.png
--------------------------------------------------------------------------------
/tests/data/files/300x300-without-extension:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rkit/filemanager-yii2/02c7e772710690163f1f1b8a5c5822d18ffaf9f6/tests/data/files/300x300-without-extension
--------------------------------------------------------------------------------
/tests/data/files/300x300.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rkit/filemanager-yii2/02c7e772710690163f1f1b8a5c5822d18ffaf9f6/tests/data/files/300x300.png
--------------------------------------------------------------------------------
/tests/data/files/500x500.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rkit/filemanager-yii2/02c7e772710690163f1f1b8a5c5822d18ffaf9f6/tests/data/files/500x500.png
--------------------------------------------------------------------------------
/tests/data/fixtures/file.php:
--------------------------------------------------------------------------------
1 | [
5 | [
6 | 'id' => 1,
7 | 'user_id' => 1,
8 | 'title' => 'Title-1',
9 | 'name' => 'File-1',
10 | 'date_create' => '2010-01-01 01:01:01',
11 | 'date_update' => '2010-01-01 01:01:01',
12 | 'ip' => 0,
13 | ],
14 | [
15 | 'id' => 2,
16 | 'user_id' => 1,
17 | 'title' => 'Title-2',
18 | 'name' => 'File-2',
19 | 'date_create' => '2010-01-01 01:01:01',
20 | 'date_update' => '2010-01-01 01:01:01',
21 | 'ip' => 0,
22 | ],
23 | ],
24 | ];
25 |
--------------------------------------------------------------------------------
/tests/data/models/File.php:
--------------------------------------------------------------------------------
1 | TimestampBehavior::className(),
46 | 'createdAtAttribute' => 'date_create',
47 | 'updatedAtAttribute' => 'date_update',
48 | 'value' => new \yii\db\Expression('NOW()'),
49 | ],
50 | ];
51 | }
52 |
53 | /**
54 | * @internal
55 | */
56 | public function beforeSave($insert)
57 | {
58 | if (parent::beforeSave($insert)) {
59 | if ($insert) {
60 | if (!Yii::$app instanceof \yii\console\Application) {
61 | $this->user_id = Yii::$app->user->isGuest ? 0 : Yii::$app->user->id; // @codeCoverageIgnore
62 | $this->ip = ip2long(Yii::$app->request->getUserIP()); // @codeCoverageIgnore
63 | } // @codeCoverageIgnore
64 | }
65 | return true;
66 | }
67 | return false; // @codeCoverageIgnore
68 | }
69 |
70 | /**
71 | * Generate a new name
72 | *
73 | * @param string $extension The file extension
74 | * @return string
75 | */
76 | public function generateName($extension)
77 | {
78 | $name = date('YmdHis') . substr(md5(microtime() . uniqid()), 0, 10);
79 | $this->name = $name . '.' . $extension;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/tests/data/models/News.php:
--------------------------------------------------------------------------------
1 | hasOne(File::class, ['id' => 'file_id'])
42 | ->viaTable('{{%news_files}}', ['news_id' => 'id'], function ($query) {
43 | $query->where(['type' => 1]);
44 | });
45 | }
46 |
47 | /**
48 | * @return \yii\db\ActiveQuery
49 | */
50 | public function getGalleryFiles()
51 | {
52 | return $this
53 | ->hasMany(File::class, ['id' => 'file_id'])
54 | ->viaTable('{{%news_files}}', ['news_id' => 'id'], function ($query) {
55 | $query->where(['type' => 2]);
56 | })
57 | ->innerJoin('{{%news_files}}', '`file_id` = `file`.`id`')
58 | ->orderBy(['position' => SORT_ASC]);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/data/models/User.php:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 | = Html::textInput(Html::getInputName($model, $attribute) . '[]', $file->id, [
10 | 'class' => 'form-control',
11 | ])?>
12 |
13 |
--------------------------------------------------------------------------------
/tests/migrations/m141230_075228_create_file.php:
--------------------------------------------------------------------------------
1 | db->driverName === 'mysql') {
11 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
12 | }
13 |
14 | $this->createTable('{{%file}}', [
15 | 'id' => Schema::TYPE_PK,
16 | 'user_id' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0',
17 | 'title' => Schema::TYPE_STRING . " NOT NULL DEFAULT ''",
18 | 'name' => Schema::TYPE_STRING . " NOT NULL DEFAULT ''",
19 | 'date_create' => Schema::TYPE_TIMESTAMP . ' NULL DEFAULT NULL',
20 | 'date_update' => Schema::TYPE_TIMESTAMP . ' NULL DEFAULT NULL',
21 | 'ip' => Schema::TYPE_BIGINT . '(20) NOT NULL DEFAULT 0',
22 | ], $tableOptions);
23 | }
24 |
25 | public function safeDown()
26 | {
27 | $this->dropTable('{{%file}}');
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/migrations/m141230_075229_create_news.php:
--------------------------------------------------------------------------------
1 | db->driverName === 'mysql') {
11 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
12 | }
13 |
14 | $this->createTable('{{%news}}', [
15 | 'id' => Schema::TYPE_PK,
16 | 'title' => Schema::TYPE_STRING . " NOT NULL DEFAULT ''",
17 | 'image' => Schema::TYPE_STRING . " NOT NULL DEFAULT ''",
18 | ], $tableOptions);
19 | }
20 |
21 | public function safeDown()
22 | {
23 | $this->dropTable('{{%news}}');
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/tests/migrations/m161026_103037_create_news_files.php:
--------------------------------------------------------------------------------
1 | db->driverName === 'mysql') {
11 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_general_ci ENGINE=InnoDB';
12 | }
13 |
14 | $this->createTable('{{%news_files}}', [
15 | 'id' => Schema::TYPE_PK,
16 | 'news_id' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0',
17 | 'file_id' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0',
18 | 'type' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0',
19 | 'position' => Schema::TYPE_INTEGER . ' NOT NULL DEFAULT 0',
20 | ], $tableOptions);
21 |
22 | $this->createIndex('link', '{{%news_files}}', 'news_id, file_id');
23 | $this->createIndex('type_news', '{{%news_files}}', 'type, news_id');
24 | }
25 |
26 | public function safeDown()
27 | {
28 | $this->dropTable('{{%news_files}}');
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/tests/yii:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | run();
10 |
11 | exit($exitCode);
12 |
--------------------------------------------------------------------------------