ModulesController::relationships()   A
last analyzed

Complexity

Conditions 2
Paths 4

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 4
nop 2
dl 0
loc 22
rs 9.7998
c 0
b 0
f 0
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2018 ChannelWeb Srl, Chialab Srl
5
 *
6
 * This file is part of BEdita: you can redistribute it and/or modify
7
 * it under the terms of the GNU Lesser General Public License as published
8
 * by the Free Software Foundation, either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * See LICENSE.LGPL or <http://gnu.org/licenses/lgpl-3.0.html> for more details.
12
 */
13
namespace App\Controller;
14
15
use App\Utility\ApiConfigTrait;
16
use App\Utility\CacheTools;
17
use App\Utility\Message;
18
use App\Utility\Schema;
19
use BEdita\SDK\BEditaClientException;
20
use BEdita\WebTools\Utility\ApiTools;
21
use Cake\Core\Configure;
22
use Cake\Event\Event;
23
use Cake\Event\EventInterface;
24
use Cake\Http\Exception\UnauthorizedException;
25
use Cake\Http\Response;
26
use Cake\I18n\I18n;
27
use Cake\Utility\Hash;
28
use Exception;
29
use Psr\Log\LogLevel;
30
31
/**
32
 * Modules controller: list, add, edit, remove objects
33
 *
34
 * @property \App\Controller\Component\CategoriesComponent $Categories
35
 * @property \App\Controller\Component\ChildrenComponent $Children
36
 * @property \App\Controller\Component\HistoryComponent $History
37
 * @property \App\Controller\Component\ObjectsEditorsComponent $ObjectsEditors
38
 * @property \App\Controller\Component\ParentsComponent $Parents
39
 * @property \App\Controller\Component\ProjectConfigurationComponent $ProjectConfiguration
40
 * @property \App\Controller\Component\PropertiesComponent $Properties
41
 * @property \App\Controller\Component\QueryComponent $Query
42
 * @property \App\Controller\Component\ThumbsComponent $Thumbs
43
 * @property \BEdita\WebTools\Controller\Component\ApiFormatterComponent $ApiFormatter
44
 */
45
class ModulesController extends AppController
46
{
47
    use ApiConfigTrait;
48
49
    /**
50
     * Object type currently used
51
     *
52
     * @var string|null
53
     */
54
    protected ?string $objectType = null;
55
56
    /**
57
     * @inheritDoc
58
     */
59
    public function initialize(): void
60
    {
61
        parent::initialize();
62
63
        $this->loadComponent('Children');
64
        $this->loadComponent('History');
65
        $this->loadComponent('ObjectsEditors');
66
        $this->loadComponent('Parents');
67
        $this->loadComponent('Properties');
68
        $this->loadComponent('ProjectConfiguration');
69
        $this->loadComponent('Query');
70
        $this->loadComponent('Thumbs', Configure::read('Thumbs', []));
71
        $this->loadComponent('BEdita/WebTools.ApiFormatter');
72
        if ($this->getRequest()->getParam('object_type')) {
73
            $this->objectType = $this->getRequest()->getParam('object_type');
74
            $this->Modules->setConfig('currentModuleName', $this->objectType);
75
            $this->Schema->setConfig('type', $this->objectType);
76
        }
77
        $this->FormProtection->setConfig('unlockedActions', ['save', 'setup']);
78
    }
79
80
    /**
81
     * {@inheritDoc}
82
     *
83
     * @codeCoverageIgnore
84
     */
85
    public function beforeRender(EventInterface $event): ?Response
86
    {
87
        $this->set('objectType', $this->objectType);
88
89
        return parent::beforeRender($event);
90
    }
91
92
    /**
93
     * Display resources list.
94
     *
95
     * @return \Cake\Http\Response|null
96
     */
97
    public function index(): ?Response
98
    {
99
        $this->getRequest()->allowMethod(['get']);
100
101
        // handle filter and query parameters using session
102
        $result = $this->applySessionFilter();
103
        if ($result != null) {
104
            return $result;
105
        }
106
107
        try {
108
            $params = $this->Query->index();
109
            $response = $this->apiClient->getObjects($this->objectType, $params);
110
            if (empty($params['q']) && empty($params['filter'])) {
111
                CacheTools::setModuleCount((array)$response, $this->Modules->getConfig('currentModuleName'));
112
            }
113
        } catch (BEditaClientException $e) {
114
            $this->log($e->getMessage(), LogLevel::ERROR);
115
            $this->Flash->error($e->getMessage(), ['params' => $e]);
116
            // remove session filter to avoid error repetition
117
            $session = $this->getRequest()->getSession();
118
            $session->delete(sprintf('%s.filter', $this->Modules->getConfig('currentModuleName')));
119
120
            return $this->redirect(['_name' => 'dashboard']);
121
        }
122
123
        $this->ProjectConfiguration->read();
124
125
        $response = $this->ApiFormatter->embedIncluded((array)$response);
126
        $objects = (array)Hash::get($response, 'data');
127
        $this->set('objects', $objects);
128
        $this->set('meta', (array)Hash::get($response, 'meta'));
129
        $this->set('links', (array)Hash::get($response, 'links'));
130
        $this->set('types', ['right' => $this->Schema->descendants($this->objectType)]);
131
132
        $this->set('properties', $this->Properties->indexList($this->objectType));
133
134
        // base/custom filters for filter view
135
        $this->set('filter', $this->Properties->filterList($this->objectType));
136
137
        // base/custom bulk actions for index view
138
        $this->set('bulkActions', $this->Properties->bulkList($this->objectType));
139
140
        // objectTypes schema
141
        $this->set('schema', $this->getSchemaForIndex($this->objectType));
142
143
        // custom properties
144
        $this->set('customProps', $this->Schema->customProps($this->objectType));
145
146
        // set all types (use cache)
147
        $this->set('allConcreteTypes', $this->Schema->allConcreteTypes());
148
149
        // set prevNext for views navigations
150
        $this->setObjectNav($objects);
151
152
        return null;
153
    }
154
155
    /**
156
     * View single resource.
157
     *
158
     * @param string|int $id Resource ID.
159
     * @return \Cake\Http\Response|null
160
     */
161
    public function view(string|int $id): ?Response
162
    {
163
        $this->getRequest()->allowMethod(['get']);
164
165
        try {
166
            $query = ['count' => 'all'];
167
            $response = $this->apiClient->getObject($id, $this->objectType, $query);
168
        } catch (BEditaClientException $e) {
169
            // Error! Back to index.
170
            $this->log($e->getMessage(), LogLevel::ERROR);
171
            $this->Flash->error(__('Error retrieving the requested content'), ['params' => $e]);
172
173
            return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]);
174
        }
175
        $this->ProjectConfiguration->read();
176
177
        $revision = Hash::get($response, 'meta.schema.' . $this->objectType . '.revision', null);
178
        $schema = $this->Schema->getSchema($this->objectType, $revision);
179
180
        $object = $response['data'];
181
182
        // setup `currentAttributes` and recover failure data from session.
183
        $this->Modules->setupAttributes($object);
184
185
        $included = !empty($response['included']) ? $response['included'] : [];
186
        $typeIncluded = Hash::combine($included, '{n}.id', '{n}', '{n}.type');
187
        $streams = Hash::get($typeIncluded, 'streams');
188
        $this->History->load($id, $object);
189
        $this->set(compact('object', 'included', 'schema', 'streams'));
190
        $this->set('properties', $this->Properties->viewGroups($object, $this->objectType));
191
        $this->set('foldersSchema', $this->Schema->getSchema('folders'));
192
193
        $computedRelations = array_reduce(
194
            array_keys($object['relationships']),
195
            function ($acc, $relName) use ($schema) {
196
                $acc[$relName] = (array)Hash::get($schema, sprintf('relations.%s', $relName), []);
197
198
                return $acc;
199
            },
200
            [],
201
        );
202
        $this->setupViewRelations($computedRelations);
203
204
        // set objectNav
205
        $objectNav = $this->getObjectNav((string)$id);
206
        $this->set('objectNav', $objectNav);
207
208
        $this->ObjectsEditors->update((string)$id);
209
210
        return null;
211
    }
212
213
    /**
214
     * View single resource by id, doing a proper redirect (302) to resource module view by type.
215
     * If no resource found by ID, redirect to referer.
216
     *
217
     * @param string|int $id Resource ID.
218
     * @return \Cake\Http\Response|null
219
     */
220
    public function uname(string|int $id): ?Response
221
    {
222
        try {
223
            $response = $this->apiClient->get(sprintf('/objects/%s', $id));
224
        } catch (BEditaClientException $e) {
225
            $msg = $e->getMessage();
226
            $msgNotFound = sprintf(__('Resource "%s" not found', true), $id);
227
            $msgNotAvailable = sprintf(__('Resource "%s" not available. Error: %s', true), $id, $msg);
228
            $error = $e->getCode() === 404 ? $msgNotFound : $msgNotAvailable;
229
            $this->Flash->error($error);
230
231
            return $this->redirect($this->referer());
232
        }
233
        $_name = 'modules:view';
234
        $object_type = $response['data']['type'];
235
        $id = $response['data']['id'];
236
237
        return $this->redirect(compact('_name', 'object_type', 'id'));
238
    }
239
240
    /**
241
     * Display new resource form.
242
     *
243
     * @return \Cake\Http\Response|null
244
     */
245
    public function create(): ?Response
246
    {
247
        $this->viewBuilder()->setTemplate('view');
248
249
        // Create stub object with empty `attributes`.
250
        $schema = $this->Schema->getSchema();
251
        if (!is_array($schema)) {
252
            $this->Flash->error(__('Cannot create abstract objects or objects without schema'));
253
254
            return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]);
255
        }
256
        $attributes = array_fill_keys(
257
            array_keys(
258
                array_filter(
259
                    $schema['properties'],
260
                    function ($schema) {
261
                        return empty($schema['readOnly']);
262
                    },
263
                ),
264
            ),
265
            null,
266
        );
267
        $object = [
268
            'type' => $this->objectType,
269
            'attributes' => $attributes,
270
        ];
271
272
        $this->set(compact('object', 'schema'));
273
        $this->set('properties', $this->Properties->viewGroups($object, $this->objectType));
274
        $this->ProjectConfiguration->read();
275
276
        $this->setupViewRelations((array)Hash::get($schema, 'relations'));
277
278
        return null;
279
    }
280
281
    /**
282
     * Create new object from ajax request.
283
     *
284
     * @return void
285
     */
286
    public function save(): void
287
    {
288
        $this->viewBuilder()->setClassName('Json'); // force json response
289
        $this->getRequest()->allowMethod(['post']);
290
        $requestData = $this->prepareRequest($this->objectType);
291
        unset($requestData['_csrfToken']);
292
        // extract related objects data
293
        $relatedData = (array)Hash::get($requestData, '_api');
294
        unset($requestData['_api']);
295
296
        try {
297
            $uname = Hash::get($requestData, 'uname');
298
            if (!empty($uname) && is_numeric($uname)) {
299
                $this->set(['error' => __('Invalid numeric uname. Change it to a valid string')]);
300
                $this->setSerialize(['error']);
301
302
                return;
303
            }
304
            $id = (string)Hash::get($requestData, 'id');
305
            // skip save if no data changed
306
            $schema = (array)$this->Schema->getSchema($this->objectType);
307
            $permissions = (array)Hash::get($requestData, 'permissions');
308
            $skipSaveObject = $this->Modules->skipSaveObject($id, $requestData);
309
            $skipSaveRelated = $this->Modules->skipSaveRelated($id, $relatedData);
310
            $skipSavePermissions = $this->Modules->skipSavePermissions($id, $permissions, $schema);
311
            if ($skipSaveObject && $skipSaveRelated && $skipSavePermissions) {
312
                $response = $this->apiClient->getObject($id, $this->objectType, ['count' => 'all']);
313
                $this->Thumbs->urls($response);
314
                $this->set((array)$response);
315
                $this->setSerialize(array_keys($response));
316
317
                return;
318
            }
319
320
            // upload file (if available)
321
            $this->Modules->upload($requestData);
322
323
            // save data
324
            $lang = I18n::getLocale();
325
            $headers = ['Accept-Language' => $lang];
326
            if (!$skipSaveObject) {
327
                $response = $this->apiClient->save($this->objectType, $requestData, $headers);
328
            } else {
329
                $response = $this->apiClient->getObject($id, $this->objectType);
330
            }
331
            if (!$skipSavePermissions) {
332
                try {
333
                    $this->savePermissions(
334
                        (array)$response,
335
                        $schema,
336
                        $permissions,
337
                    );
338
                } catch (BEditaClientException $error) {
339
                    $this->handleError($error);
340
                }
341
            }
342
            $id = (string)Hash::get($response, 'data.id');
343
            if (!$skipSaveRelated) {
344
                try {
345
                    $this->Modules->saveRelated($id, $this->objectType, $relatedData);
346
                } catch (BEditaClientException $error) {
347
                    $this->handleError($error);
348
                }
349
            }
350
            $options = [
351
                'id' => Hash::get($response, 'data.id'),
352
                'type' => $this->objectType,
353
                'data' => $requestData,
354
            ];
355
            $event = new Event('Controller.afterSave', $this, $options);
356
            $this->getEventManager()->dispatch($event);
357
        } catch (BEditaClientException $error) {
358
            $this->handleError($error);
359
360
            return;
361
        }
362
        if ($response['data']) {
363
            $response['data'] = [ $response['data'] ];
364
        }
365
366
        $this->Thumbs->urls($response);
367
368
        $this->set((array)$response);
369
        $this->setSerialize(array_keys($response));
370
    }
371
372
    /**
373
     * Handle exception error: log, flash and set.
374
     *
375
     * @param \BEdita\SDK\BEditaClientException $exception The exception
376
     * @return void
377
     */
378
    protected function handleError(BEditaClientException $exception): void
379
    {
380
        $message = new Message($exception);
381
        $this->log($message->get(), LogLevel::ERROR);
382
        $this->Flash->error($message->get(), ['params' => $exception]);
383
        $error = $this->viewBuilder()->getVar('error') ?? [];
384
        $error = is_array($error) ? array_merge($error, [$message->get()]) : [$error, $message->get()];
385
        $this->set(['error' => $error]);
386
        $this->setSerialize(['error']);
387
    }
388
389
    /**
390
     * Clone single object.
391
     *
392
     * @param string|int $id Object ID.
393
     * @return \Cake\Http\Response|null
394
     */
395
    public function clone(string|int $id): ?Response
396
    {
397
        $this->viewBuilder()->setTemplate('view');
398
        $schema = $this->Schema->getSchema();
399
        if (!is_array($schema)) {
400
            $this->Flash->error(__('Cannot create abstract objects or objects without schema'));
401
402
            return $this->redirect(['_name' => 'modules:list', 'object_type' => $this->objectType]);
403
        }
404
        try {
405
            $modified = [
406
                'title' => $this->getRequest()->getQuery('title'),
407
                'status' => 'draft',
408
            ];
409
            $reset = (array)Configure::read(sprintf('Clone.%s.reset', $this->objectType));
410
            foreach ($reset as $field) {
411
                $modified[$field] = null;
412
            }
413
            $included = [];
414
            foreach (['relationships', 'translations'] as $attribute) {
415
                if ($this->getRequest()->getQuery($attribute) === 'true') {
416
                    $included[] = $attribute;
417
                }
418
            }
419
            $clone = $this->apiClient->clone($this->objectType, $id, $modified, $included);
420
            $id = (string)Hash::get($clone, 'data.id');
421
        } catch (BEditaClientException $e) {
422
            $this->log($e->getMessage(), LogLevel::ERROR);
423
            $this->Flash->error($e->getMessage(), ['params' => $e]);
424
        }
425
426
        return $this->redirect(['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $id]);
427
    }
428
429
    /**
430
     * Delete single resource.
431
     *
432
     * @return \Cake\Http\Response|null
433
     */
434
    public function delete(): ?Response
435
    {
436
        $this->getRequest()->allowMethod(['post']);
437
        $id = $this->getRequest()->getData('id');
438
        $ids = $this->getRequest()->getData('ids');
439
        $ids = is_string($ids) ? explode(',', $ids) : $ids;
440
        $ids = empty($ids) ? [$id] : $ids;
441
        try {
442
            $this->apiClient->deleteObjects($ids, $this->objectType);
443
            $eventManager = $this->getEventManager();
444
            foreach ($ids as $id) {
445
                $event = new Event('Controller.afterDelete', $this, ['id' => $id, 'type' => $this->objectType]);
446
                $eventManager->dispatch($event);
447
            }
448
        } catch (BEditaClientException $e) {
449
            $this->log($e->getMessage(), LogLevel::ERROR);
450
            $this->Flash->error($e->getMessage(), ['params' => $e]);
451
            $id = $this->getRequest()->getData('id');
452
            $options = empty($id) ? $this->referer() : ['_name' => 'modules:view', 'object_type' => $this->objectType, 'id' => $id];
453
454
            return $this->redirect($options);
455
        }
456
        $this->Flash->success(__('Object(s) deleted'));
457
458
        return $this->redirect([
459
            '_name' => 'modules:list',
460
            'object_type' => $this->objectType,
461
        ]);
462
    }
463
464
    /**
465
     * Relation data load via API => `GET /:object_type/:id/related/:relation`
466
     *
467
     * @param string|int $id The object ID.
468
     * @param string $relation The relation name.
469
     * @return void
470
     */
471
    public function related(string|int $id, string $relation): void
472
    {
473
        $this->getRequest()->allowMethod(['get']);
474
        $this->viewBuilder()->setClassName('Json');
475
        if ($id === 'new') {
476
            $this->set('data', []);
477
            $this->setSerialize(['data']);
478
479
            return;
480
        }
481
        $query = $this->Query->prepare($this->getRequest()->getQueryParams());
482
        try {
483
            $response = $this->apiClient->getRelated($id, $this->objectType, $relation, $query);
484
            $response = $this->ApiFormatter->embedIncluded((array)$response);
485
        } catch (BEditaClientException $error) {
486
            $this->handleError($error);
487
488
            return;
489
        }
490
        $this->Thumbs->urls($response);
491
        $this->set((array)$response);
492
        $this->setSerialize(array_keys($response));
493
    }
494
495
    /**
496
     * Load resources of $type callig api `GET /:type/`
497
     * Json response
498
     *
499
     * @param string|int $id the object identifier.
500
     * @param string $type the resource type name.
501
     * @return void
502
     */
503
    public function resources(string|int $id, string $type): void
504
    {
505
        $this->getRequest()->allowMethod(['get']);
506
        $this->viewBuilder()->setClassName('Json');
507
        $query = $this->Query->prepare($this->getRequest()->getQueryParams());
508
        try {
509
            $response = $this->apiClient->get($type, $query);
510
        } catch (BEditaClientException $error) {
511
            $this->handleError($error);
512
513
            return;
514
        }
515
516
        $this->set((array)$response);
517
        $this->setSerialize(array_keys($response));
518
    }
519
520
    /**
521
     * Relation data load calling api `GET /:object_type/:id/relationships/:relation`
522
     * Json response
523
     *
524
     * @param string|int $id The object ID.
525
     * @param string $relation The relation name.
526
     * @return void
527
     */
528
    public function relationships(string|int $id, string $relation): void
529
    {
530
        $this->getRequest()->allowMethod(['get']);
531
        $this->viewBuilder()->setClassName('Json');
532
        $available = $this->availableRelationshipsUrl($relation);
533
534
        try {
535
            $query = $this->Query->prepare($this->getRequest()->getQueryParams());
536
            $response = $this->apiClient->get($available, $query);
537
538
            $this->Thumbs->urls($response);
539
        } catch (BEditaClientException $ex) {
540
            $this->log($ex->getMessage(), LogLevel::ERROR);
541
542
            $this->set('error', $ex->getMessage());
543
            $this->setSerialize(['error']);
544
545
            return;
546
        }
547
548
        $this->set((array)$response);
549
        $this->setSerialize(array_keys($response));
550
    }
551
552
    /**
553
     * Retrieve URL to get objects available for a relation
554
     *
555
     * @param string $relation The relation name.
556
     * @return string
557
     */
558
    protected function availableRelationshipsUrl(string $relation): string
559
    {
560
        $defaults = [
561
            'children' => '/objects',
562
            'parent' => '/folders',
563
            'parents' => '/folders',
564
        ];
565
        $defaultUrl = (string)Hash::get($defaults, $relation);
566
        if (!empty($defaultUrl)) {
567
            return $defaultUrl;
568
        }
569
570
        $relationsSchema = $this->Schema->getRelationsSchema();
571
        $types = $this->Modules->relatedTypes($relationsSchema, $relation);
572
573
        return count($types) === 1 ? sprintf('/%s', $types[0]) : '/objects?filter[type][]=' . implode('&filter[type][]=', $types);
574
    }
575
576
    /**
577
     * get object properties and format them for index
578
     *
579
     * @param string $objectType objecte type name
580
     * @return array $schema
581
     */
582
    public function getSchemaForIndex(string $objectType): array
583
    {
584
        $schema = (array)$this->Schema->getSchema($objectType);
585
586
        // if prop is an enum then prepend an empty string for select element
587
        if (!empty($schema['properties'])) {
588
            foreach ($schema['properties'] as &$property) {
589
                if (isset($property['enum'])) {
590
                    array_unshift($property['enum'], '');
591
                }
592
            }
593
        }
594
595
        return $schema;
596
    }
597
598
    /**
599
     * Get objectType
600
     *
601
     * @return string|null
602
     */
603
    public function getObjectType(): ?string
604
    {
605
        return $this->objectType;
606
    }
607
608
    /**
609
     * Set objectType
610
     *
611
     * @param string|null $objectType The object type
612
     * @return void
613
     */
614
    public function setObjectType(?string $objectType): void
615
    {
616
        $this->objectType = $objectType;
617
    }
618
619
    /**
620
     * Set schemasByType and filtersByType, considering relations and schemas.
621
     *
622
     * @param array $relations The relations
623
     * @return void
624
     */
625
    private function setupViewRelations(array $relations): void
626
    {
627
        // setup relations schema
628
        $relationsSchema = $this->Schema->getRelationsSchema();
629
        $this->set('relationsSchema', $relationsSchema);
630
631
        // setup relations metadata
632
        $this->Modules->setupRelationsMeta(
633
            $relationsSchema,
634
            $relations,
635
            $this->Properties->relationsList($this->objectType),
636
            $this->Properties->hiddenRelationsList($this->objectType),
637
            $this->Properties->readonlyRelationsList($this->objectType),
638
        );
639
640
        // set right types, considering the object type relations
641
        $rel = (array)$this->viewBuilder()->getVar('relationsSchema');
642
        $rightTypes = Schema::rightTypes($rel);
643
        $this->set('rightTypes', $rightTypes);
644
645
        // set schemas for relations right types
646
        $schemasByType = $this->Schema->getSchemasByType($rightTypes);
647
        $this->set('schemasByType', $schemasByType);
648
        $this->set('filtersByType', $this->Properties->filtersByType($rightTypes));
649
    }
650
651
    /**
652
     * Get list of users / no email, no relationships, no links, no schema, no included.
653
     *
654
     * @return void
655
     */
656
    public function users(): void
657
    {
658
        $this->viewBuilder()->setClassName('Json');
659
        $this->getRequest()->allowMethod('get');
660
        $query = array_merge(
661
            $this->getRequest()->getQueryParams(),
662
            ['fields' => 'id,title,username,name,surname,last_login'],
663
        );
664
        $response = (array)$this->apiClient->get('users', $query);
665
        $response = ApiTools::cleanResponse($response);
666
        $data = (array)Hash::get($response, 'data');
667
        $meta = (array)Hash::get($response, 'meta');
668
        $this->set(compact('data', 'meta'));
669
        $this->setSerialize(['data', 'meta']);
670
    }
671
672
    /**
673
     * Get single resource, minimal data / no relationships, no links, no schema, no included.
674
     *
675
     * @param string $id The object ID
676
     * @return void
677
     */
678
    public function get(string $id): void
679
    {
680
        $this->viewBuilder()->setClassName('Json');
681
        $this->getRequest()->allowMethod('get');
682
        $data = $meta = [];
683
        $query = $this->getRequest()->getQueryParams();
684
        $type = (string)Hash::get($query, 'type');
685
        $fields = array_unique(array_merge(
686
            explode(',', 'id,title,description,uname,status,media_url'),
687
            explode(',', (string)Hash::get($query, 'fields', '')),
688
        ));
689
        $query['fields'] = implode(',', $fields);
690
        if ($type == null) {
691
            $response = (array)$this->apiClient->getObject($id, 'objects', $query);
692
            $type = (string)Hash::get($response, 'data.type');
693
        }
694
        $filter = (array)$this->getRequest()->getQuery('filter');
695
        $types = (string)Hash::get($filter, 'type');
696
        $filterType = !empty($types) ? explode(',', (string)Hash::get($filter, 'type')) : [];
697
        if (count($filterType) === 0 || in_array($type, $filterType)) {
698
            $response = (array)$this->apiClient->getObject($id, $type, $query);
699
            $response = $this->ApiFormatter->embedIncluded($response);
700
            $stream = (array)Hash::get($response, 'data.relationships.streams.data.0', []);
701
            $response = ApiTools::cleanResponse($response);
702
            $data = (array)Hash::get($response, 'data');
703
            $data['attributes'] = array_merge($data['attributes'], (array)Hash::get($stream, 'attributes', []));
704
            $data['attributes'] = array_filter($data['attributes'], fn($key) => in_array($key, $fields), ARRAY_FILTER_USE_KEY);
705
            $meta = (array)Hash::get($response, 'meta');
706
            $meta = array_merge($meta, (array)Hash::get($stream, 'meta', []));
707
            $meta = array_filter($meta, fn($key) => in_array($key, $fields), ARRAY_FILTER_USE_KEY);
708
        }
709
        $this->set(compact('data', 'meta'));
710
        $this->setSerialize(['data', 'meta']);
711
    }
712
713
    /**
714
     * Setup module.
715
     *
716
     * @return \Cake\Http\Response|null
717
     */
718
    public function setup(): ?Response
719
    {
720
        /** @var \Authentication\Identity|null $user */
721
        $user = $this->Authentication->getIdentity();
722
        $roles = (array)$user->get('roles');
723
        if (!in_array('admin', $roles)) {
724
            throw new UnauthorizedException(__('You are not authorized to access here'));
725
        }
726
        $this->getRequest()->allowMethod(['get', 'post']);
727
        if ($this->getRequest()->is('post')) {
728
            $this->viewBuilder()->setClassName('Json');
729
            try {
730
                $requestData = $this->getRequest()->getData();
731
                $configurationKey = $requestData['configurationKey'] ?? null;
732
                unset($requestData['configurationKey']);
733
                $propertyName = explode('.', $configurationKey)[0];
734
                $subkey = explode('.', $configurationKey)[1];
735
                $propertyValue = (array)Configure::read($propertyName);
736
                $propertyValue = (array)Hash::insert($propertyValue, $subkey, $requestData);
737
                $this->saveApiConfig($propertyName, $propertyValue);
738
                $response = 'Configuration saved';
739
                $this->set('response', $response);
740
                $this->setSerialize(['response']);
741
            } catch (Exception $e) {
742
                $error = $e->getMessage();
743
                $this->set('error', $error);
744
                $this->setSerialize(['error']);
745
            }
746
        }
747
748
        return null;
749
    }
750
}
751