Completed
Push — master ( d13e3c...db85f8 )
by Frank
02:26
created

WebDAVAdapter::isDirectory()   A

Complexity

Conditions 2
Paths 2

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 2
eloc 2
nc 2
nop 1
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\HttpException;
16
17
class WebDAVAdapter extends AbstractAdapter
18
{
19
    use StreamedReadingTrait;
20
    use StreamedCopyTrait {
21
        StreamedCopyTrait::copy as streamedCopy;
22
    }
23
    use NotSupportingVisibilityTrait;
24
25
    private static $metadataFields = [
26
        '{DAV:}displayname',
27
        '{DAV:}getcontentlength',
28
        '{DAV:}getcontenttype',
29
        '{DAV:}getlastmodified',
30
        '{DAV:}iscollection',
31
    ];
32
33
    /**
34
     * @var array
35
     */
36
    protected static $resultMap = [
37
        '{DAV:}getcontentlength' => 'size',
38
        '{DAV:}getcontenttype' => 'mimetype',
39
        'content-length' => 'size',
40
        'content-type' => 'mimetype',
41
    ];
42
43
    /**
44
     * @var Client
45
     */
46
    protected $client;
47
48
    /**
49
     * @var bool
50
     */
51
    protected $useStreamedCopy = true;
52
53
    /**
54
     * Constructor.
55
     *
56
     * @param Client $client
57
     * @param string $prefix
58
     * @param bool $useStreamedCopy
59
     */
60
    public function __construct(Client $client, $prefix = null, $useStreamedCopy = true)
61
    {
62
        $this->client = $client;
63
        $this->setPathPrefix($prefix);
64
        $this->setUseStreamedCopy($useStreamedCopy);
65
    }
66
67
    /**
68
     * url encode a path
69
     *
70
     * @param string $path
71
     *
72
     * @return string
73
     */
74
    protected function encodePath($path)
75
	{
76
		$a = explode('/', $path);
77
		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...
78
			$a[$i] = rawurlencode($a[$i]);
79
		}
80
		return implode('/', $a);
81
	}
82
83
    /**
84
     * {@inheritdoc}
85
     */
86
    public function getMetadata($path)
87
    {
88
        $location = $this->applyPathPrefix($this->encodePath($path));
89
90
        try {
91
            $result = $this->client->propFind($location, self::$metadataFields);
92
93
            return $this->normalizeObject($result, $path);
94
        } catch (Exception $e) {
95
            return false;
96
        } catch (HttpException $e) {
97
            return false;
98
        }
99
    }
100
101
    /**
102
     * {@inheritdoc}
103
     */
104
    public function has($path)
105
    {
106
        return $this->getMetadata($path);
107
    }
108
109
    /**
110
     * {@inheritdoc}
111
     */
112
    public function read($path)
113
    {
114
        $location = $this->applyPathPrefix($this->encodePath($path));
115
116
        try {
117
            $response = $this->client->request('GET', $location);
118
119
            if ($response['statusCode'] !== 200) {
120
                return false;
121
            }
122
123
            return array_merge([
124
                'contents' => $response['body'],
125
                'timestamp' => strtotime(is_array($response['headers']['last-modified'])
126
                    ? current($response['headers']['last-modified'])
127
                    : $response['headers']['last-modified']),
128
                'path' => $path,
129
            ], Util::map($response['headers'], static::$resultMap));
130
        } catch (Exception $e) {
131
            return false;
132
        }
133
    }
134
135
    /**
136
     * {@inheritdoc}
137
     */
138
    public function write($path, $contents, Config $config)
139
    {
140
        if (!$this->createDir(Util::dirname($path), $config)) {
141
            return false;
142
        }
143
144
        $location = $this->applyPathPrefix($this->encodePath($path));
145
        $response = $this->client->request('PUT', $location, $contents);
146
147
        if ($response['statusCode'] >= 400) {
148
            return false;
149
        }
150
151
        $result = compact('path', 'contents');
152
153
        if ($config->get('visibility')) {
154
            throw new LogicException(__CLASS__.' does not support visibility settings.');
155
        }
156
157
        return $result;
158
    }
159
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function writeStream($path, $resource, Config $config)
164
    {
165
        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...
166
    }
167
168
    /**
169
     * {@inheritdoc}
170
     */
171
    public function update($path, $contents, Config $config)
172
    {
173
        return $this->write($path, $contents, $config);
174
    }
175
176
    /**
177
     * {@inheritdoc}
178
     */
179
    public function updateStream($path, $resource, Config $config)
180
    {
181
        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...
182
    }
183
184
    /**
185
     * {@inheritdoc}
186
     */
187
    public function rename($path, $newpath)
188
    {
189
        $location = $this->applyPathPrefix($this->encodePath($path));
190
        $newLocation = $this->applyPathPrefix($this->encodePath($newpath));
191
192
        try {
193
            $response = $this->client->request('MOVE', '/'.ltrim($location, '/'), null, [
194
                'Destination' => '/'.ltrim($newLocation, '/'),
195
            ]);
196
197
            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
198
                return true;
199
            }
200
        } catch (NotFound $e) {
201
            // Would have returned false here, but would be redundant
202
        }
203
204
        return false;
205
    }
206
207
    /**
208
     * {@inheritdoc}
209
     */
210
    public function copy($path, $newpath)
211
    {
212
        if ($this->useStreamedCopy === true) {
213
            return $this->streamedCopy($path, $newpath);
214
        } else {
215
            return $this->nativeCopy($path, $newpath);
216
        }
217
    }
218
219
    /**
220
     * {@inheritdoc}
221
     */
222
    public function delete($path)
223
    {
224
        $location = $this->applyPathPrefix($this->encodePath($path));
225
226
        try {
227
            $this->client->request('DELETE', $location);
228
229
            return true;
230
        } catch (NotFound $e) {
231
            return false;
232
        }
233
    }
234
235
    /**
236
     * {@inheritdoc}
237
     */
238
    public function createDir($path, Config $config)
239
    {
240
        $encodedPath = $this->encodePath($path);
241
        $path = trim($path, '/');
242
243
        $result = compact('path') + ['type' => 'dir'];
244
245
        if (Util::normalizeDirname($path) === '' || $this->has($path)) {
246
            return $result;
247
        }
248
249
        $directories = explode('/', $path);
250
        if (count($directories) > 1) {
251
            $parentDirectories = array_splice($directories, 0, count($directories) - 1);
252
            if (!$this->createDir(implode('/', $parentDirectories), $config)) {
253
                return false;
254
            }
255
        }
256
257
        $location = $this->applyPathPrefix($encodedPath);
258
        $response = $this->client->request('MKCOL', $location);
259
260
        if ($response['statusCode'] !== 201) {
261
            return false;
262
        }
263
264
        return $result;
265
    }
266
267
    /**
268
     * {@inheritdoc}
269
     */
270
    public function deleteDir($dirname)
271
    {
272
        return $this->delete($dirname);
273
    }
274
275
    /**
276
     * {@inheritdoc}
277
     */
278
    public function listContents($directory = '', $recursive = false)
279
    {
280
        $location = $this->applyPathPrefix($this->encodePath($directory));
281
        $response = $this->client->propFind($location . '/', self::$metadataFields, 1);
282
283
        array_shift($response);
284
        $result = [];
285
286
        foreach ($response as $path => $object) {
287
            $path = urldecode($this->removePathPrefix($path));
288
            $object = $this->normalizeObject($object, $path);
289
            $result[] = $object;
290
291
            if ($recursive && $object['type'] === 'dir') {
292
                $result = array_merge($result, $this->listContents($object['path'], true));
293
            }
294
        }
295
296
        return $result;
297
    }
298
299
    /**
300
     * {@inheritdoc}
301
     */
302
    public function getSize($path)
303
    {
304
        return $this->getMetadata($path);
305
    }
306
307
    /**
308
     * {@inheritdoc}
309
     */
310
    public function getTimestamp($path)
311
    {
312
        return $this->getMetadata($path);
313
    }
314
315
    /**
316
     * {@inheritdoc}
317
     */
318
    public function getMimetype($path)
319
    {
320
        return $this->getMetadata($path);
321
    }
322
323
    /**
324
     * @return boolean
325
     */
326
    public function getUseStreamedCopy()
327
    {
328
        return $this->useStreamedCopy;
329
    }
330
331
    /**
332
     * @param boolean $useStreamedCopy
333
     */
334
    public function setUseStreamedCopy($useStreamedCopy)
335
    {
336
        $this->useStreamedCopy = (bool)$useStreamedCopy;
337
    }
338
339
    /**
340
     * Copy a file through WebDav COPY method.
341
     *
342
     * @param string $path
343
     * @param string $newPath
344
     *
345
     * @return bool
346
     */
347
    protected function nativeCopy($path, $newPath)
348
    {
349
        if (!$this->createDir(Util::dirname($newPath), new Config())) {
350
            return false;
351
        }
352
353
        $location = $this->applyPathPrefix($this->encodePath($path));
354
        $newLocation = $this->applyPathPrefix($this->encodePath($newPath));
355
356
        try {
357
            $destination = $this->client->getAbsoluteUrl($newLocation);
358
            $response = $this->client->request('COPY', '/'.ltrim($location, '/'), null, [
359
                'Destination' => $destination,
360
            ]);
361
362
            if ($response['statusCode'] >= 200 && $response['statusCode'] < 300) {
363
                return true;
364
            }
365
        } catch (NotFound $e) {
366
            // Would have returned false here, but would be redundant
367
        }
368
369
        return false;
370
    }
371
372
    /**
373
     * Normalise a WebDAV repsonse object.
374
     *
375
     * @param array  $object
376
     * @param string $path
377
     *
378
     * @return array
379
     */
380
    protected function normalizeObject(array $object, $path)
381
    {
382
        if ($this->isDirectory($object)) {
383
            return ['type' => 'dir', 'path' => trim($path, '/')];
384
        }
385
386
        $result = Util::map($object, static::$resultMap);
387
388
        if (isset($object['{DAV:}getlastmodified'])) {
389
            $result['timestamp'] = strtotime($object['{DAV:}getlastmodified']);
390
        }
391
392
        $result['type'] = 'file';
393
        $result['path'] = trim($path, '/');
394
395
        return $result;
396
    }
397
398
    private function isDirectory(array $object)
399
    {
400
        return isset($object['{DAV:}iscollection']) && $object['{DAV:}iscollection'] === '1';
401
    }
402
}
403