Test Failed
Push — master ( 656c0f...c8313f )
by Julien
15:26
created

Rest::getAction()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 3
Bugs 0 Features 0
Metric Value
eloc 11
c 3
b 0
f 0
dl 0
loc 17
ccs 0
cts 12
cp 0
rs 9.9
cc 4
nc 8
nop 1
crap 20
1
<?php
2
/**
3
 * This file is part of the Zemit Framework.
4
 *
5
 * (c) Zemit Team <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE.txt
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Zemit\Mvc\Controller;
12
13
use League\Csv\CharsetConverter;
14
use Phalcon\Events\Manager;
15
use Phalcon\Http\Response;
16
use Phalcon\Http\ResponseInterface;
17
use Phalcon\Mvc\Dispatcher;
18
use Phalcon\Mvc\Model\Resultset;
19
use League\Csv\Writer;
20
use Phalcon\Version;
21
use Zemit\Db\Profiler;
22
use Zemit\Di\Injectable;
23
use Zemit\Utils;
24
use Zemit\Utils\Slug;
25
26
/**
27
 * Class Rest
28
 *
29
 * @author Julien Turbide <[email protected]>
30
 * @copyright Zemit Team <[email protected]>
31
 *
32
 * @since 1.0
33
 * @version 1.0
34
 *
35
 *
36
 * @property Profiler $profiler
37
 * @package Zemit\Mvc\Controller
38
 */
39
class Rest extends \Zemit\Mvc\Controller
40
{
41
    use Model;
0 ignored issues
show
Bug introduced by
The trait Zemit\Mvc\Controller\Model requires the property $loader which is not provided by Zemit\Mvc\Controller\Rest.
Loading history...
42
    
43
    /**
44
     * Rest Bootstrap
45
     */
46
    public function indexAction($id = null)
47
    {
48
        $this->restForwarding($id);
49
    }
50
    
51
    /**
52
     * Rest bootstrap forwarding
53
     *
54
     * @return ResponseInterface
55
     */
56
    protected function restForwarding($id = null)
57
    {
58
        $id ??= $this->getParam('id');
59
        
60
        if ($this->request->isPost() || $this->request->isPut() || $this->request->isPatch()) {
61
            $this->dispatcher->forward(['action' => 'save']);
62
        }
63
        else if ($this->request->isDelete()) {
64
            $this->dispatcher->forward(['action' => 'delete']);
65
        }
66
        else if ($this->request->isGet()) {
67
            if (is_null($id)) {
68
                $this->dispatcher->forward(['action' => 'getList']);
69
            }
70
            else {
71
                $this->dispatcher->forward(['action' => 'get']);
72
            }
73
        }
74
    }
75
    
76
    /**
77
     * Retrieving a single record
78
     * Alias of method getAction()
79
     *
80
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
81
     * @deprecated Should use getAction() method instead
82
     * @return bool|ResponseInterface
83
     */
84
    public function getSingleAction($id = null)
85
    {
86
        return $this->getAction($id);
87
    }
88
    
89
    /**
90
     * Retrieving a single record
91
     *
92
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
93
     *
94
     * @return bool|ResponseInterface
95
     */
96
    public function getAction($id = null)
97
    {
98
        $modelName = $this->getModelClassName();
99
        $single = $this->getSingle($id, $modelName, null);
100
        
101
        $ret = [];
102
        $ret['single'] = $single ? $this->expose($single) : false;
103
        $ret['model'] = $modelName;
104
        $ret['source'] = $single ? $single->getSource() : false;
0 ignored issues
show
Bug introduced by
The method getSource() does not exist on Phalcon\Mvc\Model\Resultset. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

104
        $ret['source'] = $single ? $single->/** @scrutinizer ignore-call */ getSource() : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
105
        $this->view->setVars($ret);
0 ignored issues
show
Bug introduced by
The method setVars() does not exist on Phalcon\Mvc\ViewInterface. Did you maybe mean setVar()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

105
        $this->view->/** @scrutinizer ignore-call */ 
106
                     setVars($ret);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
106
        
107
        if (!$single) {
108
            $this->response->setStatusCode(404, 'Not Found');
109
            return false;
110
        }
111
        
112
        return $this->setRestResponse((bool)$single);
0 ignored issues
show
Bug introduced by
(bool)$single of type boolean is incompatible with the type array|null expected by parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

112
        return $this->setRestResponse(/** @scrutinizer ignore-type */ (bool)$single);
Loading history...
113
    }
114
    
115
    /**
116
     * Retrieving a record list
117
     * Alias of method getListAction()
118
     *
119
     * @deprecated Should use getListAction() method instead
120
     * @return ResponseInterface
121
     * @throws \Exception
122
     */
123
    public function getAllAction()
124
    {
125
        return $this->getListAction();
126
    }
127
    
128
    /**
129
     * Retrieving a record list
130
     *
131
     * @return ResponseInterface
132
     * @throws \Exception
133
     */
134
    public function getListAction()
135
    {
136
        $model = $this->getModelClassName();
137
        
138
        /** @var Resultset $with */
139
        $find = $this->getFind();
140
        $with = $model::with($this->getListWith() ?: [], $find ?: []);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getListWith() targeting Zemit\Mvc\Controller\Rest::getListWith() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
141
        $list = $this->listExpose($with);
142
143
//        $list = is_array($list) ? array_values(array_filter($list)) : $list; // @todo do we really need to do this?
144
        $this->view->list = $list;
145
        $this->view->listCount = count($list);
146
        $this->view->totalCount = $model::find($this->getFindCount($find));
147
        $this->view->totalCount = is_int($this->view->totalCount) ? $this->view->totalCount : count($this->view->totalCount); // @todo fix count to work with rollup when joins
148
        $this->view->limit = $find['limit'] ?? false;
149
        $this->view->offset = $find['offset'] ?? false;
150
        $this->view->find = ($this->config->app->debug || $this->config->debug->enable) ? $find : false;
0 ignored issues
show
Bug Best Practice introduced by
The property enable does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
151
        
152
        return $this->setRestResponse();
153
    }
154
    
155
    /**
156
     * Exporting a record list into a CSV stream
157
     *
158
     * @return ResponseInterface|null
159
     * @throws \Exception
160
     */
161
    public function exportAction()
162
    {
163
        $model = $this->getModelClassName();
164
        
165
        $find = $this->getFind();
166
        $with = $model::with($this->getExportWith() ?: [], $find ?: []);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExportWith() targeting Zemit\Mvc\Controller\Rest::getExportWith() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
167
        $list = $this->exportExpose($with);
168
        
169
        $this->flatternArrayForCsv($list);
170
        $this->formatColumnText($list);
171
        
172
        return $this->download($list);
173
    }
174
    
175
    /**
176
     * Download a JSON / CSV / XLSX
177
     * @param $list
178
     * @param $fileName
179
     * @param $contentType
180
     * @param $params
181
     * @return ResponseInterface|void
182
     * @throws \League\Csv\CannotInsertRecord
183
     * @throws \League\Csv\InvalidArgument
184
     * @throws \Zemit\Exception
185
     */
186
    public function download($list = [], $fileName = null, $contentType = null, $params = null) {
187
        $params ??= $this->getParams();
188
        $contentType ??= $this->getContentType();
189
        $fileName ??= ucfirst(Slug::generate(basename(str_replace('\\', '/', $this->getModelClassName())))) . ' List (' . date('Y-m-d') . ')';
190
        
191
        if ($contentType === 'json') {
192
//            $this->response->setJsonContent($list);
193
            $this->response->setContent(json_encode($list, JSON_PRETTY_PRINT, 2048));
194
            $this->response->setContentType('application/json');
195
            $this->response->setHeader('Content-disposition', 'attachment; filename="' . addslashes($fileName) . '.json"');
196
            return $this->response->send();
197
        }
198
    
199
        // CSV
200
        if ($contentType === 'csv') {
201
        
202
            // Get CSV custom request parameters
203
            $mode = $params['mode'] ?? null;
204
            $delimiter = $params['delimiter'] ?? null;
205
            $newline = $params['newline'] ?? null;
206
            $escape = $params['escape'] ?? null;
207
            $outputBOM = $params['outputBOM'] ?? null;
208
            $skipIncludeBOM = $params['skipIncludeBOM'] ?? null;
209
210
//            $csv = Writer::createFromFileObject(new \SplTempFileObject());
211
            $csv = Writer::createFromStream(fopen('php://memory', 'r+'));
212
        
213
            // CSV - MS Excel on MacOS
214
            if ($mode === 'mac') {
215
                $csv->setOutputBOM(Writer::BOM_UTF16_LE); // utf-16
216
                $csv->setDelimiter("\t"); // tabs separated
217
                $csv->setNewline("\r\n"); // new lines
218
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-16');
219
            }
220
        
221
            // CSV - MS Excel on Windows
222
            else {
223
                $csv->setOutputBOM(Writer::BOM_UTF8); // utf-8
224
                $csv->setDelimiter(','); // comma separated
225
                $csv->setNewline("\n"); // new line windows
226
                CharsetConverter::addTo($csv, 'UTF-8', 'UTF-8');
227
            }
228
        
229
            // Apply forced params from request
230
            if (isset($outputBOM)) {
231
                $csv->setOutputBOM($outputBOM);
232
            }
233
            if (isset($delimiter)) {
234
                $csv->setDelimiter($delimiter);
235
            }
236
            if (isset($newline)) {
237
                $csv->setNewline($newline);
238
            }
239
            if (isset($escape)) {
240
                $csv->setEscape($escape);
241
            }
242
            if ($skipIncludeBOM) {
243
                $csv->skipInputBOM();
244
            }
245
            else {
246
                $csv->includeInputBOM();
247
            }
248
        
249
            // CSV
250
            if (isset($list[0])) {
251
                $csv->insertOne(array_keys($list[0]));
252
                $csv->insertAll($list);
253
            }
254
            $csv->output($fileName . '.csv');
255
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
256
        }
257
    
258
        // XLSX
259
        if ($contentType === 'xlsx') {
260
            $xlsxArray = [];
261
            if (isset($list[0])) {
262
                $xlsxArray [] = array_keys($list[0]);
263
            }
264
            foreach ($list as $array) {
265
                $xlsxArray [] = array_values($array);
266
            }
267
            $xlsx = \SimpleXLSXGen::fromArray($xlsxArray);
0 ignored issues
show
Bug introduced by
The type SimpleXLSXGen was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
268
            $xlsx->downloadAs($fileName . '.xlsx');
269
            die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
270
        }
271
        
272
        // Something went wrong
273
        throw new \Exception('Failed to export `' . $this->getModelClassName() . '` using content-type `' . $contentType . '`', 400);
274
    }
275
    
276
    /**
277
     * Expose a single model
278
     */
279
    public function expose(\Zemit\Mvc\Model $item, $expose = null)
280
    {
281
        $expose ??= $this->getExpose();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExpose() targeting Zemit\Mvc\Controller\Rest::getExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
282
        return $item->expose($expose);
283
    }
284
    
285
    /**
286
     * Expose a list of model
287
     */
288
    public function listExpose(iterable $items, $listExpose = null): array
289
    {
290
        $listExpose ??= $this->getListExpose();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getListExpose() targeting Zemit\Mvc\Controller\Rest::getListExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
291
        $ret = [];
292
        
293
        /** @var \Zemit\Mvc\Model $item */
294
        foreach ($items as $item) {
295
            $ret [] = $this->expose($item, $listExpose);
296
        }
297
        
298
        return $ret;
299
    }
300
    
301
    /**
302
     * Expose a list of model
303
     */
304
    public function exportExpose(iterable $items, $exportExpose = null): array
305
    {
306
        $exportExpose ??= $this->getExportExpose();
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getExportExpose() targeting Zemit\Mvc\Controller\Rest::getExportExpose() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
307
        return $this->listExpose($items, $exportExpose);
308
    }
309
    
310
    /**
311
     * @param array|null $array
312
     *
313
     * @return array|null
314
     */
315
    public function flatternArrayForCsv(?array &$list = null)
316
    {
317
        
318
        foreach ($list as $listKey => $listValue) {
319
            foreach ($listValue as $column => $value) {
320
                if (is_array($value) || is_object($value)) {
321
                    $value = $this->concatListFieldElementForCsv($value, ' ');
322
                    $list[$listKey][$column] = $this->arrayFlatten($value, $column);
0 ignored issues
show
Bug introduced by
It seems like $value can also be of type object; however, parameter $array of Zemit\Mvc\Controller\Rest::arrayFlatten() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

322
                    $list[$listKey][$column] = $this->arrayFlatten(/** @scrutinizer ignore-type */ $value, $column);
Loading history...
323
                    if (is_array($list[$listKey][$column])) {
324
                        foreach ($list[$listKey][$column] as $childKey => $childValue) {
325
                            $list[$listKey][$childKey] = $childValue;
326
                            unset ($list[$listKey][$column]);
0 ignored issues
show
Coding Style introduced by
Space before opening parenthesis of function call prohibited
Loading history...
327
                        }
328
                    }
329
                }
330
            }
331
        }
332
    }
333
    
334
    /**
335
     * @param array|object $list
336
     * @param string|null $seperator
337
     *
338
     * @return array|object
339
     */
340
    public function concatListFieldElementForCsv($list, $seperator = ' ')
341
    {
342
        foreach ($list as $valueKey => $element) {
343
            if (is_array($element) || is_object($element)) {
344
                $lastKey = array_key_last($list);
0 ignored issues
show
Bug introduced by
It seems like $list can also be of type object; however, parameter $array of array_key_last() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

344
                $lastKey = array_key_last(/** @scrutinizer ignore-type */ $list);
Loading history...
345
                if ($valueKey === $lastKey) {
346
                    continue;
347
                }
348
                foreach ($element as $elKey => $elValue) {
349
                    $list[$lastKey][$elKey] .= $seperator . $elValue;
350
                    if ($lastKey != $valueKey) {
351
                        unset($list[$valueKey]);
352
                    }
353
                }
354
            }
355
        }
356
        
357
        return $list;
358
    }
359
    
360
    /**
361
     * @param array|null $array
362
     * @param string|null $alias
363
     *
364
     * @return array|null
365
     */
366
    function arrayFlatten(?array $array, ?string $alias = null)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for arrayFlatten.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
367
    {
368
        $return = [];
369
        foreach ($array as $key => $value) {
370
            if (is_array($value)) {
371
                $return = array_merge($return, $this->arrayFlatten($value, $alias));
372
            }
373
            else {
374
                $return[$alias . '.' . $key] = $value;
375
            }
376
        }
377
        return $return;
378
    }
379
    
380
    /**
381
     * @param array|null $listValue
382
     *
383
     * @return array|null
384
     */
385
    public function mergeColumns(?array $listValue)
386
    {
387
        $columnToMergeList = $this->getExportMergeColum();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $columnToMergeList is correct as $this->getExportMergeColum() targeting Zemit\Mvc\Controller\Rest::getExportMergeColum() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
388
        if (!$columnToMergeList || empty($columnToMergeList)) {
0 ignored issues
show
introduced by
$columnToMergeList is of type null, thus it always evaluated to false.
Loading history...
389
            return $listValue;
390
        }
391
        
392
        $columnList = [];
393
        foreach ($columnToMergeList as $columnToMerge) {
394
            foreach ($columnToMerge['columns'] as $column) {
395
                if (isset($listValue[$column])) {
396
                    $columnList[$columnToMerge['name']][] = $listValue[$column];
397
                    unset($listValue[$column]);
398
                }
399
            }
400
            $listValue[$columnToMerge['name']] = implode(' ', $columnList[$columnToMerge['name']] ?? []);
401
        }
402
        
403
        return $listValue;
404
    }
405
    
406
    /**
407
     * @param array|null $list
408
     *
409
     * @return array|null
410
     */
411
    public function formatColumnText(?array &$list)
412
    {
413
        foreach ($list as $listKey => $listValue) {
414
            
415
            $mergeColumArray = $this->mergeColumns($listValue);
416
            if (!empty($mergeColumArray)) {
417
                $list[$listKey] = $mergeColumArray;
418
            }
419
            
420
            $formatArray = $this->getExportFormatFieldText($listValue);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $formatArray is correct as $this->getExportFormatFieldText($listValue) targeting Zemit\Mvc\Controller\Res...ExportFormatFieldText() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
421
            if ($formatArray) {
422
                $columNameList = array_keys($formatArray);
0 ignored issues
show
Bug introduced by
$formatArray of type void is incompatible with the type array expected by parameter $array of array_keys(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

422
                $columNameList = array_keys(/** @scrutinizer ignore-type */ $formatArray);
Loading history...
423
                foreach ($formatArray as $formatKey => $formatValue) {
0 ignored issues
show
Bug introduced by
The expression $formatArray of type void is not traversable.
Loading history...
424
                    if (isset($formatValue['text'])) {
425
                        $list[$listKey][$formatKey] = $formatValue['text'];
426
                    }
427
                    
428
                    if (isset($formatValue['rename'])) {
429
                        
430
                        $list[$listKey][$formatValue['rename']] = $formatValue['text'] ?? ($list[$listKey][$formatKey] ?? null);
431
                        if ($formatValue['rename'] !== $formatKey) {
432
                            foreach ($columNameList as $columnKey => $columnValue) {
433
                                
434
                                if ($formatKey === $columnValue) {
435
                                    $columNameList[$columnKey] = $formatValue['rename'];
436
                                }
437
                            }
438
                            
439
                            unset($list[$listKey][$formatKey]);
440
                        }
441
                    }
442
                }
443
                
444
                if (isset($formatArray['reorderColumns']) && $formatArray['reorderColumns']) {
445
                    $list[$listKey] = $this->arrayCustomOrder($list[$listKey], $columNameList);
446
                }
447
            }
448
        }
449
        
450
        return $list;
451
    }
452
    
453
    /**
454
     * @param array $arrayToOrder
455
     * @param array $orderList
456
     *
457
     * @return array
458
     */
459
    function arrayCustomOrder($arrayToOrder, $orderList)
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
Comprehensibility Best Practice introduced by
It is recommend to declare an explicit visibility for arrayCustomOrder.

Generally, we recommend to declare visibility for all methods in your source code. This has the advantage of clearly communication to other developers, and also yourself, how this method should be consumed.

If you are not sure which visibility to choose, it is a good idea to start with the most restrictive visibility, and then raise visibility as needed, i.e. start with private, and only raise it to protected if a sub-class needs to have access, or public if an external class needs access.

Loading history...
460
    {
461
        $ordered = [];
462
        foreach ($orderList as $key) {
463
            if (array_key_exists($key, $arrayToOrder)) {
464
                $ordered[$key] = $arrayToOrder[$key];
465
            }
466
        }
467
        return $ordered;
468
    }
469
    
470
    /**
471
     * Count a record list
472
     * @TODO add total count / deleted count / active count
473
     *
474
     * @return ResponseInterface
475
     */
476
    public function countAction()
477
    {
478
        $model = $this->getModelClassName();
479
        
480
        /** @var \Zemit\Mvc\Model $entity */
481
        $entity = new $model();
482
        
483
        $this->view->totalCount = $model::count($this->getFindCount($this->getFind()));
484
        $this->view->totalCount = is_int($this->view->totalCount) ? $this->view->totalCount : count($this->view->totalCount);
485
        $this->view->model = get_class($entity);
486
        $this->view->source = $entity->getSource();
487
        
488
        return $this->setRestResponse();
489
    }
490
    
491
    /**
492
     * Prepare a new model for the frontend
493
     *
494
     * @return ResponseInterface
495
     */
496
    public function newAction()
497
    {
498
        $model = $this->getModelClassName();
499
        
500
        /** @var \Zemit\Mvc\Model $entity */
501
        $entity = new $model();
502
        $entity->assign($this->getParams(), $this->getWhiteList(), $this->getColumnMap());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getWhiteList() targeting Zemit\Mvc\Controller\Rest::getWhiteList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure the usage of $this->getColumnMap() targeting Zemit\Mvc\Controller\Rest::getColumnMap() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
503
        
504
        $this->view->model = get_class($entity);
505
        $this->view->source = $entity->getSource();
506
        $this->view->single = $this->expose($entity);
507
        
508
        return $this->setRestResponse();
509
    }
510
    
511
    /**
512
     * Prepare a new model for the frontend
513
     *
514
     * @return ResponseInterface
515
     */
516
    public function validateAction($id = null)
517
    {
518
        $model = $this->getModelClassName();
519
        
520
        /** @var \Zemit\Mvc\Model $entity */
521
        $entity = $this->getSingle($id);
522
        $new = !$entity;
0 ignored issues
show
introduced by
$entity is of type Zemit\Mvc\Model, thus it always evaluated to true.
Loading history...
523
        
524
        if ($new) {
0 ignored issues
show
introduced by
The condition $new is always false.
Loading history...
525
            $entity = new $model();
526
        }
527
        
528
        $entity->assign($this->getParams(), $this->getWhiteList(), $this->getColumnMap());
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getColumnMap() targeting Zemit\Mvc\Controller\Rest::getColumnMap() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
Are you sure the usage of $this->getWhiteList() targeting Zemit\Mvc\Controller\Rest::getWhiteList() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
529
        
530
        /**
531
         * Event to run
532
         * @see https://docs.phalcon.io/4.0/en/db-models-events
533
         */
534
        $events = [
535
            'beforeCreate' => null,
536
            'beforeUpdate' => null,
537
            'beforeSave' => null,
538
            'beforeValidationOnCreate' => null,
539
            'beforeValidationOnUpdate' => null,
540
            'beforeValidation' => null,
541
            'prepareSave' => null,
542
            'validation' => null,
543
            'afterValidationOnCreate' => null,
544
            'afterValidationOnUpdate' => null,
545
            'afterValidation' => null,
546
        ];
547
        
548
        // run events, as it would normally
549
        foreach ($events as $event => $state) {
550
            $this->skipped = false;
0 ignored issues
show
Bug Best Practice introduced by
The property skipped does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
551
            
552
            // skip depending wether it's a create or update
553
            if (strpos($event, $new ? 'Update' : 'Create') !== false) {
554
                continue;
555
            }
556
            
557
            // fire the event, allowing to fail or skip
558
            $events[$event] = $entity->fireEventCancel($event);
559
            if ($events[$event] === false) {
560
                // event failed
561
                break;
562
            }
563
            
564
            // event was skipped, just for consistencies purpose
565
            if ($this->skipped) {
566
                continue;
567
            }
568
        }
569
        
570
        $this->view->model = get_class($entity);
571
        $this->view->source = $entity->getSource();
572
        $this->view->single = $this->expose($entity);
573
        $this->view->messages = $entity->getMessages();
574
        $this->view->events = $events;
575
        $this->view->validated = empty($this->view->messages);
576
        
577
        return $this->setRestResponse($this->view->validated);
0 ignored issues
show
Bug introduced by
$this->view->validated of type boolean is incompatible with the type array|null expected by parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

577
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $this->view->validated);
Loading history...
578
    }
579
    
580
    /**
581
     * Saving a record (create & update)
582
     *
583
     * @param null|int $id
584
     *
585
     * @return ResponseInterface
586
     */
587
    public function saveAction($id = null)
588
    {
589
        $ret = $this->save($id);
590
        $this->view->setVars($ret);
591
        
592
        return $this->setRestResponse($ret['saved']);
593
    }
594
    
595
    /**
596
     * Deleting a record
597
     *
598
     * @param null|int $id
599
     *
600
     * @return bool|ResponseInterface
601
     */
602
    public function deleteAction($id = null)
603
    {
604
        $entity = $this->getSingle($id);
605
        
606
        $ret = [];
607
        $ret['deleted'] = $entity && $entity->delete();
608
        $ret['single'] = $entity ? $this->expose($entity) : false;
609
        $ret['messages'] = $entity ? $entity->getMessages() : false;
610
        $this->view->setVars($ret);
611
        
612
        if (!$entity) {
613
            $this->response->setStatusCode(404, 'Not Found');
614
            return false;
615
        }
616
        
617
        return $this->setRestResponse($ret['deleted']);
0 ignored issues
show
Bug introduced by
$ret['deleted'] of type boolean is incompatible with the type array|null expected by parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

617
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $ret['deleted']);
Loading history...
618
    }
619
    
620
    /**
621
     * Restoring record
622
     *
623
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
624
     *
625
     * @return bool|ResponseInterface
626
     */
627
    public function restoreAction($id = null)
628
    {
629
        $entity = $this->getSingle($id);
630
    
631
        $ret = [];
632
        $ret['restored'] = $entity && $entity->restore();
0 ignored issues
show
Bug introduced by
The method restore() does not exist on Phalcon\Mvc\Model\Resultset. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

632
        $ret['restored'] = $entity && $entity->/** @scrutinizer ignore-call */ restore();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
633
        $ret['single'] = $entity ? $this->expose($entity) : false;
634
        $ret['messages'] = $entity ? $entity->getMessages() : false;
635
        $this->view->setVars($ret);
636
        
637
        if (!$entity) {
638
            $this->response->setStatusCode(404, 'Not Found');
639
            return false;
640
        }
641
        
642
        return $this->setRestResponse($ret['restored']);
0 ignored issues
show
Bug introduced by
$ret['restored'] of type boolean is incompatible with the type array|null expected by parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

642
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $ret['restored']);
Loading history...
643
    }
644
    
645
    /**
646
     * Re-ordering a position
647
     *
648
     * @param null $id
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $id is correct as it would always require null to be passed?
Loading history...
649
     * @param null $position
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $position is correct as it would always require null to be passed?
Loading history...
650
     *
651
     * @return bool|ResponseInterface
652
     */
653
    public function reorderAction($id = null, $position = null)
654
    {
655
        $entity = $this->getSingle($id);
656
        $position = $this->getParam('position', 'int', $position);
657
        
658
        $ret = [];
659
        $ret['reordered'] = $entity ? $entity->reorder($position) : false;
0 ignored issues
show
Bug introduced by
The method reorder() does not exist on Phalcon\Mvc\Model\Resultset. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

659
        $ret['reordered'] = $entity ? $entity->/** @scrutinizer ignore-call */ reorder($position) : false;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method reorder() does not exist on Zemit\Mvc\Model. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

659
        $ret['reordered'] = $entity ? $entity->/** @scrutinizer ignore-call */ reorder($position) : false;
Loading history...
660
        $ret['single'] = $entity ? $this->expose($entity) : false;
661
        $ret['messages'] = $entity ? $entity->getMessages() : false;
662
        $this->view->setVars($ret);
663
        
664
        if (!$entity) {
665
            $this->response->setStatusCode(404, 'Not Found');
666
            return false;
667
        }
668
        
669
        return $this->setRestResponse($ret['reordered']);
0 ignored issues
show
Bug introduced by
It seems like $ret['reordered'] can also be of type false; however, parameter $response of Zemit\Mvc\Controller\Rest::setRestResponse() does only seem to accept array|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

669
        return $this->setRestResponse(/** @scrutinizer ignore-type */ $ret['reordered']);
Loading history...
670
    }
671
    
672
    /**
673
     * Sending an error as an http response
674
     *
675
     * @param null $error
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $error is correct as it would always require null to be passed?
Loading history...
676
     * @param null $response
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $response is correct as it would always require null to be passed?
Loading history...
677
     *
678
     * @return ResponseInterface
679
     */
680
    public function setRestErrorResponse($code = 400, $status = 'Bad Request', $response = null)
681
    {
682
        return $this->setRestResponse($response, $code, $status);
683
    }
684
    
685
    /**
686
     * Sending rest response as an http response
687
     *
688
     * @param array|null $response
689
     * @param null $status
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $status is correct as it would always require null to be passed?
Loading history...
690
     * @param null $code
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $code is correct as it would always require null to be passed?
Loading history...
691
     * @param int $jsonOptions
692
     * @param int $depth
693
     *
694
     * @return ResponseInterface
695
     */
696
    public function setRestResponse($response = null, $code = null, $status = null, $jsonOptions = 0, $depth = 512)
697
    {
698
        $debug = $this->config->app->debug ?? false;
699
        
700
        // keep forced status code or set our own
701
        $responseStatusCode = $this->response->getStatusCode();
702
        $reasonPhrase = $this->response->getReasonPhrase();
703
        $status ??= $reasonPhrase ?: 'OK';
704
        $code ??= (int)$responseStatusCode ?: 200;
705
        $view = $this->view->getParamsToView();
706
        $hash = hash('sha512', json_encode($view));
707
        
708
        /**
709
         * Debug section
710
         * - Versions
711
         * - Request
712
         * - Identity
713
         * - Profiler
714
         * - Dispatcher
715
         * - Router
716
         */
717
        $request = $debug ? $this->request->toArray() : null;
0 ignored issues
show
introduced by
The method toArray() does not exist on Phalcon\Http\Request. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

717
        $request = $debug ? $this->request->/** @scrutinizer ignore-call */ toArray() : null;
Loading history...
Bug introduced by
The method toArray() does not exist on Phalcon\Http\RequestInterface. It seems like you code against a sub-type of Phalcon\Http\RequestInterface such as Zemit\Http\Request. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

717
        $request = $debug ? $this->request->/** @scrutinizer ignore-call */ toArray() : null;
Loading history...
718
        $identity = $debug ? $this->identity->getIdentity() : null;
719
        $profiler = $debug && $this->profiler ? $this->profiler->toArray() : null;
720
        $dispatcher = $debug ? $this->dispatcher->toArray() : null;
721
        $router = $debug ? $this->router->toArray() : null;
722
        
723
        $api = $debug ? [
724
            'php' => phpversion(),
725
            'phalcon' => Version::get(),
726
            'zemit' => $this->config->core->version,
0 ignored issues
show
Bug Best Practice introduced by
The property version does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
727
            'core' => $this->config->core->name,
0 ignored issues
show
Bug Best Practice introduced by
The property name does not exist on Zemit\Bootstrap\Config. Since you implemented __get, consider adding a @property annotation.
Loading history...
728
            'app' => $this->config->app->version,
729
            'name' => $this->config->app->name,
730
        ] : [];
731
        $api['version'] = '0.1';
732
        
733
        $this->response->setStatusCode($code, $code . ' ' . $status);
734
        
735
        // @todo handle this correctly
736
        // @todo private vs public cache type
737
        $cache = $this->getCache();
738
        if (!empty($cache['lifetime'])) {
739
            if ($this->response->getStatusCode() === 200) {
740
                $this->response->setCache($cache['lifetime']);
741
                $this->response->setEtag($hash);
742
            }
743
        }
744
        else {
745
            $this->response->setCache(0);
746
            $this->response->setHeader('Cache-Control', 'no-cache, max-age=0');
747
        }
748
        
749
        return $this->response->setJsonContent(array_merge([
750
            'api' => $api,
751
            'timestamp' => date('c'),
752
            'hash' => $hash,
753
            'status' => $status,
754
            'code' => $code,
755
            'response' => $response,
756
            'view' => $view,
757
        ], $debug ? [
758
            'identity' => $identity,
759
            'profiler' => $profiler,
760
            'request' => $request,
761
            'dispatcher' => $dispatcher,
762
            'router' => $router,
763
            'memory' => Utils::getMemoryUsage(),
764
        ] : []), $jsonOptions, $depth);
0 ignored issues
show
Unused Code introduced by
The call to Phalcon\Http\ResponseInterface::setJsonContent() has too many arguments starting with $jsonOptions. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

764
        return $this->response->/** @scrutinizer ignore-call */ setJsonContent(array_merge([

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
765
    }
766
    
767
    public function beforeExecuteRoute(Dispatcher $dispatcher)
0 ignored issues
show
Unused Code introduced by
The parameter $dispatcher is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

767
    public function beforeExecuteRoute(/** @scrutinizer ignore-unused */ Dispatcher $dispatcher)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
768
    {
769
        // @todo use eventsManager from service provider instead
770
        $this->eventsManager->enablePriorities(true);
771
        // @todo see if we can implement receiving an array of responses globally: V2
772
        // $this->eventsManager->collectResponses(true);
773
        
774
        // retrieve events based on the config roles and features
775
        $permissions = $this->config->get('permissions')->toArray() ?? [];
776
        $featureList = $permissions['features'] ?? [];
777
        $roleList = $permissions['roles'] ?? [];
778
        
779
        foreach ($roleList as $role => $rolePermission) {
780
            
781
            if (isset($rolePermission['features'])) {
782
                foreach ($rolePermission['features'] as $feature) {
783
                    $rolePermission = array_merge_recursive($rolePermission, $featureList[$feature] ?? []);
784
                    // @todo remove duplicates
785
                }
786
            }
787
            
788
            $behaviorsContext = $rolePermission['behaviors'] ?? [];
789
            foreach ($behaviorsContext as $className => $behaviors) {
790
                if (is_int($className) || get_class($this) === $className) {
791
                    $this->attachBehaviors($behaviors, 'rest');
792
                }
793
                if ($this->getModelClassName() === $className) {
794
                    $this->attachBehaviors($behaviors, 'model');
795
                }
796
            }
797
        }
798
    }
799
    
800
    public function attachBehaviors($behaviors, $eventType = 'rest')
801
    {
802
        if (!is_array($behaviors)) {
803
            $behaviors = [$behaviors];
804
        }
805
        foreach ($behaviors as $behavior) {
806
            $event = new $behavior();
807
            if ($event instanceof Injectable) {
808
                $event->setDI($this->getDI());
809
            }
810
            $this->eventsManager->attach($event->eventType ?? $eventType, $event, $event->priority ?? Manager::DEFAULT_PRIORITY);
0 ignored issues
show
Bug Best Practice introduced by
The property priority does not exist on Zemit\Di\Injectable. Since you implemented __get, consider adding a @property annotation.
Loading history...
Unused Code introduced by
The call to Phalcon\Events\ManagerInterface::attach() has too many arguments starting with $event->priority ?? Phal...nager::DEFAULT_PRIORITY. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

810
            $this->eventsManager->/** @scrutinizer ignore-call */ 
811
                                  attach($event->eventType ?? $eventType, $event, $event->priority ?? Manager::DEFAULT_PRIORITY);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
Bug Best Practice introduced by
The property eventType does not exist on Zemit\Di\Injectable. Since you implemented __get, consider adding a @property annotation.
Loading history...
811
        }
812
    }
813
    
814
    /**
815
     * Handle rest response automagically
816
     *
817
     * @param Dispatcher $dispatcher
818
     */
819
    public function afterExecuteRoute(Dispatcher $dispatcher)
820
    {
821
        $response = $dispatcher->getReturnedValue();
822
        
823
        // Avoid breaking default phalcon behaviour
824
        if ($response instanceof Response) {
825
            return;
826
        }
827
        
828
        // Merge response into view variables
829
        if (is_array($response)) {
830
            $this->view->setVars($response, true);
831
        }
832
        
833
        // Return our Rest normalized response
834
        $dispatcher->setReturnedValue($this->setRestResponse(is_array($response) ? null : $response));
835
    }
836
}
837