Passed
Branch backend-2.5 (202aa7)
by Jonathan
20:39
created

AbstractReportingCloud::getBaseUriFromEnv()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 26
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 4

Importance

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