Passed
Branch backend-2.5 (281b0d)
by Jonathan
14:44
created

getBaseUriFromConstOrEnvVar()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 18
ccs 11
cts 11
cp 1
rs 9.9332
c 0
b 0
f 0
cc 3
nc 3
nop 0
crap 3
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * ReportingCloud PHP SDK
6
 *
7
 * PHP SDK for ReportingCloud Web API. Authored and supported by Text Control GmbH.
8
 *
9
 * @link      https://www.reporting.cloud to learn more about ReportingCloud
10
 * @link      https://github.com/TextControl/txtextcontrol-reportingcloud-php for the canonical source repository
11
 * @license   https://raw.githubusercontent.com/TextControl/txtextcontrol-reportingcloud-php/master/LICENSE.md
12
 * @copyright © 2019 Text Control GmbH
13
 */
14
15
namespace TxTextControl\ReportingCloud;
16
17
use GuzzleHttp\Client;
18
use GuzzleHttp\Exception\TransferException;
19
use GuzzleHttp\RequestOptions;
20
use Psr\Http\Message\ResponseInterface;
21
use TxTextControl\ReportingCloud\Exception\InvalidArgumentException;
22
use TxTextControl\ReportingCloud\Exception\RuntimeException;
23
use TxTextControl\ReportingCloud\Filter\Filter;
24
use TxTextControl\ReportingCloud\Stdlib\ConsoleUtils;
25
use TxTextControl\ReportingCloud\Stdlib\StringUtils;
26
27
/**
28
 * Abstract ReportingCloud
29
 *
30
 * @package TxTextControl\ReportingCloud
31
 * @author  Jonathan Maron (@JonathanMaron)
32
 */
33
abstract class AbstractReportingCloud
34
{
35
    // <editor-fold desc="Constants (default values)">
36
37
    /**
38
     * Default date/time format of backend is 'ISO 8601'
39
     *
40
     * Note, last letter is 'P' and not 'O':
41
     *
42
     * O - Difference to Greenwich time (GMT) in hours (e.g. +0200)
43
     * P - Difference to Greenwich time (GMT) with colon between hours and minutes (e.g. +02:00)
44
     *
45
     * Backend uses the 'P' variant
46
     *
47
     * @const DEFAULT_DATE_FORMAT
48
     */
49
    public const DEFAULT_DATE_FORMAT = 'Y-m-d\TH:i:sP';
50
51
    /**
52
     * Default time zone of backend
53
     *
54
     * @const DEFAULT_TIME_ZONE
55
     */
56
    public const DEFAULT_TIME_ZONE = 'UTC';
57
58
    /**
59
     * Default base URI of backend
60
     *
61
     * @const DEFAULT_BASE_URI
62
     */
63
    protected const DEFAULT_BASE_URI = 'https://api.reporting.cloud';
64
65
    /**
66
     * Default version string of backend
67
     *
68
     * @const DEFAULT_VERSION
69
     */
70
    protected const DEFAULT_VERSION = 'v1';
71
72
    /**
73
     * Default timeout of backend in seconds
74
     *
75
     * @const DEFAULT_TIMEOUT
76
     */
77
    protected const DEFAULT_TIMEOUT = 120;
78
79
    /**
80
     * Default test flag of backend
81
     *
82
     * @const DEFAULT_TEST
83
     */
84
    protected const DEFAULT_TEST = false;
85
86
    /**
87
     * Default debug flag of REST client
88
     *
89
     * @const DEFAULT_DEBUG
90
     */
91
    protected const DEFAULT_DEBUG = false;
92
93
    // </editor-fold>
94
95
    // <editor-fold desc="Constants (document dividers)">
96
97
    /**
98
     * Document divider - none
99
     */
100
    public const DOCUMENT_DIVIDER_NONE = 1;
101
102
    /**
103
     * Document divider - new paragraph
104
     */
105
    public const DOCUMENT_DIVIDER_NEW_PARAGRAPH = 2;
106
107
    /**
108
     * Document divider - new section
109
     */
110
    public const DOCUMENT_DIVIDER_NEW_SECTION = 3;
111
112
    // </editor-fold>
113
114
    // <editor-fold desc="Constants (file formats)">
115
116
    /**
117
     * DOC file format
118
     */
119
    public const FILE_FORMAT_DOC = 'DOC';
120
121
    /**
122
     * DOCX file format
123
     */
124
    public const FILE_FORMAT_DOCX = 'DOCX';
125
126
    /**
127
     * HTML file format
128
     */
129
    public const FILE_FORMAT_HTML = 'HTML';
130
131
    /**
132
     * PDF file format
133
     */
134
    public const FILE_FORMAT_PDF = 'PDF';
135
136
    /**
137
     * PDF/A file format
138
     */
139
    public const FILE_FORMAT_PDFA = 'PDFA';
140
141
    /**
142
     * RTF file format
143
     */
144
    public const FILE_FORMAT_RTF = 'RTF';
145
146
    /**
147
     * TX (Text Control) file format
148
     */
149
    public const FILE_FORMAT_TX = 'TX';
150
151
    /**
152
     * Pure text file format
153
     */
154
    public const FILE_FORMAT_TXT = 'TXT';
155
156
    /**
157
     * Bitmap file format
158
     */
159
    public const FILE_FORMAT_BMP = 'BMP';
160
161
    /**
162
     * GIF file format
163
     */
164
    public const FILE_FORMAT_GIF = 'GIF';
165
166
    /**
167
     * JPEG file format
168
     */
169
    public const FILE_FORMAT_JPG = 'JPG';
170
171
    /**
172
     * PNG file format
173
     */
174
    public const FILE_FORMAT_PNG = 'PNG';
175
176
    /**
177
     * XLSX file format
178
     */
179
    public const FILE_FORMAT_XLSX = 'XLSX';
180
181
    // </editor-fold>
182
183
    // <editor-fold desc="Constants (file format collections)">
184
185
    /**
186
     * Image file formats
187
     */
188
    public const FILE_FORMATS_IMAGE
189
        = [
190
            self::FILE_FORMAT_BMP,
191
            self::FILE_FORMAT_GIF,
192
            self::FILE_FORMAT_JPG,
193
            self::FILE_FORMAT_PNG,
194
        ];
195
196
    /**
197
     * Template file formats
198
     */
199
    public const FILE_FORMATS_TEMPLATE
200
        = [
201
            self::FILE_FORMAT_DOC,
202
            self::FILE_FORMAT_DOCX,
203
            self::FILE_FORMAT_RTF,
204
            self::FILE_FORMAT_TX,
205
        ];
206
207
    /**
208
     * Document file formats
209
     */
210
    public const FILE_FORMATS_DOCUMENT
211
        = [
212
            self::FILE_FORMAT_DOC,
213
            self::FILE_FORMAT_DOCX,
214
            self::FILE_FORMAT_HTML,
215
            self::FILE_FORMAT_PDF,
216
            self::FILE_FORMAT_RTF,
217
            self::FILE_FORMAT_TX,
218
        ];
219
220
    /**
221
     * Return file formats
222
     */
223
    public const FILE_FORMATS_RETURN
224
        = [
225
            self::FILE_FORMAT_DOC,
226
            self::FILE_FORMAT_DOCX,
227
            self::FILE_FORMAT_HTML,
228
            self::FILE_FORMAT_PDF,
229
            self::FILE_FORMAT_PDFA,
230
            self::FILE_FORMAT_RTF,
231
            self::FILE_FORMAT_TX,
232
            self::FILE_FORMAT_TXT,
233
        ];
234
235
    // </editor-fold>
236
237
    // <editor-fold desc="Properties">
238
239
    /**
240
     * Backend API key
241
     *
242
     * @var string|null
243
     */
244
    private $apiKey;
245
246
    /**
247
     * Backend username
248
     *
249
     * @var string|null
250
     */
251
    private $username;
252
253
    /**
254
     * Backend password
255
     *
256
     * @var string|null
257
     */
258
    private $password;
259
260
    /**
261
     * Backend base URI
262
     *
263
     * @var string|null
264
     */
265
    private $baseUri;
266
267
    /**
268
     * Debug flag of REST client
269
     *
270
     * @var bool|null
271
     */
272
    private $debug;
273
274
    /**
275
     * When true, API call does not count against quota
276
     * "TEST MODE" watermark is added to document
277
     *
278
     * @var bool|null
279
     */
280
    private $test;
281
282
    /**
283
     * Backend timeout in seconds
284
     *
285
     * @var int|null
286
     */
287
    private $timeout;
288
289
    /**
290
     * Backend version string
291
     *
292
     * @var string|null
293
     */
294
    private $version;
295
296
    /**
297
     * REST client to backend
298
     *
299
     * @var Client|null
300
     */
301
    private $client;
302
303
    // </editor-fold>
304
305
    // <editor-fold desc="Methods">
306
307
    /**
308
     * Return the API key
309
     *
310
     * @return string|null
311
     */
312 97
    public function getApiKey(): ?string
313
    {
314 97
        return $this->apiKey;
315
    }
316
317
    /**
318
     * Set the API key
319
     *
320
     * @param string $apiKey
321
     *
322
     * @return AbstractReportingCloud
323
     */
324 274
    public function setApiKey(string $apiKey): self
325
    {
326 274
        $this->apiKey = $apiKey;
327
328 274
        return $this;
329
    }
330
331
    /**
332
     * Return the username
333
     *
334
     * @return string|null
335
     */
336 15
    public function getUsername(): ?string
337
    {
338 15
        return $this->username;
339
    }
340
341
    /**
342
     * Set the username
343
     *
344
     * @param string $username
345
     *
346
     * @return AbstractReportingCloud
347
     */
348 12
    public function setUsername(string $username): self
349
    {
350 12
        $this->username = $username;
351
352 12
        return $this;
353
    }
354
355
    /**
356
     * Return the password
357
     *
358
     * @return string|null
359
     */
360 15
    public function getPassword(): ?string
361
    {
362 15
        return $this->password;
363
    }
364
365
    /**
366
     * Set the password
367
     *
368
     * @param string $password
369
     *
370
     * @return AbstractReportingCloud
371
     */
372 12
    public function setPassword(string $password): self
373
    {
374 12
        $this->password = $password;
375
376 12
        return $this;
377
    }
378
379
    /**
380
     * Return the base URI of the backend web service
381
     *
382
     * @return string|null
383
     */
384 283
    public function getBaseUri(): ?string
385
    {
386 283
        return $this->baseUri;
387
    }
388
389
    /**
390
     * Set the base URI of the backend web service
391
     *
392
     * @param string $baseUri
393
     *
394
     * @return AbstractReportingCloud
395
     */
396 283
    public function setBaseUri(string $baseUri): self
397
    {
398 283
        $this->baseUri = $baseUri;
399
400 283
        return $this;
401
    }
402
403
    /**
404
     * Return the debug flag
405
     *
406
     * @return bool|null
407
     */
408 283
    public function getDebug(): ?bool
409
    {
410 283
        return $this->debug;
411
    }
412
413
    /**
414
     * Set the debug flag
415
     *
416
     * @param bool $debug Debug flag
417
     *
418
     * @return AbstractReportingCloud
419
     */
420 283
    public function setDebug(bool $debug): self
421
    {
422 283
        $this->debug = $debug;
423
424 283
        return $this;
425
    }
426
427
    /**
428
     * Return the test flag
429
     *
430
     * @return bool|null
431
     */
432 283
    public function getTest(): ?bool
433
    {
434 283
        return $this->test;
435
    }
436
437
    /**
438
     * Set the test flag
439
     *
440
     * @param bool $test
441
     *
442
     * @return AbstractReportingCloud
443
     */
444 283
    public function setTest(bool $test): self
445
    {
446 283
        $this->test = $test;
447
448 283
        return $this;
449
    }
450
451
    /**
452
     * Get the timeout (in seconds) of the backend web service
453
     *
454
     * @return int|null
455
     */
456 283
    public function getTimeout(): ?int
457
    {
458 283
        return $this->timeout;
459
    }
460
461
    /**
462
     * Set the timeout (in seconds) of the backend web service
463
     *
464
     * @param int $timeout
465
     *
466
     * @return AbstractReportingCloud
467
     */
468 283
    public function setTimeout(int $timeout): self
469
    {
470 283
        $this->timeout = $timeout;
471
472 283
        return $this;
473
    }
474
475
    /**
476
     * Get the version string of the backend web service
477
     *
478
     * @return string|null
479
     */
480 283
    public function getVersion(): ?string
481
    {
482 283
        return $this->version;
483
    }
484
485
    /**
486
     * Set the version string of the backend web service
487
     *
488
     * @param string $version
489
     *
490
     * @return AbstractReportingCloud
491
     */
492 283
    public function setVersion(string $version): self
493
    {
494 283
        $this->version = $version;
495
496 283
        return $this;
497
    }
498
499
    /**
500
     * Return the REST client of the backend web service
501
     *
502
     * @return Client|null
503
     */
504 97
    public function getClient(): ?Client
505
    {
506 97
        if (!$this->client instanceof Client) {
507
508
            $headers = [
509 97
                'Authorization' => $this->getAuthorizationHeader()
510
            ];
511
512
            $options = [
513 94
                'base_uri'              => $this->getBaseUri(),
514 94
                RequestOptions::TIMEOUT => $this->getTimeout(),
515 94
                RequestOptions::DEBUG   => $this->getDebug(),
516 94
                RequestOptions::HEADERS => $headers,
517
            ];
518
519 94
            $client = new Client($options);
520
521 94
            $this->setClient($client);
522
        }
523
524 94
        return $this->client;
525
    }
526
527
    /**
528
     * Set the REST client of the backend web service
529
     *
530
     * @param Client $client
531
     *
532
     * @return AbstractReportingCloud
533
     */
534 94
    public function setClient(Client $client): self
535
    {
536 94
        $this->client = $client;
537
538 94
        return $this;
539
    }
540
541
    /**
542
     * Assign default values to option properties
543
     *
544
     * @return ReportingCloud
545
     */
546 283
    protected function setDefaultOptions(): self
547
    {
548 283
        if (null === $this->getBaseUri()) {
549 283
            $baseUri = $this->getBaseUriFromConstOrEnvVar() ?? self::DEFAULT_BASE_URI;
550 283
            $this->setBaseUri($baseUri);
551
        }
552
553 283
        if (null === $this->getDebug()) {
554 283
            $this->setDebug(self::DEFAULT_DEBUG);
555
        }
556
557 283
        if (null === $this->getTest()) {
558 283
            $this->setTest(self::DEFAULT_TEST);
559
        }
560
561 283
        if (null === $this->getTimeout()) {
562 283
            $this->setTimeout(self::DEFAULT_TIMEOUT);
563
        }
564
565 283
        if (null === $this->getVersion()) {
566 283
            $this->setVersion(self::DEFAULT_VERSION);
567
        }
568
569 283
        return $this;
570
    }
571
572
    /**
573
     * Return the base URI from the PHP const or environment variable "REPORTING_CLOUD_BASE_URI",
574
     * checking that the hostname and sub-domain match the known hostname and sub-domain.
575
     *
576
     * Return null, if the environment variable has not been set or is empty.
577
     *
578
     * @throws InvalidArgumentException
579
     * @return string|null
580
     */
581 283
    protected function getBaseUriFromConstOrEnvVar(): ?string
582
    {
583 283
        $baseUri = ConsoleUtils::baseUri();
584
585 283
        if (empty($baseUri)) {
586 6
            return null;
587
        }
588
589 283
        $sdkHost = (string) parse_url(self::DEFAULT_BASE_URI, PHP_URL_HOST);
590 283
        $envHost = (string) parse_url($baseUri, PHP_URL_HOST);
591
592 283
        if (!StringUtils::endsWith($envHost, $sdkHost)) {
593 3
            $format  = 'Base URI from environment variable "%s" with value "%s" does not end in "%s"';
594 3
            $message = sprintf($format, ConsoleUtils::BASE_URI, $baseUri, $sdkHost);
595 3
            throw new InvalidArgumentException($message);
596
        }
597
598 283
        return $baseUri;
599
    }
600
601
    /**
602
     * Request the URI with options
603
     *
604
     * @param string $method  HTTP method
605
     * @param string $uri     URI
606
     * @param array  $options Options
607
     *
608
     * @return ResponseInterface
609
     * @throws RuntimeException
610
     */
611 91
    protected function request(string $method, string $uri, array $options): ResponseInterface
612
    {
613 91
        $client = $this->getClient();
614
615 88
        if (null === $client) {
616
            $message = 'No HTTP Client has been set.';
617
            throw new RuntimeException($message);
618
        }
619
620
        try {
621 88
            $test = (bool) $this->getTest();
622 88
            if ($test) {
623 3
                $options[RequestOptions::QUERY]['test'] = Filter::filterBooleanToString($test);
624
            }
625 88
            $response = $client->request($method, $uri, $options);
626 6
        } catch (TransferException $e) {
627 6
            $message = (string) $e->getMessage();
628 6
            $code    = (int) $e->getCode();
629 6
            throw new RuntimeException($message, $code);
630
        }
631
632 85
        return $response;
633
    }
634
635
    /**
636
     * Construct URI with version number
637
     *
638
     * @param string $uri URI
639
     *
640
     * @return string
641
     */
642 91
    protected function uri(string $uri): string
643
    {
644 91
        $version = (string) $this->getVersion();
645
646 91
        return sprintf('/%s%s', $version, $uri);
647
    }
648
649
    /**
650
     * Return Authorization Header, with either API key or username and password
651
     *
652
     * @return string
653
     * @throws InvalidArgumentException
654
     */
655 97
    private function getAuthorizationHeader(): string
656
    {
657 97
        $apiKey = (string) $this->getApiKey();
658
659 97
        if (!empty($apiKey)) {
660 91
            return sprintf('ReportingCloud-APIKey %s', $apiKey);
661
        }
662
663 6
        $username = (string) $this->getUsername();
664 6
        $password = (string) $this->getPassword();
665
666 6
        if (!empty($username) && !empty($password)) {
667 3
            $value = sprintf('%s:%s', $username, $password);
668 3
            return sprintf('Basic %s', base64_encode($value));
669
        }
670
671 3
        $message = 'Either the API key, or username and password must be set for authorization';
672 3
        throw new InvalidArgumentException($message);
673
    }
674
675
    // </editor-fold>
676
}
677