Passed
Pull Request — master (#1296)
by Dante
01:44
created

ModulesController::handleError()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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