├── .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 |
--------------------------------------------------------------------------------