Passed
Pull Request — master (#61)
by Dante
01:14
created

BEditaClient::thumbs()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 4
c 0
b 0
f 0
nc 3
nop 2
dl 0
loc 8
rs 10
1
<?php
2
declare(strict_types=1);
3
4
/**
5
 * BEdita, API-first content management framework
6
 * Copyright 2023 Atlas Srl, ChannelWeb Srl, Chialab Srl
7
 *
8
 * Licensed under The MIT License
9
 * For full copyright and license information, please see the LICENSE.txt
10
 * Redistributions of files must retain the above copyright notice.
11
 */
12
13
namespace BEdita\SDK;
14
15
use Exception;
16
17
/**
18
 * BEdita API Client class
19
 */
20
class BEditaClient extends BaseClient
21
{
22
    /**
23
     * Classic authentication via POST /auth using username and password
24
     *
25
     * @param string $username username
26
     * @param string $password password
27
     * @return array|null Response in array format
28
     */
29
    public function authenticate(string $username, string $password): ?array
30
    {
31
        // remove `Authorization` header containing user data in JWT token when using API KEY
32
        $headers = $this->getDefaultHeaders();
33
        if (!empty($headers['X-Api-Key'])) {
34
            unset($headers['Authorization']);
35
            $this->setDefaultHeaders($headers);
36
        }
37
        $body = (string)json_encode(compact('username', 'password') + ['grant_type' => 'password']);
38
39
        return $this->post('/auth', $body, ['Content-Type' => 'application/json']);
40
    }
41
42
    /**
43
     * Bulk edit objects using `POST /bulk/edit` endpoint.
44
     * If the endpoint is not available, it fallback to edit one by one (retrocompatible way).
45
     *
46
     * @param array $ids Object ids
47
     * @param array $data Data to modify
48
     * @return array
49
     */
50
    public function bulkEdit(array $ids, array $data): array
51
    {
52
        $result = [];
53
        try {
54
            $ids = array_map('intval', $ids);
55
            $result = (array)$this->post(
56
                '/bulk/edit',
57
                json_encode(compact('ids', 'data')),
58
                ['Content-Type' => 'application/json'],
59
            );
60
        } catch (Exception $e) {
61
            $result['saved'] = [];
62
            $result['errors'] = [];
63
            // fallback to edit one by one, to be retrocompatible
64
            foreach ($ids as $id) {
65
                try {
66
                    $response = $this->getObject($id);
67
                    $type = $response['data']['type'];
68
                    $response = $this->save($type, $data + ['id' => $id]);
69
                    $result['saved'][] = $response['data']['id'];
70
                } catch (Exception $e) {
71
                    $responseBody = $this->getResponseBody();
72
                    $status = $responseBody['error']['status'];
73
                    $message = $responseBody['error']['message'] ?? $e->getMessage();
74
                    $message = intval($status) === 403 ? '[403] Forbidden' : $message;
75
                    $result['errors'][] = [
76
                        'id' => $id,
77
                        'message' => $message,
78
                    ];
79
                }
80
            }
81
        }
82
83
        return $result;
84
    }
85
86
    /**
87
     * GET a list of resources or objects of a given type
88
     *
89
     * @param string $type Object type name
90
     * @param array|null $query Optional query string
91
     * @param array|null $headers Custom request headers
92
     * @return array|null Response in array format
93
     */
94
    public function getObjects(string $type = 'objects', ?array $query = null, ?array $headers = null): ?array
95
    {
96
        return $this->get(sprintf('/%s', $type), $query, $headers);
97
    }
98
99
    /**
100
     * GET a single object of a given type
101
     *
102
     * @param string|int $id Object id
103
     * @param string $type Object type name
104
     * @param array|null $query Optional query string
105
     * @param array|null $headers Custom request headers
106
     * @return array|null Response in array format
107
     */
108
    public function getObject(
109
        string|int $id,
110
        string $type = 'objects',
111
        ?array $query = null,
112
        ?array $headers = null,
113
    ): ?array {
114
        return $this->get(sprintf('/%s/%s', $type, $id), $query, $headers);
115
    }
116
117
    /**
118
     * Get a list of related resources or objects
119
     *
120
     * @param string|int $id Resource id or object uname/id
121
     * @param string $type Type name
122
     * @param string $relation Relation name
123
     * @param array|null $query Optional query string
124
     * @param array|null $headers Custom request headers
125
     * @return array|null Response in array format
126
     */
127
    public function getRelated(
128
        string|int $id,
129
        string $type,
130
        string $relation,
131
        ?array $query = null,
132
        ?array $headers = null,
133
    ): ?array {
134
        return $this->get(sprintf('/%s/%s/%s', $type, $id, $relation), $query, $headers);
135
    }
136
137
    /**
138
     * Add a list of related resources or objects
139
     *
140
     * @param string|int $id Resource id or object uname/id
141
     * @param string $type Type name
142
     * @param string $relation Relation name
143
     * @param array $data Related resources or objects to add, MUST contain id and type
144
     * @param array|null $headers Custom request headers
145
     * @return array|null Response in array format
146
     */
147
    public function addRelated(
148
        string|int $id,
149
        string $type,
150
        string $relation,
151
        array $data,
152
        ?array $headers = null,
153
    ): ?array {
154
        return $this->post(
155
            sprintf('/%s/%s/relationships/%s', $type, $id, $relation),
156
            json_encode(compact('data')),
157
            $headers,
158
        );
159
    }
160
161
    /**
162
     * Remove a list of related resources or objects
163
     *
164
     * @param string|int $id Resource id or object uname/id
165
     * @param string $type Type name
166
     * @param string $relation Relation name
167
     * @param array $data Related resources or objects to remove from relation
168
     * @param array|null $headers Custom request headers
169
     * @return array|null Response in array format
170
     */
171
    public function removeRelated(
172
        string|int $id,
173
        string $type,
174
        string $relation,
175
        array $data,
176
        ?array $headers = null,
177
    ): ?array {
178
        return $this->delete(
179
            sprintf('/%s/%s/relationships/%s', $type, $id, $relation),
180
            json_encode(compact('data')),
181
            $headers,
182
        );
183
    }
184
185
    /**
186
     * Replace a list of related resources or objects: previuosly related are removed and replaced with these.
187
     *
188
     * @param string|int $id Object id
189
     * @param string $type Object type name
190
     * @param string $relation Relation name
191
     * @param array $data Related resources or objects to insert
192
     * @param array|null $headers Custom request headers
193
     * @return array|null Response in array format
194
     */
195
    public function replaceRelated(
196
        string|int $id,
197
        string $type,
198
        string $relation,
199
        array $data,
200
        ?array $headers = null,
201
    ): ?array {
202
        return $this->patch(
203
            sprintf(
204
                '/%s/%s/relationships/%s',
205
                $type,
206
                $id,
207
                $relation,
208
            ),
209
            json_encode(compact('data')),
210
            $headers,
211
        );
212
    }
213
214
    /**
215
     * Create a new object or resource (POST) or modify an existing one (PATCH)
216
     *
217
     * @param string $type Object or resource type name
218
     * @param array $data Object or resource data to save
219
     * @param array|null $headers Custom request headers
220
     * @return array|null Response in array format
221
     */
222
    public function save(string $type, array $data, ?array $headers = null): ?array
223
    {
224
        $id = null;
225
        if (array_key_exists('id', $data)) {
226
            $id = $data['id'];
227
            unset($data['id']);
228
        }
229
230
        $body = [
231
            'data' => [
232
                'type' => $type,
233
                'attributes' => $data,
234
            ],
235
        ];
236
        if (!$id) {
237
            return $this->post(sprintf('/%s', $type), json_encode($body), $headers);
238
        }
239
        $body['data']['id'] = $id;
240
241
        return $this->patch(sprintf('/%s/%s', $type, $id), json_encode($body), $headers);
242
    }
243
244
    /**
245
     * [DEPRECATED] Create a new object (POST) or modify an existing one (PATCH)
246
     *
247
     * @param string $type Object type name
248
     * @param array $data Object data to save
249
     * @param array|null $headers Custom request headers
250
     * @return array|null Response in array format
251
     * @deprecated Use `save()` method instead
252
     * @codeCoverageIgnore
253
     */
254
    public function saveObject(string $type, array $data, ?array $headers = null): ?array
255
    {
256
        return $this->save($type, $data, $headers);
257
    }
258
259
    /**
260
     * Delete an object (DELETE) => move to trashcan.
261
     *
262
     * @param string|int $id Object id
263
     * @param string $type Object type name
264
     * @return array|null Response in array format
265
     */
266
    public function deleteObject(string|int $id, string $type): ?array
267
    {
268
        return $this->delete(sprintf('/%s/%s', $type, $id));
269
    }
270
271
    /**
272
     * Delete objects (DELETE) => move to trashcan.
273
     *
274
     * @param array $ids Object ids
275
     * @param string|null $type Object type name
276
     * @return array|null Response in array format
277
     */
278
    public function deleteObjects(array $ids, string $type = 'objects'): ?array
279
    {
280
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
281
        try {
282
            $response = $this->delete(sprintf('/%s?ids=%s', $type, implode(',', $ids)));
283
        } catch (Exception $e) {
284
            // fallback to delete one by one, to be retrocompatible
285
            foreach ($ids as $id) {
286
                $response = !empty($response) ? $response : $this->deleteObject($id, $type);
287
            }
288
        }
289
290
        return $response;
291
    }
292
293
    /**
294
     * Remove an object => permanently remove object from trashcan.
295
     *
296
     * @param string|int $id Object id
297
     * @return array|null Response in array format
298
     */
299
    public function remove(string|int $id): ?array
300
    {
301
        return $this->delete(sprintf('/trash/%s', $id));
302
    }
303
304
    /**
305
     * Remove objects => permanently remove objects from trashcan.
306
     *
307
     * @param array $ids Object ids
308
     * @return array|null Response in array format
309
     */
310
    public function removeObjects(array $ids): ?array
311
    {
312
        $response = null;
0 ignored issues
show
Unused Code introduced by
The assignment to $response is dead and can be removed.
Loading history...
313
        try {
314
            $response = $this->delete(sprintf('/trash?ids=%s', implode(',', $ids)));
315
        } catch (Exception $e) {
316
            // fallback to delete one by one, to be retrocompatible
317
            foreach ($ids as $id) {
318
                $response = !empty($response) ? $response : $this->remove($id);
319
            }
320
        }
321
322
        return $response;
323
    }
324
325
    /**
326
     * Upload file (POST)
327
     *
328
     * @param string $filename The file name
329
     * @param string $filepath File full path: could be on a local filesystem or a remote reachable URL
330
     * @param array|null $headers Custom request headers
331
     * @return array|null Response in array format
332
     * @throws \BEdita\SDK\BEditaClientException
333
     */
334
    public function upload(string $filename, string $filepath, ?array $headers = null): ?array
335
    {
336
        if (!file_exists($filepath)) {
337
            throw new BEditaClientException('File not found', 500);
338
        }
339
        $file = file_get_contents($filepath);
340
        if (!$file) {
341
            throw new BEditaClientException('File get contents failed', 500);
342
        }
343
        if (empty($headers['Content-Type'])) {
344
            $headers['Content-Type'] = mime_content_type($filepath);
345
        }
346
347
        return $this->post(sprintf('/streams/upload/%s', $filename), $file, $headers);
348
    }
349
350
    /**
351
     * Create media by type and body data and link it to a stream:
352
     *  - `POST /:type` with `$body` as payload, create media object
353
     *  - `PATCH /streams/:stream_id/relationships/object` modify stream adding relation to media
354
     *  - `GET /:type/:id` get media data
355
     *
356
     * @param string $streamId The stream identifier
357
     * @param string $type The type
358
     * @param array $body The body data
359
     * @return array|null Response in array format
360
     * @throws \BEdita\SDK\BEditaClientException
361
     */
362
    public function createMediaFromStream(string $streamId, string $type, array $body): ?array
363
    {
364
        $id = $this->createMedia($type, $body);
365
        $this->addStreamToMedia($streamId, $id, $type);
366
367
        return $this->getObject($id, $type);
368
    }
369
370
    /**
371
     * Create media.
372
     *
373
     * @param string $type The type
374
     * @param array $body The body
375
     * @return string
376
     * @throws \BEdita\SDK\BEditaClientException
377
     */
378
    public function createMedia(string $type, array $body): string
379
    {
380
        $response = $this->post(sprintf('/%s', $type), json_encode($body));
381
        if (empty($response)) {
382
            throw new BEditaClientException('Invalid response from POST ' . sprintf('/%s', $type));
383
        }
384
385
        return (string)$response['data']['id'];
386
    }
387
388
    /**
389
     * Add stream to media using patch /streams/%s/relationships/object.
390
     *
391
     * @param string $streamId The stream ID
392
     * @param string $id The object ID
393
     * @param string $type The type
394
     * @return void
395
     * @throws \BEdita\SDK\BEditaClientException
396
     */
397
    public function addStreamToMedia(string $streamId, string $id, string $type): void
398
    {
399
        $response = $this->patch(
400
            sprintf('/streams/%s/relationships/object', $streamId),
401
            json_encode([
402
                'data' => [
403
                    'id' => $id,
404
                    'type' => $type,
405
                ],
406
            ]),
407
        );
408
        if (empty($response)) {
409
            throw new BEditaClientException(
410
                'Invalid response from PATCH ' . sprintf('/streams/%s/relationships/object', $id),
411
            );
412
        }
413
    }
414
415
    /**
416
     * Thumbnail request using `GET /media/thumbs` endpoint
417
     *
418
     *  Usage:
419
     *          thumbs(123) => `GET /media/thumbs/123`
420
     *          thumbs(123, ['preset' => 'glide']) => `GET /media/thumbs/123&preset=glide`
421
     *          thumbs(null, ['ids' => '123,124,125']) => `GET /media/thumbs?ids=123,124,125`
422
     *          thumbs(null, ['ids' => '123,124,125', 'preset' => 'async']) => `GET /media/thumbs?ids=123,124,125&preset=async`
423
     *          thumbs(123, ['options' => ['w' => 100, 'h' => 80, 'fm' => 'jpg']]) => `GET /media/thumbs/123/options[w]=100&options[h]=80&options[fm]=jpg` (these options could be not available... just set in preset(s))
424
     *
425
     * @param int|null $id the media Id.
426
     * @param array $query The query params for thumbs call.
427
     * @return array|null Response in array format
428
     */
429
    public function thumbs(?int $id = null, array $query = []): ?array
430
    {
431
        if (empty($id) && empty($query['ids'])) {
432
            throw new BEditaClientException('Invalid empty id|ids for thumbs');
433
        }
434
        $endpoint = empty($id) ? '/media/thumbs' : sprintf('/media/thumbs/%d', $id);
435
436
        return $this->get($endpoint, $query);
437
    }
438
439
    /**
440
     * Get JSON SCHEMA of a resource or object
441
     *
442
     * @param string $type Object or resource type name
443
     * @return array|null JSON SCHEMA in array format
444
     */
445
    public function schema(string $type): ?array
446
    {
447
        return $this->get(
448
            sprintf('/model/schema/%s', $type),
449
            null,
450
            ['Accept' => 'application/schema+json'],
451
        );
452
    }
453
454
    /**
455
     * Get info of a relation (data, params) and get left/right object types
456
     *
457
     * @param string $name relation name
458
     * @return array|null relation data in array format
459
     */
460
    public function relationData(string $name): ?array
461
    {
462
        return $this->get(
463
            sprintf('/model/relations/%s', $name),
464
            ['include' => 'left_object_types,right_object_types'],
465
        );
466
    }
467
468
    /**
469
     * Restore object from trash
470
     *
471
     * @param string|int $id Object id
472
     * @param string $type Object type name
473
     * @return array|null Response in array format
474
     */
475
    public function restoreObject(string|int $id, string $type): ?array
476
    {
477
        return $this->patch(
478
            sprintf('/trash/%s', $id),
479
            json_encode([
480
                'data' => [
481
                    'id' => $id,
482
                    'type' => $type,
483
                ],
484
            ]),
485
        );
486
    }
487
488
    /**
489
     * Restore objects from trash
490
     *
491
     * @param array $ids Object ids
492
     * @param string|null $type Object type
493
     * @return array|null Response in array format
494
     */
495
    public function restoreObjects(array $ids, string $type = 'objects'): ?array
496
    {
497
        $res = null;
498
        foreach ($ids as $id) {
499
            $res = !empty($res) ? $res : $this->restoreObject($id, $type);
500
        }
501
502
        return $res;
503
    }
504
505
    /**
506
     * Clone an object.
507
     * This requires BEdita API >= 5.36.0
508
     *
509
     * @param string $type Object type name
510
     * @param string $id Source object id
511
     * @param array $modified Object attributes to overwrite
512
     * @param array $include Associations included: can be 'relationships' and 'translations'
513
     * @param array|null $headers Custom request headers
514
     * @return array|null Response in array format
515
     */
516
    public function clone(string $type, string $id, array $modified, array $include, ?array $headers = null): ?array
517
    {
518
        $body = json_encode([
519
            'data' => [
520
                'type' => $type,
521
                'attributes' => $modified,
522
                'meta' => [
523
                    'include' => $include,
524
                ],
525
            ],
526
        ]);
527
528
        return $this->post(sprintf('/%s/%s/actions/clone', $type, $id), $body, $headers);
529
    }
530
}
531