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