Passed
Push — master ( fd689d...aa7312 )
by Luo
03:43
created

OssAdapter::update()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 3
dl 0
loc 3
ccs 0
cts 3
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the iidestiny/flysystem-oss.
5
 *
6
 * (c) iidestiny <[email protected]>
7
 *
8
 * This source file is subject to the MIT license that is bundled
9
 * with this source code in the file LICENSE.
10
 */
11
12
namespace Iidestiny\Flysystem\Oss;
13
14
use Iidestiny\Flysystem\Oss\Traits\SignatureTrait;
15
use League\Flysystem\AdapterInterface;
16
use League\Flysystem\Adapter\AbstractAdapter;
17
use League\Flysystem\Adapter\Polyfill\NotSupportingVisibilityTrait;
18
use League\Flysystem\Config;
19
use OSS\Core\OssException;
20
use OSS\OssClient;
21
22
/**
23
 * Class OssAdapter.
24
 *
25
 * @author iidestiny <[email protected]>
26
 */
27
class OssAdapter extends AbstractAdapter
28
{
29
    use NotSupportingVisibilityTrait, SignatureTrait;
30
31
    /**
32
     * @var
33
     */
34
    protected $accessKeyId;
35
36
    /**
37
     * @var
38
     */
39
    protected $accessKeySecret;
40
41
    /**
42
     * @var
43
     */
44
    protected $endpoint;
45
46
    /**
47
     * @var
48
     */
49
    protected $bucket;
50
51
    /**
52
     * @var
53
     */
54
    protected $isCName;
55
56
    /**
57
     * @var OssClient
58
     */
59
    protected $client;
60
61
    /**
62
     * @var array|mixed[]
63
     */
64
    protected $params;
65
66
    /**
67
     * @var bool
68
     */
69
    protected $useSSL = false;
70
71
    /**
72
     * OssAdapter constructor.
73
     *
74
     * @param       $accessKeyId
75
     * @param       $accessKeySecret
76
     * @param       $endpoint
77
     * @param       $bucket
78
     * @param bool  $isCName
79
     * @param mixed ...$params
80
     *
81
     * @throws OssException
82
     */
83
    public function __construct($accessKeyId, $accessKeySecret, $endpoint, $bucket, $isCName = false, ...$params)
84
    {
85
        $this->accessKeyId = $accessKeyId;
86
        $this->accessKeySecret = $accessKeySecret;
87
        $this->endpoint = $endpoint;
88
        $this->bucket = $bucket;
89
        $this->isCName = $isCName;
90
        $this->params = $params;
91
        $this->initClient();
92
        $this->checkEndpoint();
93
    }
94
95
    /**
96
     * init oss client.
97
     *
98
     * @throws OssException
99
     */
100
    protected function initClient()
101
    {
102
        if (empty($this->client)) {
103
            $this->client = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint, $this->isCName, ...$this->params);
0 ignored issues
show
Bug introduced by
$this->params is expanded, but the parameter $securityToken of OSS\OssClient::__construct() does not expect variable arguments. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

103
            $this->client = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint, $this->isCName, /** @scrutinizer ignore-type */ ...$this->params);
Loading history...
104
        }
105
    }
106
107
    /**
108
     * oss 直传配置.
109
     *
110
     * @param string $prefix
111
     * @param null   $callBackUrl
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $callBackUrl is correct as it would always require null to be passed?
Loading history...
112
     * @param int    $expire
113
     *
114
     * @return false|string
115
     *
116
     * @throws \Exception
117
     */
118
    public function signatureConfig($prefix = '', $callBackUrl = null, $expire = 30)
119
    {
120
        if (!empty($prefix)) {
121
            $prefix = ltrim($prefix, '/');
122
        }
123
124
        $callbackParam = [
125
            'callbackUrl' => $callBackUrl,
126
            'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
127
            'callbackBodyType' => 'application/x-www-form-urlencoded',
128
        ];
129
        $callbackString = json_encode($callbackParam);
130
        $base64_callback_body = base64_encode($callbackString);
131
132
        $now = time();
133
        $end = $now + $expire;
134
        $expiration = $this->gmt_iso8601($end);
135
136
        // 最大文件大小.用户可以自己设置
137
        $condition = [
138
            0 => 'content-length-range',
139
            1 => 0,
140
            2 => 1048576000,
141
        ];
142
        $conditions[] = $condition;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$conditions was never initialized. Although not strictly required by PHP, it is generally a good practice to add $conditions = array(); before regardless.
Loading history...
143
144
        $start = [
145
            0 => 'starts-with',
146
            1 => '$key',
147
            2 => $prefix,
148
        ];
149
        $conditions[] = $start;
150
151
        $arr = [
152
            'expiration' => $expiration,
153
            'conditions' => $conditions,
154
        ];
155
        $policy = json_encode($arr);
156
        $base64_policy = base64_encode($policy);
157
        $string_to_sign = $base64_policy;
158
        $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $this->accessKeySecret, true));
159
160
        $response = [];
161
        $response['accessid'] = $this->accessKeyId;
162
        $response['host'] = $this->normalizeHost();
163
        $response['policy'] = $base64_policy;
164
        $response['signature'] = $signature;
165
        $response['expire'] = $end;
166
        $response['callback'] = $base64_callback_body;
167
        $response['dir'] = $prefix;  // 这个参数是设置用户上传文件时指定的前缀。
168
169
        return json_encode($response);
170
    }
171
172
    /**
173
     * sign url.
174
     *
175
     * @param $path
176
     * @param $timeout
177
     *
178
     * @return bool|string
179
     */
180
    public function signUrl($path, $timeout)
181
    {
182
        $path = $this->applyPathPrefix($path);
183
184
        try {
185
            $path = $this->client->signUrl($this->bucket, $path, $timeout);
186
        } catch (OssException $exception) {
187
            return false;
188
        }
189
190
        return $path;
191
    }
192
193
    /**
194
     * write a file.
195
     *
196
     * @param string $path
197
     * @param string $contents
198
     * @param Config $config
199
     *
200
     * @return array|bool|false
201
     */
202
    public function write($path, $contents, Config $config)
203
    {
204
        $path = $this->applyPathPrefix($path);
205
206
        $options = [];
207
208
        if ($config->has('options')) {
209
            $options = $config->get('options');
210
        }
211
212
        $this->client->putObject($this->bucket, $path, $contents, $options);
213
214
        return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::write() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
215
    }
216
217
    /**
218
     * Write a new file using a stream.
219
     *
220
     * @param string   $path
221
     * @param resource $resource
222
     * @param Config   $config
223
     *
224
     * @return array|bool|false
225
     */
226
    public function writeStream($path, $resource, Config $config)
227
    {
228
        $contents = stream_get_contents($resource);
229
230
        return $this->write($path, $contents, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->write($path, $contents, $config) returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::writeStream() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
231
    }
232
233
    /**
234
     * Update a file.
235
     *
236
     * @param string $path
237
     * @param string $contents
238
     * @param Config $config
239
     *
240
     * @return array|bool|false
241
     */
242
    public function update($path, $contents, Config $config)
243
    {
244
        return $this->write($path, $contents, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->write($path, $contents, $config) returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::update() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
245
    }
246
247
    /**
248
     * Update a file using a stream.
249
     *
250
     * @param string   $path
251
     * @param resource $resource
252
     * @param Config   $config
253
     *
254
     * @return array|bool|false
255
     */
256
    public function updateStream($path, $resource, Config $config)
257
    {
258
        return $this->writeStream($path, $resource, $config);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->writeStrea...th, $resource, $config) returns the type true which is incompatible with the return type mandated by League\Flysystem\AdapterInterface::updateStream() of array|false.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
259
    }
260
261
    /**
262
     * rename a file.
263
     *
264
     * @param string $path
265
     * @param string $newpath
266
     *
267
     * @return bool
268
     *
269
     * @throws OssException
270
     */
271
    public function rename($path, $newpath)
272
    {
273
        if (!$this->copy($path, $newpath)) {
274
            return false;
275
        }
276
277
        return $this->delete($path);
278
    }
279
280
    /**
281
     * copy a file.
282
     *
283
     * @param string $path
284
     * @param string $newpath
285
     *
286
     * @return bool
287
     */
288
    public function copy($path, $newpath)
289
    {
290
        $path = $this->applyPathPrefix($path);
291
        $newpath = $this->applyPathPrefix($newpath);
292
293
        try {
294
            $this->client->copyObject($this->bucket, $path, $this->bucket, $newpath);
295
        } catch (OssException $exception) {
296
            return false;
297
        }
298
299
        return true;
300
    }
301
302
    /**
303
     * delete a file.
304
     *
305
     * @param string $path
306
     *
307
     * @return bool
308
     *
309
     * @throws OssException
310
     */
311
    public function delete($path)
312
    {
313
        $path = $this->applyPathPrefix($path);
314
315
        try {
316
            $this->client->deleteObject($this->bucket, $path);
317
        } catch (OssException $ossException) {
318
            return false;
319
        }
320
321
        return !$this->has($path);
322
    }
323
324
    /**
325
     * Delete a directory.
326
     *
327
     * @param string $dirname
328
     *
329
     * @return bool
330
     */
331
    public function deleteDir($dirname)
332
    {
333
        return true;
334
    }
335
336
    /**
337
     * create a directory.
338
     *
339
     * @param string $dirname
340
     * @param Config $config
341
     *
342
     * @return array|false
343
     */
344
    public function createDir($dirname, Config $config)
345
    {
346
        return ['path' => $dirname, 'type' => 'dir'];
347
    }
348
349
    /**
350
     * visibility.
351
     *
352
     * @param string $path
353
     * @param string $visibility
354
     *
355
     * @return array|bool|false
356
     */
357
    public function setVisibility($path, $visibility)
358
    {
359
        $object = $this->applyPathPrefix($path);
360
        $acl = (AdapterInterface::VISIBILITY_PUBLIC === $visibility) ? OssClient::OSS_ACL_TYPE_PUBLIC_READ : OssClient::OSS_ACL_TYPE_PRIVATE;
361
362
        try {
363
            $this->client->putObjectAcl($this->bucket, $object, $acl);
364
        } catch (OssException $exception) {
365
            return false;
366
        }
367
368
        return compact('visibility');
369
    }
370
371
    /**
372
     * Check whether a file exists.
373
     *
374
     * @param string $path
375
     *
376
     * @return array|bool|null
377
     */
378
    public function has($path)
379
    {
380
        $path = $this->applyPathPrefix($path);
381
382
        return $this->client->doesObjectExist($this->bucket, $path);
383
    }
384
385
    /**
386
     * Get resource url.
387
     *
388
     * @param string $path
389
     *
390
     * @return string
391
     */
392
    public function getUrl($path)
393
    {
394
        return $this->normalizeHost().ltrim($path, '/');
395
    }
396
397
    /**
398
     * read a file.
399
     *
400
     * @param string $path
401
     *
402
     * @return array|bool|false
403
     */
404
    public function read($path)
405
    {
406
        try {
407
            $contents = $this->getObject($path);
408
        } catch (OssException $exception) {
409
            return false;
410
        }
411
412
        return compact('contents', 'path');
413
    }
414
415
    /**
416
     * read a file stream.
417
     *
418
     * @param string $path
419
     *
420
     * @return array|bool|false
421
     */
422
    public function readStream($path)
423
    {
424
        try {
425
            $stream = $this->getObject($path);
426
        } catch (OssException $exception) {
427
            return false;
428
        }
429
430
        return compact('stream', 'path');
431
    }
432
433
    /**
434
     * Lists all files in the directory.
435
     *
436
     * @param string $directory
437
     * @param bool   $recursive
438
     *
439
     * @return array
440
     *
441
     * @throws OssException
442
     */
443
    public function listContents($directory = '', $recursive = false)
444
    {
445
        $list = [];
446
447
        $result = $this->listDirObjects($directory, true);
448
449
        if (!empty($result['objects'])) {
450
            foreach ($result['objects'] as $files) {
451
                if (!$fileInfo = $this->normalizeFileInfo($files)) {
452
                    continue;
453
                }
454
455
                $list[] = $fileInfo;
456
            }
457
        }
458
459
        return $list;
460
    }
461
462
    /**
463
     * get meta data.
464
     *
465
     * @param string $path
466
     *
467
     * @return array|bool|false
468
     */
469
    public function getMetadata($path)
470
    {
471
        $path = $this->applyPathPrefix($path);
472
473
        try {
474
            $metadata = $this->client->getObjectMeta($this->bucket, $path);
475
        } catch (OssException $exception) {
476
            return false;
477
        }
478
479
        return $metadata;
480
    }
481
482
    /**
483
     * get the size of file.
484
     *
485
     * @param string $path
486
     *
487
     * @return array|false
488
     */
489
    public function getSize($path)
490
    {
491
        return $this->normalizeFileInfo(['Key' => $path]);
492
    }
493
494
    /**
495
     * get mime type.
496
     *
497
     * @param string $path
498
     *
499
     * @return array|bool|false
500
     */
501
    public function getMimetype($path)
502
    {
503
        if (!$fileInfo = $this->normalizeFileInfo(['Key' => $path])) {
504
            return false;
505
        }
506
507
        return ['mimetype' => $fileInfo['type']];
508
    }
509
510
    /**
511
     * get timestamp.
512
     *
513
     * @param string $path
514
     *
515
     * @return array|false
516
     */
517
    public function getTimestamp($path)
518
    {
519
        return $this->normalizeFileInfo(['Key' => $path]);
520
    }
521
522
    /**
523
     * normalize Host.
524
     *
525
     * @return string
526
     */
527
    protected function normalizeHost()
528
    {
529
530
        if ($this->isCName) {
531
            $domain = $this->endpoint;
532
        } else {
533
            $domain = $this->bucket.'.'.$this->endpoint;
534
        }
535
536
        if ($this->useSSL) {
537
            $domain = "https://{$domain}";
538
        } else {
539
            $domain = "http://{$domain}";
540
        }
541
542
        return rtrim($domain, '/').'/';
543
    }
544
545
    /**
546
     * Check the endpoint to see if SSL can be used
547
     */
548
    protected function checkEndpoint()
549
    {
550
        if (strpos($this->endpoint, 'http://') === 0) {
551
            $this->endpoint = substr($this->endpoint, strlen('http://'));
552
            $this->useSSL   = false;
553
        } elseif (strpos($this->endpoint, 'https://') === 0) {
554
            $this->endpoint = substr($this->endpoint, strlen('https://'));
555
            $this->useSSL   = true;
556
        }
557
    }
558
559
    /**
560
     * Read an object from the OssClient.
561
     *
562
     * @param $path
563
     *
564
     * @return string
565
     */
566
    protected function getObject($path)
567
    {
568
        $path = $this->applyPathPrefix($path);
569
570
        return $this->client->getObject($this->bucket, $path);
571
    }
572
573
    /**
574
     * File list core method.
575
     *
576
     * @param string $dirname
577
     * @param bool   $recursive
578
     *
579
     * @return array
580
     *
581
     * @throws OssException
582
     */
583
    public function listDirObjects($dirname = '', $recursive = false)
584
    {
585
        $delimiter = '/';
586
        $nextMarker = '';
587
        $maxkeys = 1000;
588
589
        $result = [];
590
591
        while (true) {
592
            $options = [
593
                'delimiter' => $delimiter,
594
                'prefix' => $dirname,
595
                'max-keys' => $maxkeys,
596
                'marker' => $nextMarker,
597
            ];
598
599
            try {
600
                $listObjectInfo = $this->client->listObjects($this->bucket, $options);
601
            } catch (OssException $exception) {
602
                throw $exception;
603
            }
604
605
            $nextMarker = $listObjectInfo->getNextMarker();
606
            $objectList = $listObjectInfo->getObjectList();
607
            $prefixList = $listObjectInfo->getPrefixList();
608
609
            if (!empty($objectList)) {
610
                foreach ($objectList as $objectInfo) {
611
                    $object['Prefix'] = $dirname;
612
                    $object['Key'] = $objectInfo->getKey();
613
                    $object['LastModified'] = $objectInfo->getLastModified();
614
                    $object['eTag'] = $objectInfo->getETag();
615
                    $object['Type'] = $objectInfo->getType();
616
                    $object['Size'] = $objectInfo->getSize();
617
                    $object['StorageClass'] = $objectInfo->getStorageClass();
618
                    $result['objects'][] = $object;
619
                }
620
            } else {
621
                $result['objects'] = [];
622
            }
623
624
            if (!empty($prefixList)) {
625
                foreach ($prefixList as $prefixInfo) {
626
                    $result['prefix'][] = $prefixInfo->getPrefix();
627
                }
628
            } else {
629
                $result['prefix'] = [];
630
            }
631
632
            // Recursive directory
633
            if ($recursive) {
634
                foreach ($result['prefix'] as $prefix) {
635
                    $next = $this->listDirObjects($prefix, $recursive);
636
                    $result['objects'] = array_merge($result['objects'], $next['objects']);
637
                }
638
            }
639
640
            if ('' === $nextMarker) {
641
                break;
642
            }
643
        }
644
645
        return $result;
646
    }
647
648
    /**
649
     * normalize file info.
650
     *
651
     * @param array $stats
652
     *
653
     * @return array
654
     */
655
    protected function normalizeFileInfo(array $stats)
656
    {
657
        $filePath = ltrim($stats['Key'], '/');
658
659
        $meta = $this->getMetadata($filePath) ?? [];
660
661
        if (empty($meta)) {
662
            return [];
663
        }
664
665
        return [
666
            'type' => $meta['content-type'],
667
            'path' => $filePath,
668
            'timestamp' => $meta['info']['filetime'],
669
            'size' => $meta['content-length'],
670
        ];
671
    }
672
}
673