BEditaClient::restoreObject()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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