Completed
Push — master ( 5fb6f5...d13e3c )
by Frank
11s
created

WebDAVAdapter::writeStream()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 3
1
<?php
2
3
namespace League\Flysystem\WebDAV;
4
5
use League\Flysystem\Adapter\AbstractAdapter;
6
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
7
use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait;
8
use League\Flysystem\Adapter\Polyfill\StreamedReadingTrait;
9
use League\Flysystem\Config;
10
use League\Flysystem\Util;
11
use LogicException;
12
use Sabre\DAV\Client;
13
use Sabre\DAV\Exception;
14
use Sabre\DAV\Exception\NotFound;
15
use Sabre\HTTP\ClientHttpException;
16
use Sabre\HTTP\HttpException;
17
18
class WebDAVAdapter extends AbstractAdapter
19
{
20
    use StreamedReadingTrait;
21
    use StreamedCopyTrait {
22
        StreamedCopyTrait::copy as streamedCopy;
23
    }
24
    use NotSupportingVisibilityTrait;
25
26
    /**
27
     * @var array
28
     */
29
    protected static $resultMap = [
30
        '{DAV:}getcontentlength' => 'size',
31
        '{DAV:}getcontenttype' => 'mimetype',
32
        'content-length' => 'size',
33
        'content-type' => 'mimetype',
34
    ];
35
36
    /**
37
     * @var Client
38
     */
39
    protected $client;
40
41
    /**
42
     * @var bool
43
     */
44
    protected $useStreamedCopy = true;
45
46
    /**
47
     * Constructor.
48
     *
49
     * @param Client $client
50
     * @param string $prefix
51
     * @param bool $useStreamedCopy
52
     */
53
    public function __construct(Client $client, $prefix = null, $useStreamedCopy = true)
54
    {
55
        $this->client = $client;
56
        $this->setPathPrefix($prefix);
57
        $this->setUseStreamedCopy($useStreamedCopy);
58
    }
59
60
    /**
61
     * url encode a path
62
     *
63
     * @param string $path
64
     *
65
     * @return string
66
     */
67
    protected function encodePath($path)
68
	{
69
		$a = explode('/', $path);
70
		for ($i=0; $i<count($a); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
71
			$a[$i] = rawurlencode($a[$i]);
72
		}
73
		return implode('/', $a);
74
	}
75
76
    /**
77
     * {@inheritdoc}
78
     */
79
    public function getMetadata($path)
80
    {
81
        $location = $this->applyPathPrefix($this->encodePath($path));
82
83
        try {
84
            $result = $this->client->propFind($location, [
85
                '{DAV:}displayname',
86
                '{DAV:}getcontentlength',
87
                '{DAV:}getcontenttype',
88
                '{DAV:}getlastmodified',
89
            ]);
90
91
            return $this->normalizeObject($result, $path);
92
        } catch (Exception $e) {
93
            return false;
94
        } catch (HttpException $e) {
95
            return false;
96
        }
97
    }
98
99
    /**
100
     * {@inheritdoc}
101
     */
102
    public function has($path)
103
    {
104
        return $this->getMetadata($path);
105
    }
106
107
    /**
108
     * {@inheritdoc}
109
     */
110
    public function read($path)
111
    {
112
        $location = $this->applyPathPrefix($this->encodePath($path));
113
114
        try {
115
            $response = $this->client->request('GET', $location);
116
117
            if ($response['statusCode'] !== 200) {
118
                return false;
119
            }
120
121
            return array_merge([
122
                'contents' => $response['body'],
123
                'timestamp' => strtotime(is_array($response['headers']['last-modified'])
124
                    ? current($response['headers']['last-modified'])
125
                    : $response['headers']['last-modified']),
126
                'path' => $path,
127
            ], Util::map($response['headers'], static::$resultMap));
128
        } catch (Exception $e) {
129
            return false;
130
        }
131
    }
132
133
    /**
134
     * {@inheritdoc}
135
     */
136
    public function write($path, $contents, Config $config)
137
    {
138
        if (!$this->createDir(Util::dirname($path), $config)) {
139
            return false;
140
        }
141
142
        $location = $this->applyPathPrefix($this->encodePath($path));
143
        $response = $this->client->request('PUT', $location, $contents);
144
145
        if ($response['statusCode'] >= 400) {
146
            return false;
147
        }
148
149
        $result = compact('path', 'contents');
150
151
        if ($config->get('visibility')) {
152
            throw new LogicException(__CLASS__.' does not support visibility settings.');
153
        }
154
155
        return $result;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161
    public function writeStream($path, $resource, Config $config)
162
    {
163
        return $this->write($path, $resource, $config);
0 ignored issues
show
Documentation introduced by
$resource is of type resource, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
164
    }
165
166
    /**
167
     * {@inheritdoc}
168
     */
169
    public function update($path, $contents, Config $config)
170
    {
171
        return $this->write($path, $contents, $config);
172
    }
173
174
    /**
175
     * {@inheritdoc}
176
     */
177
    public function updateStream($path, $resource, Config $config)
178
    {
179
        return $this->update($path, $resource, $config);
0 ignored issues
show
Documentation introduced by
$resource is of type resource, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
180
    }
181
182
    /**
183
     * {@inheritdoc}
184
     */
185
    public function rename($path, $newpath)
186
    {
187
        $location = $this->applyPathPrefix($this->encodePath($path));
188
        $newLocation = $this->applyPathPrefix($this->encodePath($newpath));
189
190
        try {
191
            $response = $this->client->request('MOVE', '/'.ltrim($location, '/'), null, [
192
                'Destination' => '/'.ltrim($newLocation, '/'),
193
            ]);
194
195
            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
196
                return true;
197
            }
198
        } catch (NotFound $e) {
199
            // Would have returned false here, but would be redundant
200
        }
201
202
        return false;
203
    }
204
205
    /**
206
     * {@inheritdoc}
207
     */
208
    public function copy($path, $newpath)
209
    {
210
        if ($this->useStreamedCopy === true) {
211
            return $this->streamedCopy($path, $newpath);
212
        } else {
213
            return $this->nativeCopy($path, $newpath);
214
        }
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function delete($path)
221
    {
222
        $location = $this->applyPathPrefix($this->encodePath($path));
223
224
        try {
225
            $this->client->request('DELETE', $location);
226
227
            return true;
228
        } catch (NotFound $e) {
229
            return false;
230
        }
231
    }
232
233
    /**
234
     * {@inheritdoc}
235
     */
236
    public function createDir($path, Config $config)
237
    {
238
        $encodedPath = $this->encodePath($path);
239
        $path = trim($path, '/');
240
241
        $result = compact('path') + ['type' => 'dir'];
242
243
        if (Util::normalizeDirname($path) === '' || $this->has($path)) {
244
            return $result;
245
        }
246
247
        $directories = explode('/', $path);
248
        if (count($directories) > 1) {
249
            $parentDirectories = array_splice($directories, 0, count($directories) - 1);
250
            if (!$this->createDir(implode('/', $parentDirectories), $config)) {
251
                return false;
252
            }
253
        }
254
255
        $location = $this->applyPathPrefix($encodedPath);
256
        $response = $this->client->request('MKCOL', $location);
257
258
        if ($response['statusCode'] !== 201) {
259
            return false;
260
        }
261
262
        return $result;
263
    }
264
265
    /**
266
     * {@inheritdoc}
267
     */
268
    public function deleteDir($dirname)
269
    {
270
        return $this->delete($dirname);
271
    }
272
273
    /**
274
     * {@inheritdoc}
275
     */
276
    public function listContents($directory = '', $recursive = false)
277
    {
278
        $location = $this->applyPathPrefix($this->encodePath($directory));
279
        $response = $this->client->propFind($location . '/', [
280
            '{DAV:}displayname',
281
            '{DAV:}getcontentlength',
282
            '{DAV:}getcontenttype',
283
            '{DAV:}getlastmodified',
284
        ], 1);
285
286
        array_shift($response);
287
        $result = [];
288
289
        foreach ($response as $path => $object) {
290
            $path = urldecode($this->removePathPrefix($path));
291
            $object = $this->normalizeObject($object, $path);
292
            $result[] = $object;
293
294
            if ($recursive && $object['type'] === 'dir') {
295
                $result = array_merge($result, $this->listContents($object['path'], true));
296
            }
297
        }
298
299
        return $result;
300
    }
301
302
    /**
303
     * {@inheritdoc}
304
     */
305
    public function getSize($path)
306
    {
307
        return $this->getMetadata($path);
308
    }
309
310
    /**
311
     * {@inheritdoc}
312
     */
313
    public function getTimestamp($path)
314
    {
315
        return $this->getMetadata($path);
316
    }
317
318
    /**
319
     * {@inheritdoc}
320
     */
321
    public function getMimetype($path)
322
    {
323
        return $this->getMetadata($path);
324
    }
325
326
    /**
327
     * @return boolean
328
     */
329
    public function getUseStreamedCopy()
330
    {
331
        return $this->useStreamedCopy;
332
    }
333
334
    /**
335
     * @param boolean $useStreamedCopy
336
     */
337
    public function setUseStreamedCopy($useStreamedCopy)
338
    {
339
        $this->useStreamedCopy = (bool)$useStreamedCopy;
340
    }
341
342
    /**
343
     * Copy a file through WebDav COPY method.
344
     *
345
     * @param string $path
346
     * @param string $newPath
347
     *
348
     * @return bool
349
     */
350
    protected function nativeCopy($path, $newPath)
351
    {
352
        if (!$this->createDir(Util::dirname($newPath), new Config())) {
353
            return false;
354
        }
355
356
        $location = $this->applyPathPrefix($this->encodePath($path));
357
        $newLocation = $this->applyPathPrefix($this->encodePath($newPath));
358
359
        try {
360
            $destination = $this->client->getAbsoluteUrl($newLocation);
361
            $response = $this->client->request('COPY', '/'.ltrim($location, '/'), null, [
362
                'Destination' => $destination,
363
            ]);
364
365
            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
366
                return true;
367
            }
368
        } catch (NotFound $e) {
369
            // Would have returned false here, but would be redundant
370
        }
371
372
        return false;
373
    }
374
375
    /**
376
     * Normalise a WebDAV repsonse object.
377
     *
378
     * @param array  $object
379
     * @param string $path
380
     *
381
     * @return array
382
     */
383
    protected function normalizeObject(array $object, $path)
384
    {
385
        if (! isset($object['{DAV:}getcontentlength']) or $object['{DAV:}getcontentlength'] == "") {
386
            return ['type' => 'dir', 'path' => trim($path, '/')];
387
        }
388
389
        $result = Util::map($object, static::$resultMap);
390
391
        if (isset($object['{DAV:}getlastmodified'])) {
392
            $result['timestamp'] = strtotime($object['{DAV:}getlastmodified']);
393
        }
394
395
        $result['type'] = 'file';
396
        $result['path'] = trim($path, '/');
397
398
        return $result;
399
    }
400
}
401