Passed
Pull Request — master (#1222)
by
unknown
01:37
created

TreeController::initialize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 5
rs 10
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2024 Atlas 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\Event\TreeCacheEventHandler;
16
use App\Utility\CacheTools;
17
use BEdita\SDK\BEditaClientException;
18
use BEdita\WebTools\ApiClientProvider;
19
use Cake\Cache\Cache;
20
use Cake\Event\Event;
21
use Cake\Utility\Hash;
22
use Psr\Log\LogLevel;
23
24
/**
25
 * Tree Controller: get tree data using cache
26
 */
27
class TreeController extends AppController
28
{
29
    public function initialize(): void
30
    {
31
        parent::initialize();
32
33
        $this->Security->setConfig('unlockedActions', ['slug']);
34
    }
35
    /**
36
     * Get tree data.
37
     * Use this for /tree?filter[roots]&... and /tree?filter[parent]=x&...
38
     * Use cache to store data.
39
     *
40
     * @return void
41
     */
42
    public function get(): void
43
    {
44
        $this->getRequest()->allowMethod(['get']);
45
        $this->viewBuilder()->setClassName('Json');
46
        $query = $this->getRequest()->getQueryParams();
47
        $tree = $this->treeData($query);
48
        $this->set('tree', $tree);
49
        $this->setSerialize(['tree']);
50
    }
51
52
    /**
53
     * Get node by ID.
54
     * Use cache to store data.
55
     *
56
     * @param string $id The ID.
57
     * @return void
58
     */
59
    public function node(string $id): void
60
    {
61
        $this->getRequest()->allowMethod(['get']);
62
        $this->viewBuilder()->setClassName('Json');
63
        $node = $this->fetchNodeData($id);
64
        $this->set('node', $node);
65
        $this->setSerialize(['node']);
66
    }
67
68
    /**
69
     * Get parent by ID.
70
     * Use cache to store data.
71
     *
72
     * @param string $id The ID.
73
     * @return void
74
     */
75
    public function parent(string $id): void
76
    {
77
        $this->getRequest()->allowMethod(['get']);
78
        $this->viewBuilder()->setClassName('Json');
79
        $parent = $this->fetchParentData($id);
80
        $this->set('parent', $parent);
81
        $this->setSerialize(['parent']);
82
    }
83
84
    /**
85
     * Get parents by ID and type.
86
     * Use cache to store data.
87
     *
88
     * @param string $type The type.
89
     * @param string $id The ID.
90
     * @return void
91
     */
92
    public function parents(string $type, string $id): void
93
    {
94
        $this->getRequest()->allowMethod(['get']);
95
        $this->viewBuilder()->setClassName('Json');
96
        $parents = $this->fetchParentsData($id, $type);
97
        $this->set('parents', $parents);
98
        $this->setSerialize(['parents']);
99
    }
100
101
    public function slug(): ?Response 
102
    {
103
        $this->getRequest()->allowMethod(['post']);
104
        $this->viewBuilder()->setClassName('Json');
105
        $response = $error = null;
106
        try {
107
            $data = (array)$this->getRequest()->getData();
108
            $parentId = $data['parent'];
109
            $newSlug = $data['slug'];
110
            $type = $data['type'];
111
            $id = $data['id'];
112
            $body = [
113
                'data' => [
114
                    [
115
                        'type' => $type,
116
                        'id' => $id,
117
                        'meta' => [
118
                            'relation' => [
119
                                'slug' => $newSlug,
120
                            ],
121
                        ],
122
                    ]
123
                ],
124
            ];
125
            $response = $this->apiClient->post(
126
                sprintf('/folders/%s/relationships/children', $parentId),
127
                json_encode($body)
128
            );
129
            // Clearing cache after successful save
130
            Cache::clearGroup('tree', TreeCacheEventHandler::CACHE_CONFIG);
131
        } catch (eBEditaClientException $err) {
132
            $error = $e->getMessage();
133
            $this->log($error, 'error');
134
            $this->set('error', $error);
135
        }
136
        $this->set('response', $response);
137
        $this->set('error', $error);
138
        $this->setSerialize(['response', 'error']);
139
140
        return null;
141
        
142
    }
143
144
    /**
145
     * Get tree data by query params.
146
     * Use cache to store data.
147
     *
148
     * @param array $query Query params.
149
     * @return array
150
     */
151
    public function treeData(array $query): array
152
    {
153
        $filter = Hash::get($query, 'filter', []);
154
        $subkey = !empty($filter['parent']) ? sprintf('parent-%s', $filter['parent']) : 'roots';
155
        $tmp = array_filter(
156
            $query,
157
            function ($key) {
158
                return $key !== 'filter';
159
            },
160
            ARRAY_FILTER_USE_KEY
161
        );
162
        $key = CacheTools::cacheKey(sprintf('tree-%s-%s', $subkey, md5(serialize($tmp))));
163
        $data = [];
164
        try {
165
            $data = Cache::remember(
166
                $key,
167
                function () use ($query) {
168
                    return $this->fetchTreeData($query);
169
                },
170
                TreeCacheEventHandler::CACHE_CONFIG
171
            );
172
        } catch (BEditaClientException $e) {
173
            // Something bad happened
174
            $this->log($e->getMessage(), LogLevel::ERROR);
175
176
            return [];
177
        }
178
179
        return $data;
180
    }
181
182
    /**
183
     * Get node from ID.
184
     * It uses cache to store data.
185
     *
186
     * @param string $id The ID.
187
     * @return array|null
188
     */
189
    public function fetchNodeData(string $id): ?array
190
    {
191
        $key = CacheTools::cacheKey(sprintf('tree-node-%s', $id));
192
        $data = [];
193
        try {
194
            $data = Cache::remember(
195
                $key,
196
                function () use ($id) {
197
                    $response = ApiClientProvider::getApiClient()->get(sprintf('/folders/%s', $id));
198
                    $data = (array)Hash::get($response, 'data');
199
200
                    return $this->minimalData($data);
201
                },
202
                TreeCacheEventHandler::CACHE_CONFIG
203
            );
204
        } catch (BEditaClientException $e) {
205
            // Something bad happened
206
            $this->log($e->getMessage(), LogLevel::ERROR);
207
208
            return [];
209
        }
210
211
        return $data;
212
    }
213
214
    /**
215
     * Get parent from ID.
216
     * It uses cache to store data.
217
     *
218
     * @param string $id The ID.
219
     * @return array|null
220
     */
221
    public function fetchParentData(string $id): ?array
222
    {
223
        $key = CacheTools::cacheKey(sprintf('tree-parent-%s', $id));
224
        $data = [];
225
        try {
226
            $data = Cache::remember(
227
                $key,
228
                function () use ($id) {
229
                    $response = ApiClientProvider::getApiClient()->get(sprintf('/folders/%s/parent', $id));
230
                    $data = (array)Hash::get($response, 'data');
231
232
                    return $this->minimalDataWithMeta($data);
233
                },
234
                TreeCacheEventHandler::CACHE_CONFIG
235
            );
236
        } catch (BEditaClientException $e) {
237
            // Something bad happened
238
            $this->log($e->getMessage(), LogLevel::ERROR);
239
240
            return [];
241
        }
242
243
        return $data;
244
    }
245
246
    /**
247
     * Get parent from ID.
248
     * It uses cache to store data.
249
     *
250
     * @param string $id The ID.
251
     * @param string $type The type.
252
     * @return array
253
     */
254
    public function fetchParentsData(string $id, string $type): array
255
    {
256
        $key = CacheTools::cacheKey(sprintf('tree-parents-%s-%s', $id, $type));
257
        $data = [];
258
        try {
259
            $data = Cache::remember(
260
                $key,
261
                function () use ($id, $type) {
262
                    $response = ApiClientProvider::getApiClient()->get(sprintf('/%s/%s?include=parents', $type, $id));
263
                    $included = (array)Hash::get($response, 'included');
264
                    foreach ($included as &$item) {
265
                        $item = $this->minimalDataWithMeta((array)$item);
266
                    }
267
268
                    return $included;
269
                },
270
                TreeCacheEventHandler::CACHE_CONFIG
271
            );
272
        } catch (BEditaClientException $e) {
273
            // Something bad happened
274
            $this->log($e->getMessage(), LogLevel::ERROR);
275
276
            return [];
277
        }
278
279
        return $data;
280
    }
281
282
    /**
283
     * Fetch tree data from API.
284
     * Retrieve minimal data for folders: id, status, title.
285
     * Return data and meta (no links, no included).
286
     *
287
     * @param array $query Query params.
288
     * @return array
289
     */
290
    protected function fetchTreeData(array $query): array
291
    {
292
        $fields = 'id,status,title,perms,relation,slug_path';
293
        $response = ApiClientProvider::getApiClient()->get('/folders', compact('fields') + $query);
294
        $data = (array)Hash::get($response, 'data');
295
        $meta = (array)Hash::get($response, 'meta');
296
        foreach ($data as &$item) {
297
            $item = $this->minimalData((array)$item);
298
        }
299
300
        return compact('data', 'meta');
301
    }
302
303
    /**
304
     * Get minimal data for object.
305
     *
306
     * @param array $fullData Full data.
307
     * @return array
308
     */
309
    protected function minimalData(array $fullData): array
310
    {
311
        if (empty($fullData)) {
312
            return [];
313
        }
314
        $meta = (array)Hash::get($fullData, 'meta');
315
        $meta['slug_path_compact'] = $this->slugPathCompact((array)Hash::get($meta, 'slug_path'));
316
317
        return [
318
            'id' => (string)Hash::get($fullData, 'id'),
319
            'type' => (string)Hash::get($fullData, 'type'),
320
            'attributes' => [
321
                'title' => (string)Hash::get($fullData, 'attributes.title'),
322
                'status' => (string)Hash::get($fullData, 'attributes.status'),
323
            ],
324
            'meta' => $meta,
325
        ];
326
    }
327
328
    /**
329
     * Get minimal data for object with meta.
330
     *
331
     * @param array $fullData Full data.
332
     * @return array|null
333
     */
334
    protected function minimalDataWithMeta(array $fullData): ?array
335
    {
336
        if (empty($fullData)) {
337
            return null;
338
        }
339
340
        return [
341
            'id' => (string)Hash::get($fullData, 'id'),
342
            'type' => (string)Hash::get($fullData, 'type'),
343
            'attributes' => [
344
                'title' => (string)Hash::get($fullData, 'attributes.title'),
345
                'status' => (string)Hash::get($fullData, 'attributes.status'),
346
            ],
347
            'meta' => [
348
                'path' => (string)Hash::get($fullData, 'meta.path'),
349
                'slug_path' => (array)Hash::get($fullData, 'meta.slug_path'),
350
                'slug_path_compact' => $this->slugPathCompact((array)Hash::get($fullData, 'meta.slug_path')),
351
                'relation' => [
352
                    'canonical' => (string)Hash::get($fullData, 'meta.relation.canonical'),
353
                    'depth_level' => (string)Hash::get($fullData, 'meta.relation.depth_level'),
354
                    'menu' => (string)Hash::get($fullData, 'meta.relation.menu'),
355
                    'slug' => (string)Hash::get($fullData, 'meta.relation.slug'),
356
                ],
357
            ],
358
        ];
359
    }
360
361
    /**
362
     * Get compact slug path.
363
     *
364
     * @param array $slugPath Slug path.
365
     * @return string
366
     */
367
    protected function slugPathCompact(array $slugPath): string
368
    {
369
        $slugPathCompact = '';
370
        foreach ($slugPath as $item) {
371
            $slugPathCompact = sprintf('%s/%s', $slugPathCompact, (string)Hash::get($item, 'slug'));
372
        }
373
374
        return $slugPathCompact;
375
    }
376
}
377