Passed
Pull Request — master (#1298)
by Dante
01:33
created

AdministrationBaseController::loadData()   B

Complexity

Conditions 9
Paths 16

Size

Total Lines 38
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

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