Test Failed
Branch backend-2.5 (785da6)
by Jonathan
15:44 queued 34s
created

AbstractReportingCloud   A

Complexity

Total Complexity 37

Size/Duplication

Total Lines 643
Duplicated Lines 0 %

Test Coverage

Coverage 97.87%

Importance

Changes 0
Metric Value
wmc 37
eloc 146
dl 0
loc 643
ccs 92
cts 94
cp 0.9787
rs 9.44
c 0
b 0
f 0

23 Methods

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