AdministrationBaseController::endpoint()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 7
c 0
b 0
f 0
rs 10
cc 2
nc 2
nop 0
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2022 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\Admin;
14
15
use App\Controller\AppController;
16
use BEdita\SDK\BEditaClientException;
17
use BEdita\WebTools\Utility\ApiTools;
18
use Cake\Event\EventInterface;
19
use Cake\Http\Exception\UnauthorizedException;
20
use Cake\Http\Response;
21
use Cake\Utility\Hash;
22
23
/**
24
 * Administration Controller
25
 */
26
abstract class AdministrationBaseController extends AppController
27
{
28
    /**
29
     * Endpoint
30
     *
31
     * @var string
32
     */
33
    protected string $endpoint = '/admin';
34
35
    /**
36
     * Resource type in use
37
     *
38
     * @var string|null
39
     */
40
    protected ?string $resourceType = null;
41
42
    /**
43
     * Readonly flag view.
44
     *
45
     * @var bool
46
     */
47
    protected bool $readonly = true;
48
49
    /**
50
     * Deleteonly flag view.
51
     *
52
     * @var bool
53
     */
54
    protected bool $deleteonly = false;
55
56
    /**
57
     * Properties to show in index columns
58
     *
59
     * @var array
60
     */
61
    protected array $properties = [];
62
63
    /**
64
     * Properties to json decode before save
65
     *
66
     * @var array
67
     */
68
    protected array $propertiesForceJson = [];
69
70
    /**
71
     * Properties that are secrets
72
     *
73
     * @var array
74
     */
75
    protected array $propertiesSecrets = [];
76
77
    /**
78
     * Meta to show in index columns
79
     *
80
     * @var array
81
     */
82
    protected array $meta = ['created', 'modified'];
83
84
    /**
85
     * Sort field
86
     *
87
     * @var string|null
88
     */
89
    protected ?string $sortBy = null;
90
91
    /**
92
     * @inheritDoc
93
     */
94
    public function initialize(): void
95
    {
96
        parent::initialize();
97
        $this->loadComponent('Properties');
98
    }
99
100
    /**
101
     * {@inheritDoc}
102
     *
103
     * Restrict `model` module access to `admin`
104
     */
105
    public function beforeFilter(EventInterface $event): ?Response
106
    {
107
        $res = parent::beforeFilter($event);
108
        if ($res !== null) {
109
            return $res;
110
        }
111
112
        /** @var \Authentication\Identity|null $user */
113
        $user = $this->Authentication->getIdentity();
114
        $roles = (array)$user->get('roles');
115
        if (empty($roles) || !in_array('admin', $roles)) {
116
            throw new UnauthorizedException(__('Module access not authorized'));
117
        }
118
119
        return null;
120
    }
121
122
    /**
123
     * Index method
124
     *
125
     * @return \Cake\Http\Response|null
126
     */
127
    public function index(): ?Response
128
    {
129
        $this->getRequest()->allowMethod(['get']);
130
        try {
131
            $response = $this->loadData();
132
        } catch (BEditaClientException $e) {
133
            $this->log($e->getMessage(), 'error');
134
            $this->Flash->error($e->getMessage(), ['params' => $e]);
135
136
            return $this->redirect(['_name' => 'dashboard']);
137
        }
138
139
        $this->set('resources', (array)Hash::get($response, 'data'));
140
        $this->set('meta', (array)Hash::get($response, 'meta'));
141
        $this->set('links', (array)Hash::get($response, 'links'));
142
        $this->set('resourceType', $this->resourceType);
143
        $this->set('properties', $this->properties);
144
        $this->set('propertiesSecrets', $this->propertiesSecrets);
145
        $this->set('metaColumns', $this->meta);
146
        $this->set('filter', []);
147
        $this->set('schema', (array)$this->Schema->getSchema($this->resourceType));
148
        $this->set('readonly', $this->readonly);
149
        $this->set('deleteonly', $this->deleteonly);
150
151
        return null;
152
    }
153
154
    /**
155
     * Save data
156
     *
157
     * @return \Cake\Http\Response|null
158
     */
159
    public function save(): ?Response
160
    {
161
        $this->getRequest()->allowMethod(['post']);
162
        $data = (array)$this->getRequest()->getData();
163
        $id = (string)Hash::get($data, 'id');
164
        $body = $this->prepareBody($data);
165
        $endpoint = $this->endpoint();
166
        try {
167
            if (empty($id)) {
168
                $this->apiClient->post($endpoint, json_encode($body));
169
            } else {
170
                $body['data']['id'] = $id;
171
                $this->apiClient->patch(sprintf('%s/%s', $endpoint, $id), json_encode($body));
172
            }
173
        } catch (BEditaClientException $e) {
174
            $this->log($e->getMessage(), 'error');
175
            $this->Flash->error($e->getMessage(), ['params' => $e]);
176
        }
177
178
        return $this->redirect(['_name' => sprintf('admin:list:%s', $this->resourceType)]);
179
    }
180
181
    /**
182
     * Remove roles by ID
183
     *
184
     * @param string $id The role ID
185
     * @return \Cake\Http\Response|null
186
     */
187
    public function remove(string $id): ?Response
188
    {
189
        $this->getRequest()->allowMethod(['post']);
190
        try {
191
            $this->apiClient->delete(sprintf('%s/%s', $this->endpoint(), $id));
192
        } catch (BEditaClientException $e) {
193
            $this->log($e->getMessage(), 'error');
194
            $this->Flash->error($e->getMessage(), ['params' => $e]);
195
        }
196
197
        return $this->redirect(['_name' => sprintf('admin:list:%s', $this->resourceType)]);
198
    }
199
200
    /**
201
     * Get endpoint by resource type and endpoint.
202
     * Roles => /roles
203
     * Other => /admin/:endpoint
204
     *
205
     * @return string
206
     */
207
    protected function endpoint(): string
208
    {
209
        if ($this->resourceType === 'roles') {
210
            return $this->endpoint;
211
        }
212
213
        return sprintf('%s/%s', $this->endpoint, $this->resourceType);
214
    }
215
216
    /**
217
     * Get all results iterating over pagination.
218
     *
219
     * @return array
220
     */
221
    protected function loadData(): array
222
    {
223
        $resourceEndpoint = sprintf('%s/%s', $this->endpoint, $this->resourceType);
224
        $endpoint = $this->resourceType === 'roles' ? 'roles' : $resourceEndpoint;
225
        $resultResponse = ['data' => []];
226
        $pageCount = $page = 1;
227
        $total = 0;
228
        $limit = 500;
229
        while ($limit > $total && $page <= $pageCount) {
230
            $response = (array)$this->apiClient->get($endpoint, compact('page') + ['page_size' => 100]);
231
            $response = ApiTools::cleanResponse($response);
232
            $resultResponse['data'] = array_merge(
233
                $resultResponse['data'],
234
                (array)Hash::get($response, 'data'),
235
            );
236
            $resultResponse['meta'] = Hash::get($response, 'meta');
237
            $pageCount = (int)Hash::get($response, 'meta.pagination.page_count');
238
            $count = (int)Hash::get($response, 'meta.pagination.page_items');
239
            $total += $count;
240
            $page++;
241
        }
242
        if ($this->sortBy != null && !empty($resultResponse['data'])) {
243
            $attributesKeys = array_keys($resultResponse['data'][0]['attributes'] ?? []);
244
            $metaKeys = array_keys($resultResponse['data'][0]['meta'] ?? []);
245
            if (in_array($this->sortBy, $attributesKeys) || in_array($this->sortBy, $metaKeys)) {
246
                $key = in_array($this->sortBy, $attributesKeys) ? 'attributes' : 'meta';
247
                usort($resultResponse['data'], function ($a, $b) use ($key) {
248
                    return strcmp(
249
                        (string)Hash::get($a, sprintf('%s.%s', $key, $this->sortBy)),
250
                        (string)Hash::get($b, sprintf('%s.%s', $key, $this->sortBy)),
251
                    );
252
                });
253
            }
254
        }
255
256
        return $resultResponse;
257
    }
258
259
    /**
260
     * Prepare body for request
261
     *
262
     * @param array $data The data
263
     * @return array
264
     */
265
    protected function prepareBody(array $data): array
266
    {
267
        foreach ($this->propertiesForceJson as $property) {
268
            $data[$property] = json_decode((string)Hash::get($data, $property), true);
269
        }
270
        $attributes = array_filter($data, function ($key) {
271
            return $key !== 'id';
272
        }, ARRAY_FILTER_USE_KEY);
273
274
        return [
275
            'data' => [
276
                'type' => $this->resourceType,
277
                'attributes' => $attributes,
278
            ],
279
        ];
280
    }
281
}
282