├── .gitignore
├── .scrutinizer.yml
├── .travis.yml
├── composer.json
├── phpunit.hhvm.xml
├── phpunit.php
├── phpunit.xml
├── readme.md
├── src
└── DropboxAdapter.php
└── tests
└── DropboxAdapterTests.php
/.gitignore:
--------------------------------------------------------------------------------
1 | vendor
2 | bin
3 | composer.lock
4 | coverage
5 | coverage.xml
--------------------------------------------------------------------------------
/.scrutinizer.yml:
--------------------------------------------------------------------------------
1 | filter:
2 | paths: [src/*]
3 | checks:
4 | php:
5 | code_rating: true
6 | remove_extra_empty_lines: true
7 | remove_php_closing_tag: true
8 | remove_trailing_whitespace: true
9 | fix_use_statements:
10 | remove_unused: true
11 | preserve_multiple: false
12 | preserve_blanklines: true
13 | order_alphabetically: true
14 | fix_php_opening_tag: true
15 | fix_linefeed: true
16 | fix_line_ending: true
17 | fix_identation_4spaces: true
18 | fix_doc_comments: true
19 | tools:
20 | external_code_coverage:
21 | timeout: 600
22 | runs: 3
23 | php_code_coverage: false
24 | php_code_sniffer:
25 | config:
26 | standard: PSR2
27 | filter:
28 | paths: ['src']
29 | php_loc:
30 | enabled: true
31 | excluded_dirs: [vendor, spec, stubs]
32 | php_cpd:
33 | enabled: true
34 | excluded_dirs: [vendor, spec, stubs]
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 5.4
5 | - 5.5
6 | - 5.6
7 | - hhvm
8 |
9 | install:
10 | - travis_retry composer install --no-interaction --prefer-source
11 |
12 | script:
13 | - bin/phpunit
14 |
15 | after_script:
16 | - wget https://scrutinizer-ci.com/ocular.phar
17 | - bash -c 'if [ "$TRAVIS_PHP_VERSION" != "hhvm" ]; then php ocular.phar code-coverage:upload --format=php-clover coverage.xml; fi;'
18 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "league/flysystem-dropbox",
3 | "description": "Flysystem adapter for Dropbox",
4 | "license": "MIT",
5 | "authors": [
6 | {
7 | "name": "Frank de Jonge",
8 | "email": "info@frenky.net"
9 | }
10 | ],
11 | "require": {
12 | "php": ">=5.4.0",
13 | "league/flysystem": "~1.0",
14 | "dropbox/dropbox-sdk": "~1.1"
15 | },
16 | "require-dev": {
17 | "phpunit/phpunit": "~4.8"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "League\\Flysystem\\Dropbox\\": "src/"
22 | }
23 | },
24 | "config": {
25 | "bin-dir": "bin"
26 | },
27 | "min-stability": "dev",
28 | "prefer-stable": true,
29 | "extra": {
30 | "branch-alias": {
31 | "dev-master": "1.0-dev"
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/phpunit.hhvm.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | ./tests/
17 |
18 |
19 |
20 |
21 | ./src/
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/phpunit.php:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 | ./tests/
17 |
18 |
19 |
20 |
21 | ./src/
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Flysystem Adapter for Dropbox
2 |
3 | [](https://twitter.com/frankdejonge)
4 | [](https://travis-ci.org/thephpleague/flysystem-dropbox)
5 | [](https://scrutinizer-ci.com/g/thephpleague/flysystem-dropbox/code-structure)
6 | [](https://scrutinizer-ci.com/g/thephpleague/flysystem-dropbox)
7 | [](LICENSE)
8 | [](https://packagist.org/packages/league/flysystem-dropbox)
9 | [](https://packagist.org/packages/league/flysystem-dropbox)
10 |
11 |
12 | ## Installation
13 |
14 | ```bash
15 | composer require league/flysystem-dropbox
16 | ```
17 |
18 | ## Usage
19 |
20 | Visit https://www.dropbox.com/developers/apps and get your "App secret".
21 |
22 | You can also generate OAuth access token for testing using the Dropbox App Console without going through the authorization flow.
23 |
24 | ~~~ php
25 | use League\Flysystem\Dropbox\DropboxAdapter;
26 | use League\Flysystem\Filesystem;
27 | use Dropbox\Client;
28 |
29 | $client = new Client($accessToken, $appSecret);
30 | $adapter = new DropboxAdapter($client, [$prefix]);
31 |
32 | $filesystem = new Filesystem($adapter);
33 | ~~~
34 |
--------------------------------------------------------------------------------
/src/DropboxAdapter.php:
--------------------------------------------------------------------------------
1 | 'size',
23 | 'mime_type' => 'mimetype',
24 | ];
25 |
26 | /**
27 | * @var Client
28 | */
29 | protected $client;
30 |
31 | /**
32 | * Constructor.
33 | *
34 | * @param Client $client
35 | * @param string $prefix
36 | */
37 | public function __construct(Client $client, $prefix = null)
38 | {
39 | $this->client = $client;
40 | $this->setPathPrefix($prefix);
41 | }
42 |
43 | /**
44 | * {@inheritdoc}
45 | */
46 | public function has($path)
47 | {
48 | return $this->getMetadata($path);
49 | }
50 |
51 | /**
52 | * {@inheritdoc}
53 | */
54 | public function write($path, $contents, Config $config)
55 | {
56 | return $this->upload($path, $contents, WriteMode::add());
57 | }
58 |
59 | /**
60 | * {@inheritdoc}
61 | */
62 | public function writeStream($path, $resource, Config $config)
63 | {
64 | return $this->uploadStream($path, $resource, WriteMode::add());
65 | }
66 |
67 | /**
68 | * {@inheritdoc}
69 | */
70 | public function update($path, $contents, Config $config)
71 | {
72 | return $this->upload($path, $contents, WriteMode::force());
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | public function updateStream($path, $resource, Config $config)
79 | {
80 | return $this->uploadStream($path, $resource, WriteMode::force());
81 | }
82 |
83 | /**
84 | * {@inheritdoc}
85 | */
86 | public function read($path)
87 | {
88 | if ( ! $object = $this->readStream($path)) {
89 | return false;
90 | }
91 |
92 | $object['contents'] = stream_get_contents($object['stream']);
93 | fclose($object['stream']);
94 | unset($object['stream']);
95 |
96 | return $object;
97 | }
98 |
99 | /**
100 | * {@inheritdoc}
101 | */
102 | public function readStream($path)
103 | {
104 | $stream = fopen('php://temp', 'w+');
105 | $location = $this->applyPathPrefix($path);
106 |
107 | if ( ! $this->client->getFile($location, $stream)) {
108 | fclose($stream);
109 |
110 | return false;
111 | }
112 |
113 | rewind($stream);
114 |
115 | return compact('stream');
116 | }
117 |
118 | /**
119 | * {@inheritdoc}
120 | */
121 | public function rename($path, $newpath)
122 | {
123 | $path = $this->applyPathPrefix($path);
124 | $newpath = $this->applyPathPrefix($newpath);
125 |
126 | try {
127 | $this->client->move($path, $newpath);
128 | } catch (Exception $e) {
129 | return false;
130 | }
131 |
132 | return true;
133 | }
134 |
135 | /**
136 | * {@inheritdoc}
137 | */
138 | public function copy($path, $newpath)
139 | {
140 | $path = $this->applyPathPrefix($path);
141 | $newpath = $this->applyPathPrefix($newpath);
142 |
143 | try {
144 | $this->client->copy($path, $newpath);
145 | } catch (Exception $e) {
146 | return false;
147 | }
148 |
149 | return true;
150 | }
151 |
152 | /**
153 | * {@inheritdoc}
154 | */
155 | public function delete($path)
156 | {
157 | $location = $this->applyPathPrefix($path);
158 | $result = $this->client->delete($location);
159 |
160 | return isset($result['is_deleted']) ? $result['is_deleted'] : false;
161 | }
162 |
163 | /**
164 | * {@inheritdoc}
165 | */
166 | public function deleteDir($path)
167 | {
168 | return $this->delete($path);
169 | }
170 |
171 | /**
172 | * {@inheritdoc}
173 | */
174 | public function createDir($path, Config $config)
175 | {
176 | $location = $this->applyPathPrefix($path);
177 | $result = $this->client->createFolder($location);
178 |
179 | if ($result === null) {
180 | return false;
181 | }
182 |
183 | return $this->normalizeResponse($result, $path);
184 | }
185 |
186 | /**
187 | * {@inheritdoc}
188 | */
189 | public function getMetadata($path)
190 | {
191 | $location = $this->applyPathPrefix($path);
192 |
193 | try {
194 | $object = $this->client->getMetadata($location);
195 | } catch(Exception_BadResponseCode $e) {
196 | if ($e->getStatusCode() === 301) {
197 | return false;
198 | }
199 |
200 | throw $e;
201 | }
202 |
203 | if ( ! $object) {
204 | return false;
205 | }
206 |
207 | return $this->normalizeResponse($object, $path);
208 | }
209 |
210 | /**
211 | * {@inheritdoc}
212 | */
213 | public function getMimetype($path)
214 | {
215 | return $this->getMetadata($path);
216 | }
217 |
218 | /**
219 | * {@inheritdoc}
220 | */
221 | public function getSize($path)
222 | {
223 | return $this->getMetadata($path);
224 | }
225 |
226 | /**
227 | * {@inheritdoc}
228 | */
229 | public function getTimestamp($path)
230 | {
231 | return $this->getMetadata($path);
232 | }
233 |
234 | /**
235 | * {@inheritdoc}
236 | */
237 | public function getClient()
238 | {
239 | return $this->client;
240 | }
241 |
242 | /**
243 | * {@inheritdoc}
244 | */
245 | public function listContents($directory = '', $recursive = false)
246 | {
247 | $listing = [];
248 | $directory = trim($directory, '/.');
249 | $location = $this->applyPathPrefix($directory);
250 |
251 | if ( ! $result = $this->client->getMetadataWithChildren($location)) {
252 | return [];
253 | }
254 |
255 | foreach ($result['contents'] as $object) {
256 | $path = $this->removePathPrefix($object['path']);
257 | $listing[] = $this->normalizeResponse($object, $path);
258 |
259 | if ($recursive && $object['is_dir']) {
260 | $listing = array_merge($listing, $this->listContents($path, true));
261 | }
262 | }
263 |
264 | return $listing;
265 | }
266 |
267 | /**
268 | * Apply the path prefix.
269 | *
270 | * @param string $path
271 | *
272 | * @return string prefixed path
273 | */
274 | public function applyPathPrefix($path)
275 | {
276 |
277 | $path = parent::applyPathPrefix($path);
278 |
279 | return '/' . ltrim(rtrim($path, '/'), '/');
280 | }
281 |
282 | /**
283 | * Do the actual upload of a string file.
284 | *
285 | * @param string $path
286 | * @param string $contents
287 | * @param WriteMode $mode
288 | *
289 | * @return array|false file metadata
290 | */
291 | protected function upload($path, $contents, WriteMode $mode)
292 | {
293 | $location = $this->applyPathPrefix($path);
294 |
295 | if ( ! $result = $this->client->uploadFileFromString($location, $mode, $contents)) {
296 | return false;
297 | }
298 |
299 | return $this->normalizeResponse($result, $path);
300 | }
301 |
302 | /**
303 | * Do the actual upload of a file resource.
304 | *
305 | * @param string $path
306 | * @param resource $resource
307 | * @param WriteMode $mode
308 | *
309 | * @return array|false file metadata
310 | */
311 | protected function uploadStream($path, $resource, WriteMode $mode)
312 | {
313 | $location = $this->applyPathPrefix($path);
314 |
315 | // If size is zero, consider it unknown.
316 | $size = Util::getStreamSize($resource) ?: null;
317 |
318 | if ( ! $result = $this->client->uploadFile($location, $mode, $resource, $size)) {
319 | return false;
320 | }
321 |
322 | return $this->normalizeResponse($result, $path);
323 | }
324 |
325 | /**
326 | * Normalize a Dropbox response.
327 | *
328 | * @param array $response
329 | *
330 | * @return array
331 | */
332 | protected function normalizeResponse(array $response)
333 | {
334 | $result = ['path' => ltrim($this->removePathPrefix($response['path']), '/')];
335 |
336 | if (isset($response['modified'])) {
337 | $result['timestamp'] = strtotime($response['modified']);
338 | }
339 |
340 | $result = array_merge($result, Util::map($response, static::$resultMap));
341 | $result['type'] = $response['is_dir'] ? 'dir' : 'file';
342 |
343 | return $result;
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/tests/DropboxAdapterTests.php:
--------------------------------------------------------------------------------
1 | prophesize('Dropbox\Client');
14 |
15 | return [
16 | [new Dropbox($mock->reveal(), 'prefix'), $mock],
17 | ];
18 | }
19 |
20 | /**
21 | * @dataProvider dropboxProvider
22 | */
23 | public function testWrite($adapter, $mock)
24 | {
25 | $mock->uploadFileFromString(Argument::any(), Argument::any(), Argument::any())->willReturn([
26 | 'is_dir' => false,
27 | 'modified' => '10 September 2000',
28 | 'path' => '/prefix/something',
29 | ], false);
30 |
31 | $result = $adapter->write('something', 'contents', new Config());
32 | $this->assertInternalType('array', $result);
33 | $this->assertArrayHasKey('type', $result);
34 | $this->assertEquals('file', $result['type']);
35 | $this->assertFalse($adapter->write('something', 'something', new Config()));
36 | }
37 |
38 | /**
39 | * @dataProvider dropboxProvider
40 | */
41 | public function testUpdate(Dropbox $adapter, $mock)
42 | {
43 | $mock->uploadFileFromString(Argument::any(), Argument::any(), Argument::any())->willReturn([
44 | 'is_dir' => false,
45 | 'modified' => '10 September 2000',
46 | 'path' => '/prefix/something'
47 | ], false);
48 |
49 | $result = $adapter->update('something', 'contents', new Config());
50 | $this->assertInternalType('array', $result);
51 | $this->assertArrayHasKey('type', $result);
52 | $this->assertEquals('file', $result['type']);
53 | $this->assertFalse($adapter->update('something', 'something', new Config()));
54 | }
55 |
56 | /**
57 | * @dataProvider dropboxProvider
58 | */
59 | public function testWriteStream(Dropbox $adapter, $mock)
60 | {
61 | $mock->uploadFile(Argument::any(), Argument::any(), Argument::any(), null)->willReturn([
62 | 'is_dir' => false,
63 | 'modified' => '10 September 2000',
64 | 'path' => '/prefix/something'
65 | ], false);
66 |
67 | $result = $adapter->writeStream('something', tmpfile(), new Config());
68 | $this->assertInternalType('array', $result);
69 | $this->assertArrayHasKey('type', $result);
70 | $this->assertEquals('file', $result['type']);
71 | $this->assertFalse($adapter->writeStream('something', tmpfile(), new Config()));
72 | }
73 |
74 | /**
75 | * @dataProvider dropboxProvider
76 | */
77 | public function testUpdateStream(Dropbox $adapter, $mock)
78 | {
79 | $mock->uploadFile(Argument::any(), Argument::any(), Argument::any(), null)->willReturn([
80 | 'is_dir' => false,
81 | 'modified' => '10 September 2000',
82 | 'path' => '/prefix/something'
83 | ], false);
84 |
85 | $result = $adapter->updateStream('something', tmpfile(), new Config());
86 | $this->assertInternalType('array', $result);
87 | $this->assertArrayHasKey('type', $result);
88 | $this->assertEquals('file', $result['type']);
89 | $this->assertFalse($adapter->updateStream('something', tmpfile(), new Config()));
90 | }
91 |
92 | public function metadataProvider()
93 | {
94 | return [
95 | ['getMetadata'],
96 | ['getMimetype'],
97 | ['getTimestamp'],
98 | ['getSize'],
99 | ['has'],
100 | ];
101 | }
102 |
103 | /**
104 | * @dataProvider metadataProvider
105 | */
106 | public function testMetadataCalls($method)
107 | {
108 | $mock = $this->prophesize('Dropbox\Client');
109 | $mock->getMetadata('/one')->willReturn([
110 | 'is_dir' => false,
111 | 'modified' => '10 September 2000',
112 | 'path' => '/one'
113 | ], false);
114 |
115 | $adapter = new Dropbox($mock->reveal());
116 | $this->assertInternalType('array', $adapter->{$method}('one', 'two'));
117 | $this->assertFalse($adapter->{$method}('one', 'two'));
118 | }
119 |
120 | public function testMetadataFileWasMovedFailure()
121 | {
122 | $mock = $this->prophesize('Dropbox\Client');
123 | $mock->getMetadata('/one')->willThrow(new Exception_BadResponseCode('ERROR', 301));
124 |
125 | $adapter = new Dropbox($mock->reveal());
126 | $this->assertFalse($adapter->has('one'));
127 | }
128 |
129 | public function testMetadataFileWasNotMovedFailure()
130 | {
131 | $this->setExpectedException('Dropbox\Exception_BadResponseCode');
132 | $mock = $this->prophesize('Dropbox\Client');
133 | $mock->getMetadata('/one')->willThrow(new Exception_BadResponseCode('ERROR', 500));
134 |
135 | (new Dropbox($mock->reveal()))->has('one');
136 | }
137 |
138 | /**
139 | * @dataProvider dropboxProvider
140 | */
141 | public function testRead($adapter, $mock)
142 | {
143 | $stream = tmpfile();
144 | fwrite($stream, 'something');
145 | $mock->getFile(Argument::any(), Argument::any())->willReturn($stream, false);
146 | $this->assertInternalType('array', $adapter->read('something'));
147 | $this->assertFalse($adapter->read('something'));
148 | fclose($stream);
149 | }
150 |
151 | /**
152 | * @dataProvider dropboxProvider
153 | */
154 | public function testReadStream(Dropbox $adapter, $mock)
155 | {
156 | $stream = tmpfile();
157 | fwrite($stream, 'something');
158 | $mock->getFile(Argument::any(), Argument::any())->willReturn($stream, false);
159 | $this->assertInternalType('array', $adapter->readStream('something'));
160 | $this->assertFalse($adapter->readStream('something'));
161 | fclose($stream);
162 | }
163 |
164 | /**
165 | * @dataProvider dropboxProvider
166 | */
167 | public function testDelete(Dropbox $adapter, $mock)
168 | {
169 | $mock->delete('/prefix/something')->willReturn(['is_deleted' => true]);
170 | $this->assertTrue($adapter->delete('something'));
171 | $this->assertTrue($adapter->deleteDir('something'));
172 | }
173 |
174 | /**
175 | * @dataProvider dropboxProvider
176 | */
177 | public function testCreateDir(Dropbox $adapter, $mock)
178 | {
179 | $mock->createFolder('/prefix/fail/please')->willReturn(null);
180 | $mock->createFolder('/prefix/pass/please')->willReturn([
181 | 'is_dir' => true,
182 | 'path' => '/prefix/pass/please',
183 | ]);
184 | $this->assertFalse($adapter->createDir('fail/please', new Config()));
185 | $expected = ['path' => 'pass/please', 'type' => 'dir'];
186 | $this->assertEquals($expected, $adapter->createDir('pass/please', new Config()));
187 | }
188 |
189 | /**
190 | * @dataProvider dropboxProvider
191 | */
192 | public function testListContents(Dropbox $adapter, $mock)
193 | {
194 | $mock->getMetadataWithChildren(Argument::type('string'))->willReturn(
195 | ['contents' => [
196 | ['is_dir' => true, 'path' => 'dirname'],
197 | ]],
198 | ['contents' => [
199 | ['is_dir' => false, 'path' => 'dirname/file'],
200 | ]],
201 | false
202 | );
203 |
204 | $result = $adapter->listContents('', true);
205 | $this->assertCount(2, $result);
206 | $this->assertEquals([], $adapter->listContents('', false));
207 | }
208 |
209 | /**
210 | * @dataProvider dropboxProvider
211 | */
212 | public function testRename($adapter, $mock)
213 | {
214 | $mock->move(Argument::type('string'), Argument::type('string'))->willReturn(['is_dir' => false, 'path' => 'something']);
215 | $this->assertTrue($adapter->rename('something', 'something'));
216 | }
217 |
218 | /**
219 | * @dataProvider dropboxProvider
220 | */
221 | public function testRenameFail($adapter, $mock)
222 | {
223 | $mock->move('/prefix/something', '/prefix/something')->willThrow(new \Dropbox\Exception('Message'));
224 |
225 | $this->assertFalse($adapter->rename('something', 'something'));
226 | }
227 |
228 | /**
229 | * @dataProvider dropboxProvider
230 | */
231 | public function testCopy($adapter, $mock)
232 | {
233 | $mock->copy(Argument::type('string'), Argument::type('string'))->willReturn(['is_dir' => false, 'path' => 'something']);
234 | $this->assertTrue($adapter->copy('something', 'something'));
235 | }
236 |
237 | /**
238 | * @dataProvider dropboxProvider
239 | */
240 | public function testCopyFail($adapter, $mock)
241 | {
242 | $mock->copy(Argument::any(), Argument::any())->willThrow(new \Dropbox\Exception('Message'));
243 |
244 | $this->assertFalse($adapter->copy('something', 'something'));
245 | }
246 |
247 | /**
248 | * @dataProvider dropboxProvider
249 | */
250 | public function testGetClient($adapter)
251 | {
252 | $this->assertInstanceOf('Dropbox\Client', $adapter->getClient());
253 | }
254 | }
255 |
--------------------------------------------------------------------------------