Passed
Push — master ( 10550a...7f54b7 )
by Dante
01:34
created

TreeController::fetchParentData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 23
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 14
nc 2
nop 1
dl 0
loc 23
rs 9.7998
c 0
b 0
f 0
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\Utility\Hash;
21
use Psr\Log\LogLevel;
22
23
/**
24
 * Tree Controller: get tree data using cache
25
 */
26
class TreeController extends AppController
27
{
28
    /**
29
     * Get tree data.
30
     * Use this for /tree?filter[roots]&... and /tree?filter[parent]=x&...
31
     * Use cache to store data.
32
     *
33
     * @return void
34
     */
35
    public function get(): void
36
    {
37
        $this->getRequest()->allowMethod(['get']);
38
        $this->viewBuilder()->setClassName('Json');
39
        $query = $this->getRequest()->getQueryParams();
40
        $tree = $this->treeData($query);
41
        $this->set('tree', $tree);
42
        $this->setSerialize(['tree']);
43
    }
44
45
    /**
46
     * Get node by ID.
47
     * Use cache to store data.
48
     *
49
     * @param string $id The ID.
50
     * @return void
51
     */
52
    public function node(string $id): void
53
    {
54
        $this->getRequest()->allowMethod(['get']);
55
        $this->viewBuilder()->setClassName('Json');
56
        $node = $this->fetchNodeData($id);
57
        $this->set('node', $node);
58
        $this->setSerialize(['node']);
59
    }
60
61
    /**
62
     * Get parent by ID.
63
     * Use cache to store data.
64
     *
65
     * @param string $id The ID.
66
     * @return void
67
     */
68
    public function parent(string $id): void
69
    {
70
        $this->getRequest()->allowMethod(['get']);
71
        $this->viewBuilder()->setClassName('Json');
72
        $parent = $this->fetchParentData($id);
73
        $this->set('parent', $parent);
74
        $this->setSerialize(['parent']);
75
    }
76
77
    /**
78
     * Get parents by ID and type.
79
     * Use cache to store data.
80
     *
81
     * @param string $type The type.
82
     * @param string $id The ID.
83
     * @return void
84
     */
85
    public function parents(string $type, string $id): void
86
    {
87
        $this->getRequest()->allowMethod(['get']);
88
        $this->viewBuilder()->setClassName('Json');
89
        $parents = $this->fetchParentsData($id, $type);
90
        $this->set('parents', $parents);
91
        $this->setSerialize(['parents']);
92
    }
93
94
    /**
95
     * Get tree data by query params.
96
     * Use cache to store data.
97
     *
98
     * @param array $query Query params.
99
     * @return array
100
     */
101
    public function treeData(array $query): array
102
    {
103
        $filter = Hash::get($query, 'filter', []);
104
        $subkey = !empty($filter['parent']) ? sprintf('parent-%s', $filter['parent']) : 'roots';
105
        $tmp = array_filter(
106
            $query,
107
            function ($key) {
108
                return $key !== 'filter';
109
            },
110
            ARRAY_FILTER_USE_KEY
111
        );
112
        $key = CacheTools::cacheKey(sprintf('tree-%s-%s', $subkey, md5(serialize($tmp))));
113
        $data = [];
114
        try {
115
            $data = Cache::remember(
116
                $key,
117
                function () use ($query) {
118
                    return $this->fetchTreeData($query);
119
                },
120
                TreeCacheEventHandler::CACHE_CONFIG
121
            );
122
        } catch (BEditaClientException $e) {
123
            // Something bad happened
124
            $this->log($e->getMessage(), LogLevel::ERROR);
125
126
            return [];
127
        }
128
129
        return $data;
130
    }
131
132
    /**
133
     * Get node from ID.
134
     * It uses cache to store data.
135
     *
136
     * @param string $id The ID.
137
     * @return array|null
138
     */
139
    public function fetchNodeData(string $id): ?array
140
    {
141
        $key = CacheTools::cacheKey(sprintf('tree-node-%s', $id));
142
        $data = [];
143
        try {
144
            $data = Cache::remember(
145
                $key,
146
                function () use ($id) {
147
                    $response = ApiClientProvider::getApiClient()->get(sprintf('/folders/%s', $id));
148
                    $data = (array)Hash::get($response, 'data');
149
150
                    return $this->minimalData($data);
151
                },
152
                TreeCacheEventHandler::CACHE_CONFIG
153
            );
154
        } catch (BEditaClientException $e) {
155
            // Something bad happened
156
            $this->log($e->getMessage(), LogLevel::ERROR);
157
158
            return [];
159
        }
160
161
        return $data;
162
    }
163
164
    /**
165
     * Get parent from ID.
166
     * It uses cache to store data.
167
     *
168
     * @param string $id The ID.
169
     * @return array|null
170
     */
171
    public function fetchParentData(string $id): ?array
172
    {
173
        $key = CacheTools::cacheKey(sprintf('tree-parent-%s', $id));
174
        $data = [];
175
        try {
176
            $data = Cache::remember(
177
                $key,
178
                function () use ($id) {
179
                    $response = ApiClientProvider::getApiClient()->get(sprintf('/folders/%s/parent', $id));
180
                    $data = (array)Hash::get($response, 'data');
181
182
                    return $this->minimalDataWithMeta($data);
183
                },
184
                TreeCacheEventHandler::CACHE_CONFIG
185
            );
186
        } catch (BEditaClientException $e) {
187
            // Something bad happened
188
            $this->log($e->getMessage(), LogLevel::ERROR);
189
190
            return [];
191
        }
192
193
        return $data;
194
    }
195
196
    /**
197
     * Get parent from ID.
198
     * It uses cache to store data.
199
     *
200
     * @param string $id The ID.
201
     * @param string $type The type.
202
     * @return array
203
     */
204
    public function fetchParentsData(string $id, string $type): array
205
    {
206
        $key = CacheTools::cacheKey(sprintf('tree-parents-%s-%s', $id, $type));
207
        $data = [];
208
        try {
209
            $data = Cache::remember(
210
                $key,
211
                function () use ($id, $type) {
212
                    $response = ApiClientProvider::getApiClient()->get(sprintf('/%s/%s?include=parents', $type, $id));
213
                    $included = (array)Hash::get($response, 'included');
214
                    foreach ($included as &$item) {
215
                        $item = $this->minimalData((array)$item);
216
                    }
217
218
                    return $included;
219
                },
220
                TreeCacheEventHandler::CACHE_CONFIG
221
            );
222
        } catch (BEditaClientException $e) {
223
            // Something bad happened
224
            $this->log($e->getMessage(), LogLevel::ERROR);
225
226
            return [];
227
        }
228
229
        return $data;
230
    }
231
232
    /**
233
     * Fetch tree data from API.
234
     * Retrieve minimal data for folders: id, status, title.
235
     * Return data and meta (no links, no included).
236
     *
237
     * @param array $query Query params.
238
     * @return array
239
     */
240
    protected function fetchTreeData(array $query): array
241
    {
242
        $fields = 'id,status,title';
243
        $response = ApiClientProvider::getApiClient()->get('/folders', compact('fields') + $query);
244
        $data = (array)Hash::get($response, 'data');
245
        $meta = (array)Hash::get($response, 'meta');
246
        foreach ($data as &$item) {
247
            $item = $this->minimalData((array)$item);
248
        }
249
250
        return compact('data', 'meta');
251
    }
252
253
    /**
254
     * Get minimal data for object.
255
     *
256
     * @param array $fullData Full data.
257
     * @return array
258
     */
259
    protected function minimalData(array $fullData): array
260
    {
261
        if (empty($fullData)) {
262
            return [];
263
        }
264
265
        return [
266
            'id' => (string)Hash::get($fullData, 'id'),
267
            'type' => (string)Hash::get($fullData, 'type'),
268
            'attributes' => [
269
                'title' => (string)Hash::get($fullData, 'attributes.title'),
270
                'status' => (string)Hash::get($fullData, 'attributes.status'),
271
            ],
272
        ];
273
    }
274
275
    /**
276
     * Get minimal data for object with meta.
277
     *
278
     * @param array $fullData Full data.
279
     * @return array|null
280
     */
281
    protected function minimalDataWithMeta(array $fullData): ?array
282
    {
283
        if (empty($fullData)) {
284
            return null;
285
        }
286
287
        return [
288
            'id' => (string)Hash::get($fullData, 'id'),
289
            'type' => (string)Hash::get($fullData, 'type'),
290
            'attributes' => [
291
                'title' => (string)Hash::get($fullData, 'attributes.title'),
292
                'status' => (string)Hash::get($fullData, 'attributes.status'),
293
            ],
294
            'meta' => [
295
                'path' => (string)Hash::get($fullData, 'meta.path'),
296
                'relation' => [
297
                    'canonical' => (string)Hash::get($fullData, 'meta.relation.canonical'),
298
                    'depth_level' => (string)Hash::get($fullData, 'meta.relation.depth_level'),
299
                    'menu' => (string)Hash::get($fullData, 'meta.relation.menu'),
300
                ],
301
            ],
302
        ];
303
    }
304
}
305