Passed
Pull Request — master (#61)
by Dante
13:11
created

BEditaClient::deleteObjects()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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