├── .gitignore ├── .travis.yml ├── LICENSE ├── Module.php ├── README.md ├── composer.json ├── config └── module.config.php ├── docs ├── 00-basic-usage.md ├── 01-config.md ├── 02-upload-adapters.md ├── 03-controller-plugin.md ├── 04-hydrator-strategy.md └── 05-view-helper.md ├── phpunit.xml ├── src └── RdnUpload │ ├── Adapter │ ├── AdapterInterface.php │ ├── AdapterManager.php │ ├── Filesystem.php │ ├── Gaufrette.php │ └── Local.php │ ├── Container.php │ ├── ContainerInterface.php │ ├── ContainerObject │ ├── Gaufrette.php │ ├── LazyResponse.php │ ├── Local.php │ └── ObjectInterface.php │ ├── Controller │ └── Plugin │ │ └── Uploads.php │ ├── Factory │ ├── Adapter │ │ ├── AdapterManager.php │ │ ├── Filesystem.php │ │ ├── Gaufrette.php │ │ └── Local.php │ ├── Container.php │ ├── Controller │ │ └── Plugin │ │ │ └── Uploads.php │ └── View │ │ └── Helper │ │ └── Uploads.php │ ├── File │ ├── File.php │ ├── FileInterface.php │ └── Input.php │ ├── Hydrator │ └── Strategy │ │ └── Upload.php │ ├── Module.php │ └── View │ └── Helper │ └── Uploads.php └── tests ├── RdnUpload ├── Adapter │ └── LocalTest.php └── ContainerTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /composer.lock 3 | /vendor 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | php: 3 | - "7.2" 4 | - "7.1" 5 | - "7.0" 6 | install: 7 | - composer install 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Rafi Adnan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Module.php: -------------------------------------------------------------------------------- 1 | array( 25 | 'RdnUpload', 26 | // ... 27 | ), 28 | ); 29 | ~~~ 30 | 31 | ## How to use 32 | 33 | All interactions are done through the **upload container**. The upload container allows us to upload, fetch, delete, and optionally download files. 34 | 35 | ~~~php 36 | $adapter = new RdnUpload\Adapter\Local('data/uploads', '/files'); 37 | $uploads = new RdnUpload\Container($adapter); 38 | 39 | var_dump($_FILES['foo']); 40 | // array( 41 | // 'name' => 'sample-foo.png' 42 | // 'type' => 'image/png' 43 | // 'tmp_name' => '/tmp/php5Wx0aJ' 44 | // 'error' => 0 45 | // 'size' => 15726 46 | // ) 47 | 48 | /** 49 | * Upload the file to the `data/uploads/` directory. 50 | */ 51 | $id = $uploads->upload($_FILES['foo']); 52 | 53 | /** 54 | * Use this id to fetch, download, and delete this file object. 55 | */ 56 | echo $id; // b/6/1/b61cd9dbf7fdabc7bd67f27cc066d0fc50eacdc4/sample-foo.png 57 | 58 | $object = $uploads->get($id); 59 | 60 | echo $object->getBasename(); // sample-foo.png 61 | echo $object->getPublicUrl(); // /files/b/6/1/b61cd9dbf7fdabc7bd67f27cc066d0fc50eacdc4/sample-foo.png 62 | ~~~ 63 | 64 | ### `upload($input)` 65 | 66 | Files can be uploaded to the container using a single `$_FILES` array item or an object implementing `RdnUpload\File\FileInterface`. 67 | 68 | The container will return a unique string identifier for the uploaded object. 69 | 70 | ~~~php 71 | /** @var RdnUpload\ContainerInterface $uploads */ 72 | 73 | /** 74 | * Upload a file using a single $_FILES item. 75 | */ 76 | $id = $uploads->upload($_FILES['foo']); 77 | 78 | /** 79 | * Upload a file using an input object. 80 | */ 81 | $input = new RdnUpload\File\Input($_FILES['foo']); 82 | $id = $uploads->upload($input); 83 | 84 | /** 85 | * Upload a local file. 86 | */ 87 | $input = new RdnUpload\File\File('sample-foo.png', '/path/to/sample-foo.png'); 88 | $id = $uploads->upload($input); 89 | ~~~ 90 | 91 | Remember to store the returned identifier which you will use to retrieve the uploaded object, delete it, etc. 92 | 93 | ### `has($id)` 94 | 95 | Check whether an object with the given identifier exists in the upload container. 96 | 97 | ### `get($id)` 98 | 99 | Get the object with the given identifier. Returns an object implementing `RdnUpload\Object\ObjectInterface`. 100 | 101 | ~~~php 102 | /** @var RdnUpload\ContainerInterface $uploads */ 103 | 104 | $object = $uploads->get($id); 105 | 106 | echo $object->getBasename(); 107 | echo $object->getExtension(); 108 | 109 | echo $object->getPublicUrl(); 110 | echo $object; // The __toString() method will return the output of getPublicUrl() 111 | ~~~ 112 | 113 | ### `delete($id)` 114 | 115 | Delete the object with the given identifier. 116 | 117 | ### `download($id)` 118 | 119 | Download a local temporary copy of the object with the given identifier. Returns an object implementing `RdnUpload\File\FileInterface`. 120 | 121 | ~~~php 122 | /** @var RdnUpload\ContainerInterface $uploads */ 123 | 124 | $file = $uploads->download($id); 125 | 126 | $img = new Imagick($file->getPath()); 127 | $img->cropThumbnailImage(64, 64); 128 | $img->writeImage(); 129 | 130 | $thumbId = $uploads->upload($file); 131 | ~~~ 132 | 133 | ## Controller Plugin 134 | 135 | The module comes with the `uploads()` plugin that allows us to easily access the upload container along with some other goodies. 136 | 137 | ~~~php 138 | class FooController 139 | { 140 | public function viewAction() 141 | { 142 | $id = /* ... */; 143 | $object = $this->uploads()->get($id); 144 | } 145 | } 146 | ~~~ 147 | 148 | You have access to the following methods from this plugin: 149 | 150 | * `upload($input)` 151 | * `get($id)` 152 | * `download($id)` 153 | * `has($id)` 154 | * `delete($id)` 155 | * `getContainer()` - Returns the upload container. 156 | 157 | ### Authorization 158 | 159 | In many cases instead of allowing public web access to uploaded files you would like to place them behind some form of authorization. The `getResponse()` method inside the controller plugin makes this easy as pie. 160 | 161 | ~~~php 162 | // inside a controller action 163 | 164 | $id = /* ... */; 165 | 166 | if (/* not allowed to view file */) 167 | { 168 | throw new AccessDeniedException("You do not have access to this file!"); 169 | } 170 | 171 | return $this->uploads()->getResponse($id); 172 | ~~~ 173 | 174 | ## Hydrator Strategy (Forms) 175 | 176 | The module comes with a strategy object that can be hooked up to a `file` form input object and it will take care of the rest. 177 | 178 | ~~~php 179 | /** @var RdnUpload\ContainerInterface $uploads */ 180 | 181 | $strategy = new RdnUpload\Hydrator\Strategy\Upload($uploads); 182 | 183 | $hydrator = new Zend\Stdlib\Hydrator\ClassMethods; 184 | $hydrator->addStrategy('file', $strategy); 185 | ~~~ 186 | 187 | ## View Helper 188 | 189 | The module comes with the `uploads()` view helper that makes it really easy to render an uploaded object's public url. 190 | 191 | ~~~php 192 | 193 | 194 | uploads()->has($id)): ?> 195 | 196 | 197 | ~~~ 198 | 199 | You have access to the following methods from this helper: 200 | 201 | * `get($id)` 202 | * `has($id)` 203 | * `getContainer()` - Returns the upload container. 204 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "radnan/rdn-upload", 3 | "description": "Zend Framework 2/3 module to manage file uploads easily and securely", 4 | "keywords": ["zf2", "zf3", "zendframework", "zend", "file", "upload", "cloud"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "radnan", 9 | "email": "rafiadnan47@gmail.com" 10 | } 11 | ], 12 | "require-dev": { 13 | "knplabs/gaufrette": "*", 14 | "mikey179/vfsstream": "*", 15 | "phpunit/phpunit": "^6.0 || ^7.0", 16 | "zendframework/zend-mvc": "^3.1", 17 | "zendframework/zend-hydrator": "^2.3" 18 | }, 19 | "suggest": { 20 | "knplabs/gaufrette": "Provides a lot more adapters" 21 | }, 22 | "autoload": { 23 | "psr-0": { 24 | "RdnUpload\\": "src/" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /config/module.config.php: -------------------------------------------------------------------------------- 1 | array( 5 | 'aliases' => array( 6 | 'uploads' => 'RdnUpload:Uploads', 7 | ), 8 | 9 | 'factories' => array( 10 | 'RdnUpload:Uploads' => 'RdnUpload\Factory\Controller\Plugin\Uploads', 11 | ), 12 | ), 13 | 14 | 'rdn_upload' => array( 15 | 'adapter' => 'Local', 16 | 'temp_dir' => null, 17 | ), 18 | 19 | 'rdn_upload_adapters' => array( 20 | 'aliases' => array( 21 | 'Filesystem' => 'Local', 22 | ), 23 | 'factories' => array( 24 | 'Local' => 'RdnUpload\Factory\Adapter\Local', 25 | 'Gaufrette' => 'RdnUpload\Factory\Adapter\Gaufrette', 26 | ), 27 | 28 | 'configs' => array( 29 | 'Local' => array( 30 | 'upload_path' => 'data/uploads', 31 | 'public_path' => '/files', 32 | ), 33 | 34 | 'Gaufrette' => array( 35 | 'filesystem' => null, 36 | 'public_path' => null, 37 | ), 38 | ), 39 | ), 40 | 41 | 'service_manager' => array( 42 | 'factories' => array( 43 | 'RdnUpload\Adapter\AdapterManager' => 'RdnUpload\Factory\Adapter\AdapterManager', 44 | 'RdnUpload\Container' => 'RdnUpload\Factory\Container', 45 | ), 46 | ), 47 | 48 | 'view_helpers' => array( 49 | 'aliases' => array( 50 | 'uploads' => 'RdnUpload:Uploads', 51 | ), 52 | 53 | 'factories' => array( 54 | 'RdnUpload:Uploads' => 'RdnUpload\Factory\View\Helper\Uploads', 55 | ), 56 | ), 57 | ); 58 | -------------------------------------------------------------------------------- /docs/00-basic-usage.md: -------------------------------------------------------------------------------- 1 | Basic usage 2 | =========== 3 | 4 | ## How to install 5 | 6 | ### 1. Require package 7 | 8 | Use [composer](http://getcomposer.org) to require the `radnan/rdn-upload` package: 9 | 10 | ~~~bash 11 | $ composer require radnan/rdn-upload:2.* 12 | ~~~ 13 | 14 | ### 2. Activate module 15 | 16 | Activate the module by including it in your `application.config.php` file: 17 | 18 | ~~~php 19 | array( 23 | 'RdnUpload', 24 | // ... 25 | ), 26 | ); 27 | ~~~ 28 | 29 | ## How to use 30 | 31 | All interactions are done through the **upload container**. The upload container allows us to upload, fetch, delete, and optionally download files. 32 | 33 | ~~~php 34 | $adapter = new RdnUpload\Adapter\Local('data/uploads', '/files'); 35 | $uploads = new RdnUpload\Container($adapter); 36 | 37 | var_dump($_FILES['foo']); 38 | // array( 39 | // 'name' => 'sample-foo.png' 40 | // 'type' => 'image/png' 41 | // 'tmp_name' => '/tmp/php5Wx0aJ' 42 | // 'error' => 0 43 | // 'size' => 15726 44 | // ) 45 | 46 | /** 47 | * Upload the file to the `data/uploads/` directory. 48 | */ 49 | $id = $uploads->upload($_FILES['foo']); 50 | 51 | /** 52 | * Use this id to fetch, download, and delete this file object. 53 | */ 54 | echo $id; // b/6/1/b61cd9dbf7fdabc7bd67f27cc066d0fc50eacdc4/sample-foo.png 55 | 56 | $object = $uploads->get($id); 57 | 58 | echo $object->getBasename(); // sample-foo.png 59 | echo $object->getPublicUrl(); // /files/b/6/1/b61cd9dbf7fdabc7bd67f27cc066d0fc50eacdc4/sample-foo.png 60 | ~~~ 61 | 62 | ## How to create adapters 63 | 64 | There are 2 steps involved in creating adapters: 65 | 66 | 1. Create your adapter and register it with the `RdnUpload\Adapter\AdapterManager` service locator 67 | 3. Configure the upload container service `RdnUpload\Container` to use your adapter 68 | 69 | ### 1. Upload container adapter service locator 70 | 71 | Please read the [Upload container adapter service locator](02-upload-adapters.md) documentation. 72 | 73 | ### 2. Upload container service 74 | 75 | Please read the [Configuration](01-config.md) documentation. 76 | 77 | ## Interfaces 78 | 79 | The following interfaces are available in the module: 80 | 81 | ### `RdnUpload\ContainerInterface` 82 | 83 | THe primary upload container object must implement this interface. 84 | 85 | ### `RdnUpload\Adapter\AdapterInterface` 86 | 87 | Upload container adapters must implement this interface. These objects handle the actual file operations. 88 | 89 | ### `RdnUpload\File\FileInterface` 90 | 91 | The upload container and adapters consume files via this interface. 92 | 93 | ### `RdnUpload\Object\ObjectInterface` 94 | 95 | Uploaded objects implement this interface. 96 | -------------------------------------------------------------------------------- /docs/01-config.md: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | The `RdnUpload\Container` service is configured using the `rdn_upload` configuration option. The following default configuration is provided by the module: 5 | 6 | ~~~php 7 | array( 11 | 'adapter' => 'Local', 12 | 'temp_dir' => null, 13 | ), 14 | ); 15 | ~~~ 16 | 17 | The `adapter` option points to an adapter loaded from the [upload container adapter service locator](02-upload-adapters.md). 18 | 19 | The `temp_dir` option can be used to customize where temporarily downloaded files are placed. If `null`, the `temp_dir` option will default to `sys_get_temp_dir()`. 20 | -------------------------------------------------------------------------------- /docs/02-upload-adapters.md: -------------------------------------------------------------------------------- 1 | Upload container adapter service locator 2 | ======================================== 3 | 4 | The `RdnUpload\Adapter\AdapterManager` service locator contains all upload adapters ready to be consumed by the upload container service. Adapters are any class that implements `RdnUpload\Adapter\AdapterInterface`. 5 | 6 | ## Configuration 7 | 8 | The `rdn_upload_adapters` configuration option is used to configure the service locator. 9 | 10 | ~~~php 11 | array( 15 | 'factories' => array(), 16 | 'invokables' => array(), 17 | ), 18 | ); 19 | ~~~ 20 | 21 | ## Local 22 | 23 | A local filesystem adapter is provided with the module. This module uses the following configuration by default: 24 | 25 | ~~~php 26 | array( 30 | 'configs' => array( 31 | 'Local' => array( 32 | 'upload_path' => 'data/uploads', 33 | 'public_path' => '/files', 34 | ), 35 | ), 36 | ), 37 | ); 38 | ~~~ 39 | 40 | The `upload_path` configuration option is where uploaded files are placed, relative to the project root. Create this directory in your project root and give PHP write permissions to it via `setfacl` or `chmod`. 41 | 42 | The `public_path` configuration option is prepended to uploaded files when generating the object's public URL. This option is also passed through the `basePath()` view helper. Symlink the `upload_path` to this location in your project's `public/` directory. 43 | 44 | Your project root should look something like this: 45 | 46 | ~~~bash 47 | 48 | |-- config/ 49 | |-- data/ 50 | | |-- cache/ 51 | | `-- uploads/ 52 | |-- module/ 53 | `-- public/ 54 | |-- files -> ../data/uploads 55 | `-- index.php 56 | ~~~ 57 | 58 | ## Gaufrette 59 | 60 | This adapter will allow you to use a [Gaufrette](https://github.com/KnpLabs/Gaufrette) filesystem with any kind of adapter (local, amazon, openstack, etc.). 61 | 62 | ~~~php 63 | array( 67 | 'configs' => array( 68 | 'Gaufrette' => array( 69 | 'filesystem' => 'My\GaufretteService', 70 | 'public_path' => '/files', 71 | ), 72 | ), 73 | ), 74 | 75 | 'service_manager' => array( 76 | 'factories' => array( 77 | 'My\GaufretteService' => function() 78 | { 79 | $adapter = new Gaufrette\Adapter\Local('data/uploads'); 80 | return new Gaufrette\Filesystem($adapter); 81 | }, 82 | ), 83 | ), 84 | ); 85 | ~~~ 86 | 87 | The `filesystem` configuration option is the name of a service that will return a Gaufrette filesystem object. 88 | 89 | The `public_path` configuration option is prepended to uploaded files when generating the object's public URL. 90 | -------------------------------------------------------------------------------- /docs/03-controller-plugin.md: -------------------------------------------------------------------------------- 1 | Controller plugin 2 | ================= 3 | 4 | The module comes with the `uploads()` plugin that allows us to easily access the upload container along with some other goodies. 5 | 6 | ~~~php 7 | class FooController 8 | { 9 | public function viewAction() 10 | { 11 | $id = /* ... */; 12 | $object = $this->uploads()->get($id); 13 | } 14 | } 15 | ~~~ 16 | 17 | You have access to the following methods from this plugin: 18 | 19 | * `upload($input)` 20 | * `get($id)` 21 | * `download($id)` 22 | * `has($id)` 23 | * `delete($id)` 24 | * `getContainer()` - Returns the upload container. 25 | 26 | ## Authorization 27 | 28 | In many cases instead of allowing public web access to uploaded files you would like to place them behind some form of authorization. The `getResponse()` method inside the controller plugin makes this easy as pie. 29 | 30 | ~~~php 31 | // inside a controller action 32 | 33 | $id = /* ... */; 34 | 35 | if (/* not allowed to view file */) 36 | { 37 | throw new AccessDeniedException("You do not have access to this file!"); 38 | } 39 | 40 | return $this->uploads()->getResponse($id); 41 | ~~~ 42 | 43 | ## Configuration 44 | 45 | The controller plugin is configured as an alias to `RdnUpload:Uploads`. If you have another plugin with this same name, you can change the plugin's name by using the following configuration: 46 | 47 | ~~~php 48 | array( 52 | 'aliases' => array( 53 | 'fooUploads' => 'RdnUpload:Uploads', 54 | ), 55 | ), 56 | ); 57 | ~~~ 58 | -------------------------------------------------------------------------------- /docs/04-hydrator-strategy.md: -------------------------------------------------------------------------------- 1 | Hydrator strategy 2 | ================= 3 | 4 | The module comes with a strategy object that can be hooked up to a `file` form input object and it will take care of the rest. 5 | 6 | ~~~php 7 | /** @var RdnUpload\ContainerInterface $uploads */ 8 | 9 | $strategy = new RdnUpload\Hydrator\Strategy\Upload($uploads); 10 | 11 | $hydrator = new Zend\Stdlib\Hydrator\ClassMethods; 12 | $hydrator->addStrategy('file', $strategy); 13 | ~~~ 14 | 15 | The strategy will extract and store the object's identifier. 16 | 17 | During hydration, if no file is uploaded it will return the stored identifier. Otherwise it will upload the new file, delete the old file if there was one, and finally return the new identifier. 18 | 19 | ## Pre process 20 | 21 | Many times you will need to perform some pre-processing before a file is uploaded. Simply provide the pre-process callback as a second argument to the strategy: 22 | 23 | ~~~php 24 | /** @var RdnUpload\ContainerInterface $uploads */ 25 | 26 | use RdnUpload\File; 27 | use RdnUpload\Hydrator\Strategy; 28 | 29 | $strategy = new Strategy\Upload($uploads, function(File\FileInterface $input) 30 | { 31 | $img = new Imagick($input->getPath()); 32 | $img->cropThumbnailImage(64, 64); 33 | $img->writeImage(); 34 | 35 | return $input; 36 | }); 37 | 38 | $hydrator = new Zend\Stdlib\Hydrator\ClassMethods; 39 | $hydrator->addStrategy('file', $strategy); 40 | ~~~ 41 | -------------------------------------------------------------------------------- /docs/05-view-helper.md: -------------------------------------------------------------------------------- 1 | View helper 2 | =========== 3 | 4 | The module comes with the `uploads()` view helper that makes it really easy to render an uploaded object's public url. 5 | 6 | ~~~php 7 | 8 | 9 | uploads()->has($id)): ?> 10 | 11 | 12 | ~~~ 13 | 14 | You have access to the following methods from this helper: 15 | 16 | * `get($id)` 17 | * `has($id)` 18 | * `getContainer()` - Returns the upload container. 19 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | ./tests/ 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/RdnUpload/Adapter/AdapterInterface.php: -------------------------------------------------------------------------------- 1 | filesystem = $filesystem; 30 | $this->publicPath = rtrim($publicPath, '/'); 31 | } 32 | 33 | public function upload($id, FileInterface $input) 34 | { 35 | $this->filesystem->write($id, file_get_contents($input->getPath())); 36 | } 37 | 38 | public function get($id) 39 | { 40 | $this->assertHasObject($id); 41 | 42 | $file = new GFile($id, $this->filesystem); 43 | return new ContainerObject\Gaufrette($file, $this->publicPath); 44 | } 45 | 46 | /** 47 | * @inheritdoc 48 | * @throws \RuntimeException 49 | */ 50 | public function download($id, FileInterface $output) 51 | { 52 | $source = new GFile($id, $this->filesystem); 53 | 54 | ErrorHandler::start(); 55 | $flag = file_put_contents($output->getPath(), $source->getContent()); 56 | ErrorHandler::stop(true); 57 | 58 | if ($flag === false) 59 | { 60 | throw new \RuntimeException("Could not download file ({$id})"); 61 | } 62 | } 63 | 64 | public function has($id) 65 | { 66 | return $this->filesystem->has($id); 67 | } 68 | 69 | 70 | public function delete($id) 71 | { 72 | $this->assertHasObject($id); 73 | 74 | $this->filesystem->delete($id); 75 | } 76 | 77 | private function assertHasObject($id) 78 | { 79 | if (!$this->has($id)) 80 | { 81 | throw new \RuntimeException("File does not exist ($id)"); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/RdnUpload/Adapter/Local.php: -------------------------------------------------------------------------------- 1 | uploadPath = rtrim($uploadPath, $this->pathTrimMask); 56 | $this->publicPath = rtrim($publicPath, $this->pathTrimMask); 57 | } 58 | 59 | /** 60 | * Get the full file path of a file with the given id and an optional path prefix. 61 | * 62 | * @param string $id 63 | * @param string $pathPrefix 64 | * 65 | * @return string 66 | */ 67 | protected function getFilepath($id, $pathPrefix = null) 68 | { 69 | if ($pathPrefix === null) 70 | { 71 | $pathPrefix = $this->uploadPath; 72 | } 73 | else 74 | { 75 | $pathPrefix = rtrim($pathPrefix, $this->pathTrimMask); 76 | } 77 | 78 | return $pathPrefix . '/' . $id; 79 | } 80 | 81 | /** 82 | * @inheritdoc 83 | * @throws \RuntimeException if move operation is unsuccessful 84 | */ 85 | public function upload($id, FileInterface $input) 86 | { 87 | $targetPath = $this->getFilepath($id); 88 | $targetDir = dirname($targetPath); 89 | if (!is_dir($targetDir)) 90 | { 91 | mkdir($targetDir, 0777, true); 92 | } 93 | 94 | ErrorHandler::start(); 95 | if ($input instanceof File\Input) 96 | { 97 | $flag = move_uploaded_file($input->getPath(), $targetPath); 98 | } 99 | else 100 | { 101 | $flag = rename($input->getPath(), $targetPath); 102 | chmod($targetPath, 0660); 103 | } 104 | ErrorHandler::stop(true); 105 | 106 | if (!$flag) 107 | { 108 | $this->purge($targetPath); 109 | 110 | throw new \RuntimeException("Could not move file ({$input->getPath()})"); 111 | } 112 | } 113 | 114 | public function get($id) 115 | { 116 | if (!$this->has($id)) 117 | { 118 | throw new \RuntimeException("File does not exist ($id)"); 119 | } 120 | 121 | $file = new ContainerObject\Local($this->getFilepath($id), $this->getFilepath($id, $this->publicPath)); 122 | return $file; 123 | } 124 | 125 | /** 126 | * @inheritdoc 127 | * @throws \RuntimeException if copy operation is unsuccessful 128 | */ 129 | public function download($id, FileInterface $output) 130 | { 131 | $source = $this->getFilepath($id); 132 | 133 | ErrorHandler::start(); 134 | $flag = copy($source, $output->getPath()); 135 | ErrorHandler::stop(true); 136 | 137 | if (!$flag) 138 | { 139 | throw new \RuntimeException("Could not copy file ({$source})"); 140 | } 141 | } 142 | 143 | public function has($id) 144 | { 145 | if (empty($id)) 146 | { 147 | throw new \InvalidArgumentException('ID cannot be empty'); 148 | } 149 | 150 | return file_exists($this->getFilepath($id)); 151 | } 152 | 153 | /** 154 | * @inheritdoc 155 | * @throws \RuntimeException if file does not exist or delete operation fails 156 | */ 157 | public function delete($id) 158 | { 159 | if (!$this->has($id)) 160 | { 161 | throw new \RuntimeException("File does not exist ($id)"); 162 | } 163 | 164 | $path = $this->getFilepath($id); 165 | 166 | ErrorHandler::start(); 167 | $flag = unlink($path); 168 | ErrorHandler::stop(true); 169 | 170 | if (!$flag) 171 | { 172 | throw new \RuntimeException("Could not delete file ($path)"); 173 | } 174 | 175 | $this->purge($path); 176 | } 177 | 178 | /** 179 | * Remove all empty directories starting from the leaf node and moving all the way up to the upload path. 180 | * 181 | * @param string $path 182 | */ 183 | protected function purge($path) 184 | { 185 | $directory = pathinfo($path, PATHINFO_DIRNAME); 186 | while ($directory != $this->uploadPath) 187 | { 188 | if (count(glob($directory .'/*'))) 189 | { 190 | break; 191 | } 192 | 193 | rmdir($directory); 194 | $directory = dirname($directory); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/RdnUpload/Container.php: -------------------------------------------------------------------------------- 1 | tempDir = ini_get('upload_tmp_dir') ?: sys_get_temp_dir(); 29 | if ($adapter) 30 | { 31 | $this->setAdapter($adapter); 32 | } 33 | if ($tempDir) 34 | { 35 | $this->tempDir = $tempDir; 36 | } 37 | } 38 | 39 | public function setAdapter(AdapterInterface $adapter) 40 | { 41 | $this->adapter = $adapter; 42 | } 43 | 44 | public function getAdapter() 45 | { 46 | return $this->adapter; 47 | } 48 | 49 | public function upload($input) 50 | { 51 | if (is_array($input)) 52 | { 53 | $input = new File\Input($input); 54 | } 55 | 56 | if (!$input instanceof FileInterface) 57 | { 58 | throw new \InvalidArgumentException(sprintf( 59 | "Input must be an object implementing %s" 60 | , __NAMESPACE__ .'\File\FileInterface' 61 | )); 62 | } 63 | 64 | $id = $this->generateSequence($input->getBasename()); 65 | if ($this->has($id)) 66 | { 67 | return $this->upload($input); 68 | } 69 | 70 | $this->adapter->upload($id, $input); 71 | 72 | return $id; 73 | } 74 | 75 | public function get($id) 76 | { 77 | if (empty($id)) 78 | { 79 | throw new \InvalidArgumentException('ID cannot be empty'); 80 | } 81 | 82 | return $this->adapter->get($id); 83 | } 84 | 85 | public function download($id) 86 | { 87 | if (empty($id)) 88 | { 89 | throw new \InvalidArgumentException('ID cannot be empty'); 90 | } 91 | 92 | $object = $this->adapter->get($id); 93 | $output = new File\File($object->getBasename(), $this->generateTempPath()); 94 | 95 | $this->adapter->download($id, $output); 96 | 97 | return $output; 98 | } 99 | 100 | public function has($id) 101 | { 102 | if (empty($id)) 103 | { 104 | return false; 105 | } 106 | 107 | return $this->adapter->has($id); 108 | } 109 | 110 | public function delete($id) 111 | { 112 | if (empty($id)) 113 | { 114 | throw new \InvalidArgumentException('ID cannot be empty'); 115 | } 116 | 117 | return $this->adapter->delete($id); 118 | } 119 | 120 | /** 121 | * Generate a unique/random sequence. 122 | * 123 | * @param string $basename 124 | * 125 | * @return string 126 | */ 127 | protected function generateSequence($basename) 128 | { 129 | $basename = $this->sanitize($basename); 130 | 131 | $hash = hash('sha1', uniqid('', true) . mt_rand() . $basename); 132 | $prefix = implode('/', str_split(substr($hash, 0, 3))); 133 | 134 | return $prefix .'/'. $hash .'/'. $basename; 135 | } 136 | 137 | /** 138 | * @return string 139 | */ 140 | protected function generateTempPath() 141 | { 142 | return tempnam($this->tempDir, 'rdnu'); 143 | } 144 | 145 | /** 146 | * @param string $basename 147 | * 148 | * @return string 149 | */ 150 | protected function sanitize($basename) 151 | { 152 | $filename = pathinfo($basename, PATHINFO_FILENAME); 153 | $extension = pathinfo($basename, PATHINFO_EXTENSION); 154 | 155 | $filename = str_replace(' ', '-', $filename); 156 | $filename = preg_replace('/[^a-z0-9\.\-\_]/i', '', $filename); 157 | $filename = substr($filename, 0, 100); 158 | 159 | $filename = trim($filename, '-_.') ?: 'no-name'; 160 | 161 | return $filename . ($extension ? '.'. $extension : ''); 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/RdnUpload/ContainerInterface.php: -------------------------------------------------------------------------------- 1 | file = $file; 27 | $this->publicPath = $publicPath; 28 | } 29 | 30 | /** 31 | * Get the public URL to the file. 32 | * 33 | * @return string 34 | */ 35 | public function getPublicUrl() 36 | { 37 | return $this->publicPath .'/'. $this->file->getName(); 38 | } 39 | 40 | public function getContent() 41 | { 42 | return $this->file->getContent(); 43 | } 44 | 45 | public function getBasename() 46 | { 47 | return basename($this->file->getName()); 48 | } 49 | 50 | public function getExtension() 51 | { 52 | return pathinfo($this->file->getName(), PATHINFO_EXTENSION); 53 | } 54 | 55 | public function getContentLength() 56 | { 57 | return $this->file->getSize(); 58 | } 59 | 60 | public function getContentType() 61 | { 62 | $info = new \finfo(FILEINFO_MIME_TYPE); 63 | return $info->buffer($this->getContent()); 64 | } 65 | 66 | public function getLastModified() 67 | { 68 | return new DateTime('@'. $this->file->getMtime()); 69 | } 70 | 71 | public function __toString() 72 | { 73 | return $this->getPublicUrl(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/RdnUpload/ContainerObject/LazyResponse.php: -------------------------------------------------------------------------------- 1 | file = $file; 18 | } 19 | 20 | public function __toString() 21 | { 22 | return $this->file->getContent(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/RdnUpload/ContainerObject/Local.php: -------------------------------------------------------------------------------- 1 | path = $path; 29 | $this->publicUrl = $publicUrl; 30 | } 31 | 32 | public function getPath() 33 | { 34 | return $this->path; 35 | } 36 | 37 | public function getPublicUrl() 38 | { 39 | return $this->publicUrl; 40 | } 41 | 42 | public function getContent() 43 | { 44 | ob_start(); 45 | readfile($this->path); 46 | return ob_get_clean(); 47 | } 48 | 49 | public function getBasename() 50 | { 51 | return pathinfo($this->path, PATHINFO_BASENAME); 52 | } 53 | 54 | public function getExtension() 55 | { 56 | return pathinfo($this->path, PATHINFO_EXTENSION); 57 | } 58 | 59 | public function getContentLength() 60 | { 61 | return filesize($this->path); 62 | } 63 | 64 | public function getContentType() 65 | { 66 | $info = new \finfo(FILEINFO_MIME_TYPE); 67 | return $info->file($this->path); 68 | } 69 | 70 | public function getLastModified() 71 | { 72 | $time = new DateTime; 73 | $time->setTimestamp(filemtime($this->path)); 74 | return $time; 75 | } 76 | 77 | public function __toString() 78 | { 79 | return $this->publicUrl; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/RdnUpload/ContainerObject/ObjectInterface.php: -------------------------------------------------------------------------------- 1 | container = $container; 25 | } 26 | 27 | /** 28 | * Return the file object if an id is given, otherwise return the plugin itself. 29 | * 30 | * @param string $id 31 | * 32 | * @return self|ObjectInterface 33 | */ 34 | public function __invoke($id = null) 35 | { 36 | if (func_num_args() == 0) 37 | { 38 | return $this; 39 | } 40 | 41 | return $this->get($id); 42 | } 43 | 44 | /** 45 | * Return a response object for a given file. 46 | * 47 | * @param string $id 48 | * @param string $filename Filename to use instead of the file's actual basename 49 | * @param bool $forceDownload Whether to force client to download the file 50 | * 51 | * @return HttpResponse 52 | */ 53 | public function getResponse($id, $filename = null, $forceDownload = false) 54 | { 55 | $object = $this->container->get($id); 56 | 57 | if ($filename === null) 58 | { 59 | $filename = $object->getBasename(); 60 | } 61 | 62 | if (!pathinfo($filename, PATHINFO_EXTENSION) && $object->getExtension()) 63 | { 64 | $filename .= '.'. $object->getExtension(); 65 | } 66 | 67 | /** @var HttpResponse $response */ 68 | $response = $this->controller->getResponse(); 69 | 70 | $response->setContent(new LazyResponse($object)); 71 | $response->getHeaders()->addHeaders(array( 72 | 'Content-Type' => $object->getContentType() ?: 'application/octet-stream', 73 | 'Content-Disposition' => ($forceDownload ? 'attachment' : 'inline') .';filename="'. str_replace('"', '\\"', $filename) .'"', 74 | 'Content-Transfer-Encoding' => 'binary', 75 | 'Expires' => '-1 year', 76 | 'Cache-Control' => 'must-revalidate', 77 | 'Pragma' => 'public', 78 | 'Content-Length' => $object->getContentLength(), 79 | )); 80 | 81 | return $response; 82 | } 83 | 84 | /** 85 | * Generate a thumbnail from an object and return the ID of the new object. 86 | * 87 | * @param string $id 88 | * @param int $width 89 | * @param int $height 90 | * 91 | * @return string 92 | */ 93 | public function generateThumbnail($id, $width, $height = null) 94 | { 95 | if ($height === null) 96 | { 97 | $height = $width; 98 | } 99 | 100 | $temp = $this->container->download($id); 101 | 102 | $img = new \Imagick($temp->getPath()); 103 | $img->cropThumbnailImage($width, $height); 104 | $img->writeImage(); 105 | 106 | return $this->container->upload($temp); 107 | } 108 | 109 | /** 110 | * Get the upload container. 111 | * 112 | * @return ContainerInterface 113 | */ 114 | public function getContainer() 115 | { 116 | return $this->container; 117 | } 118 | 119 | /** 120 | * @param array|FileInterface $input 121 | * 122 | * @return string 123 | */ 124 | public function upload($input) 125 | { 126 | return $this->container->upload($input); 127 | } 128 | 129 | /** 130 | * @param string $id 131 | * 132 | * @return ObjectInterface 133 | */ 134 | public function get($id) 135 | { 136 | return $this->container->get($id); 137 | } 138 | 139 | /** 140 | * @param string $id 141 | * 142 | * @return FileInterface 143 | */ 144 | public function download($id) 145 | { 146 | return $this->container->download($id); 147 | } 148 | 149 | /** 150 | * @param string $id 151 | * 152 | * @return boolean 153 | */ 154 | public function has($id) 155 | { 156 | return $this->container->has($id); 157 | } 158 | 159 | /** 160 | * @param string $id 161 | */ 162 | public function delete($id) 163 | { 164 | return $this->container->delete($id); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/RdnUpload/Factory/Adapter/AdapterManager.php: -------------------------------------------------------------------------------- 1 | get('Config'); 14 | $adapters = new Adapter\AdapterManager($services, $config['rdn_upload_adapters']); 15 | 16 | return $adapters; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/RdnUpload/Factory/Adapter/Filesystem.php: -------------------------------------------------------------------------------- 1 | get('Config'); 14 | $config = $config['rdn_upload_adapters']['configs']['Gaufrette']; 15 | 16 | if (!isset($config['filesystem'])) 17 | { 18 | throw new \InvalidArgumentException("You must set the 'rdn_upload_adapters.configs.Gaufrette.filesystem' configuration option to a valid Gaufrette filesystem service name"); 19 | } 20 | 21 | $filesystem = $services->get($config['filesystem']); 22 | return new Adapter\Gaufrette($filesystem, $config['public_path']); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/RdnUpload/Factory/Adapter/Local.php: -------------------------------------------------------------------------------- 1 | get('Config'); 14 | if (isset($config['rdn_upload_adapters']['configs']['Filesystem'])) 15 | { 16 | $legacyConfig = $config['rdn_upload_adapters']['configs']['Filesystem']; 17 | } 18 | else 19 | { 20 | $legacyConfig = array(); 21 | } 22 | $config = array_replace_recursive($legacyConfig, $config['rdn_upload_adapters']['configs']['Local']); 23 | 24 | if ($services->has('ViewHelperManager')) 25 | { 26 | $helpers = $services->get('ViewHelperManager'); 27 | $config['public_path'] = call_user_func($helpers->get('BasePath'), $config['public_path']); 28 | } 29 | 30 | return new Adapter\Local($config['upload_path'], $config['public_path']); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/RdnUpload/Factory/Container.php: -------------------------------------------------------------------------------- 1 | get('Config'); 14 | $config = $config['rdn_upload']; 15 | 16 | $adapters = $services->get('RdnUpload\Adapter\AdapterManager'); 17 | $adapter = $adapters->get($config['adapter']); 18 | 19 | return new RdnUpload\Container($adapter, $config['temp_dir']); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/RdnUpload/Factory/Controller/Plugin/Uploads.php: -------------------------------------------------------------------------------- 1 | get('RdnUpload\Container'); 15 | return new Plugin\Uploads($uploads); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/RdnUpload/Factory/View/Helper/Uploads.php: -------------------------------------------------------------------------------- 1 | get('RdnUpload\Container'); 16 | return new Helper\Uploads($uploads); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/RdnUpload/File/File.php: -------------------------------------------------------------------------------- 1 | basename = $basename; 29 | $this->path = $path; 30 | } 31 | 32 | public function getBasename() 33 | { 34 | return $this->basename; 35 | } 36 | 37 | public function getFilename() 38 | { 39 | return pathinfo($this->basename, PATHINFO_FILENAME); 40 | } 41 | 42 | public function getExtension() 43 | { 44 | return pathinfo($this->basename, PATHINFO_EXTENSION); 45 | } 46 | 47 | public function getPath() 48 | { 49 | return $this->path; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/RdnUpload/File/FileInterface.php: -------------------------------------------------------------------------------- 1 | container = $container; 29 | $this->preUpload = $preUpload; 30 | } 31 | 32 | public function extract($value) 33 | { 34 | $this->value = $value; 35 | return $value; 36 | } 37 | 38 | public function hydrate($data) 39 | { 40 | if ($data['error'] === UPLOAD_ERR_OK) 41 | { 42 | $input = new File\Input($data); 43 | if ($this->preUpload) 44 | { 45 | $input = call_user_func($this->preUpload, $input); 46 | } 47 | 48 | $newValue = $this->container->upload($input); 49 | 50 | if ($this->value && $this->container->has($this->value)) 51 | { 52 | try 53 | { 54 | $this->container->delete($this->value); 55 | } 56 | catch (\Exception $ex) 57 | { 58 | $this->container->delete($newValue); 59 | throw $ex; 60 | } 61 | } 62 | 63 | $this->value = $newValue; 64 | return $this->value; 65 | } 66 | 67 | return $this->value; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/RdnUpload/Module.php: -------------------------------------------------------------------------------- 1 | getPath() .'/config/module.config.php'; 10 | } 11 | 12 | public function getPath() 13 | { 14 | return dirname(dirname(__DIR__)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/RdnUpload/View/Helper/Uploads.php: -------------------------------------------------------------------------------- 1 | container = $container; 22 | } 23 | 24 | /** 25 | * @param string $id 26 | * 27 | * @return self|ObjectInterface 28 | */ 29 | public function __invoke($id = null) 30 | { 31 | if (func_num_args() == 0) 32 | { 33 | return $this; 34 | } 35 | 36 | return $this->get($id); 37 | } 38 | 39 | /** 40 | * Get the upload container. 41 | * 42 | * @return ContainerInterface 43 | */ 44 | public function getContainer() 45 | { 46 | return $this->container; 47 | } 48 | 49 | /** 50 | * @param string $id 51 | * 52 | * @return ObjectInterface 53 | */ 54 | public function get($id) 55 | { 56 | return $this->container->get($id); 57 | } 58 | 59 | /** 60 | * @param string $id 61 | * 62 | * @return boolean 63 | */ 64 | public function has($id) 65 | { 66 | return $this->container->has($id); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tests/RdnUpload/Adapter/LocalTest.php: -------------------------------------------------------------------------------- 1 | vfs = vfsStream::setup('root', null, array( 26 | 'uploads' => array( 27 | 'baz.txt' => 'Sample baz content', 28 | ), 29 | 'tmp' => array( 30 | 'foo.txt' => 'Sample foo content', 31 | ), 32 | )); 33 | $this->adapter = new Local(vfsStream::url('root/uploads'), '/files'); 34 | } 35 | 36 | public function testUpload() 37 | { 38 | $this->assertTrue($this->vfs->getChild('tmp')->hasChild('foo.txt')); 39 | $this->assertFalse($this->vfs->getChild('uploads')->hasChild('bar')); 40 | 41 | $input = new File('foo.txt', vfsStream::url('root/tmp/foo.txt')); 42 | $this->adapter->upload('bar/foo.txt', $input); 43 | 44 | $this->assertFalse($this->vfs->hasChild('foo.txt')); 45 | $this->assertTrue($this->vfs->getChild('uploads')->hasChild('bar')); 46 | $this->assertTrue($this->vfs->getChild('uploads')->getChild('bar')->hasChild('foo.txt')); 47 | } 48 | 49 | public function testGet() 50 | { 51 | $this->assertTrue($this->vfs->getChild('uploads')->hasChild('baz.txt')); 52 | 53 | $object = $this->adapter->get('baz.txt'); 54 | 55 | $this->assertInstanceOf('RdnUpload\ContainerObject\ObjectInterface', $object); 56 | $this->assertEquals(vfsStream::url('root/uploads/baz.txt'), $object->getPath()); 57 | $this->assertEquals('text/plain', $object->getContentType()); 58 | } 59 | 60 | public function testDownload() 61 | { 62 | $this->assertFalse($this->vfs->getChild('tmp')->hasChild('pot.txt')); 63 | 64 | $output = new File('pot.txt', vfsStream::url('root/tmp/pot.txt')); 65 | $this->adapter->download('baz.txt', $output); 66 | 67 | $this->assertTrue($this->vfs->getChild('tmp')->hasChild('pot.txt')); 68 | } 69 | 70 | public function testHas() 71 | { 72 | $this->assertTrue($this->vfs->getChild('uploads')->hasChild('baz.txt')); 73 | $this->assertTrue($this->adapter->has('baz.txt')); 74 | } 75 | 76 | public function testDelete() 77 | { 78 | $this->assertTrue($this->vfs->getChild('uploads')->hasChild('baz.txt')); 79 | $this->adapter->delete('baz.txt'); 80 | $this->assertFalse($this->vfs->getChild('uploads')->hasChild('baz.txt')); 81 | } 82 | 83 | public function testFactory() 84 | { 85 | $config = include __DIR__ .'/../../../config/module.config.php'; 86 | 87 | $config['rdn_upload_adapters']['configs']['Local']['upload_path'] = vfsStream::url('root/uploads'); 88 | 89 | $services = new ServiceManager($config['service_manager']); 90 | $services->setService('Config', $config); 91 | 92 | $adapters = $services->get('RdnUpload\Adapter\AdapterManager'); 93 | $adapter = $adapters->get('Local'); 94 | 95 | $this->assertInstanceOf('RdnUpload\Adapter\Local', $adapter); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /tests/RdnUpload/ContainerTest.php: -------------------------------------------------------------------------------- 1 | vfs = vfsStream::setup('root', null, array( 27 | 'uploads' => array( 28 | 'baz.txt' => 'Sample baz content', 29 | ), 30 | 'tmp' => array( 31 | 'foo.txt' => 'Sample foo content', 32 | ), 33 | )); 34 | $adapter = new Local(vfsStream::url('root/uploads'), '/files'); 35 | $this->uploads = new Container($adapter, vfsStream::url('root/tmp')); 36 | } 37 | 38 | public function testHas() 39 | { 40 | $this->assertTrue($this->uploads->has('baz.txt')); 41 | } 42 | 43 | public function testGet() 44 | { 45 | $object = $this->uploads->get('baz.txt'); 46 | $this->assertEquals('baz.txt', $object->getBasename()); 47 | $this->assertEquals('Sample baz content', $object->getContent()); 48 | } 49 | 50 | public function testUpload() 51 | { 52 | $this->assertTrue($this->vfs->getChild('tmp')->hasChild('foo.txt')); 53 | $this->assertFalse($this->vfs->getChild('uploads')->hasChild('bar')); 54 | 55 | $input = new File('foo.txt', vfsStream::url('root/tmp/foo.txt')); 56 | $id = $this->uploads->upload($input); 57 | 58 | $this->assertFalse($this->vfs->hasChild('foo.txt')); 59 | 60 | $parts = explode('/', $id); 61 | $leaf = array_pop($parts); 62 | $child = $this->vfs->getChild('uploads'); 63 | foreach ($parts as $part) 64 | { 65 | $this->assertTrue($child->hasChild($part)); 66 | $child = $child->getChild($part); 67 | } 68 | 69 | $this->assertTrue($child->hasChild($leaf)); 70 | } 71 | 72 | public function testFactory() 73 | { 74 | $config = include __DIR__ .'/../../config/module.config.php'; 75 | 76 | $config['rdn_upload_adapters']['configs']['Local']['upload_path'] = vfsStream::url('root/uploads'); 77 | 78 | $services = new ServiceManager($config['service_manager']); 79 | $services->setService('Config', $config); 80 | 81 | $uploads = $services->get('RdnUpload\Container'); 82 | $this->assertInstanceOf('RdnUpload\Container', $uploads); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('RdnUpload\\', __DIR__); 7 | --------------------------------------------------------------------------------