Issues (7)

src/OssAdapter.php (7 issues)

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\Config;
16
use League\Flysystem\DirectoryAttributes;
17
use League\Flysystem\PathPrefixer;
18
use League\Flysystem\Visibility;
19
use OSS\Core\OssException;
20
use OSS\OssClient;
21
use League\Flysystem\FileAttributes;
22
use League\Flysystem\FilesystemAdapter;
23
use League\Flysystem\FilesystemException;
24
use League\Flysystem\InvalidVisibilityProvided;
25
use League\Flysystem\StorageAttributes;
26
use League\Flysystem\UnableToCopyFile;
27
use League\Flysystem\UnableToCreateDirectory;
28
use League\Flysystem\UnableToDeleteDirectory;
29
use League\Flysystem\UnableToDeleteFile;
30
use League\Flysystem\UnableToMoveFile;
31
use League\Flysystem\UnableToReadFile;
32
use League\Flysystem\UnableToRetrieveMetadata;
33
use League\Flysystem\UnableToSetVisibility;
34
use League\Flysystem\UnableToWriteFile;
35
36
/**
37
 * Class OssAdapter.
38
 *
39
 * @author iidestiny <[email protected]>
40
 */
41
class OssAdapter implements FilesystemAdapter
42
{
43
    use SignatureTrait;
44
45
    // 系统参数
46
    /**
47
     *
48
     */
49
    const SYSTEM_FIELD = [
50
        'bucket'   => '${bucket}',
51
        'etag'     => '${etag}',
52
        'filename' => '${object}',
53
        'size'     => '${size}',
54
        'mimeType' => '${mimeType}',
55
        'height'   => '${imageInfo.height}',
56
        'width'    => '${imageInfo.width}',
57
        'format'   => '${imageInfo.format}',
58
    ];
59
60
    /**
61
     * @var
62
     */
63
    protected $accessKeyId;
64
65
    /**
66
     * @var
67
     */
68
    protected $accessKeySecret;
69
70
    /**
71
     * @var
72
     */
73
    protected $endpoint;
74
75
    /**
76
     * @var
77
     */
78
    protected $bucket;
79
80
    /**
81
     * @var
82
     */
83
    protected $isCName;
84
85
    /**
86
     * @var array
87
     */
88
    protected $buckets;
89
90
    /**
91
     * @var OssClient
92
     */
93
    protected $client;
94
95
    /**
96
     * @var array|mixed[]
97
     */
98
    protected $params;
99
100
    /**
101
     * @var bool
102
     */
103
    protected $useSSL = false;
104
105
    /**
106
     * @var string|null
107
     */
108
    protected $cdnUrl = null;
109
110
    /**
111
     * @var PathPrefixer
112
     */
113
    protected $prefixer;
114
115
    /**
116
     * @param        $accessKeyId
117
     * @param        $accessKeySecret
118
     * @param        $endpoint
119
     * @param        $bucket
120
     * @param bool   $isCName
121
     * @param string $prefix
122
     * @param array  $buckets
123
     * @param        ...$params
124
     *
125
     * @throws OssException
126
     */
127
    public function __construct($accessKeyId, $accessKeySecret, $endpoint, $bucket, bool $isCName = false, string $prefix = '', array $buckets = [], ...$params)
128
    {
129
        $this->accessKeyId = $accessKeyId;
130
        $this->accessKeySecret = $accessKeySecret;
131
        $this->endpoint = $endpoint;
132
        $this->bucket = $bucket;
133
        $this->isCName = $isCName;
134
        $this->prefixer = new PathPrefixer($prefix, DIRECTORY_SEPARATOR);
135
        $this->buckets = $buckets;
136
        $this->params = $params;
137
        $this->initClient();
138
        $this->checkEndpoint();
139
    }
140
141
    /**
142
     * 设置cdn的url.
143
     *
144
     * @param string|null $url
145
     */
146
    public function setCdnUrl(?string $url)
147
    {
148
        $this->cdnUrl = $url;
149
    }
150
151
    /**
152
     * @return OssClient
153
     */
154
    public function ossKernel(): OssClient
155
    {
156
        return $this->getClient();
157
    }
158
159
    /**
160
     * 调用不同的桶配置.
161
     *
162
     * @param $bucket
163
     *
164
     * @return $this
165
     * @throws OssException
166
     * @throws \Exception
167
     */
168
    public function bucket($bucket): OssAdapter
169
    {
170
        if (!isset($this->buckets[$bucket])) {
171
            throw new \Exception('bucket is not exist.');
172
        }
173
        $bucketConfig = $this->buckets[$bucket];
174
175
        $this->accessKeyId = $bucketConfig['access_key'];
176
        $this->accessKeySecret = $bucketConfig['secret_key'];
177
        $this->endpoint = $bucketConfig['endpoint'];
178
        $this->bucket = $bucketConfig['bucket'];
179
        $this->isCName = $bucketConfig['isCName'];
180
181
        $this->initClient();
182
        $this->checkEndpoint();
183
184
        return $this;
185
    }
186
187
    /**
188
     * init oss client.
189
     *
190
     * @throws OssException
191
     */
192
    protected function initClient()
193
    {
194
        $this->client = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint, $this->isCName, ...$this->params);
0 ignored issues
show
$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

194
        $this->client = new OssClient($this->accessKeyId, $this->accessKeySecret, $this->endpoint, $this->isCName, /** @scrutinizer ignore-type */ ...$this->params);
Loading history...
195
    }
196
197
    /**
198
     * get ali sdk kernel class.
199
     *
200
     * @return OssClient
201
     */
202
    public function getClient(): OssClient
203
    {
204
        return $this->client;
205
    }
206
207
    /**
208
     * 验签.
209
     *
210
     * @return array
211
     */
212
    public function verify(): array
213
    {
214
        // oss 前面header、公钥 header
215
        $authorizationBase64 = '';
216
        $pubKeyUrlBase64 = '';
217
218
        if (isset($_SERVER['HTTP_AUTHORIZATION'])) {
219
            $authorizationBase64 = $_SERVER['HTTP_AUTHORIZATION'];
220
        }
221
222
        if (isset($_SERVER['HTTP_X_OSS_PUB_KEY_URL'])) {
223
            $pubKeyUrlBase64 = $_SERVER['HTTP_X_OSS_PUB_KEY_URL'];
224
        }
225
226
        // 验证失败
227
        if ('' == $authorizationBase64 || '' == $pubKeyUrlBase64) {
228
            return [false, ['CallbackFailed' => 'authorization or pubKeyUrl is null']];
229
        }
230
231
        // 获取OSS的签名
232
        $authorization = base64_decode($authorizationBase64);
233
        // 获取公钥
234
        $pubKeyUrl = base64_decode($pubKeyUrlBase64);
235
        // 请求验证
236
        $ch = curl_init();
237
        curl_setopt($ch, CURLOPT_URL, $pubKeyUrl);
238
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
239
        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
240
        $pubKey = curl_exec($ch);
241
242
        if ('' == $pubKey) {
243
            return [false, ['CallbackFailed' => 'curl is fail']];
244
        }
245
246
        // 获取回调 body
247
        $body = file_get_contents('php://input');
248
        // 拼接待签名字符串
249
        $path = $_SERVER['REQUEST_URI'];
250
        $pos = strpos($path, '?');
251
        if (false === $pos) {
252
            $authStr = urldecode($path) . "\n" . $body;
253
        } else {
254
            $authStr = urldecode(substr($path, 0, $pos)) . substr($path, $pos, strlen($path) - $pos) . "\n" . $body;
255
        }
256
        // 验证签名
257
        $ok = openssl_verify($authStr, $authorization, $pubKey, OPENSSL_ALGO_MD5);
258
259
        if (1 !== $ok) {
260
            return [false, ['CallbackFailed' => 'verify is fail, Illegal data']];
261
        }
262
263
        parse_str($body, $data);
264
265
        return [true, $data];
266
    }
267
268
    /**
269
     * oss 直传配置.
270
     *
271
     * @param string $prefix
272
     * @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...
273
     * @param array  $customData
274
     * @param int    $expire
275
     * @param int    $contentLengthRangeValue
276
     * @param array  $systemData
277
     *
278
     * @return false|string
279
     *
280
     * @throws \Exception
281
     */
282
    public function signatureConfig(string $prefix = '', $callBackUrl = null, array $customData = [], int $expire = 30, int $contentLengthRangeValue = 1048576000, array $systemData = [])
283
    {
284
        $prefix = $this->prefixer->prefixPath($prefix);
285
286
        // 系统参数
287
        $system = [];
288
        if (empty($systemData)) {
289
            $system = self::SYSTEM_FIELD;
290
        } else {
291
            foreach ($systemData as $key => $value) {
292
                if (!in_array($value, self::SYSTEM_FIELD)) {
293
                    throw new \InvalidArgumentException("Invalid oss system filed: ${value}");
294
                }
295
                $system[$key] = $value;
296
            }
297
        }
298
299
        // 自定义参数
300
        $callbackVar = [];
301
        $data = [];
302
        if (!empty($customData)) {
303
            foreach ($customData as $key => $value) {
304
                $callbackVar['x:' . $key] = $value;
305
                $data[$key] = '${x:' . $key . '}';
306
            }
307
        }
308
309
        $callbackParam = [
310
            'callbackUrl'      => $callBackUrl,
311
            'callbackBody'     => urldecode(http_build_query(array_merge($system, $data))),
312
            'callbackBodyType' => 'application/x-www-form-urlencoded',
313
        ];
314
        $callbackString = json_encode($callbackParam);
315
        $base64CallbackBody = base64_encode($callbackString);
316
317
        $now = time();
318
        $end = $now + $expire;
319
        $expiration = $this->gmt_iso8601($end);
320
321
        // 最大文件大小.用户可以自己设置
322
        $condition = [
323
            0 => 'content-length-range',
324
            1 => 0,
325
            2 => $contentLengthRangeValue,
326
        ];
327
        $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...
328
329
        $start = [
330
            0 => 'starts-with',
331
            1 => '$key',
332
            2 => $prefix,
333
        ];
334
        $conditions[] = $start;
335
336
        $arr = [
337
            'expiration' => $expiration,
338
            'conditions' => $conditions,
339
        ];
340
        $policy = json_encode($arr);
341
        $base64Policy = base64_encode($policy);
342
        $stringToSign = $base64Policy;
343
        $signature = base64_encode(hash_hmac('sha1', $stringToSign, $this->accessKeySecret, true));
344
345
        $response = [];
346
        $response['accessid'] = $this->accessKeyId;
347
        $response['host'] = $this->normalizeHost();
348
        $response['policy'] = $base64Policy;
349
        $response['signature'] = $signature;
350
        $response['expire'] = $end;
351
        $response['callback'] = $base64CallbackBody;
352
        $response['callback-var'] = $callbackVar;
353
        $response['dir'] = $prefix;  // 这个参数是设置用户上传文件时指定的前缀。
354
355
        return json_encode($response);
356
    }
357
358
    /**
359
     * sign url
360
     *
361
     * @param        $path
362
     * @param        $timeout
363
     * @param array  $options
364
     * @param string $method
365
     *
366
     * @return false|\OSS\Http\ResponseCore|string
367
     */
368
    public function getTemporaryUrl($path, $timeout, array $options = [], string $method = OssClient::OSS_HTTP_GET)
369
    {
370
        $path = $this->prefixer->prefixPath($path);
371
372
        try {
373
            $path = $this->client->signUrl($this->bucket, $path, $timeout, $method, $options);
374
        } catch (OssException $exception) {
375
            return false;
376
        }
377
378
        return $path;
379
    }
380
381
    /**
382
     * @param string $path
383
     * @param string $contents
384
     * @param Config $config
385
     *
386
     * @return void
387
     */
388
    public function write(string $path, string $contents, Config $config): void
389
    {
390
        $path = $this->prefixer->prefixPath($path);
391
        $options = $config->get('options', []);
392
393
        try {
394
            $this->client->putObject($this->bucket, $path, $contents, $options);
395
        } catch (\Exception $exception) {
396
            throw UnableToWriteFile::atLocation($path, $exception->getMessage());
397
        }
398
    }
399
400
    /**
401
     * Write a new file using a stream.
402
     *
403
     * @param string $path
404
     * @param        $contents
405
     * @param Config $config
406
     *
407
     * @return void
408
     */
409
    public function writeStream(string $path, $contents, Config $config): void
410
    {
411
        $path = $this->prefixer->prefixPath($path);
412
        $options = $config->get('options', []);
413
414
        try {
415
            $this->client->uploadStream($this->bucket, $this->prefixer->prefixPath($path), $contents, $options);
416
        } catch (OssException $exception) {
417
            throw UnableToWriteFile::atLocation($path, $exception->getErrorCode(), $exception);
418
        }
419
    }
420
421
    /**
422
     * @param string $source
423
     * @param string $destination
424
     * @param Config $config
425
     *
426
     * @return void
427
     */
428
    public function move(string $source, string $destination, Config $config): void
429
    {
430
        try {
431
            $this->copy($source, $destination, $config);
432
            $this->delete($source);
433
        } catch (\Exception $exception) {
434
            throw UnableToMoveFile::fromLocationTo($source, $destination);
435
        }
436
    }
437
438
    /**
439
     * @param string $source
440
     * @param string $destination
441
     * @param Config $config
442
     *
443
     * @return void
444
     */
445
    public function copy(string $source, string $destination, Config $config): void
446
    {
447
        $path = $this->prefixer->prefixPath($source);
448
        $newPath = $this->prefixer->prefixPath($destination);
449
450
        try {
451
            $this->client->copyObject($this->bucket, $path, $this->bucket, $newPath);
452
        } catch (OssException $exception) {
453
            throw UnableToCopyFile::fromLocationTo($source, $destination);
454
        }
455
    }
456
457
    /**
458
     * delete a file.
459
     *
460
     * @param string $path
461
     *
462
     * @return void
463
     */
464
    public function delete(string $path): void
465
    {
466
        $path = $this->prefixer->prefixPath($path);
467
468
        try {
469
            $this->client->deleteObject($this->bucket, $path);
470
        } catch (OssException $ossException) {
471
            throw UnableToDeleteFile::atLocation($path);
472
        }
473
    }
474
475
    /**
476
     * @param string $path
477
     *
478
     * @return void
479
     * @throws OssException
480
     */
481
    public function deleteDirectory(string $path): void
482
    {
483
        try {
484
            $contents = $this->listContents($path, false);
485
            $files = [];
486
            foreach ($contents as $i => $content) {
487
                if ($content instanceof DirectoryAttributes) {
488
                    $this->deleteDirectory($content->path());
489
                    continue;
490
                }
491
                $files[] = $this->prefixer->prefixPath($content->path());
492
                if ($i && $i % 100 == 0) {
493
                    $this->client->deleteObjects($this->bucket, $files);
494
                    $files = [];
495
                }
496
            }
497
            !empty($files) && $this->client->deleteObjects($this->bucket, $files);
498
            $this->client->deleteObject($this->bucket, $this->prefixer->prefixDirectoryPath($path));
499
        } catch (OssException $exception) {
500
            throw UnableToDeleteDirectory::atLocation($path, $exception->getErrorCode(), $exception);
501
        }
502
    }
503
504
    /**
505
     * @param string $path
506
     * @param Config $config
507
     *
508
     * @return void
509
     */
510
    public function createDirectory(string $path, Config $config): void
511
    {
512
        try {
513
            $this->client->createObjectDir($this->bucket, $this->prefixer->prefixPath($path));
514
        } catch (OssException $exception) {
515
            throw UnableToCreateDirectory::dueToFailure($path, $exception);
516
        }
517
    }
518
519
    /**
520
     * visibility.
521
     *
522
     * @param string $path
523
     * @param string $visibility
524
     *
525
     * @return array|bool|false
526
     */
527
    public function setVisibility(string $path, string $visibility): void
528
    {
529
        $object = $this->prefixer->prefixPath($path);
530
531
        $acl = Visibility::PUBLIC === $visibility ? OssClient::OSS_ACL_TYPE_PUBLIC_READ : OssClient::OSS_ACL_TYPE_PRIVATE;
532
533
        try {
534
            $this->client->putObjectAcl($this->bucket, $object, $acl);
535
        } catch (OssException $exception) {
536
            throw UnableToSetVisibility::atLocation($path, $exception->getMessage());
537
        }
538
    }
539
540
    /**
541
     * @param string $path
542
     *
543
     * @return FileAttributes
544
     */
545
    public function visibility(string $path): FileAttributes
546
    {
547
        try {
548
            $acl = $this->client->getObjectAcl($this->bucket, $this->prefixer->prefixPath($path), []);
549
        } catch (OssException $exception) {
550
            throw UnableToRetrieveMetadata::visibility($path, $exception->getMessage());
551
        }
552
553
        return new FileAttributes($path, null, $acl === OssClient::OSS_ACL_TYPE_PRIVATE ? Visibility::PRIVATE : Visibility::PUBLIC);
554
    }
555
556
    /**
557
     * Check whether a file exists.
558
     *
559
     * @param string $path
560
     *
561
     * @return array|bool|null
562
     */
563
    public function fileExists(string $path): bool
564
    {
565
        $path = $this->prefixer->prefixPath($path);
566
567
        return $this->client->doesObjectExist($this->bucket, $path);
568
    }
569
570
    /**
571
     * Get resource url.
572
     *
573
     * @param string $path
574
     *
575
     * @return string
576
     */
577
    public function getUrl(string $path): string
578
    {
579
        $path = $this->prefixer->prefixPath($path);
580
581
        if (!is_null($this->cdnUrl)) {
582
            return rtrim($this->cdnUrl, '/') . '/' . ltrim($path, '/');
583
        }
584
585
        return $this->normalizeHost() . ltrim($path, '/');
586
    }
587
588
    /**
589
     * read a file.
590
     *
591
     * @param string $path
592
     *
593
     * @return string
594
     */
595
    public function read(string $path): string
596
    {
597
        $path = $this->prefixer->prefixPath($path);
598
599
        try {
600
            return $this->client->getObject($this->bucket, $path);
601
        } catch (\Exception $exception) {
602
            throw UnableToReadFile::fromLocation($path, $exception->getMessage());
603
        }
604
    }
605
606
    /**
607
     * read a file stream.
608
     *
609
     * @param string $path
610
     *
611
     * @return array|bool|false
612
     */
613
    public function readStream(string $path)
614
    {
615
        $stream = fopen('php://temp', 'w+b');
616
617
        try {
618
            fwrite($stream, $this->client->getObject($this->bucket, $path, [OssClient::OSS_FILE_DOWNLOAD => $stream]));
619
        } catch (OssException $exception) {
620
            fclose($stream);
621
            throw UnableToReadFile::fromLocation($path, $exception->getMessage());
622
        }
623
        rewind($stream);
624
625
        return $stream;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $stream returns the type resource which is incompatible with the documented return type array|boolean.
Loading history...
626
    }
627
628
    /**
629
     * @param string $path
630
     * @param bool   $deep
631
     *
632
     * @return iterable
633
     * @throws \Exception
634
     */
635
    public function listContents(string $path, bool $deep): iterable
636
    {
637
        $directory = $this->prefixer->prefixDirectoryPath($path);
638
        $nextMarker = '';
639
        while (true) {
640
            $options = [
641
                OssClient::OSS_PREFIX => $directory,
642
                OssClient::OSS_MARKER => $nextMarker,
643
            ];
644
645
            try {
646
                $listObjectInfo = $this->client->listObjects($this->bucket, $options);
647
                $nextMarker = $listObjectInfo->getNextMarker();
648
            } catch (OssException $exception) {
649
                throw new \Exception($exception->getErrorMessage(), 0, $exception);
650
            }
651
652
            $prefixList = $listObjectInfo->getPrefixList();
653
            foreach ($prefixList as $prefixInfo) {
654
                $subPath = $this->prefixer->stripDirectoryPrefix($prefixInfo->getPrefix());
655
                if ($subPath == $path) {
656
                    continue;
657
                }
658
                yield new DirectoryAttributes($subPath);
659
                if ($deep === true) {
660
                    $contents = $this->listContents($subPath, $deep);
661
                    foreach ($contents as $content) {
662
                        yield $content;
663
                    }
664
                }
665
            }
666
667
            $listObject = $listObjectInfo->getObjectList();
668
            if (!empty($listObject)) {
669
                foreach ($listObject as $objectInfo) {
670
                    $objectPath = $this->prefixer->stripPrefix($objectInfo->getKey());
671
                    $objectLastModified = strtotime($objectInfo->getLastModified());
672
                    if (substr($objectPath, -1, 1) == '/') {
673
                        continue;
674
                    }
675
                    yield new FileAttributes($objectPath, $objectInfo->getSize(), null, $objectLastModified);
676
                }
677
            }
678
679
            if ($listObjectInfo->getIsTruncated() !== "true") {
680
                break;
681
            }
682
        }
683
    }
684
685
    /**
686
     * @param $path
687
     *
688
     * @return FileAttributes
689
     */
690
    public function getMetadata($path): FileAttributes
691
    {
692
        try {
693
            $result = $this->client->getObjectMeta($this->bucket, $this->prefixer->prefixPath($path));
694
        } catch (OssException $exception) {
695
            throw UnableToRetrieveMetadata::create($path, "metadata", $exception->getErrorCode(), $exception);
696
        }
697
698
        $size = isset($result["content-length"]) ? intval($result["content-length"]) : 0;
699
        $timestamp = isset($result["last-modified"]) ? strtotime($result["last-modified"]) : 0;
700
        $mimetype = $result["content-type"] ?? "";
701
702
        return new FileAttributes($path, $size, null, $timestamp, $mimetype);
703
    }
704
705
    /**
706
     * get the size of file.
707
     *
708
     * @param string $path
709
     *
710
     * @return array|false
711
     */
712
    public function fileSize(string $path): FileAttributes
713
    {
714
        $meta = $this->getMetadata($path);
715
        if ($meta->fileSize() === null) {
716
            throw UnableToRetrieveMetadata::fileSize($path);
717
        }
718
        return $meta;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $meta returns the type League\Flysystem\FileAttributes which is incompatible with the documented return type array|false.
Loading history...
719
    }
720
721
    /**
722
     * get mime type.
723
     *
724
     * @param string $path
725
     *
726
     * @return array|false
727
     */
728
    public function mimeType(string $path): FileAttributes
729
    {
730
        $meta = $this->getMetadata($path);
731
        if ($meta->mimeType() === null) {
732
            throw UnableToRetrieveMetadata::mimeType($path);
733
        }
734
735
        return $meta;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $meta returns the type League\Flysystem\FileAttributes which is incompatible with the documented return type array|false.
Loading history...
736
    }
737
738
    /**
739
     * get timestamp.
740
     *
741
     * @param string $path
742
     *
743
     * @return array|false
744
     */
745
    public function lastModified(string $path): FileAttributes
746
    {
747
        $meta = $this->getMetadata($path);
748
        if ($meta->lastModified() === null) {
749
            throw UnableToRetrieveMetadata::lastModified($path);
750
        }
751
        return $meta;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $meta returns the type League\Flysystem\FileAttributes which is incompatible with the documented return type array|false.
Loading history...
752
    }
753
754
    /**
755
     * normalize Host.
756
     *
757
     * @return string
758
     */
759
    protected function normalizeHost(): string
760
    {
761
        if ($this->isCName) {
762
            $domain = $this->endpoint;
763
        } else {
764
            $domain = $this->bucket . '.' . $this->endpoint;
765
        }
766
767
        if ($this->useSSL) {
768
            $domain = "https://{$domain}";
769
        } else {
770
            $domain = "http://{$domain}";
771
        }
772
773
        return rtrim($domain, '/') . '/';
774
    }
775
776
    /**
777
     * Check the endpoint to see if SSL can be used.
778
     */
779
    protected function checkEndpoint()
780
    {
781
        if (0 === strpos($this->endpoint, 'http://')) {
782
            $this->endpoint = substr($this->endpoint, strlen('http://'));
783
            $this->useSSL = false;
784
        } elseif (0 === strpos($this->endpoint, 'https://')) {
785
            $this->endpoint = substr($this->endpoint, strlen('https://'));
786
            $this->useSSL = true;
787
        }
788
    }
789
}
790