Passed
Pull Request — master (#853)
by Stefano
02:38
created

BulkController::moveToPosition()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 8
c 1
b 0
f 0
dl 0
loc 11
rs 10
cc 3
nc 3
nop 1
1
<?php
2
/**
3
 * BEdita, API-first content management framework
4
 * Copyright 2021 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\Core\Bulk\CustomBulkActionInterface;
16
use BEdita\SDK\BEditaClientException;
17
use Cake\Core\App;
18
use Cake\Http\Response;
19
use Cake\Utility\Hash;
20
use Psr\Log\LogLevel;
21
22
/**
23
 * Bulk Controller.
24
 */
25
class BulkController extends AppController
26
{
27
    /**
28
     * Object type currently used
29
     *
30
     * @var string
31
     */
32
    protected $objectType = null;
33
34
    /**
35
     * Selected objects IDs
36
     *
37
     * @var array
38
     */
39
    protected $ids = [];
40
41
    /**
42
     * Selected categories
43
     *
44
     * @var array|string
45
     */
46
    protected $categories = [];
47
48
    /**
49
     * Errors
50
     *
51
     * @var array
52
     */
53
    protected $errors = [];
54
55
    /**
56
     * @inheritDoc
57
     */
58
    public function initialize(): void
59
    {
60
        parent::initialize();
61
62
        $this->objectType = $this->getRequest()->getParam('object_type');
63
    }
64
65
    /**
66
     * Bulk change attribute value for selected ids.
67
     *
68
     * @return \Cake\Http\Response|null
69
     */
70
    public function attribute(): ?Response
71
    {
72
        $requestData = $this->getRequest()->getData();
73
        $this->ids = explode(',', (string)Hash::get($requestData, 'ids'));
74
        $this->saveAttribute($requestData['attributes']);
75
        $this->showResult();
76
77
        return $this->modulesListRedirect();
78
    }
79
80
    /**
81
     * Bulk custom action for selected ids.
82
     *
83
     * @return \Cake\Http\Response|null
84
     */
85
    public function custom(): ?Response
86
    {
87
        $requestData = $this->request->getData();
88
        $this->ids = explode(',', (string)Hash::get($requestData, 'ids'));
89
        $this->performCustomAction((string)Hash::get($requestData, 'custom_action'));
90
        $this->showResult();
91
92
        return $this->modulesListRedirect();
93
    }
94
95
    /**
96
     * Perform bulk action via custom class.
97
     *
98
     * @param string $bulkClass Custom action class name.
99
     * @return void
100
     */
101
    protected function performCustomAction(string $bulkClass): void
102
    {
103
        $class = App::className($bulkClass);
104
        if (empty($class)) {
105
            $this->errors[] = __('Custom action class {0} not found', $bulkClass);
106
107
            return;
108
        }
109
        $bulkAction = new $class();
110
        if (!$bulkAction instanceof CustomBulkActionInterface) {
111
            $this->errors[] = __('Custom action class {0} is not valid', $bulkClass);
112
113
            return;
114
        }
115
116
        $this->errors = $bulkAction->bulkAction($this->ids, $this->objectType);
117
    }
118
119
    /**
120
     * Redirect to modules index.
121
     *
122
     * @return \Cake\Http\Response|null
123
     */
124
    protected function modulesListRedirect(): ?Response
125
    {
126
        return $this->redirect([
127
            '_name' => 'modules:list',
128
            'object_type' => $this->objectType,
129
            '?' => $this->request->getQuery(),
130
        ]);
131
    }
132
133
    /**
134
     * Bulk associate categories to selected ids.
135
     *
136
     * @return \Cake\Http\Response|null
137
     */
138
    public function categories(): ?Response
139
    {
140
        $requestData = $this->getRequest()->getData();
141
        $this->ids = explode(',', (string)Hash::get($requestData, 'ids'));
142
        $this->categories = (string)Hash::get($requestData, 'categories');
143
        $this->loadCategories();
144
        $this->saveCategories();
145
        $this->showResult();
146
147
        return $this->modulesListRedirect();
148
    }
149
150
    /**
151
     * Bulk associate position in tree to selected ids.
152
     *
153
     * @return \Cake\Http\Response|null
154
     */
155
    public function position(): ?Response
156
    {
157
        $requestData = $this->getRequest()->getData();
158
        $this->ids = explode(',', (string)Hash::get($requestData, 'ids'));
159
        $folder = (string)Hash::get($requestData, 'folderSelected');
160
        $action = (string)Hash::get($requestData, 'action');
161
        if ($action === 'copy') {
162
            $this->copyToPosition($folder);
163
        } else { // move
164
            $this->moveToPosition($folder);
165
        }
166
        $this->showResult();
167
168
        return $this->modulesListRedirect();
169
    }
170
171
    /**
172
     * Save attribute for selected objects.
173
     *
174
     * @param array $attributes The attributes data
175
     * @return void
176
     */
177
    protected function saveAttribute(array $attributes): void
178
    {
179
        foreach ($this->ids as $id) {
180
            try {
181
                $this->apiClient->save($this->objectType, compact('id') + $attributes);
182
            } catch (BEditaClientException $e) {
183
                $this->errors[] = ['id' => $id, 'message' => $e->getAttributes()];
184
            }
185
        }
186
    }
187
188
    /**
189
     * Load categories by type and ids.
190
     *
191
     * @return void
192
     */
193
    protected function loadCategories(): void
194
    {
195
        $schema = (array)$this->Schema->getSchema($this->objectType);
196
        $schemaCategories = (array)Hash::extract($schema, 'categories');
197
        $ids = explode(',', (string)$this->categories);
198
        $this->categories = [];
199
        foreach ($schemaCategories as $schemaCategory) {
200
            if (in_array($schemaCategory['id'], $ids)) {
201
                $this->categories[] = $schemaCategory['name'];
202
            }
203
        }
204
    }
205
206
    /**
207
     * Save categories for selected objects.
208
     *
209
     * @return void
210
     */
211
    protected function saveCategories(): void
212
    {
213
        foreach ($this->ids as $id) {
214
            try {
215
                $object = $this->apiClient->getObject($id, $this->objectType, ['fields' => 'categories']);
216
                $objectCategories = (array)Hash::extract($object, 'data.attributes.categories.{n}.name');
217
                $this->categories = array_unique(array_merge((array)$this->categories, $objectCategories));
218
                $payload = compact('id');
219
                $payload['categories'] = $this->remapCategories((array)$this->categories);
220
                $this->apiClient->save($this->objectType, $payload);
221
            } catch (BEditaClientException $e) {
222
                $this->errors[] = ['id' => $id, 'message' => $e->getAttributes()];
223
            }
224
        }
225
    }
226
227
    /**
228
     * Remap categories, returning an array of items 'name':<category>
229
     *
230
     * @param array $input The input categories
231
     * @return array
232
     */
233
    protected function remapCategories(array $input): array
234
    {
235
        return array_map(function ($category) {
236
237
            return ['name' => $category];
238
        }, $input);
239
    }
240
241
    /**
242
     * Copy selected objects to position.
243
     *
244
     * @param string $position The folder ID
245
     * @return void
246
     */
247
    protected function copyToPosition(string $position): void
248
    {
249
        $ids = array_reverse($this->ids);
250
        foreach ($ids as $id) {
251
            try {
252
                $this->apiClient->addRelated($position, 'folders', 'children', [
253
                    [
254
                        'id' => $id,
255
                        'type' => $this->objectType,
256
                    ],
257
                ]);
258
            } catch (BEditaClientException $e) {
259
                $this->errors[] = ['id' => $id, 'message' => $e->getAttributes()];
260
            }
261
        }
262
    }
263
264
    /**
265
     * Move selected objects to position.
266
     *
267
     * @param string $position The folder ID
268
     * @return void
269
     */
270
    protected function moveToPosition(string $position): void
271
    {
272
        $ids = array_reverse($this->ids);
273
        foreach ($ids as $id) {
274
            try {
275
                $this->apiClient->replaceRelated($id, $this->objectType, 'parents', [
276
                    'id' => $position,
277
                    'type' => 'folders',
278
                ]);
279
            } catch (BEditaClientException $e) {
280
                $this->errors[] = ['id' => $id, 'message' => $e->getAttributes()];
281
            }
282
        }
283
    }
284
285
    /**
286
     * Display success or error messages.
287
     *
288
     * @return void
289
     */
290
    protected function showResult(): void
291
    {
292
        if (empty($this->errors)) {
293
            $this->Flash->success(__('Bulk action performed on {0} objects', count($this->ids)));
294
295
            return;
296
        }
297
        $this->log('Error: ' . json_encode($this->errors), LogLevel::ERROR);
298
        $this->Flash->error(__('Bulk Action failed on: '), ['params' => $this->errors]);
299
    }
300
301
    /**
302
     * Return errors.
303
     *
304
     * @return array
305
     * @codeCoverageIgnore
306
     */
307
    public function getErrors(): array
308
    {
309
        return $this->errors;
310
    }
311
}
312