├── tests
├── files
│ ├── file
│ ├── file.txt
│ ├── file.bmp
│ ├── file.gif
│ ├── file.jpg
│ ├── file.mp3
│ ├── file.png
│ ├── file.wav
│ └── file.zip
├── assets
│ └── .gitignore
├── data
│ └── db.sqlite
├── views
│ ├── test
│ │ ├── attachments-input-view.php
│ │ ├── attachments-table-view.php
│ │ └── attachments-table-view-no-delete.php
│ └── layouts
│ │ └── main.php
├── FileTest.php
├── bootstrap.php
├── models
│ └── Comment.php
├── ModuleTest.php
├── AttachmentsInputTest.php
├── AttachmentsTableTest.php
├── TestCase.php
└── FileControllerTest.php
├── .gitignore
├── src
├── messages
│ ├── zh-CN
│ │ └── attachments.php
│ ├── zh-TW
│ │ └── attachments.php
│ ├── fr
│ │ └── attachments.php
│ ├── id
│ │ └── attachments.php
│ ├── it
│ │ └── attachments.php
│ └── ru
│ │ └── attachments.php
├── events
│ └── FileEvent.php
├── models
│ ├── UploadForm.php
│ └── File.php
├── ModuleTrait.php
├── migrations
│ └── m150127_040544_add_attachments.php
├── controllers
│ └── FileController.php
├── components
│ ├── AttachmentsTable.php
│ ├── AttachmentsInput.php
│ └── AttachmentsTableWithPreview.php
├── Module.php
└── behaviors
│ └── FileBehavior.php
├── .travis.yml
├── .scrutinizer.yml
├── phpunit.xml.dist
├── composer.json
├── README.md
└── composer.lock
/tests/files/file:
--------------------------------------------------------------------------------
1 | file 1
--------------------------------------------------------------------------------
/tests/files/file.txt:
--------------------------------------------------------------------------------
1 | file 1
--------------------------------------------------------------------------------
/tests/assets/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 | !.gitignore
3 |
--------------------------------------------------------------------------------
/tests/data/db.sqlite:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/data/db.sqlite
--------------------------------------------------------------------------------
/tests/files/file.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/files/file.bmp
--------------------------------------------------------------------------------
/tests/files/file.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/files/file.gif
--------------------------------------------------------------------------------
/tests/files/file.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/files/file.jpg
--------------------------------------------------------------------------------
/tests/files/file.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/files/file.mp3
--------------------------------------------------------------------------------
/tests/files/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/files/file.png
--------------------------------------------------------------------------------
/tests/files/file.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/files/file.wav
--------------------------------------------------------------------------------
/tests/files/file.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CTOlet/yii2-attachments/HEAD/tests/files/file.zip
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | .idea/
3 | build/
4 | vendor/
5 | tests/uploads/
6 |
7 | #use git update-index --assume-unchanged tests/data/db.sqlite
8 | #tests/data/db.sqlite
--------------------------------------------------------------------------------
/src/messages/zh-CN/attachments.php:
--------------------------------------------------------------------------------
1 | '模型不能为空。',
8 | 'The behavior FileBehavior has not been attached to the model.' => 'FileBehavior行为尚未附加到模型。',
9 | 'File name' => '文件名'
10 | ];
11 |
--------------------------------------------------------------------------------
/src/messages/zh-TW/attachments.php:
--------------------------------------------------------------------------------
1 | '模型不能為空。',
8 | 'The behavior FileBehavior has not been attached to the model.' => 'FileBehavior行為尚未附加到模型。',
9 | 'File name' => '文件名'
10 | ];
11 |
--------------------------------------------------------------------------------
/tests/views/test/attachments-input-view.php:
--------------------------------------------------------------------------------
1 | $model
15 | ]);
16 |
--------------------------------------------------------------------------------
/tests/views/test/attachments-table-view.php:
--------------------------------------------------------------------------------
1 | $model
15 | ]);
16 |
--------------------------------------------------------------------------------
/src/messages/fr/attachments.php:
--------------------------------------------------------------------------------
1 | 'Le modèle ne peut pas être vide',
7 | 'The behavior FileBehavior has not been attached to the model.' => 'Le comportement FileBehavior n\'a pas été attaché au modèle.',
8 | 'File name' => 'Nom de fichier'
9 | ];
10 |
--------------------------------------------------------------------------------
/src/messages/id/attachments.php:
--------------------------------------------------------------------------------
1 | 'Model tidak boleh kosong.',
8 | 'The behavior FileBehavior has not been attached to the model.' => 'behavior `FileBehavior` belum dilampirkan ke model.',
9 | 'File name' => 'Nama file'
10 | ];
11 |
--------------------------------------------------------------------------------
/src/messages/it/attachments.php:
--------------------------------------------------------------------------------
1 |
4 | */
5 |
6 | return [
7 | 'The model cannot be empty.' => 'Il modello non può essere vuoto.',
8 | 'The behavior FileBehavior has not been attached to the model.' => 'Il behavior FileBehavior non è stato associato al modello.',
9 | 'File name' => 'Nome file'
10 | ];
11 |
--------------------------------------------------------------------------------
/src/messages/ru/attachments.php:
--------------------------------------------------------------------------------
1 | 'Модель не может быть пустой.',
11 | 'The behavior FileBehavior has not been attached to the model.' => 'Поведение FileBehavior не привязано к модели.',
12 | 'File name' => 'Название файла'
13 | ];
--------------------------------------------------------------------------------
/tests/views/test/attachments-table-view-no-delete.php:
--------------------------------------------------------------------------------
1 | $model,
15 | 'showDeleteButton' => false,
16 | ]);
17 |
18 | echo \nemmo\attachments\components\AttachmentsTableWithPreview::widget([
19 | 'model' => $model,
20 | 'showDeleteButton' => false,
21 | ]);
--------------------------------------------------------------------------------
/tests/views/layouts/main.php:
--------------------------------------------------------------------------------
1 |
11 |
12 | beginPage(); ?>
13 |
14 |
15 |
16 | head(); ?>
17 |
18 |
19 | beginBody() ?>
20 | = $content ?>
21 | endBody(); ?>
22 |
23 |
24 | endPage() ?>
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | dist: trusty
4 |
5 | php:
6 | - 5.5
7 | - 5.6
8 | - 7.0
9 | - 7.1
10 |
11 | install:
12 | - travis_retry composer self-update && composer --version
13 | - export PATH="$HOME/.composer/vendor/bin:$PATH"
14 | - COMPOSER_MEMORY_LIMIT=-1 travis_retry composer install --prefer-dist --no-interaction
15 |
16 | script:
17 | - vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover
18 |
19 | after_script:
20 | - wget https://scrutinizer-ci.com/ocular.phar
21 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover
22 |
--------------------------------------------------------------------------------
/src/events/FileEvent.php:
--------------------------------------------------------------------------------
1 | _files;
20 | }
21 |
22 | /**
23 | * @param nemmo\attachments\models\File[] $files
24 | */
25 | public function setFiles($files)
26 | {
27 | $this->_files = $files;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/models/UploadForm.php:
--------------------------------------------------------------------------------
1 | getModule()->rules)
32 | ];
33 | }
34 | }
--------------------------------------------------------------------------------
/src/ModuleTrait.php:
--------------------------------------------------------------------------------
1 | _module == null) {
26 | $this->_module = \Yii::$app->getModule('attachments');
27 | }
28 |
29 | if (!$this->_module) {
30 | throw new \Exception("Yii2 attachment module not found, may be you didn't add it to your config?");
31 | }
32 |
33 | return $this->_module;
34 | }
35 | }
--------------------------------------------------------------------------------
/tests/FileTest.php:
--------------------------------------------------------------------------------
1 | assertFalse($file->validate());
20 | $this->assertArrayHasKey('name', $file->errors);
21 | $this->assertArrayHasKey('model', $file->errors);
22 | $this->assertArrayHasKey('itemId', $file->errors);
23 | $this->assertArrayHasKey('hash', $file->errors);
24 | $this->assertArrayHasKey('size', $file->errors);
25 | //$this->assertArrayHasKey('type', $file->errors);
26 | $this->assertArrayHasKey('mime', $file->errors);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 | ./tests
15 |
16 |
17 |
18 |
19 | ./src/
20 |
21 | ./src/messages/
22 | ./src/migrations/
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nemmo/yii2-attachments",
3 | "description": "Extension for file uploading and attaching to the models",
4 | "type": "yii2-extension",
5 | "keywords": [
6 | "yii2",
7 | "extension",
8 | "attachment",
9 | "file",
10 | "upload"
11 | ],
12 | "license": "BSD-4-Clause",
13 | "homepage": "https://github.com/Nemmo/yii2-attachments",
14 | "authors": [
15 | {
16 | "name": "Alimzhan Abuov aka Nemmo",
17 | "email": "alimzhan.abu@yandex.kz",
18 | "homepage": "http://alimzhan.kz",
19 | "role": "Developer"
20 | }
21 | ],
22 | "require": {
23 | "php": ">=5.4.0",
24 | "yiisoft/yii2": "~2.0.0",
25 | "kartik-v/yii2-widget-fileinput": "~1.1.1",
26 | "himiklab/yii2-colorbox-widget": "*"
27 | },
28 | "require-dev": {
29 | "phpunit/phpunit": "~4.0",
30 | "phpunit/dbunit": "~1.0"
31 | },
32 | "autoload": {
33 | "psr-4": {
34 | "nemmo\\attachments\\": "src"
35 | }
36 | },
37 | "extra": {
38 | "asset-installer-paths": {
39 | "npm-asset-library": "vendor/npm",
40 | "bower-asset-library": "vendor/bower"
41 | }
42 | },
43 | "repositories": [
44 | {
45 | "type": "composer",
46 | "url": "https://asset-packagist.org"
47 | }
48 | ],
49 | "config": {
50 | "process-timeout": 1800,
51 | "fxp-asset": {
52 | "enabled": false
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/migrations/m150127_040544_add_attachments.php:
--------------------------------------------------------------------------------
1 | db->driverName === 'mysql') {
16 | // http://stackoverflow.com/questions/766809/whats-the-difference-between-utf8-general-ci-and-utf8-unicode-ci
17 | $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';
18 | }
19 |
20 | $this->createTable($this->getModule()->tableName, [
21 | 'id' => Schema::TYPE_PK,
22 | 'name' => Schema::TYPE_STRING . ' not null',
23 | 'model' => Schema::TYPE_STRING . ' not null',
24 | 'itemId' => Schema::TYPE_INTEGER . ' not null',
25 | 'hash' => Schema::TYPE_STRING . ' not null',
26 | 'size' => Schema::TYPE_INTEGER . ' not null',
27 | 'type' => Schema::TYPE_STRING . ' not null',
28 | 'mime' => Schema::TYPE_STRING . ' not null'
29 | ], $tableOptions);
30 |
31 | $this->createIndex('file_model', $this->getModule()->tableName, 'model');
32 | $this->createIndex('file_item_id', $this->getModule()->tableName, 'itemId');
33 | }
34 |
35 | public function down()
36 | {
37 | $this->dropTable($this->getModule()->tableName);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/tests/ModuleTest.php:
--------------------------------------------------------------------------------
1 | _module = Yii::$app->getModule('attachments');
23 | }
24 |
25 | protected function tearDown()
26 | {
27 | Yii::$app->setModule('attachments', $this->_module);
28 |
29 | parent::tearDown();
30 | }
31 |
32 | public function testInitException()
33 | {
34 | Yii::$app->setModule('attachments', [
35 | 'class' => Module::className(),
36 | 'storePath' => ''
37 | ]);
38 | $this->setExpectedException('Exception', 'Setup {storePath} and {tempPath} in module properties');
39 | Yii::$app->getModule('attachments');
40 | }
41 |
42 | public function testInit()
43 | {
44 | Yii::$app->setModule('attachments', [
45 | 'class' => Module::className()
46 | ]);
47 | /** @var Module $module */
48 | $module = Yii::$app->getModule('attachments');
49 | $this->assertEquals([
50 | 'maxFiles' => 3
51 | ], $module->rules);
52 |
53 | $newRules = [
54 | 'maxFiles' => 10,
55 | 'mimeTypes' => 'image/png',
56 | 'maxSize' => 1024
57 | ];
58 | Yii::$app->setModule('attachments', [
59 | 'class' => Module::className(),
60 | 'rules' => $newRules
61 | ]);
62 | $module = Yii::$app->getModule('attachments');
63 | $this->assertEquals($newRules, $module->rules);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/models/File.php:
--------------------------------------------------------------------------------
1 | getModule('attachments')->tableName;
32 | }
33 |
34 | /**
35 | * @inheritDoc
36 | */
37 | public function fields()
38 | {
39 | return [
40 | 'url'
41 | ];
42 | }
43 |
44 | /**
45 | * @inheritdoc
46 | */
47 | public function rules()
48 | {
49 | return [
50 | [['name', 'model', 'itemId', 'hash', 'size', 'mime'], 'required'],
51 | [['itemId', 'size'], 'integer'],
52 | [['name', 'model', 'hash', 'type', 'mime'], 'string', 'max' => 255]
53 | ];
54 | }
55 |
56 | /**
57 | * @inheritdoc
58 | */
59 | public function attributeLabels()
60 | {
61 | return [
62 | 'id' => 'ID',
63 | 'name' => 'Name',
64 | 'model' => 'Model',
65 | 'itemId' => 'Item ID',
66 | 'hash' => 'Hash',
67 | 'size' => 'Size',
68 | 'type' => 'Type',
69 | 'mime' => 'Mime'
70 | ];
71 | }
72 |
73 | public function getUrl($inline = false)
74 | {
75 | return Url::to(['/attachments/file/download', 'id' => $this->id,'inline'=>$inline]);
76 | }
77 |
78 | public function getPath()
79 | {
80 | return $this->getModule()->getFilesDirPath($this->hash) . DIRECTORY_SEPARATOR . $this->hash . '.' . $this->type;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/tests/AttachmentsInputTest.php:
--------------------------------------------------------------------------------
1 | setExpectedException('\yii\base\InvalidConfigException');
21 | AttachmentsInput::widget();
22 | }
23 |
24 | public function testDefaultConfig()
25 | {
26 | Yii::$app->controller = new Controller('test', Yii::$app);
27 | $response = Yii::$app->controller->render('attachments-input-view', [
28 | 'model' => new Comment()
29 | ]);
30 |
31 | $this->assertContains("var fileInput = $('#file-input');", $response);
32 | $this->assertContains("UploadForm[file][]", $response);
33 | $this->assertContains('jquery.js', $response);
34 | $this->assertContains('fileinput.js', $response);
35 | $this->assertContains('fileinput.css', $response);
36 | $this->assertContains('kv-widgets.css', $response);
37 | }
38 |
39 | public function testDefaultConfigOldModel()
40 | {
41 | $comment = new Comment();
42 | $comment->text = 'test';
43 | $this->generateFiles(['png', 'jpg', 'txt']);
44 | $comment->save();
45 |
46 | Yii::$app->controller = new Controller('test', Yii::$app);
47 | $response = Yii::$app->controller->render('attachments-input-view', [
48 | 'model' => $comment
49 | ]);
50 |
51 | $this->assertContains("var fileInput = $('#file-input');", $response);
52 | $this->assertContains("UploadForm[file][]", $response);
53 | $this->assertContains('jquery.js', $response);
54 | $this->assertContains('fileinput.js', $response);
55 | $this->assertContains('fileinput.css', $response);
56 | $this->assertContains('kv-widgets.css', $response);
57 | $this->assertContains('file-preview-image', $response);
58 | $this->assertContains('file-preview-other', $response);
59 | $this->assertContains('attachments%2Ffile%2Fdelete', $response);
60 | }
61 | }
--------------------------------------------------------------------------------
/tests/AttachmentsTableTest.php:
--------------------------------------------------------------------------------
1 | setExpectedException('\yii\base\InvalidConfigException');
22 | AttachmentsTable::widget();
23 | }
24 |
25 | public function testEmptyBehaviorConfig()
26 | {
27 | $this->setExpectedException('\yii\base\InvalidConfigException');
28 | $model = new ActiveRecord();
29 | AttachmentsTable::widget(['model' => $model]);
30 | }
31 |
32 | public function testDefaultConfig()
33 | {
34 | $comment = new Comment();
35 | $comment->text = 'test';
36 |
37 | $types = ['png', 'txt', 'jpg'];
38 | $this->generateFiles($types);
39 | Yii::$app->runAction('attachments/file/upload');
40 |
41 | $comment->save();
42 |
43 | Yii::$app->controller = new Controller('test', Yii::$app, ['action' => 'test']);
44 | $response = Yii::$app->controller->render('attachments-table-view', [
45 | 'model' => $comment
46 | ]);
47 |
48 | $this->assertContains('table table-striped table-bordered table-condensed', $response);
49 | $this->assertContains("yii.gridView.js", $response);
50 | $this->assertContains('jquery.js', $response);
51 | $this->assertContains('yii.js', $response);
52 | $this->assertContains('yiiGridView', $response);
53 | $this->assertContains('file.png', $response);
54 | $this->assertContains('file.txt', $response);
55 | $this->assertContains('file.jpg', $response);
56 | $this->assertContains('text = 'test';
63 |
64 | $types = ['png', 'txt', 'jpg'];
65 | $this->generateFiles($types);
66 | Yii::$app->runAction('attachments/file/upload');
67 |
68 | $comment->save();
69 |
70 | Yii::$app->controller = new Controller('test', Yii::$app, ['action' => 'test']);
71 | $response = Yii::$app->controller->render('attachments-table-view-no-delete', [
72 | 'model' => $comment
73 | ]);
74 |
75 | $this->assertNotContains('file = UploadedFile::getInstances($model, 'file');
22 |
23 | if ($model->rules()[0]['maxFiles'] == 1 && sizeof($model->file) == 1) {
24 | $model->file = $model->file[0];
25 | }
26 |
27 | if ($model->file && $model->validate()) {
28 | $result['uploadedFiles'] = [];
29 | if (is_array($model->file)) {
30 | foreach ($model->file as $file) {
31 | $path = $this->getModule()->getUserDirPath() . DIRECTORY_SEPARATOR . $file->name;
32 | $file->saveAs($path);
33 | $result['uploadedFiles'][] = $file->name;
34 | }
35 | } else {
36 | $path = $this->getModule()->getUserDirPath() . DIRECTORY_SEPARATOR . $model->file->name;
37 | $model->file->saveAs($path);
38 | $result['uploadedFiles'][] = $model->file->name;
39 | }
40 | Yii::$app->response->format = Response::FORMAT_JSON;
41 | return $result;
42 | } else {
43 | Yii::$app->response->format = Response::FORMAT_JSON;
44 | return [
45 | 'error' => $model->getErrors('file')
46 | ];
47 | }
48 | }
49 |
50 | public function actionDownload($id,$inline=false)
51 | {
52 | $file = File::findOne(['id' => $id]);
53 | $filePath = $this->getModule()->getFilesDirPath($file->hash) . DIRECTORY_SEPARATOR . $file->hash . '.' . $file->type;
54 |
55 | return Yii::$app->response->sendFile($filePath, "$file->name.$file->type",['inline'=>$inline]);
56 | }
57 |
58 | public function actionDelete($id)
59 | {
60 | if ($this->getModule()->detachFile($id)) {
61 | return true;
62 | } else {
63 | return false;
64 | }
65 | }
66 |
67 | public function actionDownloadTemp($filename)
68 | {
69 | $filePath = $this->getModule()->getUserDirPath() . DIRECTORY_SEPARATOR . $filename;
70 |
71 | return Yii::$app->response->sendFile($filePath, $filename);
72 | }
73 |
74 | public function actionDeleteTemp($filename)
75 | {
76 | $userTempDir = $this->getModule()->getUserDirPath();
77 | $filePath = $userTempDir . DIRECTORY_SEPARATOR . $filename;
78 | unlink($filePath);
79 | if (!sizeof(FileHelper::findFiles($userTempDir))) {
80 | rmdir($userTempDir);
81 | }
82 |
83 | Yii::$app->response->format = Response::FORMAT_JSON;
84 | return [];
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/components/AttachmentsTable.php:
--------------------------------------------------------------------------------
1 | 'table table-striped table-bordered table-condensed'];
30 |
31 | public $showDeleteButton = true;
32 |
33 | public function init()
34 | {
35 | parent::init();
36 |
37 | if (empty($this->model)) {
38 | throw new InvalidConfigException("Property {model} cannot be blank");
39 | }
40 |
41 | $hasFileBehavior = false;
42 | foreach ($this->model->getBehaviors() as $behavior) {
43 | if (is_a($behavior, FileBehavior::className())) {
44 | $hasFileBehavior = true;
45 | }
46 | }
47 | if (!$hasFileBehavior) {
48 | throw new InvalidConfigException("The behavior {FileBehavior} has not been attached to the model.");
49 | }
50 | }
51 |
52 | public function run()
53 | {
54 | $confirm = Yii::t('yii', 'Are you sure you want to delete this item?');
55 | $js = <<view->registerJs($js);
73 |
74 | return GridView::widget([
75 | 'dataProvider' => new ArrayDataProvider(['allModels' => $this->model->getFiles()]),
76 | 'layout' => '{items}',
77 | 'tableOptions' => $this->tableOptions,
78 | 'columns' => [
79 | [
80 | 'class' => 'yii\grid\SerialColumn'
81 | ],
82 | [
83 | 'label' => $this->getModule()->t('attachments', 'File name'),
84 | 'format' => 'raw',
85 | 'value' => function ($model) {
86 | return Html::a("$model->name.$model->type", $model->getUrl());
87 | }
88 | ],
89 | [
90 | 'class' => 'yii\grid\ActionColumn',
91 | 'template' => '{delete}',
92 | 'visibleButtons' => ['delete' => $this->showDeleteButton],
93 | 'buttons' => [
94 | 'delete' => function ($url, $model, $key) {
95 | return Html::a('',
96 | '#',
97 | [
98 | 'class' => 'delete-button',
99 | 'title' => Yii::t('yii', 'Delete'),
100 | 'data-url' => Url::to(['/attachments/file/delete', 'id' => $model->id])
101 | ]
102 | );
103 | }
104 | ]
105 | ],
106 | ]
107 | ]);
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/components/AttachmentsInput.php:
--------------------------------------------------------------------------------
1 | model)) {
37 | throw new InvalidConfigException("Property {model} cannot be blank");
38 | }
39 |
40 | FileHelper::removeDirectory($this->getModule()->getUserDirPath()); // Delete all uploaded files in past
41 |
42 | $this->pluginOptions = array_replace($this->pluginOptions, [
43 | 'initialPreviewFileType'=> 'image',
44 | 'initialPreviewAsData'=> 'true', //let kartik deal with the previews!
45 | 'uploadUrl' => Url::toRoute('/attachments/file/upload'),
46 | 'uploadAsync' => false
47 | ]);
48 | if(!isset($this->pluginOptions['initialPreview'])){
49 | $this->pluginOptions = array_replace($this->pluginOptions, [
50 | 'initialPreview' => $this->model->isNewRecord ? [] : $this->model->getInitialPreview(),
51 | ]);
52 | }
53 | if(!isset($this->pluginOptions['initialPreviewConfig'])){
54 | $this->pluginOptions = array_replace($this->pluginOptions, [
55 | 'initialPreviewConfig' => $this->model->isNewRecord ? [] : $this->model->getInitialPreviewConfig(),
56 | ]);
57 | }
58 |
59 | $this->options = array_replace($this->options, [
60 | 'id' => $this->id,
61 | //'multiple' => true
62 | ]);
63 |
64 | $js = <<view->registerJs($js);
111 | }
112 |
113 | public function run()
114 | {
115 | $fileinput = FileInput::widget([
116 | 'model' => new UploadForm(),
117 | 'attribute' => 'file[]',
118 | 'options' => $this->options,
119 | 'pluginOptions' => $this->pluginOptions
120 | ]);
121 |
122 | return Html::tag('div', $fileinput, ['class' => 'form-group']);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/tests/TestCase.php:
--------------------------------------------------------------------------------
1 | mockApplication();
30 | Yii::$app->db->createCommand()->truncateTable('attach_file')->execute();
31 | Yii::$app->db->createCommand()->truncateTable('comment')->execute();
32 | Yii::$app->db->createCommand()->truncateTable('sqlite_sequence')->execute();
33 | }
34 |
35 | /**
36 | * @inheritdoc
37 | */
38 | protected function tearDown()
39 | {
40 | $this->destroyApplication();
41 | }
42 |
43 | protected function mockApplication($config = [], $appClass = '\yii\web\Application')
44 | {
45 | new $appClass(ArrayHelper::merge([
46 | 'id' => 'test-app',
47 | 'basePath' => Yii::getAlias('@tests'),
48 | 'vendorPath' => Yii::getAlias('@tests/../vendor'),
49 | 'aliases' => [
50 | '@bower' => '@vendor/bower-asset',
51 | '@npm' => '@vendor/npm-asset',
52 | ],
53 | 'modules' => [
54 | 'attachments' => [
55 | 'class' => \nemmo\attachments\Module::className(),
56 | ]
57 | ],
58 | 'components' => [
59 | // 'urlManager' => [
60 | // 'class' => \yii\web\UrlManager::className(),
61 | // 'baseUrl' => 'http://localhost',
62 | // 'scriptUrl' => '/index.php'
63 | // ],
64 | 'request' => [
65 | 'cookieValidationKey' => 'wefJDF8sfdsfSDefwqdxj9oq',
66 | 'scriptFile' => Yii::getAlias('@tests/index.php'),
67 | 'scriptUrl' => '/index.php',
68 | ],
69 | 'db' => [
70 | 'class' => \yii\db\Connection::className(),
71 | 'dsn' => 'sqlite:' . Yii::getAlias('@tests/data/db.sqlite')
72 | ],
73 | 'assetManager' => [
74 | 'basePath' => '@tests/assets',
75 | 'baseUrl' => '/',
76 | ]
77 | ]
78 | ], $config));
79 | }
80 |
81 | protected function destroyApplication()
82 | {
83 | Yii::$app = null;
84 | }
85 |
86 | /**
87 | * Creates a view for testing purposes
88 | *
89 | * @return View
90 | */
91 | protected function getView()
92 | {
93 | $view = new View();
94 | $view->setAssetManager(new AssetManager([
95 | 'basePath' => '@tests/assets',
96 | 'baseUrl' => '/',
97 | ]));
98 | return $view;
99 | }
100 |
101 | /**
102 | * Asserting two strings equality ignoring line endings
103 | *
104 | * @param string $expected
105 | * @param string $actual
106 | */
107 | public function assertEqualsWithoutLE($expected, $actual)
108 | {
109 | $expected = str_replace("\r\n", "\n", $expected);
110 | $actual = str_replace("\r\n", "\n", $actual);
111 | $this->assertEquals($expected, $actual);
112 | }
113 |
114 | public function generateFiles($types)
115 | {
116 | $_FILES = [];
117 | UploadedFile::reset();
118 |
119 | foreach ($types as $index => $type) {
120 | $file = $type ? "file.$type" : "file";
121 | $path = Yii::getAlias("@tests/files/$file");
122 | $_FILES["UploadForm[file][$index]"] = [
123 | 'name' => $file,
124 | 'type' => mime_content_type($path),
125 | 'size' => filesize($path),
126 | 'tmp_name' => $path,
127 | 'error' => 0
128 | ];
129 | }
130 | }
131 |
132 | public function checkFilesExist($types)
133 | {
134 | foreach ($types as $type) {
135 | $file = $type ? "/file.$type" : "/file";
136 | $filePath = $this->getTempDirPath() . $file;
137 | $this->assertFileExists($filePath);
138 | }
139 | }
140 |
141 | public function checkFilesNotExist($types)
142 | {
143 | foreach ($types as $type) {
144 | $file = $type ? "/file.$type" : "/file";
145 | $filePath = $this->getTempDirPath() . $file;
146 | $this->assertFileNotExists($filePath);
147 | }
148 | }
149 |
150 | public function getTempDirPath()
151 | {
152 | return Yii::getAlias('@tests/uploads/temp/' . Yii::$app->session->id);
153 | }
154 | }
--------------------------------------------------------------------------------
/src/components/AttachmentsTableWithPreview.php:
--------------------------------------------------------------------------------
1 | 'table table-striped table-bordered table-condensed'];
30 |
31 | public $showDeleteButton = true;
32 |
33 | public function init()
34 | {
35 | parent::init();
36 | }
37 |
38 | public function run()
39 | {
40 | if (!$this->model) {
41 | return Html::tag('div',
42 | Html::tag('b',
43 | Yii::t('yii', 'Error')) . ': ' . $this->getModule()->t('attachments', 'The model cannot be empty.'
44 | ),
45 | [
46 | 'class' => 'alert alert-danger'
47 | ]
48 | );
49 | }
50 |
51 | $hasFileBehavior = false;
52 | foreach ($this->model->getBehaviors() as $behavior) {
53 | if ($behavior instanceof FileBehavior) {
54 | $hasFileBehavior = true;
55 | break;
56 | }
57 | }
58 | if (!$hasFileBehavior) {
59 | return Html::tag('div',
60 | Html::tag('b',
61 | Yii::t('yii', 'Error')) . ': ' . $this->getModule()->t('attachments', 'The behavior FileBehavior has not been attached to the model.'
62 | ),
63 | [
64 | 'class' => 'alert alert-danger'
65 | ]
66 | );
67 | }
68 |
69 | $confirm = Yii::t('yii', 'Are you sure you want to delete this item?');
70 | $js = <<view->registerJs($js);
88 |
89 | return GridView::widget([
90 | 'dataProvider' => new ArrayDataProvider(['allModels' => $this->model->getFiles()]),
91 | 'layout' => '{items}',
92 | 'tableOptions' => $this->tableOptions,
93 | 'columns' => [
94 | [
95 | 'class' => 'yii\grid\SerialColumn'
96 | ],
97 | [
98 | 'label' => $this->getModule()->t('attachments', 'File name'),
99 | 'format' => 'raw',
100 | 'value' => function ($model) {
101 | return Html::a("$model->name.$model->type", $model->getUrl(), [
102 | 'class' => ' group' . $model->itemId,
103 | 'onclick' => 'return false;',
104 | ]);
105 | }
106 | ],
107 | [
108 | 'class' => 'yii\grid\ActionColumn',
109 | 'template' => '{delete}',
110 | 'visibleButtons' => ['delete' => $this->showDeleteButton],
111 | 'buttons' => [
112 | 'delete' => function ($url, $model, $key) {
113 | return Html::a('',
114 | '#',
115 | [
116 | 'class' => 'delete-button',
117 | 'title' => Yii::t('yii', 'Delete'),
118 | 'data-url' => Url::to(['/attachments/file/delete', 'id' => $model->id])
119 | ]
120 | );
121 | }
122 | ]
123 | ],
124 | ],
125 | ]) .
126 | Colorbox::widget([
127 | 'targets' => [
128 | '.group' . $this->model->id => [
129 | 'rel' => '.group' . $this->model->id,
130 | 'photo' => true,
131 | 'scalePhotos' => true,
132 | 'width' => '100%',
133 | 'height' => '100%',
134 | 'maxWidth' => 800,
135 | 'maxHeight' => 600,
136 | ],
137 | ],
138 | 'coreStyle' => 4,
139 |
140 | ]);
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Module.php:
--------------------------------------------------------------------------------
1 | storePath) || empty($this->tempPath)) {
28 | throw new Exception('Setup {storePath} and {tempPath} in module properties');
29 | }
30 |
31 | $this->rules = ArrayHelper::merge(['maxFiles' => 3], $this->rules);
32 | $this->defaultRoute = 'file';
33 | $this->registerTranslations();
34 | }
35 |
36 | public function registerTranslations()
37 | {
38 | \Yii::$app->i18n->translations['nemmo/*'] = [
39 | 'class' => PhpMessageSource::className(),
40 | 'sourceLanguage' => 'en',
41 | 'basePath' => '@vendor/nemmo/yii2-attachments/src/messages',
42 | 'fileMap' => [
43 | 'nemmo/attachments' => 'attachments.php'
44 | ],
45 | ];
46 | }
47 |
48 | public static function t($category, $message, $params = [], $language = null)
49 | {
50 | return \Yii::t('nemmo/' . $category, $message, $params, $language);
51 | }
52 |
53 | public function getStorePath()
54 | {
55 | return \Yii::getAlias($this->storePath);
56 | }
57 |
58 | public function getTempPath()
59 | {
60 | return \Yii::getAlias($this->tempPath);
61 | }
62 |
63 | /**
64 | * @param $fileHash
65 | * @return string
66 | */
67 | public function getFilesDirPath($fileHash)
68 | {
69 | $path = $this->getStorePath() . DIRECTORY_SEPARATOR . $this->getSubDirs($fileHash);
70 |
71 | FileHelper::createDirectory($path);
72 |
73 | return $path;
74 | }
75 |
76 | public function getSubDirs($fileHash, $depth = 3)
77 | {
78 | $depth = min($depth, 9);
79 | $path = '';
80 |
81 | for ($i = 0; $i < $depth; $i++) {
82 | $folder = substr($fileHash, $i * 3, 2);
83 | $path .= $folder;
84 | if ($i != $depth - 1) $path .= DIRECTORY_SEPARATOR;
85 | }
86 |
87 | return $path;
88 | }
89 |
90 | public function getUserDirPath()
91 | {
92 | \Yii::$app->session->open();
93 |
94 | $userDirPath = $this->getTempPath() . DIRECTORY_SEPARATOR . \Yii::$app->session->id;
95 | FileHelper::createDirectory($userDirPath);
96 |
97 | \Yii::$app->session->close();
98 |
99 | return $userDirPath . DIRECTORY_SEPARATOR;
100 | }
101 |
102 | public function getShortClass($obj)
103 | {
104 | $className = get_class($obj);
105 | if (preg_match('@\\\\([\w]+)$@', $className, $matches)) {
106 | $className = $matches[1];
107 | }
108 | return $className;
109 | }
110 |
111 | /**
112 | * @param $filePath string
113 | * @param $owner
114 | * @return bool|File
115 | * @throws Exception
116 | * @throws \yii\base\InvalidConfigException
117 | */
118 | public function attachFile($filePath, $owner)
119 | {
120 | if (empty($owner->id)) {
121 | throw new Exception('Parent model must have ID when you attaching a file');
122 | }
123 | if (!file_exists($filePath)) {
124 | throw new Exception("File $filePath not exists");
125 | }
126 |
127 | $fileHash = md5(microtime(true) . $filePath);
128 | $fileType = pathinfo($filePath, PATHINFO_EXTENSION);
129 | $newFileName = "$fileHash.$fileType";
130 | $fileDirPath = $this->getFilesDirPath($fileHash);
131 | $newFilePath = $fileDirPath . DIRECTORY_SEPARATOR . $newFileName;
132 |
133 | if (!copy($filePath, $newFilePath)) {
134 | throw new Exception("Cannot copy file! $filePath to $newFilePath");
135 | }
136 |
137 | $file = new File();
138 | $file->name = pathinfo($filePath, PATHINFO_FILENAME);
139 | $file->model = $this->getShortClass($owner);
140 | $file->itemId = $owner->id;
141 | $file->hash = $fileHash;
142 | $file->size = filesize($filePath);
143 | $file->type = $fileType;
144 | $file->mime = FileHelper::getMimeType($filePath);
145 |
146 | if ($file->save()) {
147 | unlink($filePath);
148 | return $file;
149 | } else {
150 | return false;
151 | }
152 | }
153 |
154 | public function detachFile($id)
155 | {
156 | /** @var File $file */
157 | $file = File::findOne(['id' => $id]);
158 | if (empty($file)) return false;
159 | $filePath = $this->getFilesDirPath($file->hash) . DIRECTORY_SEPARATOR . $file->hash . '.' . $file->type;
160 |
161 | // this is the important part of the override.
162 | // the original methods doesn't check for file_exists to be
163 | return file_exists($filePath) ? unlink($filePath) && $file->delete() : $file->delete();
164 | }
165 | }
166 |
--------------------------------------------------------------------------------
/src/behaviors/FileBehavior.php:
--------------------------------------------------------------------------------
1 | 'saveUploads',
31 | ActiveRecord::EVENT_AFTER_UPDATE => 'saveUploads',
32 | ActiveRecord::EVENT_AFTER_DELETE => 'deleteUploads'
33 | ];
34 | }
35 |
36 | public function saveUploads($event)
37 | {
38 | $files = UploadedFile::getInstancesByName('UploadForm[file]');
39 |
40 | if (!empty($files)) {
41 | foreach ($files as $file) {
42 | if (!$file->saveAs($this->getModule()->getUserDirPath() . $file->name)) {
43 | throw new \Exception(\Yii::t('yii', 'File upload failed.'));
44 | }
45 | }
46 | }
47 |
48 | $userTempDir = $this->getModule()->getUserDirPath();
49 | $attachedFiles = [];
50 | foreach (FileHelper::findFiles($userTempDir) as $file) {
51 | if (!($attachedFile = $this->getModule()->attachFile($file, $this->owner))) {
52 | throw new \Exception(\Yii::t('yii', 'File upload failed.'));
53 | }else{
54 | $attachedFiles[] = $attachedFile;
55 | }
56 | }
57 | if(count($attachedFiles)){
58 | $event = \Yii::createObject(['class' => FileEvent::class, 'files' => $attachedFiles]);
59 | $this->owner->trigger(self::EVENT_AFTER_ATTACH_FILES, $event);
60 | }
61 | rmdir($userTempDir);
62 | }
63 |
64 | public function deleteUploads($event)
65 | {
66 | foreach ($this->getFiles() as $file) {
67 | $this->getModule()->detachFile($file->id);
68 | }
69 | }
70 |
71 | /**
72 | * @return File[]
73 | * @throws \Exception
74 | */
75 | public function getFiles()
76 | {
77 | $fileQuery = File::find()
78 | ->where([
79 | 'itemId' => $this->owner->id,
80 | 'model' => $this->getModule()->getShortClass($this->owner)
81 | ]);
82 | $fileQuery->orderBy(['id' => SORT_ASC]);
83 |
84 | return $fileQuery->all();
85 | }
86 |
87 | public function getInitialPreview()
88 | {
89 | $initialPreview = [];
90 |
91 | $userTempDir = $this->getModule()->getUserDirPath();
92 | foreach (FileHelper::findFiles($userTempDir) as $file) {
93 | if (substr(FileHelper::getMimeType($file), 0, 5) === 'image') {
94 | $initialPreview[] = Html::img(['/attachments/file/download-temp', 'filename' => basename($file)], ['class' => 'file-preview-image']);
95 | } else {
96 | $initialPreview[] = Html::beginTag('div', ['class' => 'file-preview-other']) .
97 | Html::beginTag('h2') .
98 | Html::tag('i', '', ['class' => 'glyphicon glyphicon-file']) .
99 | Html::endTag('h2') .
100 | Html::endTag('div');
101 | }
102 | }
103 |
104 | foreach ($this->getFiles() as $file) {
105 | $initialPreview[] =Url::to([$file->getUrl(true)],true);
106 | }
107 |
108 | return $initialPreview;
109 | }
110 |
111 | public function getInitialPreviewConfig()
112 | {
113 | $initialPreviewConfig = [];
114 |
115 | $userTempDir = $this->getModule()->getUserDirPath();
116 | foreach (FileHelper::findFiles($userTempDir) as $file) {
117 | $filename = basename($file);
118 | $initialPreviewConfig[] = [
119 | 'caption' => $filename,
120 | 'url' => Url::to(['/attachments/file/delete-temp',
121 | 'filename' => $filename
122 | ]),
123 | ];
124 | }
125 |
126 | foreach ($this->getFiles() as $index => $file) {
127 | if(str_contains($file->mime,"image"))
128 | {
129 | $initialPreviewConfig[] = [
130 | 'caption' => "$file->name.$file->type",
131 | 'url' => Url::toRoute(['/attachments/file/delete',
132 | 'id' => $file->id])
133 | ];
134 | }
135 | elseif(str_contains($file->mime,"text")){
136 | $initialPreviewConfig[] = [
137 | 'type'=> "text",
138 | 'caption' => "$file->name.$file->type",
139 | 'url' => Url::toRoute(['/attachments/file/delete',
140 | 'id' => $file->id])
141 | ];
142 | }
143 | elseif(str_contains($file->mime,"video")){
144 | $initialPreviewConfig[] = [
145 | 'type'=> "video",
146 | 'caption' => "$file->name.$file->type",
147 | 'url' => Url::toRoute(['/attachments/file/delete',
148 | 'id' => $file->id])
149 | ];
150 | }
151 | elseif(str_contains($file->type,"doc")||str_contains($file->type,"ppt")||str_contains($file->type,"xls")){
152 | $initialPreviewConfig[] = [
153 | 'type'=> "office",
154 | 'caption' => "$file->name.$file->type",
155 | 'url' => Url::toRoute(['/attachments/file/delete',
156 | 'id' => $file->id])
157 | ];
158 | }
159 | else{
160 | $initialPreviewConfig[] = [
161 | 'type'=> $file->type,
162 | 'caption' => "$file->name.$file->type",
163 | 'url' => Url::toRoute(['/attachments/file/delete',
164 | 'id' => $file->id])
165 | ];
166 | }
167 | }
168 | return $initialPreviewConfig;
169 | }
170 | }
171 |
--------------------------------------------------------------------------------
/tests/FileControllerTest.php:
--------------------------------------------------------------------------------
1 | generateFiles($types);
25 | $response = Yii::$app->runAction('attachments/file/upload');
26 | $this->assertArrayHasKey('uploadedFiles', $response);
27 | $this->assertTrue(in_array('file.png', $response['uploadedFiles']));
28 | $this->checkFilesExist($types);
29 | foreach ($types as $type) {
30 | /** @var Response $response */
31 | $response = Yii::$app->runAction('attachments/file/download-temp', ['filename' => "file.$type"]);
32 | ob_start();
33 | $response->send();
34 | $actual = ob_get_clean();
35 | $response->clear();
36 | $expected = file_get_contents(Yii::getAlias("@tests/files/file.$type"));
37 | $this->assertEquals($expected, $actual);
38 |
39 | $response = Yii::$app->runAction('attachments/file/delete-temp', ['filename' => "file.$type"]);
40 | $this->assertEquals($response, []);
41 | }
42 | $this->checkFilesNotExist($types);
43 | $this->assertFileNotExists($this->getTempDirPath());
44 |
45 | $comment = new Comment();
46 | $comment->text = 'test';
47 | $this->assertTrue($comment->save());
48 |
49 | $file = $comment->files[0];
50 | /** @var Response $response */
51 | $response = Yii::$app->runAction('attachments/file/download', ['id' => $file->id]);
52 | ob_start();
53 | $response->send();
54 | $actual = ob_get_clean();
55 | $response->clear();
56 | $expected = file_get_contents(Yii::getAlias("@tests/files/file.{$file->type}"));
57 | $this->assertEquals($expected, $actual);
58 | $this->assertFileExists($file->path);
59 | $response = Yii::$app->runAction('attachments/file/delete', ['id' => -1]);
60 | $this->assertEquals($response, false);
61 | $response = Yii::$app->runAction('attachments/file/delete', ['id' => $file->id]);
62 | $this->assertEquals($response, true);
63 | $this->assertFileNotExists($file->path);
64 |
65 | $this->assertNotSame(false, $comment->delete());
66 | }
67 |
68 | public function testPreUpload2()
69 | {
70 | $types = ['png', 'txt', 'jpg', 'zip'];
71 | $this->generateFiles($types);
72 | $response = Yii::$app->runAction('attachments/file/upload');
73 | $this->assertArrayHasKey('error', $response);
74 | $errorMessage = 'You can upload at most 3 files.';
75 | $this->assertTrue(in_array($errorMessage, $response['error']));
76 | }
77 |
78 | public function testPreUpload3()
79 | {
80 | Yii::$app->setModule('attachments', [
81 | 'class' => Module::className(),
82 | 'rules' => [
83 | 'maxFiles' => 1
84 | ]
85 | ]);
86 | $types = ['png', 'zip'];
87 | $this->generateFiles($types);
88 | $response = Yii::$app->runAction('attachments/file/upload');
89 | $this->assertArrayHasKey('error', $response);
90 | $errorMessage = 'Please upload a file.';
91 | $this->assertTrue(in_array($errorMessage, $response['error']));
92 | }
93 |
94 | public function testPreUpload4()
95 | {
96 | Yii::$app->setModule('attachments', [
97 | 'class' => Module::className(),
98 | 'rules' => [
99 | 'maxFiles' => 1
100 | ]
101 | ]);
102 | $types = ['png'];
103 | $this->generateFiles($types);
104 | $response = Yii::$app->runAction('attachments/file/upload');
105 | $this->assertArrayHasKey('uploadedFiles', $response);
106 | $this->assertTrue(in_array('file.png', $response['uploadedFiles']));
107 | $this->checkFilesExist($types);
108 | }
109 |
110 | public function testPreUpload5()
111 | {
112 | Yii::$app->setModule('attachments', [
113 | 'class' => Module::className(),
114 | 'rules' => [
115 | 'maxFiles' => 3,
116 | 'mimeTypes' => ['image/png', 'image/jpeg']
117 | ]
118 | ]);
119 | $types = ['png', 'jpg', 'zip'];
120 | $this->generateFiles($types);
121 | $response = Yii::$app->runAction('attachments/file/upload');
122 | $this->assertArrayHasKey('error', $response);
123 | $errorMessage = 'Only files with these MIME types are allowed: image/png, image/jpeg.';
124 | $this->assertTrue(in_array($errorMessage, $response['error']));
125 | }
126 |
127 | public function testPreUpload6()
128 | {
129 | $types = ['png', ''];
130 | $this->generateFiles($types);
131 | $response = Yii::$app->runAction('attachments/file/upload');
132 | $this->assertArrayHasKey('uploadedFiles', $response);
133 | $this->assertTrue(in_array('file', $response['uploadedFiles']));
134 | $this->checkFilesExist($types);
135 |
136 | foreach ($types as $type) {
137 | $fileType = empty($type) ? $type : ".".$type;
138 | /** @var Response $response */
139 | $response = Yii::$app->runAction('attachments/file/download-temp', ['filename' => "file$fileType"]);
140 | ob_start();
141 | $response->send();
142 | $actual = ob_get_clean();
143 | $response->clear();
144 | $expected = file_get_contents(Yii::getAlias("@tests/files/file$fileType"));
145 | $this->assertEquals($expected, $actual);
146 |
147 | $response = Yii::$app->runAction('attachments/file/delete-temp', ['filename' => "file$fileType"]);
148 | $this->assertEquals($response, []);
149 | }
150 | $this->checkFilesNotExist($types);
151 | $this->assertFileNotExists($this->getTempDirPath());
152 |
153 | $comment = new Comment();
154 | $comment->text = 'test';
155 | $this->assertTrue($comment->save());
156 |
157 | foreach ($comment->files as $file) {
158 | $fileType = empty($file->type) ? $file->type : ".".$file->type;
159 | /** @var Response $response */
160 | $response = Yii::$app->runAction('attachments/file/download', ['id' => $file->id]);
161 | ob_start();
162 | $response->send();
163 | $actual = ob_get_clean();
164 | $response->clear();
165 | $expected = file_get_contents(Yii::getAlias("@tests/files/file{$fileType}"));
166 | $this->assertEquals($expected, $actual);
167 | $this->assertFileExists($file->path);
168 | $response = Yii::$app->runAction('attachments/file/delete', ['id' => -1]);
169 | $this->assertEquals($response, false);
170 | $response = Yii::$app->runAction('attachments/file/delete', ['id' => $file->id]);
171 | $this->assertEquals($response, true);
172 | $this->assertFileNotExists($file->path);
173 | }
174 |
175 | $this->assertNotSame(false, $comment->delete());
176 |
177 | }
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Yii2 attachments
2 | ================
3 | [](https://packagist.org/packages/nemmo/yii2-attachments)
4 | [](https://packagist.org/packages/nemmo/yii2-attachments)
5 | [](https://scrutinizer-ci.com/g/Nemmo/yii2-attachments/build-status/tests)
6 | [](https://scrutinizer-ci.com/g/Nemmo/yii2-attachments/?branch=tests)
7 | [](https://scrutinizer-ci.com/g/Nemmo/yii2-attachments/?branch=tests)
8 | [](https://packagist.org/packages/nemmo/yii2-attachments)
9 |
10 | Extension for file uploading and attaching to the models
11 |
12 | Demo
13 | ----
14 | You can see the demo on the [krajee](http://plugins.krajee.com/file-input/demo) website
15 |
16 | Installation
17 | ------------
18 |
19 | 1. The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
20 |
21 | Either run
22 |
23 | ```
24 | php composer.phar require nemmo/yii2-attachments "~1.0.1"
25 | ```
26 |
27 | or add
28 |
29 | ```
30 | "nemmo/yii2-attachments": "~1.0.1"
31 | ```
32 |
33 | to the require section of your `composer.json` file.
34 |
35 | 2. Add module to `common/config/main.php`
36 |
37 | ```php
38 | 'modules' => [
39 | ...
40 | 'attachments' => [
41 | 'class' => nemmo\attachments\Module::className(),
42 | 'tempPath' => '@app/uploads/temp',
43 | 'storePath' => '@app/uploads/store',
44 | 'rules' => [ // Rules according to the FileValidator
45 | 'maxFiles' => 10, // Allow to upload maximum 3 files, default to 3
46 | 'mimeTypes' => 'image/png', // Only png images
47 | 'maxSize' => 1024 * 1024 // 1 MB
48 | ],
49 | 'tableName' => '{{%attachments}}' // Optional, default to 'attach_file'
50 | ]
51 | ...
52 | ]
53 | ```
54 |
55 | 3. Apply migrations
56 |
57 |
58 | ```php
59 | 'controllerMap' => [
60 | ...
61 | 'migrate' => [
62 | 'class' => 'yii\console\controllers\MigrateController',
63 | 'migrationNamespaces' => [
64 | 'nemmo\attachments\migrations',
65 | ],
66 | ],
67 | ...
68 | ],
69 | ```
70 |
71 | ```
72 | php yii migrate/up
73 | ```
74 |
75 | 4. Attach behavior to your model (be sure that your model has "id" property)
76 |
77 | ```php
78 | public function behaviors()
79 | {
80 | return [
81 | ...
82 | 'fileBehavior' => [
83 | 'class' => \nemmo\attachments\behaviors\FileBehavior::className()
84 | ]
85 | ...
86 | ];
87 | }
88 | ```
89 |
90 | 5. Make sure that you have added `'enctype' => 'multipart/form-data'` to the ActiveForm options
91 |
92 | 6. Make sure that you specified `maxFiles` in module rules and `maxFileCount` on `AttachmentsInput` to the number that you want
93 |
94 | Usage
95 | -----
96 |
97 | 1. In the `form.php` of your model add file input
98 |
99 | ```php
100 | = \nemmo\attachments\components\AttachmentsInput::widget([
101 | 'id' => 'file-input', // Optional
102 | 'model' => $model,
103 | 'options' => [ // Options of the Kartik's FileInput widget
104 | 'multiple' => true, // If you want to allow multiple upload, default to false
105 | ],
106 | 'pluginOptions' => [ // Plugin options of the Kartik's FileInput widget
107 | 'maxFileCount' => 10 // Client max files
108 | ]
109 | ]) ?>
110 | ```
111 |
112 | 2. Use widget to show all attachments of the model in the `view.php`
113 |
114 | ```php
115 | = \nemmo\attachments\components\AttachmentsTable::widget([
116 | 'model' => $model,
117 | 'showDeleteButton' => false, // Optional. Default value is true
118 | ])?>
119 | ```
120 |
121 | 3. (Deprecated) Add onclick action to your submit button that uploads all files before submitting form
122 |
123 | ```php
124 | = Html::submitButton($model->isNewRecord ? 'Create' : 'Update', [
125 | 'class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary',
126 | 'onclick' => "$('#file-input').fileinput('upload');"
127 | ]) ?>
128 | ```
129 |
130 | 4. You can get all attached files by calling ```$model->files```, for example:
131 |
132 | ```php
133 | foreach ($model->files as $file) {
134 | echo $file->path;
135 | }
136 | ```
137 |
138 | Using Events
139 | ------------
140 | You may add the following function to your model
141 |
142 | ```php
143 | public function init(){
144 | $this->on(\nemmo\attachments\behaviors\FileBehavior::EVENT_AFTER_ATTACH_FILES, function ($event) {
145 | /** @var $files \nemmo\attachments\models\File[] */
146 | $files = $event->files;
147 | //your custom code
148 | });
149 | parent::init();
150 | }
151 | ```
152 |
153 | FileController Route - No Download option.
154 | ----------
155 | Its now possible to add "inline" to the filecontroller request.
156 | This is done using the $file->getUrl(true) function option.
157 | Use this in column actions if you want to directly display the file in the browser instead of download it.
158 | This also helps with preview configs as one no longer has to wrap the getUrl() in an image tag to stop the download of other filetypes.
159 | ```
160 | ex:
161 | foreach ($this->getFiles() as $file) {
162 | $initialPreview[] =$file->getUrl(true);
163 | }
164 | ```
165 |
166 | Change log
167 | ----------
168 | - **Sept 13, 2023** - Added ability to open files inline. Upated initialPreviewConfig and AttachementsInput to use built in previews from Kartik.
169 | - **Dec 7, 2016** - Migration namespace coming with Yii 2.0.10. Release 1.0.0-beta.3.
170 | - **Apr 19, 2016** - Refactoring and testing. Ajax removing. Release 1.0.0-beta.2.
171 | - **Aug 17, 2015** - Support for prefix on table - you can specify the table name before migrating
172 | - **Jul 9, 2015** - Fixed automatic submitting form
173 | - **Jun 19, 2015** - Fixed uploading only files without submitting whole form and submitting form with ignoring upload errors
174 | - **May 1, 2015** - Fixed uploading when connection is slow or uploading time is long. Now ```onclick``` event on submit button is deprecated
175 | - **Apr 16, 2015** - Allow users to have a custom behavior class inheriting from FileBehavior.
176 | - **Apr 4, 2015** - Now all temp uploaded files will be deleted on every new form opened.
177 | - **Mar 16, 2015** - Fix: error in generating initial preview. Add: Getting path of the attached file by calling ```$file->path```.
178 | - **Mar 5, 2015** - Fix: restrictions for the number of maximum uploaded files.
179 | - **Mar 4, 2015** - Added restrictions for number of maximum uploaded files.
180 | - **Mar 3, 2015** - Fix of the file-input widget id.
181 | - **Feb 13, 2015** - Added restrictions to files (see point 1 in the Usage section), now use ```AttachmentsInput``` widget on the form view instead of ```FileInput```
182 | - **Feb 11, 2015** - Added preview of uploaded but not saved files and ```tableOptions``` property for widget
183 | - **Feb 2, 2015** - Fix: all attached files will be deleted with the model.
184 | - **Feb 1, 2015** - AJAX or basic upload.
185 | - **Jan 30, 2015** - Several previews of images and other files, fix of required packages.
186 | - **Jan 29, 2015** - First version with basic uploading and previews.
187 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 | "This file is @generated automatically"
6 | ],
7 | "hash": "f583c564952b02b22061619aa4b79667",
8 | "content-hash": "c02d2a7a2fcc84581354af9d87ee969b",
9 | "packages": [
10 | {
11 | "name": "bower-asset/bootstrap",
12 | "version": "v3.3.5",
13 | "source": {
14 | "type": "git",
15 | "url": "https://github.com/twbs/bootstrap.git",
16 | "reference": "16b48259a62f576e52c903c476bd42b90ab22482"
17 | },
18 | "dist": {
19 | "type": "zip",
20 | "url": "https://api.github.com/repos/twbs/bootstrap/zipball/16b48259a62f576e52c903c476bd42b90ab22482",
21 | "reference": "16b48259a62f576e52c903c476bd42b90ab22482",
22 | "shasum": ""
23 | },
24 | "require": {
25 | "bower-asset/jquery": ">=1.9.1"
26 | },
27 | "type": "bower-asset-library",
28 | "extra": {
29 | "bower-asset-main": [
30 | "less/bootstrap.less",
31 | "dist/js/bootstrap.js"
32 | ],
33 | "bower-asset-ignore": [
34 | "/.*",
35 | "_config.yml",
36 | "CNAME",
37 | "composer.json",
38 | "CONTRIBUTING.md",
39 | "docs",
40 | "js/tests",
41 | "test-infra"
42 | ]
43 | },
44 | "license": [
45 | "MIT"
46 | ],
47 | "description": "The most popular front-end framework for developing responsive, mobile first projects on the web.",
48 | "keywords": [
49 | "css",
50 | "framework",
51 | "front-end",
52 | "js",
53 | "less",
54 | "mobile-first",
55 | "responsive",
56 | "web"
57 | ]
58 | },
59 | {
60 | "name": "bower-asset/jquery",
61 | "version": "2.2.3",
62 | "source": {
63 | "type": "git",
64 | "url": "https://github.com/jquery/jquery-dist.git",
65 | "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198"
66 | },
67 | "dist": {
68 | "type": "zip",
69 | "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/af22a351b2ea5801ffb1695abb3bb34d5bed9198",
70 | "reference": "af22a351b2ea5801ffb1695abb3bb34d5bed9198",
71 | "shasum": ""
72 | },
73 | "type": "bower-asset-library",
74 | "extra": {
75 | "bower-asset-main": "dist/jquery.js",
76 | "bower-asset-ignore": [
77 | "package.json"
78 | ]
79 | },
80 | "license": [
81 | "MIT"
82 | ],
83 | "keywords": [
84 | "browser",
85 | "javascript",
86 | "jquery",
87 | "library"
88 | ]
89 | },
90 | {
91 | "name": "bower-asset/jquery-colorbox",
92 | "version": "1.6.3",
93 | "source": {
94 | "type": "git",
95 | "url": "https://github.com/jackmoore/colorbox.git",
96 | "reference": "d9b74f2289b315c36b2e4b2bce01a799bf851aed"
97 | },
98 | "dist": {
99 | "type": "zip",
100 | "url": "https://api.github.com/repos/jackmoore/colorbox/zipball/d9b74f2289b315c36b2e4b2bce01a799bf851aed",
101 | "reference": "d9b74f2289b315c36b2e4b2bce01a799bf851aed",
102 | "shasum": ""
103 | },
104 | "require": {
105 | "bower-asset/jquery": ">=1.3.2"
106 | },
107 | "type": "bower-asset-library",
108 | "extra": {
109 | "bower-asset-main": "jquery.colorbox.js",
110 | "bower-asset-ignore": [
111 | "colorbox.jquery.json",
112 | "colorbox.ai",
113 | "content",
114 | "example1/index.html",
115 | "example2/index.html",
116 | "example3/index.html",
117 | "example4/index.html",
118 | "example5/index.html"
119 | ]
120 | },
121 | "description": "jQuery lightbox and modal window plugin",
122 | "keywords": [
123 | "gallery",
124 | "jquery-plugin",
125 | "lightbox",
126 | "modal",
127 | "popup",
128 | "ui"
129 | ]
130 | },
131 | {
132 | "name": "bower-asset/jquery.inputmask",
133 | "version": "3.2.7",
134 | "source": {
135 | "type": "git",
136 | "url": "https://github.com/RobinHerbots/jquery.inputmask.git",
137 | "reference": "5a72c563b502b8e05958a524cdfffafe9987be38"
138 | },
139 | "dist": {
140 | "type": "zip",
141 | "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38",
142 | "reference": "5a72c563b502b8e05958a524cdfffafe9987be38",
143 | "shasum": ""
144 | },
145 | "require": {
146 | "bower-asset/jquery": ">=1.7"
147 | },
148 | "type": "bower-asset-library",
149 | "extra": {
150 | "bower-asset-main": [
151 | "./dist/inputmask/inputmask.js"
152 | ],
153 | "bower-asset-ignore": [
154 | "**/*",
155 | "!dist/*",
156 | "!dist/inputmask/*",
157 | "!dist/min/*",
158 | "!dist/min/inputmask/*",
159 | "!extra/bindings/*",
160 | "!extra/dependencyLibs/*",
161 | "!extra/phone-codes/*"
162 | ]
163 | },
164 | "license": [
165 | "http://opensource.org/licenses/mit-license.php"
166 | ],
167 | "description": "jquery.inputmask is a jquery plugin which create an input mask.",
168 | "keywords": [
169 | "form",
170 | "input",
171 | "inputmask",
172 | "jquery",
173 | "mask",
174 | "plugins"
175 | ]
176 | },
177 | {
178 | "name": "bower-asset/punycode",
179 | "version": "v1.3.2",
180 | "source": {
181 | "type": "git",
182 | "url": "https://github.com/bestiejs/punycode.js.git",
183 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3"
184 | },
185 | "dist": {
186 | "type": "zip",
187 | "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3",
188 | "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3",
189 | "shasum": ""
190 | },
191 | "type": "bower-asset-library",
192 | "extra": {
193 | "bower-asset-main": "punycode.js",
194 | "bower-asset-ignore": [
195 | "coverage",
196 | "tests",
197 | ".*",
198 | "component.json",
199 | "Gruntfile.js",
200 | "node_modules",
201 | "package.json"
202 | ]
203 | }
204 | },
205 | {
206 | "name": "bower-asset/yii2-pjax",
207 | "version": "v2.0.6",
208 | "source": {
209 | "type": "git",
210 | "url": "https://github.com/yiisoft/jquery-pjax.git",
211 | "reference": "60728da6ade5879e807a49ce59ef9a72039b8978"
212 | },
213 | "dist": {
214 | "type": "zip",
215 | "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978",
216 | "reference": "60728da6ade5879e807a49ce59ef9a72039b8978",
217 | "shasum": ""
218 | },
219 | "require": {
220 | "bower-asset/jquery": ">=1.8"
221 | },
222 | "type": "bower-asset-library",
223 | "extra": {
224 | "bower-asset-main": "./jquery.pjax.js",
225 | "bower-asset-ignore": [
226 | ".travis.yml",
227 | "Gemfile",
228 | "Gemfile.lock",
229 | "CONTRIBUTING.md",
230 | "vendor/",
231 | "script/",
232 | "test/"
233 | ]
234 | },
235 | "license": [
236 | "MIT"
237 | ]
238 | },
239 | {
240 | "name": "cebe/markdown",
241 | "version": "1.1.0",
242 | "source": {
243 | "type": "git",
244 | "url": "https://github.com/cebe/markdown.git",
245 | "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2"
246 | },
247 | "dist": {
248 | "type": "zip",
249 | "url": "https://api.github.com/repos/cebe/markdown/zipball/54a2c49de31cc44e864ebf0500a35ef21d0010b2",
250 | "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2",
251 | "shasum": ""
252 | },
253 | "require": {
254 | "lib-pcre": "*",
255 | "php": ">=5.4.0"
256 | },
257 | "require-dev": {
258 | "cebe/indent": "*",
259 | "facebook/xhprof": "*@dev",
260 | "phpunit/phpunit": "4.1.*"
261 | },
262 | "bin": [
263 | "bin/markdown"
264 | ],
265 | "type": "library",
266 | "extra": {
267 | "branch-alias": {
268 | "dev-master": "1.1.x-dev"
269 | }
270 | },
271 | "autoload": {
272 | "psr-4": {
273 | "cebe\\markdown\\": ""
274 | }
275 | },
276 | "notification-url": "https://packagist.org/downloads/",
277 | "license": [
278 | "MIT"
279 | ],
280 | "authors": [
281 | {
282 | "name": "Carsten Brandt",
283 | "email": "mail@cebe.cc",
284 | "homepage": "http://cebe.cc/",
285 | "role": "Creator"
286 | }
287 | ],
288 | "description": "A super fast, highly extensible markdown parser for PHP",
289 | "homepage": "https://github.com/cebe/markdown#readme",
290 | "keywords": [
291 | "extensible",
292 | "fast",
293 | "gfm",
294 | "markdown",
295 | "markdown-extra"
296 | ],
297 | "time": "2015-03-06 05:28:07"
298 | },
299 | {
300 | "name": "ezyang/htmlpurifier",
301 | "version": "v4.6.0",
302 | "source": {
303 | "type": "git",
304 | "url": "https://github.com/ezyang/htmlpurifier.git",
305 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd"
306 | },
307 | "dist": {
308 | "type": "zip",
309 | "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/6f389f0f25b90d0b495308efcfa073981177f0fd",
310 | "reference": "6f389f0f25b90d0b495308efcfa073981177f0fd",
311 | "shasum": ""
312 | },
313 | "require": {
314 | "php": ">=5.2"
315 | },
316 | "type": "library",
317 | "autoload": {
318 | "psr-0": {
319 | "HTMLPurifier": "library/"
320 | },
321 | "files": [
322 | "library/HTMLPurifier.composer.php"
323 | ]
324 | },
325 | "notification-url": "https://packagist.org/downloads/",
326 | "license": [
327 | "LGPL"
328 | ],
329 | "authors": [
330 | {
331 | "name": "Edward Z. Yang",
332 | "email": "admin@htmlpurifier.org",
333 | "homepage": "http://ezyang.com"
334 | }
335 | ],
336 | "description": "Standards compliant HTML filter written in PHP",
337 | "homepage": "http://htmlpurifier.org/",
338 | "keywords": [
339 | "html"
340 | ],
341 | "time": "2013-11-30 08:25:19"
342 | },
343 | {
344 | "name": "himiklab/yii2-colorbox-widget",
345 | "version": "1.0.3",
346 | "source": {
347 | "type": "git",
348 | "url": "https://github.com/himiklab/yii2-colorbox-widget.git",
349 | "reference": "c4aee292cdeabe03e996eb4396719376c2252602"
350 | },
351 | "dist": {
352 | "type": "zip",
353 | "url": "https://api.github.com/repos/himiklab/yii2-colorbox-widget/zipball/c4aee292cdeabe03e996eb4396719376c2252602",
354 | "reference": "c4aee292cdeabe03e996eb4396719376c2252602",
355 | "shasum": ""
356 | },
357 | "require": {
358 | "bower-asset/jquery-colorbox": "*",
359 | "yiisoft/yii2": "*"
360 | },
361 | "type": "yii2-extension",
362 | "autoload": {
363 | "psr-4": {
364 | "himiklab\\colorbox\\": ""
365 | }
366 | },
367 | "notification-url": "https://packagist.org/downloads/",
368 | "license": [
369 | "MIT"
370 | ],
371 | "authors": [
372 | {
373 | "name": "HimikLab",
374 | "homepage": "https://github.com/himiklab/"
375 | }
376 | ],
377 | "description": "Customizable lightbox widget for Yii2",
378 | "keywords": [
379 | "colorbox",
380 | "lightbox",
381 | "widget",
382 | "yii2"
383 | ],
384 | "time": "2016-02-02 10:23:58"
385 | },
386 | {
387 | "name": "kartik-v/bootstrap-fileinput",
388 | "version": "v4.3.1",
389 | "source": {
390 | "type": "git",
391 | "url": "https://github.com/kartik-v/bootstrap-fileinput.git",
392 | "reference": "32b0ccb2562b9c83f12222ddd1406986a45450d1"
393 | },
394 | "dist": {
395 | "type": "zip",
396 | "url": "https://api.github.com/repos/kartik-v/bootstrap-fileinput/zipball/32b0ccb2562b9c83f12222ddd1406986a45450d1",
397 | "reference": "32b0ccb2562b9c83f12222ddd1406986a45450d1",
398 | "shasum": ""
399 | },
400 | "type": "library",
401 | "extra": {
402 | "branch-alias": {
403 | "dev-master": "4.3.x-dev"
404 | }
405 | },
406 | "autoload": {
407 | "psr-4": {
408 | "kartik\\plugins\\fileinput\\": ""
409 | }
410 | },
411 | "notification-url": "https://packagist.org/downloads/",
412 | "license": [
413 | "BSD-3-Clause"
414 | ],
415 | "authors": [
416 | {
417 | "name": "Kartik Visweswaran",
418 | "email": "kartikv2@gmail.com",
419 | "homepage": "http://www.krajee.com/"
420 | }
421 | ],
422 | "description": "An enhanced HTML 5 file input for Bootstrap 3.x with features for file preview for many file types, multiple selection, ajax uploads, and more.",
423 | "homepage": "https://github.com/kartik-v/bootstrap-fileinput",
424 | "keywords": [
425 | "ajax",
426 | "bootstrap",
427 | "delete",
428 | "file",
429 | "image",
430 | "input",
431 | "jquery",
432 | "multiple",
433 | "preview",
434 | "progress",
435 | "upload"
436 | ],
437 | "time": "2016-02-28 02:23:49"
438 | },
439 | {
440 | "name": "kartik-v/yii2-krajee-base",
441 | "version": "v1.8.4",
442 | "source": {
443 | "type": "git",
444 | "url": "https://github.com/kartik-v/yii2-krajee-base.git",
445 | "reference": "3115b09aeb15a5e06f38dc16860baf153d9bf70e"
446 | },
447 | "dist": {
448 | "type": "zip",
449 | "url": "https://api.github.com/repos/kartik-v/yii2-krajee-base/zipball/3115b09aeb15a5e06f38dc16860baf153d9bf70e",
450 | "reference": "3115b09aeb15a5e06f38dc16860baf153d9bf70e",
451 | "shasum": ""
452 | },
453 | "require": {
454 | "yiisoft/yii2-bootstrap": "@dev"
455 | },
456 | "type": "yii2-extension",
457 | "extra": {
458 | "branch-alias": {
459 | "dev-master": "1.8.x-dev"
460 | }
461 | },
462 | "autoload": {
463 | "psr-4": {
464 | "kartik\\base\\": ""
465 | }
466 | },
467 | "notification-url": "https://packagist.org/downloads/",
468 | "license": [
469 | "BSD-3-Clause"
470 | ],
471 | "authors": [
472 | {
473 | "name": "Kartik Visweswaran",
474 | "email": "kartikv2@gmail.com",
475 | "homepage": "http://www.krajee.com/"
476 | }
477 | ],
478 | "description": "Base library and foundation components for all Yii2 Krajee extensions.",
479 | "homepage": "https://github.com/kartik-v/yii2-krajee-base",
480 | "keywords": [
481 | "base",
482 | "extension",
483 | "foundation",
484 | "krajee",
485 | "widget",
486 | "yii2"
487 | ],
488 | "time": "2016-04-11 09:07:40"
489 | },
490 | {
491 | "name": "kartik-v/yii2-widget-fileinput",
492 | "version": "v1.0.4",
493 | "source": {
494 | "type": "git",
495 | "url": "https://github.com/kartik-v/yii2-widget-fileinput.git",
496 | "reference": "36f9f493c2d814529f2a195422a8af2e020fc80c"
497 | },
498 | "dist": {
499 | "type": "zip",
500 | "url": "https://api.github.com/repos/kartik-v/yii2-widget-fileinput/zipball/36f9f493c2d814529f2a195422a8af2e020fc80c",
501 | "reference": "36f9f493c2d814529f2a195422a8af2e020fc80c",
502 | "shasum": ""
503 | },
504 | "require": {
505 | "kartik-v/bootstrap-fileinput": "~4.2",
506 | "kartik-v/yii2-krajee-base": "~1.7"
507 | },
508 | "type": "yii2-extension",
509 | "autoload": {
510 | "psr-4": {
511 | "kartik\\file\\": ""
512 | }
513 | },
514 | "notification-url": "https://packagist.org/downloads/",
515 | "license": [
516 | "BSD-3-Clause"
517 | ],
518 | "authors": [
519 | {
520 | "name": "Kartik Visweswaran",
521 | "email": "kartikv2@gmail.com",
522 | "homepage": "http://www.krajee.com/"
523 | }
524 | ],
525 | "description": "An enhanced FileInput widget for Bootstrap 3.x with file preview, multiple selection, and more features (sub repo split from yii2-widgets)",
526 | "homepage": "https://github.com/kartik-v/yii2-widget-fileinput",
527 | "keywords": [
528 | "extension",
529 | "file",
530 | "form",
531 | "input",
532 | "jquery",
533 | "plugin",
534 | "upload",
535 | "widget",
536 | "yii2"
537 | ],
538 | "time": "2016-01-10 16:10:58"
539 | },
540 | {
541 | "name": "yiisoft/yii2",
542 | "version": "2.0.7",
543 | "source": {
544 | "type": "git",
545 | "url": "https://github.com/yiisoft/yii2-framework.git",
546 | "reference": "f45651582cb853b4326730d9d187a0f7a44a45a3"
547 | },
548 | "dist": {
549 | "type": "zip",
550 | "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/f45651582cb853b4326730d9d187a0f7a44a45a3",
551 | "reference": "f45651582cb853b4326730d9d187a0f7a44a45a3",
552 | "shasum": ""
553 | },
554 | "require": {
555 | "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable",
556 | "bower-asset/jquery.inputmask": "~3.2.2",
557 | "bower-asset/punycode": "1.3.*",
558 | "bower-asset/yii2-pjax": "~2.0.1",
559 | "cebe/markdown": "~1.0.0 | ~1.1.0",
560 | "ext-ctype": "*",
561 | "ext-mbstring": "*",
562 | "ezyang/htmlpurifier": "4.6.*",
563 | "lib-pcre": "*",
564 | "php": ">=5.4.0",
565 | "yiisoft/yii2-composer": "~2.0.4"
566 | },
567 | "bin": [
568 | "yii"
569 | ],
570 | "type": "library",
571 | "extra": {
572 | "branch-alias": {
573 | "dev-master": "2.0.x-dev"
574 | }
575 | },
576 | "autoload": {
577 | "psr-4": {
578 | "yii\\": ""
579 | }
580 | },
581 | "notification-url": "https://packagist.org/downloads/",
582 | "license": [
583 | "BSD-3-Clause"
584 | ],
585 | "authors": [
586 | {
587 | "name": "Qiang Xue",
588 | "email": "qiang.xue@gmail.com",
589 | "homepage": "http://www.yiiframework.com/",
590 | "role": "Founder and project lead"
591 | },
592 | {
593 | "name": "Alexander Makarov",
594 | "email": "sam@rmcreative.ru",
595 | "homepage": "http://rmcreative.ru/",
596 | "role": "Core framework development"
597 | },
598 | {
599 | "name": "Maurizio Domba",
600 | "homepage": "http://mdomba.info/",
601 | "role": "Core framework development"
602 | },
603 | {
604 | "name": "Carsten Brandt",
605 | "email": "mail@cebe.cc",
606 | "homepage": "http://cebe.cc/",
607 | "role": "Core framework development"
608 | },
609 | {
610 | "name": "Timur Ruziev",
611 | "email": "resurtm@gmail.com",
612 | "homepage": "http://resurtm.com/",
613 | "role": "Core framework development"
614 | },
615 | {
616 | "name": "Paul Klimov",
617 | "email": "klimov.paul@gmail.com",
618 | "role": "Core framework development"
619 | },
620 | {
621 | "name": "Dmitry Naumenko",
622 | "email": "d.naumenko.a@gmail.com",
623 | "role": "Core framework development"
624 | }
625 | ],
626 | "description": "Yii PHP Framework Version 2",
627 | "homepage": "http://www.yiiframework.com/",
628 | "keywords": [
629 | "framework",
630 | "yii2"
631 | ],
632 | "time": "2016-02-14 14:45:55"
633 | },
634 | {
635 | "name": "yiisoft/yii2-bootstrap",
636 | "version": "2.0.6",
637 | "source": {
638 | "type": "git",
639 | "url": "https://github.com/yiisoft/yii2-bootstrap.git",
640 | "reference": "3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5"
641 | },
642 | "dist": {
643 | "type": "zip",
644 | "url": "https://api.github.com/repos/yiisoft/yii2-bootstrap/zipball/3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5",
645 | "reference": "3fd2b8c950cce79d60e9702d6bcb24eb3c80f6c5",
646 | "shasum": ""
647 | },
648 | "require": {
649 | "bower-asset/bootstrap": "3.3.* | 3.2.* | 3.1.*",
650 | "yiisoft/yii2": ">=2.0.6"
651 | },
652 | "type": "yii2-extension",
653 | "extra": {
654 | "branch-alias": {
655 | "dev-master": "2.0.x-dev"
656 | },
657 | "asset-installer-paths": {
658 | "npm-asset-library": "vendor/npm",
659 | "bower-asset-library": "vendor/bower"
660 | }
661 | },
662 | "autoload": {
663 | "psr-4": {
664 | "yii\\bootstrap\\": ""
665 | }
666 | },
667 | "notification-url": "https://packagist.org/downloads/",
668 | "license": [
669 | "BSD-3-Clause"
670 | ],
671 | "authors": [
672 | {
673 | "name": "Qiang Xue",
674 | "email": "qiang.xue@gmail.com"
675 | }
676 | ],
677 | "description": "The Twitter Bootstrap extension for the Yii framework",
678 | "keywords": [
679 | "bootstrap",
680 | "yii2"
681 | ],
682 | "time": "2016-03-17 03:29:28"
683 | },
684 | {
685 | "name": "yiisoft/yii2-composer",
686 | "version": "2.0.4",
687 | "source": {
688 | "type": "git",
689 | "url": "https://github.com/yiisoft/yii2-composer.git",
690 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464"
691 | },
692 | "dist": {
693 | "type": "zip",
694 | "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464",
695 | "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464",
696 | "shasum": ""
697 | },
698 | "require": {
699 | "composer-plugin-api": "^1.0"
700 | },
701 | "type": "composer-plugin",
702 | "extra": {
703 | "class": "yii\\composer\\Plugin",
704 | "branch-alias": {
705 | "dev-master": "2.0.x-dev"
706 | }
707 | },
708 | "autoload": {
709 | "psr-4": {
710 | "yii\\composer\\": ""
711 | }
712 | },
713 | "notification-url": "https://packagist.org/downloads/",
714 | "license": [
715 | "BSD-3-Clause"
716 | ],
717 | "authors": [
718 | {
719 | "name": "Qiang Xue",
720 | "email": "qiang.xue@gmail.com"
721 | }
722 | ],
723 | "description": "The composer plugin for Yii extension installer",
724 | "keywords": [
725 | "composer",
726 | "extension installer",
727 | "yii2"
728 | ],
729 | "time": "2016-02-06 00:49:24"
730 | }
731 | ],
732 | "packages-dev": [
733 | {
734 | "name": "doctrine/instantiator",
735 | "version": "1.0.5",
736 | "source": {
737 | "type": "git",
738 | "url": "https://github.com/doctrine/instantiator.git",
739 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d"
740 | },
741 | "dist": {
742 | "type": "zip",
743 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d",
744 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d",
745 | "shasum": ""
746 | },
747 | "require": {
748 | "php": ">=5.3,<8.0-DEV"
749 | },
750 | "require-dev": {
751 | "athletic/athletic": "~0.1.8",
752 | "ext-pdo": "*",
753 | "ext-phar": "*",
754 | "phpunit/phpunit": "~4.0",
755 | "squizlabs/php_codesniffer": "~2.0"
756 | },
757 | "type": "library",
758 | "extra": {
759 | "branch-alias": {
760 | "dev-master": "1.0.x-dev"
761 | }
762 | },
763 | "autoload": {
764 | "psr-4": {
765 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
766 | }
767 | },
768 | "notification-url": "https://packagist.org/downloads/",
769 | "license": [
770 | "MIT"
771 | ],
772 | "authors": [
773 | {
774 | "name": "Marco Pivetta",
775 | "email": "ocramius@gmail.com",
776 | "homepage": "http://ocramius.github.com/"
777 | }
778 | ],
779 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
780 | "homepage": "https://github.com/doctrine/instantiator",
781 | "keywords": [
782 | "constructor",
783 | "instantiate"
784 | ],
785 | "time": "2015-06-14 21:17:01"
786 | },
787 | {
788 | "name": "phpdocumentor/reflection-docblock",
789 | "version": "2.0.4",
790 | "source": {
791 | "type": "git",
792 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
793 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8"
794 | },
795 | "dist": {
796 | "type": "zip",
797 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8",
798 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8",
799 | "shasum": ""
800 | },
801 | "require": {
802 | "php": ">=5.3.3"
803 | },
804 | "require-dev": {
805 | "phpunit/phpunit": "~4.0"
806 | },
807 | "suggest": {
808 | "dflydev/markdown": "~1.0",
809 | "erusev/parsedown": "~1.0"
810 | },
811 | "type": "library",
812 | "extra": {
813 | "branch-alias": {
814 | "dev-master": "2.0.x-dev"
815 | }
816 | },
817 | "autoload": {
818 | "psr-0": {
819 | "phpDocumentor": [
820 | "src/"
821 | ]
822 | }
823 | },
824 | "notification-url": "https://packagist.org/downloads/",
825 | "license": [
826 | "MIT"
827 | ],
828 | "authors": [
829 | {
830 | "name": "Mike van Riel",
831 | "email": "mike.vanriel@naenius.com"
832 | }
833 | ],
834 | "time": "2015-02-03 12:10:50"
835 | },
836 | {
837 | "name": "phpspec/prophecy",
838 | "version": "v1.6.0",
839 | "source": {
840 | "type": "git",
841 | "url": "https://github.com/phpspec/prophecy.git",
842 | "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972"
843 | },
844 | "dist": {
845 | "type": "zip",
846 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/3c91bdf81797d725b14cb62906f9a4ce44235972",
847 | "reference": "3c91bdf81797d725b14cb62906f9a4ce44235972",
848 | "shasum": ""
849 | },
850 | "require": {
851 | "doctrine/instantiator": "^1.0.2",
852 | "php": "^5.3|^7.0",
853 | "phpdocumentor/reflection-docblock": "~2.0",
854 | "sebastian/comparator": "~1.1",
855 | "sebastian/recursion-context": "~1.0"
856 | },
857 | "require-dev": {
858 | "phpspec/phpspec": "~2.0"
859 | },
860 | "type": "library",
861 | "extra": {
862 | "branch-alias": {
863 | "dev-master": "1.5.x-dev"
864 | }
865 | },
866 | "autoload": {
867 | "psr-0": {
868 | "Prophecy\\": "src/"
869 | }
870 | },
871 | "notification-url": "https://packagist.org/downloads/",
872 | "license": [
873 | "MIT"
874 | ],
875 | "authors": [
876 | {
877 | "name": "Konstantin Kudryashov",
878 | "email": "ever.zet@gmail.com",
879 | "homepage": "http://everzet.com"
880 | },
881 | {
882 | "name": "Marcello Duarte",
883 | "email": "marcello.duarte@gmail.com"
884 | }
885 | ],
886 | "description": "Highly opinionated mocking framework for PHP 5.3+",
887 | "homepage": "https://github.com/phpspec/prophecy",
888 | "keywords": [
889 | "Double",
890 | "Dummy",
891 | "fake",
892 | "mock",
893 | "spy",
894 | "stub"
895 | ],
896 | "time": "2016-02-15 07:46:21"
897 | },
898 | {
899 | "name": "phpunit/dbunit",
900 | "version": "1.4.1",
901 | "source": {
902 | "type": "git",
903 | "url": "https://github.com/sebastianbergmann/dbunit.git",
904 | "reference": "9aaee6447663ff1b0cd50c23637e04af74c5e2ae"
905 | },
906 | "dist": {
907 | "type": "zip",
908 | "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/9aaee6447663ff1b0cd50c23637e04af74c5e2ae",
909 | "reference": "9aaee6447663ff1b0cd50c23637e04af74c5e2ae",
910 | "shasum": ""
911 | },
912 | "require": {
913 | "ext-pdo": "*",
914 | "ext-simplexml": "*",
915 | "php": ">=5.3.3",
916 | "phpunit/phpunit": "~4|~5",
917 | "symfony/yaml": "~2.1|~3.0"
918 | },
919 | "bin": [
920 | "composer/bin/dbunit"
921 | ],
922 | "type": "library",
923 | "extra": {
924 | "branch-alias": {
925 | "dev-master": "1.3.x-dev"
926 | }
927 | },
928 | "autoload": {
929 | "classmap": [
930 | "PHPUnit/"
931 | ]
932 | },
933 | "notification-url": "https://packagist.org/downloads/",
934 | "include-path": [
935 | "",
936 | "../../symfony/yaml/"
937 | ],
938 | "license": [
939 | "BSD-3-Clause"
940 | ],
941 | "authors": [
942 | {
943 | "name": "Sebastian Bergmann",
944 | "email": "sb@sebastian-bergmann.de",
945 | "role": "lead"
946 | }
947 | ],
948 | "description": "DbUnit port for PHP/PHPUnit to support database interaction testing.",
949 | "homepage": "https://github.com/sebastianbergmann/dbunit/",
950 | "keywords": [
951 | "database",
952 | "testing",
953 | "xunit"
954 | ],
955 | "time": "2015-08-07 04:57:38"
956 | },
957 | {
958 | "name": "phpunit/php-code-coverage",
959 | "version": "2.2.4",
960 | "source": {
961 | "type": "git",
962 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
963 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979"
964 | },
965 | "dist": {
966 | "type": "zip",
967 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/eabf68b476ac7d0f73793aada060f1c1a9bf8979",
968 | "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979",
969 | "shasum": ""
970 | },
971 | "require": {
972 | "php": ">=5.3.3",
973 | "phpunit/php-file-iterator": "~1.3",
974 | "phpunit/php-text-template": "~1.2",
975 | "phpunit/php-token-stream": "~1.3",
976 | "sebastian/environment": "^1.3.2",
977 | "sebastian/version": "~1.0"
978 | },
979 | "require-dev": {
980 | "ext-xdebug": ">=2.1.4",
981 | "phpunit/phpunit": "~4"
982 | },
983 | "suggest": {
984 | "ext-dom": "*",
985 | "ext-xdebug": ">=2.2.1",
986 | "ext-xmlwriter": "*"
987 | },
988 | "type": "library",
989 | "extra": {
990 | "branch-alias": {
991 | "dev-master": "2.2.x-dev"
992 | }
993 | },
994 | "autoload": {
995 | "classmap": [
996 | "src/"
997 | ]
998 | },
999 | "notification-url": "https://packagist.org/downloads/",
1000 | "license": [
1001 | "BSD-3-Clause"
1002 | ],
1003 | "authors": [
1004 | {
1005 | "name": "Sebastian Bergmann",
1006 | "email": "sb@sebastian-bergmann.de",
1007 | "role": "lead"
1008 | }
1009 | ],
1010 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
1011 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
1012 | "keywords": [
1013 | "coverage",
1014 | "testing",
1015 | "xunit"
1016 | ],
1017 | "time": "2015-10-06 15:47:00"
1018 | },
1019 | {
1020 | "name": "phpunit/php-file-iterator",
1021 | "version": "1.4.1",
1022 | "source": {
1023 | "type": "git",
1024 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
1025 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0"
1026 | },
1027 | "dist": {
1028 | "type": "zip",
1029 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
1030 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0",
1031 | "shasum": ""
1032 | },
1033 | "require": {
1034 | "php": ">=5.3.3"
1035 | },
1036 | "type": "library",
1037 | "extra": {
1038 | "branch-alias": {
1039 | "dev-master": "1.4.x-dev"
1040 | }
1041 | },
1042 | "autoload": {
1043 | "classmap": [
1044 | "src/"
1045 | ]
1046 | },
1047 | "notification-url": "https://packagist.org/downloads/",
1048 | "license": [
1049 | "BSD-3-Clause"
1050 | ],
1051 | "authors": [
1052 | {
1053 | "name": "Sebastian Bergmann",
1054 | "email": "sb@sebastian-bergmann.de",
1055 | "role": "lead"
1056 | }
1057 | ],
1058 | "description": "FilterIterator implementation that filters files based on a list of suffixes.",
1059 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
1060 | "keywords": [
1061 | "filesystem",
1062 | "iterator"
1063 | ],
1064 | "time": "2015-06-21 13:08:43"
1065 | },
1066 | {
1067 | "name": "phpunit/php-text-template",
1068 | "version": "1.2.1",
1069 | "source": {
1070 | "type": "git",
1071 | "url": "https://github.com/sebastianbergmann/php-text-template.git",
1072 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
1073 | },
1074 | "dist": {
1075 | "type": "zip",
1076 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
1077 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
1078 | "shasum": ""
1079 | },
1080 | "require": {
1081 | "php": ">=5.3.3"
1082 | },
1083 | "type": "library",
1084 | "autoload": {
1085 | "classmap": [
1086 | "src/"
1087 | ]
1088 | },
1089 | "notification-url": "https://packagist.org/downloads/",
1090 | "license": [
1091 | "BSD-3-Clause"
1092 | ],
1093 | "authors": [
1094 | {
1095 | "name": "Sebastian Bergmann",
1096 | "email": "sebastian@phpunit.de",
1097 | "role": "lead"
1098 | }
1099 | ],
1100 | "description": "Simple template engine.",
1101 | "homepage": "https://github.com/sebastianbergmann/php-text-template/",
1102 | "keywords": [
1103 | "template"
1104 | ],
1105 | "time": "2015-06-21 13:50:34"
1106 | },
1107 | {
1108 | "name": "phpunit/php-timer",
1109 | "version": "1.0.7",
1110 | "source": {
1111 | "type": "git",
1112 | "url": "https://github.com/sebastianbergmann/php-timer.git",
1113 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b"
1114 | },
1115 | "dist": {
1116 | "type": "zip",
1117 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
1118 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b",
1119 | "shasum": ""
1120 | },
1121 | "require": {
1122 | "php": ">=5.3.3"
1123 | },
1124 | "type": "library",
1125 | "autoload": {
1126 | "classmap": [
1127 | "src/"
1128 | ]
1129 | },
1130 | "notification-url": "https://packagist.org/downloads/",
1131 | "license": [
1132 | "BSD-3-Clause"
1133 | ],
1134 | "authors": [
1135 | {
1136 | "name": "Sebastian Bergmann",
1137 | "email": "sb@sebastian-bergmann.de",
1138 | "role": "lead"
1139 | }
1140 | ],
1141 | "description": "Utility class for timing",
1142 | "homepage": "https://github.com/sebastianbergmann/php-timer/",
1143 | "keywords": [
1144 | "timer"
1145 | ],
1146 | "time": "2015-06-21 08:01:12"
1147 | },
1148 | {
1149 | "name": "phpunit/php-token-stream",
1150 | "version": "1.4.8",
1151 | "source": {
1152 | "type": "git",
1153 | "url": "https://github.com/sebastianbergmann/php-token-stream.git",
1154 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da"
1155 | },
1156 | "dist": {
1157 | "type": "zip",
1158 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
1159 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da",
1160 | "shasum": ""
1161 | },
1162 | "require": {
1163 | "ext-tokenizer": "*",
1164 | "php": ">=5.3.3"
1165 | },
1166 | "require-dev": {
1167 | "phpunit/phpunit": "~4.2"
1168 | },
1169 | "type": "library",
1170 | "extra": {
1171 | "branch-alias": {
1172 | "dev-master": "1.4-dev"
1173 | }
1174 | },
1175 | "autoload": {
1176 | "classmap": [
1177 | "src/"
1178 | ]
1179 | },
1180 | "notification-url": "https://packagist.org/downloads/",
1181 | "license": [
1182 | "BSD-3-Clause"
1183 | ],
1184 | "authors": [
1185 | {
1186 | "name": "Sebastian Bergmann",
1187 | "email": "sebastian@phpunit.de"
1188 | }
1189 | ],
1190 | "description": "Wrapper around PHP's tokenizer extension.",
1191 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
1192 | "keywords": [
1193 | "tokenizer"
1194 | ],
1195 | "time": "2015-09-15 10:49:45"
1196 | },
1197 | {
1198 | "name": "phpunit/phpunit",
1199 | "version": "4.8.24",
1200 | "source": {
1201 | "type": "git",
1202 | "url": "https://github.com/sebastianbergmann/phpunit.git",
1203 | "reference": "a1066c562c52900a142a0e2bbf0582994671385e"
1204 | },
1205 | "dist": {
1206 | "type": "zip",
1207 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1066c562c52900a142a0e2bbf0582994671385e",
1208 | "reference": "a1066c562c52900a142a0e2bbf0582994671385e",
1209 | "shasum": ""
1210 | },
1211 | "require": {
1212 | "ext-dom": "*",
1213 | "ext-json": "*",
1214 | "ext-pcre": "*",
1215 | "ext-reflection": "*",
1216 | "ext-spl": "*",
1217 | "php": ">=5.3.3",
1218 | "phpspec/prophecy": "^1.3.1",
1219 | "phpunit/php-code-coverage": "~2.1",
1220 | "phpunit/php-file-iterator": "~1.4",
1221 | "phpunit/php-text-template": "~1.2",
1222 | "phpunit/php-timer": ">=1.0.6",
1223 | "phpunit/phpunit-mock-objects": "~2.3",
1224 | "sebastian/comparator": "~1.1",
1225 | "sebastian/diff": "~1.2",
1226 | "sebastian/environment": "~1.3",
1227 | "sebastian/exporter": "~1.2",
1228 | "sebastian/global-state": "~1.0",
1229 | "sebastian/version": "~1.0",
1230 | "symfony/yaml": "~2.1|~3.0"
1231 | },
1232 | "suggest": {
1233 | "phpunit/php-invoker": "~1.1"
1234 | },
1235 | "bin": [
1236 | "phpunit"
1237 | ],
1238 | "type": "library",
1239 | "extra": {
1240 | "branch-alias": {
1241 | "dev-master": "4.8.x-dev"
1242 | }
1243 | },
1244 | "autoload": {
1245 | "classmap": [
1246 | "src/"
1247 | ]
1248 | },
1249 | "notification-url": "https://packagist.org/downloads/",
1250 | "license": [
1251 | "BSD-3-Clause"
1252 | ],
1253 | "authors": [
1254 | {
1255 | "name": "Sebastian Bergmann",
1256 | "email": "sebastian@phpunit.de",
1257 | "role": "lead"
1258 | }
1259 | ],
1260 | "description": "The PHP Unit Testing framework.",
1261 | "homepage": "https://phpunit.de/",
1262 | "keywords": [
1263 | "phpunit",
1264 | "testing",
1265 | "xunit"
1266 | ],
1267 | "time": "2016-03-14 06:16:08"
1268 | },
1269 | {
1270 | "name": "phpunit/phpunit-mock-objects",
1271 | "version": "2.3.8",
1272 | "source": {
1273 | "type": "git",
1274 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git",
1275 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983"
1276 | },
1277 | "dist": {
1278 | "type": "zip",
1279 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/ac8e7a3db35738d56ee9a76e78a4e03d97628983",
1280 | "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983",
1281 | "shasum": ""
1282 | },
1283 | "require": {
1284 | "doctrine/instantiator": "^1.0.2",
1285 | "php": ">=5.3.3",
1286 | "phpunit/php-text-template": "~1.2",
1287 | "sebastian/exporter": "~1.2"
1288 | },
1289 | "require-dev": {
1290 | "phpunit/phpunit": "~4.4"
1291 | },
1292 | "suggest": {
1293 | "ext-soap": "*"
1294 | },
1295 | "type": "library",
1296 | "extra": {
1297 | "branch-alias": {
1298 | "dev-master": "2.3.x-dev"
1299 | }
1300 | },
1301 | "autoload": {
1302 | "classmap": [
1303 | "src/"
1304 | ]
1305 | },
1306 | "notification-url": "https://packagist.org/downloads/",
1307 | "license": [
1308 | "BSD-3-Clause"
1309 | ],
1310 | "authors": [
1311 | {
1312 | "name": "Sebastian Bergmann",
1313 | "email": "sb@sebastian-bergmann.de",
1314 | "role": "lead"
1315 | }
1316 | ],
1317 | "description": "Mock Object library for PHPUnit",
1318 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/",
1319 | "keywords": [
1320 | "mock",
1321 | "xunit"
1322 | ],
1323 | "time": "2015-10-02 06:51:40"
1324 | },
1325 | {
1326 | "name": "sebastian/comparator",
1327 | "version": "1.2.0",
1328 | "source": {
1329 | "type": "git",
1330 | "url": "https://github.com/sebastianbergmann/comparator.git",
1331 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22"
1332 | },
1333 | "dist": {
1334 | "type": "zip",
1335 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22",
1336 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22",
1337 | "shasum": ""
1338 | },
1339 | "require": {
1340 | "php": ">=5.3.3",
1341 | "sebastian/diff": "~1.2",
1342 | "sebastian/exporter": "~1.2"
1343 | },
1344 | "require-dev": {
1345 | "phpunit/phpunit": "~4.4"
1346 | },
1347 | "type": "library",
1348 | "extra": {
1349 | "branch-alias": {
1350 | "dev-master": "1.2.x-dev"
1351 | }
1352 | },
1353 | "autoload": {
1354 | "classmap": [
1355 | "src/"
1356 | ]
1357 | },
1358 | "notification-url": "https://packagist.org/downloads/",
1359 | "license": [
1360 | "BSD-3-Clause"
1361 | ],
1362 | "authors": [
1363 | {
1364 | "name": "Jeff Welch",
1365 | "email": "whatthejeff@gmail.com"
1366 | },
1367 | {
1368 | "name": "Volker Dusch",
1369 | "email": "github@wallbash.com"
1370 | },
1371 | {
1372 | "name": "Bernhard Schussek",
1373 | "email": "bschussek@2bepublished.at"
1374 | },
1375 | {
1376 | "name": "Sebastian Bergmann",
1377 | "email": "sebastian@phpunit.de"
1378 | }
1379 | ],
1380 | "description": "Provides the functionality to compare PHP values for equality",
1381 | "homepage": "http://www.github.com/sebastianbergmann/comparator",
1382 | "keywords": [
1383 | "comparator",
1384 | "compare",
1385 | "equality"
1386 | ],
1387 | "time": "2015-07-26 15:48:44"
1388 | },
1389 | {
1390 | "name": "sebastian/diff",
1391 | "version": "1.4.1",
1392 | "source": {
1393 | "type": "git",
1394 | "url": "https://github.com/sebastianbergmann/diff.git",
1395 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e"
1396 | },
1397 | "dist": {
1398 | "type": "zip",
1399 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e",
1400 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e",
1401 | "shasum": ""
1402 | },
1403 | "require": {
1404 | "php": ">=5.3.3"
1405 | },
1406 | "require-dev": {
1407 | "phpunit/phpunit": "~4.8"
1408 | },
1409 | "type": "library",
1410 | "extra": {
1411 | "branch-alias": {
1412 | "dev-master": "1.4-dev"
1413 | }
1414 | },
1415 | "autoload": {
1416 | "classmap": [
1417 | "src/"
1418 | ]
1419 | },
1420 | "notification-url": "https://packagist.org/downloads/",
1421 | "license": [
1422 | "BSD-3-Clause"
1423 | ],
1424 | "authors": [
1425 | {
1426 | "name": "Kore Nordmann",
1427 | "email": "mail@kore-nordmann.de"
1428 | },
1429 | {
1430 | "name": "Sebastian Bergmann",
1431 | "email": "sebastian@phpunit.de"
1432 | }
1433 | ],
1434 | "description": "Diff implementation",
1435 | "homepage": "https://github.com/sebastianbergmann/diff",
1436 | "keywords": [
1437 | "diff"
1438 | ],
1439 | "time": "2015-12-08 07:14:41"
1440 | },
1441 | {
1442 | "name": "sebastian/environment",
1443 | "version": "1.3.5",
1444 | "source": {
1445 | "type": "git",
1446 | "url": "https://github.com/sebastianbergmann/environment.git",
1447 | "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf"
1448 | },
1449 | "dist": {
1450 | "type": "zip",
1451 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
1452 | "reference": "dc7a29032cf72b54f36dac15a1ca5b3a1b6029bf",
1453 | "shasum": ""
1454 | },
1455 | "require": {
1456 | "php": ">=5.3.3"
1457 | },
1458 | "require-dev": {
1459 | "phpunit/phpunit": "~4.4"
1460 | },
1461 | "type": "library",
1462 | "extra": {
1463 | "branch-alias": {
1464 | "dev-master": "1.3.x-dev"
1465 | }
1466 | },
1467 | "autoload": {
1468 | "classmap": [
1469 | "src/"
1470 | ]
1471 | },
1472 | "notification-url": "https://packagist.org/downloads/",
1473 | "license": [
1474 | "BSD-3-Clause"
1475 | ],
1476 | "authors": [
1477 | {
1478 | "name": "Sebastian Bergmann",
1479 | "email": "sebastian@phpunit.de"
1480 | }
1481 | ],
1482 | "description": "Provides functionality to handle HHVM/PHP environments",
1483 | "homepage": "http://www.github.com/sebastianbergmann/environment",
1484 | "keywords": [
1485 | "Xdebug",
1486 | "environment",
1487 | "hhvm"
1488 | ],
1489 | "time": "2016-02-26 18:40:46"
1490 | },
1491 | {
1492 | "name": "sebastian/exporter",
1493 | "version": "1.2.1",
1494 | "source": {
1495 | "type": "git",
1496 | "url": "https://github.com/sebastianbergmann/exporter.git",
1497 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e"
1498 | },
1499 | "dist": {
1500 | "type": "zip",
1501 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e",
1502 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e",
1503 | "shasum": ""
1504 | },
1505 | "require": {
1506 | "php": ">=5.3.3",
1507 | "sebastian/recursion-context": "~1.0"
1508 | },
1509 | "require-dev": {
1510 | "phpunit/phpunit": "~4.4"
1511 | },
1512 | "type": "library",
1513 | "extra": {
1514 | "branch-alias": {
1515 | "dev-master": "1.2.x-dev"
1516 | }
1517 | },
1518 | "autoload": {
1519 | "classmap": [
1520 | "src/"
1521 | ]
1522 | },
1523 | "notification-url": "https://packagist.org/downloads/",
1524 | "license": [
1525 | "BSD-3-Clause"
1526 | ],
1527 | "authors": [
1528 | {
1529 | "name": "Jeff Welch",
1530 | "email": "whatthejeff@gmail.com"
1531 | },
1532 | {
1533 | "name": "Volker Dusch",
1534 | "email": "github@wallbash.com"
1535 | },
1536 | {
1537 | "name": "Bernhard Schussek",
1538 | "email": "bschussek@2bepublished.at"
1539 | },
1540 | {
1541 | "name": "Sebastian Bergmann",
1542 | "email": "sebastian@phpunit.de"
1543 | },
1544 | {
1545 | "name": "Adam Harvey",
1546 | "email": "aharvey@php.net"
1547 | }
1548 | ],
1549 | "description": "Provides the functionality to export PHP variables for visualization",
1550 | "homepage": "http://www.github.com/sebastianbergmann/exporter",
1551 | "keywords": [
1552 | "export",
1553 | "exporter"
1554 | ],
1555 | "time": "2015-06-21 07:55:53"
1556 | },
1557 | {
1558 | "name": "sebastian/global-state",
1559 | "version": "1.1.1",
1560 | "source": {
1561 | "type": "git",
1562 | "url": "https://github.com/sebastianbergmann/global-state.git",
1563 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4"
1564 | },
1565 | "dist": {
1566 | "type": "zip",
1567 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4",
1568 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4",
1569 | "shasum": ""
1570 | },
1571 | "require": {
1572 | "php": ">=5.3.3"
1573 | },
1574 | "require-dev": {
1575 | "phpunit/phpunit": "~4.2"
1576 | },
1577 | "suggest": {
1578 | "ext-uopz": "*"
1579 | },
1580 | "type": "library",
1581 | "extra": {
1582 | "branch-alias": {
1583 | "dev-master": "1.0-dev"
1584 | }
1585 | },
1586 | "autoload": {
1587 | "classmap": [
1588 | "src/"
1589 | ]
1590 | },
1591 | "notification-url": "https://packagist.org/downloads/",
1592 | "license": [
1593 | "BSD-3-Clause"
1594 | ],
1595 | "authors": [
1596 | {
1597 | "name": "Sebastian Bergmann",
1598 | "email": "sebastian@phpunit.de"
1599 | }
1600 | ],
1601 | "description": "Snapshotting of global state",
1602 | "homepage": "http://www.github.com/sebastianbergmann/global-state",
1603 | "keywords": [
1604 | "global state"
1605 | ],
1606 | "time": "2015-10-12 03:26:01"
1607 | },
1608 | {
1609 | "name": "sebastian/recursion-context",
1610 | "version": "1.0.2",
1611 | "source": {
1612 | "type": "git",
1613 | "url": "https://github.com/sebastianbergmann/recursion-context.git",
1614 | "reference": "913401df809e99e4f47b27cdd781f4a258d58791"
1615 | },
1616 | "dist": {
1617 | "type": "zip",
1618 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791",
1619 | "reference": "913401df809e99e4f47b27cdd781f4a258d58791",
1620 | "shasum": ""
1621 | },
1622 | "require": {
1623 | "php": ">=5.3.3"
1624 | },
1625 | "require-dev": {
1626 | "phpunit/phpunit": "~4.4"
1627 | },
1628 | "type": "library",
1629 | "extra": {
1630 | "branch-alias": {
1631 | "dev-master": "1.0.x-dev"
1632 | }
1633 | },
1634 | "autoload": {
1635 | "classmap": [
1636 | "src/"
1637 | ]
1638 | },
1639 | "notification-url": "https://packagist.org/downloads/",
1640 | "license": [
1641 | "BSD-3-Clause"
1642 | ],
1643 | "authors": [
1644 | {
1645 | "name": "Jeff Welch",
1646 | "email": "whatthejeff@gmail.com"
1647 | },
1648 | {
1649 | "name": "Sebastian Bergmann",
1650 | "email": "sebastian@phpunit.de"
1651 | },
1652 | {
1653 | "name": "Adam Harvey",
1654 | "email": "aharvey@php.net"
1655 | }
1656 | ],
1657 | "description": "Provides functionality to recursively process PHP variables",
1658 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
1659 | "time": "2015-11-11 19:50:13"
1660 | },
1661 | {
1662 | "name": "sebastian/version",
1663 | "version": "1.0.6",
1664 | "source": {
1665 | "type": "git",
1666 | "url": "https://github.com/sebastianbergmann/version.git",
1667 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6"
1668 | },
1669 | "dist": {
1670 | "type": "zip",
1671 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1672 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6",
1673 | "shasum": ""
1674 | },
1675 | "type": "library",
1676 | "autoload": {
1677 | "classmap": [
1678 | "src/"
1679 | ]
1680 | },
1681 | "notification-url": "https://packagist.org/downloads/",
1682 | "license": [
1683 | "BSD-3-Clause"
1684 | ],
1685 | "authors": [
1686 | {
1687 | "name": "Sebastian Bergmann",
1688 | "email": "sebastian@phpunit.de",
1689 | "role": "lead"
1690 | }
1691 | ],
1692 | "description": "Library that helps with managing the version number of Git-hosted PHP projects",
1693 | "homepage": "https://github.com/sebastianbergmann/version",
1694 | "time": "2015-06-21 13:59:46"
1695 | },
1696 | {
1697 | "name": "symfony/yaml",
1698 | "version": "v3.0.4",
1699 | "source": {
1700 | "type": "git",
1701 | "url": "https://github.com/symfony/yaml.git",
1702 | "reference": "0047c8366744a16de7516622c5b7355336afae96"
1703 | },
1704 | "dist": {
1705 | "type": "zip",
1706 | "url": "https://api.github.com/repos/symfony/yaml/zipball/0047c8366744a16de7516622c5b7355336afae96",
1707 | "reference": "0047c8366744a16de7516622c5b7355336afae96",
1708 | "shasum": ""
1709 | },
1710 | "require": {
1711 | "php": ">=5.5.9"
1712 | },
1713 | "type": "library",
1714 | "extra": {
1715 | "branch-alias": {
1716 | "dev-master": "3.0-dev"
1717 | }
1718 | },
1719 | "autoload": {
1720 | "psr-4": {
1721 | "Symfony\\Component\\Yaml\\": ""
1722 | },
1723 | "exclude-from-classmap": [
1724 | "/Tests/"
1725 | ]
1726 | },
1727 | "notification-url": "https://packagist.org/downloads/",
1728 | "license": [
1729 | "MIT"
1730 | ],
1731 | "authors": [
1732 | {
1733 | "name": "Fabien Potencier",
1734 | "email": "fabien@symfony.com"
1735 | },
1736 | {
1737 | "name": "Symfony Community",
1738 | "homepage": "https://symfony.com/contributors"
1739 | }
1740 | ],
1741 | "description": "Symfony Yaml Component",
1742 | "homepage": "https://symfony.com",
1743 | "time": "2016-03-04 07:55:57"
1744 | }
1745 | ],
1746 | "aliases": [],
1747 | "minimum-stability": "stable",
1748 | "stability-flags": [],
1749 | "prefer-stable": false,
1750 | "prefer-lowest": false,
1751 | "platform": {
1752 | "php": ">=5.4.0"
1753 | },
1754 | "platform-dev": []
1755 | }
1756 |
--------------------------------------------------------------------------------