Passed
Push — master ( ed94ae...15a67e )
by Ludwig
02:43
created

DatamolinoClient.php (2 issues)

Labels
Severity
1
<?php
2
3
/*
4
 * This file is part of datamolino client.
5
 *
6
 * (c) 2018 cwd.at GmbH <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Cwd\Datamolino;
15
16
use Cwd\Datamolino\Model\Agenda;
17
use Cwd\Datamolino\Model\Document;
18
use Cwd\Datamolino\Model\OriginalFile;
19
use Cwd\Datamolino\Model\UploadFile;
20
use Cwd\Datamolino\Model\DocumentFile;
21
use Cwd\Datamolino\Model\User;
22
use GuzzleHttp\Psr7\Request;
23
use Http\Client\HttpClient;
24
use Symfony\Component\Finder\Finder;
25
use Symfony\Component\HttpFoundation\File\File as FileInfo;
26
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
27
use Symfony\Component\Serializer\Encoder\JsonEncoder;
28
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
29
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
30
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
31
use Symfony\Component\Serializer\Serializer;
32
33
class DatamolinoClient
34
{
35
    public const PAYLOAD_LIMIT = 1024 * 1024 * 20; // 20MB
36
37
    private $apiUrl = 'https://beta.datamolino.com';
38
    private $apiVersion = 'v1_2';
39
    private $apiUri;
40
    private $tokenUrl;
41
    private $token;
42
43
    /** @var HttpClient */
44
    private $client;
45
46
    /** @var Serializer */
47
    private $serializer;
48
49
    public function __construct(HttpClient $client)
50
    {
51
        $this->client = $client;
52
        $this->apiUri = sprintf('%s/api/%s/', $this->apiUrl, $this->apiVersion);
53
        $this->tokenUrl = $this->apiUrl.'/oauth/token';
54
55
        $normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter(), null, new ReflectionExtractor());
56
        $this->serializer = new Serializer([new DateTimeNormalizer(), $normalizer], ['json' => new JsonEncoder()]);
57
    }
58
59
    /**
60
     * @param string|Finder $finder    Location of Files or Finder Instance
61
     * @param int           $agendaId
62
     * @param string        $type
63
     * @param bool          $fileSplit
64
     * @param bool          $lacyLoad
65
     *
66
     * @return Document[]
67
     *
68
     * @throws \Http\Client\Exception
69
     */
70
    public function createDocuments($finder, int $agendaId, string $type = Document::DOCTYPE_PURCHASE, $fileSplit = false, $lacyLoad = false): array
71
    {
72
        $currentFileSize = 0;
73
        $resultDocuments = [];
74
        $documentFiles = [];
75
76
        if (!$finder instanceof Finder) {
77
            $finder = (new Finder())->in($finder);
78
        }
79
80
        foreach ($finder as $file) {
81
            $documentFile = new DocumentFile();
82
            $documentFile->setType($type)
83
                         ->setAgendaId($agendaId)
84
                         ->setFileSplit($fileSplit);
85
86
            $mimeType = (new FileInfo($file->getPathname()))->getMimeType();
87
            $currentFileSize += $file->getSize();
88
            $documentFile->setFile(
89
                new UploadFile($file->getFilename(), $mimeType, $file->getContents())
90
            );
91
92
            $documentFiles[] = $documentFile;
93
94
            if ($currentFileSize >= self::PAYLOAD_LIMIT) {
95
                $resultDocuments += $this->sendDocuments($documentFiles, $lacyLoad);
96
                $documentFiles = [];
97
            }
98
        }
99
100
        if (count($documentFiles) > 0) {
101
            $resultDocuments += $this->sendDocuments($documentFiles, $lacyLoad);
102
        }
103
104
        return $resultDocuments;
105
    }
106
107
    /**
108
     * @param string $fileUri
109
     * @param int    $agendaId
110
     * @param string $filename
111
     * @param string $type
112
     * @param bool   $fileSplit
113
     * @param bool   $lacyLoad
114
     *
115
     * @return Document
116
     *
117
     * @throws \Http\Client\Exception
118
     */
119
    public function createDocument($fileUri, $agendaId, $filename, $type = Document::DOCTYPE_PURCHASE, $fileSplit = false, $lacyLoad = false): Document
120
    {
121
        $file = new FileInfo($fileUri);
122
        $mimeType = $file->getMimeType();
123
124
        $documentFile = new DocumentFile();
125
        $documentFile->setType($type)
126
            ->setAgendaId($agendaId)
127
            ->setFileSplit($fileSplit)
128
            ->setFile(
129
                new UploadFile($file->getFilename(), $mimeType, file_get_contents($file->getPathname()))
130
            )
131
        ;
132
133
        return current($this->sendDocuments([$documentFile], $lacyLoad));
134
    }
135
136
    /**
137
     * @param DocumentFile[] $files
138
     *
139
     * @return array
140
     *
141
     * @throws \Http\Client\Exception
142
     */
143
    public function sendDocuments(array $files, $lacyLoad = true)
144
    {
145
        $payload = $this->serializer->serialize(['documents' => $files], 'json');
146
        $documents = $this->call($payload, null, 'documents', Document::class, true, 'POST');
147
148
        if ($lacyLoad) {
149
            $ids = [];
150
            /** @var Document $document */
151
            foreach ($documents as $document) {
152
                $ids[] = $document->getId();
153
            }
154
155
            return $this->getDocuments(current($files)->getAgendaId(), $ids);
156
        }
157
158
        return $documents;
159
    }
160
161
    /**
162
     * @param $document
163
     *
164
     * @return OriginalFile
165
     *
166
     * @throws \Http\Client\Exception
167
     */
168
    public function getDocumentFile($document): OriginalFile
169
    {
170
        if ($document instanceof Document) {
171
            $document = $document->getId();
172
        }
173
174
        return $this->call(null, $document, 'documents', OriginalFile::class, false, 'GET', '/original_file');
175
    }
176
177
    /**
178
     * @param int|Document $document
179
     *
180
     * @throws \Http\Client\Exception
181
     */
182
    public function deleteDocument($document): void
183
    {
184
        if ($document instanceof Document) {
185
            $document = $document->getId();
186
        }
187
188
        $this->call(null, $document, 'documents', null, false, 'DELETE');
189
    }
190
191
    /**
192
     * @param Document|int $document
193
     * @param string       $text
194
     *
195
     * @throws \Http\Client\Exception
196
     */
197
    public function repairDocument($document, $text): void
198
    {
199
        if ($document instanceof Document) {
200
            $document = $document->getId();
201
        }
202
203
        $this->call(null, $document, 'documents', OriginalFile::class, false, 'POST', sprintf(
204
            '/repair?repair_description=%s', urlencode($text))
205
        );
206
    }
207
208
    /**
209
     * @param int            $agendaId
210
     * @param array          $ids
211
     * @param \DateTime|null $modifiedSince
212
     * @param array          $states
213
     * @param int            $page
214
     * @param string         $type
215
     *
216
     * @return mixed
217
     *
218
     * @throws \Http\Client\Exception
219
     */
220
    public function getDocuments($agendaId, array $ids = [], ?\DateTime $modifiedSince = null, array $states = [], $page = 1, $type = Document::DOCTYPE_PURCHASE)
221
    {
222
        $queryString = [
223
            sprintf('agenda_id=%s', $agendaId),
224
            sprintf('page=%s', $page),
225
            sprintf('type=%s', $type),
226
        ];
227
228
        if (null !== $modifiedSince) {
229
            $queryString[] = sprintf('modified_since=%s', $modifiedSince->format('Y-m-d\TH:i:s\Z'));
230
        }
231
232
        if (count($states) > 0) {
233
            foreach ($states as $state) {
234
                $queryString[] = sprintf('state[]=%s', $state);
235
            }
236
        }
237
238
        if (count($ids) > 0) {
239
            foreach ($ids as $id) {
240
                $queryString[] = sprintf('ids[]=%s', $id);
241
            }
242
        }
243
244
        return $this->call(null, sprintf('?%s', implode('&', $queryString)), 'documents', Document::class, true, 'GET');
245
    }
246
247
    /**
248
     * @return Agenda[]
249
     *
250
     * @throws \Http\Client\Exception
251
     */
252
    public function getAgendas()
253
    {
254
        return $this->call(null, null, 'agendas', Agenda::class, true, 'GET');
255
    }
256
257
    /**
258
     * @param int $id
259
     *
260
     * @return Agenda
261
     *
262
     * @throws \Http\Client\Exception
263
     */
264
    public function getAgenda(int $id): Agenda
265
    {
266
        return $this->call(null, $id, 'agendas', Agenda::class, false, 'GET');
267
    }
268
269
    /**
270
     * @param Agenda $agenda
271
     * @param bool   $lazyLoad if false the agenda object only holds the ID
272
     *
273
     * @return Agenda
274
     *
275
     * @throws \Http\Client\Exception
276
     */
277
    public function createAgenda(Agenda $agenda, $lazyLoad = false): Agenda
278
    {
279
        $payload = $this->serializer->serialize(['agendas' => [$agenda]], 'json');
280
        $agenda = $this->call($payload, null, 'agendas', Agenda::class, false, 'POST');
281
282
        if ($lazyLoad) {
283
            return $this->getAgenda($agenda->getId());
284
        }
285
286
        return $agenda;
287
    }
288
289
    /**
290
     * @param Agenda $agenda
291
     *
292
     * @return void|
293
     *
294
     * @throws \Http\Client\Exception
295
     */
296
    public function updateAgenda(Agenda $agenda)
297
    {
298
        $payload = $this->serializer->serialize(['agendas' => [$agenda]], 'json');
299
        $this->call($payload, $agenda->getId(), 'agendas', null, false, 'PUT');
300
    }
301
302
    /**
303
     * @param Agenda $agenda
304
     *
305
     * @return mixed
306
     *
307
     * @throws \Http\Client\Exception
308
     */
309
    public function deleteAgenda(int $id): void
310
    {
311
        $this->call(null, $id, 'agendas', null, false, 'DELETE');
312
    }
313
314
    /**
315
     * @return User
316
     *
317
     * @throws \Http\Client\Exception
318
     */
319
    public function getMe(): User
320
    {
321
        // Result is different - denormalize by hand
322
        $data = $this->call(null, null, 'me', null, false, 'GET');
323
324
        return $this->denormalizeObject(User::class, [$data], false);
325
    }
326
327
    /**
328
     * @param string|null     $payload
329
     * @param int|string|null $id
330
     * @param string          $endpoint
331
     * @param string|null     $hydrationClass
332
     * @param bool            $isList
333
     * @param string          $method
334
     * @param string|null     $urlExtension   - Special case only needed when retrieving original file!
335
     *
336
     * @return mixed
337
     *
338
     * @throws \Http\Client\Exception
339
     */
340
    protected function call($payload = null, $id = null, $endpoint = '', $hydrationClass = null, $isList = false, $method = 'POST', $urlExtension = null)
341
    {
342
        if (null === $this->token) {
343
            throw new \Exception('Token not set');
344
        }
345
346
        if (in_array($method, ['GET', 'PUT', 'DELETE'])) {
347
            $format = (is_int($id)) ? '%s%s/%s' : '%s%s%s';
348
            $uri = sprintf($format, $this->apiUri, $endpoint, $id);
349
        } else {
350
            $uri = (null !== $id) ? sprintf('%s%s/%s', $this->apiUri, $endpoint, $id) : $this->apiUri.$endpoint;
351
        }
352
353
        /* Special case only needed for retrieve original file */
354
        if (null !== $urlExtension) {
355
            $uri .= $urlExtension;
356
        }
357
358
        $request = new Request($method, $uri, [
359
            'Authorization' => sprintf('Bearer %s', $this->token),
360
            'Content-Type' => 'application/json',
361
        ], $payload);
362
363
        $response = $this->client->sendRequest($request);
364
        $responseBody = $response->getBody()->getContents();
365
        $responseData = json_decode($responseBody);
366
367
        if ('dev' === getenv('APP_ENV')) {
368
            dump([$request, $responseData]);
369
        }
370
371
        if ($response->getStatusCode() > 299) {
372
            $message = isset($responseData->message) ? $responseData->message : 'Unknown';
373
            throw new \Exception(sprintf('Error on request %s: %s', $response->getStatusCode(), $message));
374
        }
375
376
        if (null !== $hydrationClass && class_exists($hydrationClass) && isset($responseData->$endpoint)) {
377
            return $this->denormalizeObject($hydrationClass, $responseData->$endpoint, $isList);
378
        } elseif (null !== $hydrationClass && !class_exists($hydrationClass)) {
379
            throw new \Exception(sprintf('HydrationClass (%s) does not exist', $hydrationClass));
380
        } elseif (null !== $hydrationClass && !isset($responseData->$endpoint)) {
381
            throw new \Exception(sprintf('Datapoint (%s) does not exist', $endpoint));
382
        }
383
384
        return $responseData;
385
    }
386
387
    /**
388
     * @param string $clientId
389
     * @param string $clientSecret
390
     * @param string $username
391
     * @param string $password
392
     *
393
     * @throws \Http\Client\Exception
394
     * @ToDo Handle Token storage
395
     */
396
    public function authenticatePassword($clientId, $clientSecret, $username, $password)
397
    {
398
        $request = new Request('POST', $this->tokenUrl, [
399
            'Content-Type' => 'application/json',
400
        ], json_encode([
401
            'client_id' => $clientId,
402
            'client_secret' => $clientSecret,
403
            'username' => $username,
404
            'password' => $password,
405
            'grant_type' => 'password',
406
        ]));
407
408
        $response = $this->getClient()->sendRequest($request);
409
        $responseBody = $response->getBody()->getContents();
410
        dump($responseBody);
0 ignored issues
show
The function dump was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

410
        /** @scrutinizer ignore-call */ 
411
        dump($responseBody);
Loading history...
411
    }
412
413
    /**
414
     * @param string $clientId
415
     * @param string $clientSecret
416
     * @param string $refreshToken
417
     *
418
     * @throws \Http\Client\Exception
419
     * @ToDo Handle Token storage
420
     */
421
    public function refreshToken($clientId, $clientSecret, $refreshToken)
422
    {
423
        $request = new Request('POST', $this->tokenUrl, [
424
            'Content-Type' => 'application/json',
425
        ], json_encode([
426
            'client_id' => $clientId,
427
            'client_secret' => $clientSecret,
428
            'refresh_token' => $refreshToken,
429
            'grant_type' => 'refresh_token',
430
        ]));
431
432
        $response = $this->getClient()->sendRequest($request);
433
        $responseBody = $response->getBody()->getContents();
434
        dump($responseBody);
0 ignored issues
show
The function dump was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

434
        /** @scrutinizer ignore-call */ 
435
        dump($responseBody);
Loading history...
435
    }
436
437
    public function setToken($token): DatamolinoClient
438
    {
439
        $this->token = $token;
440
441
        return $this;
442
    }
443
444
    /**
445
     * @return string
446
     */
447
    public function getApiUrl(): string
448
    {
449
        return $this->apiUrl;
450
    }
451
452
    /**
453
     * @param string $apiUrl
454
     *
455
     * @return DatamolinoClient
456
     */
457
    public function setApiUrl(string $apiUrl): DatamolinoClient
458
    {
459
        $this->apiUrl = $apiUrl;
460
461
        return $this;
462
    }
463
464
    /**
465
     * @return string
466
     */
467
    public function getApiVersion(): string
468
    {
469
        return $this->apiVersion;
470
    }
471
472
    /**
473
     * @param string $apiVersion
474
     *
475
     * @return DatamolinoClient
476
     */
477
    public function setApiVersion(string $apiVersion): DatamolinoClient
478
    {
479
        $this->apiVersion = $apiVersion;
480
481
        return $this;
482
    }
483
484
    /**
485
     * @return string
486
     */
487
    public function getApiUri(): string
488
    {
489
        return $this->apiUri;
490
    }
491
492
    /**
493
     * @param string $apiUri
494
     *
495
     * @return DatamolinoClient
496
     */
497
    public function setApiUri(string $apiUri): DatamolinoClient
498
    {
499
        $this->apiUri = $apiUri;
500
501
        return $this;
502
    }
503
504
    /**
505
     * @return string
506
     */
507
    public function getTokenUrl(): string
508
    {
509
        return $this->tokenUrl;
510
    }
511
512
    /**
513
     * @param string $tokenUrl
514
     *
515
     * @return DatamolinoClient
516
     */
517
    public function setTokenUrl(string $tokenUrl): DatamolinoClient
518
    {
519
        $this->tokenUrl = $tokenUrl;
520
521
        return $this;
522
    }
523
524
    protected function denormalizeObject($hydrationClass, $dataObject, $isList = false)
525
    {
526
        $result = [];
527
528
        foreach ($dataObject as $data) {
529
            $result[] = $this->serializer->denormalize($data, $hydrationClass, null, [
530
                ObjectNormalizer::DISABLE_TYPE_ENFORCEMENT => true,
531
            ]);
532
        }
533
534
        if ($isList) {
535
            return $result;
536
        }
537
538
        return current($result);
539
    }
540
541
    protected function getClient(): HttpClient
542
    {
543
        return $this->client;
544
    }
545
}
546