Issues (3)

src/BEditaClient.php (2 issues)

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