Completed
Push — dev-master ( d1cf50...eebd1b )
by Vijay
08:23
created

APIMapper::__construct()   C

Complexity

Conditions 8
Paths 20

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 19
nc 20
nop 1
dl 0
loc 31
rs 5.3846
c 0
b 0
f 0
1
<?php
2
3
namespace FFCMS\Controllers\API;
4
5
use FFMVC\Helpers;
6
use FFCMS\{Traits, Models, Mappers};
7
8
9
/**
10
 * REST API Base Controller Class.
11
 *
12
 * @author Vijay Mahrra <[email protected]>
13
 * @copyright Vijay Mahrra
14
 * @license GPLv3 (http://www.gnu.org/licenses/gpl-3.0.html)
15
 */
16
abstract class APIMapper extends API
17
{
18
    use Traits\Validation;
19
20
    /**
21
     * For admin listing and search results
22
     */
23
    use Traits\ControllerMapper;
24
25
    /**
26
     * @var object database class
27
     */
28
    protected $db;
29
30
    /**
31
     * @var string table in the db
32
     */
33
    protected $table = null;
34
35
    /**
36
     * @var string class name
37
     */
38
    protected $mapperClass;
39
40
    /**
41
     * @var \FFMVC\Mappers\Mapper mapper for class
42
     */
43
    protected $mapper;
44
45
    /**
46
     * is the user authorised for access?
47
     *
48
     * @var bool
49
     */
50
    protected $isAuthorised = false;
51
52
    /**
53
     * is the user authorised for access to the object type?
54
     *
55
     * @var bool
56
     */
57
    protected $adminOnly = true;
58
59
60
    /**
61
     * initialize
62
     * @param \Base $f3
63
     */
64
    public function __construct(\Base $f3)
65
    {
66
        parent::__construct($f3);
67
68
        // guess the table name from the class name if not specified as a class member
69
        $class = \UTF::instance()->substr(strrchr(get_class($this), '\\'),1);
70
        $this->table = empty($this->table) ? $f3->snakecase($class) : $this->table;
71
        $mapperClass = "\FFCMS\Mappers\\" . $class;
72
73
        if (class_exists($mapperClass) && $this instanceof APIMapper) {
74
            $this->mapperClass = $mapperClass;
75
            $this->mapper = new $this->mapperClass;
76
        }
77
        
78
        $this->isAuthorised = $this->validateAccess();
79
        if (empty($this->isAuthorised)) {
80
            return $this->setOAuthError('invalid_grant');
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
81
        }
82
83
        $deny = false;
84
        $isAdmin = $f3->get('isAdmin');
85
        if (!$isAdmin && !empty($this->adminOnly)) {
86
            $deny = true;
87
        }
88
89
        $this->isAuthorised = empty($deny);
90
        if ($deny) {
91
            $this->failure('authentication_error', "User does not have permission.", 401);
92
            return $this->setOAuthError('access_denied');
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
93
        }
94
    }
95
96
97
    /**
98
     * Get the associated mapper for the table
99
     *
100
     * @return
101
     */
102
    public function &getMapper()
103
    {
104
        return $this->mapper;
105
    }
106
107
108
    /**
109
     * Check permissions and load the mapper with the object in the URL param @id
110
     * for the user
111
     *
112
     * @param \Base $f3
113
     * @param array $params
114
     * @param string $idField the field used for the unique id to load by
115
     * @param string $defaultId defaule value to use if not found
0 ignored issues
show
Documentation introduced by
Should the type for parameter $defaultId not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
116
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array|boolean|\FFMVC\Mappers\Mapper? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
117
     */
118
    public function getIdObjectIfUser(\Base $f3, array $params, string $idField = 'uuid', $defaultId = null)
119
    {
120
        // valid user?
121
        if (empty($this->isAuthorised)) {
122
            return;
123
        }
124
125
        // only admin has permission to specify @id param
126
        $isAdmin = $f3->get('isAdmin');
127
        $id = !empty($params['id']) ? $params['id'] : $f3->get('REQUEST.id');
128
129
//        if ((!$isAdmin && !empty($id)) || (!$isAdmin && !empty($this->adminOnly))) {
130
        if ((!$isAdmin && !empty($this->adminOnly))) {
131
            $this->failure('authentication_error', "User does not have permission.", 401);
132
            return $this->setOAuthError('access_denied');
133
        }
134
135
        // use default user id
136
        if (empty($id)) {
137
            $id = $defaultId;
138
        }
139
140
        // load object by correct id
141
        $db = \Registry::get('db');
142
        $m = $this->getMapper();
143
        $m->load([$db->quotekey($idField) . ' = ?', $id]);
144 View Code Duplication
        if (null == $m->$idField) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
145
            $this->failure('authentication_error', "Object with @id does not exist.", 404);
146
            return $this->setOAuthError('invalid_request');
147
        }
148
149
        $this->mapper =& $m;
150
        return $m;
151
    }
152
153
154
    /**
155
     * Check permissions and load the mapper with the object in the URL param @id
156
     * if the user is an admin
157
     *
158
     * @param \Base $f3
159
     * @param param $params
0 ignored issues
show
Documentation introduced by
Should the type for parameter $params not be array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
160
     * @param string $idField the field used for the unique id to load by
161
     * @param string $defaultId defaule value to use if not found
0 ignored issues
show
Documentation introduced by
Should the type for parameter $defaultId not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
162
     * @return type
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array|boolean|\FFMVC\Mappers\Mapper? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
163
     */
164
    public function getIdObjectIfAdmin(\Base $f3, array $params, string $idField = 'uuid', $defaultId = null)
165
    {
166
        if (empty($this->isAuthorised)) {
167
            return;
168
        }
169
170
        // only admin has permission to delete @id
171
        $isAdmin = $f3->get('isAdmin');
172
        if (!$isAdmin) {
173
            $this->failure('authentication_error', "User does not have permission.", 401);
174
            return $this->setOAuthError('access_denied');
175
        }
176
177
        // invalid id
178
        $id = !empty($params['id']) ? $params['id'] : $f3->get('REQUEST.id');
179
        if (empty($id)) {
180
            $id = $defaultId;
181
        }
182
183
        if (!empty($id) && ('uuid' == $idField && 36 !== strlen($id))) {
184
            $this->failure('authentication_error', "Invalid @id parameter.", 400);
185
            return $this->setOAuthError('invalid_request');
186
        }
187
188
        // check id exists
189
        $db = \Registry::get('db');
190
        $m = $this->getMapper();
191
        $m->load([$db->quotekey($idField) . ' = ?', $id]);
192 View Code Duplication
        if (null == $m->$idField) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
193
            $this->failure('authentication_error', "Object with @id does not exist.", 404);
194
            return $this->setOAuthError('invalid_request');
195
        }
196
197
        $this->mapper =& $m;
198
        return $m;
199
    }
200
201
202
    /**
203
     * Display the data item
204
     *
205
     * @param \Base $f3
206
     * @param array $params
207
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array|boolean? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
208
     */
209 View Code Duplication
    public function get(\Base $f3, array $params)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
210
    {
211
        $isAdmin = $f3->get('isAdmin');
212
        $m = $this->getIdObjectIfUser($f3, $params, 'uuid', $params['id']);
213
        if (!is_object($m) || null == $m->uuid) {
214
            return;
215
        } elseif (!$isAdmin && $m->users_uuid !== $f3->get('uuid')) {
216
            $this->failure('authentication_error', "User does not have permission.", 401);
217
            return $this->setOAuthError('access_denied');
218
        }
219
        // return raw data for object?
220
        $adminView = $f3->get('isAdmin') && 'admin' == $f3->get('REQUEST.view');
221
        $this->data = $adminView ? $m->castFields($f3->get('REQUEST.fields')) : $m->exportArray($f3->get('REQUEST.fields'));
222
    }
223
224
225
    /**
226
     * Delete the data object indicated by @id in the request
227
     *
228
     * @param \Base $f3
229
     * @param array $params
230
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be null|array|boolean? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
231
     */
232
    public function delete(\Base $f3, array $params)
233
    {
234
        $m = $this->getIdObjectIfUser($f3, $params);
235
        if (!is_object($m) || null == $m->uuid) {
236
            return;
237
        }
238
239
        // a user can only delete if they own the object
240
        $isAdmin = $f3->get('isAdmin');
241
        if (!$isAdmin) {
242
            if (!empty($m->users_uuid) && $m->users_uuid !== $f3->get('uuid')) {
243
                $this->failure('authentication_error', "User does not have permission.", 401);
244
                return $this->setOAuthError('access_denied');
245
            }
246
        }
247
248
        $this->data = [
249
            'deleted' => $m->erase()
250
        ];
251
    }
252
253
254
    /**
255
     * list objects (list is a reserved keyword)
256
     *
257
     * @param \Base $f3
258
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be array|boolean|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
259
     */
260 View Code Duplication
    public function listingAdmin(\Base $f3)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
261
    {
262
        // must be an admin
263
        $isAdmin = $f3->get('isAdmin');
264
        if (!$isAdmin) {
265
            $this->failure('authentication_error', "User does not have permission.", 401);
266
            return $this->setOAuthError('access_denied');
267
        }
268
269
        $this->data = $this->getListingResults($f3, $this->getMapper());
270
    }
271
272
273
    /**
274
     * search objects
275
     *
276
     * @param \Base $f3
277
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be array|boolean|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
278
     */
279 View Code Duplication
    public function searchAdmin(\Base $f3)
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
280
    {
281
        // must be an admin
282
        $isAdmin = $f3->get('isAdmin');
283
        if (!$isAdmin) {
284
            $this->failure('authentication_error', "User does not have permission.", 401);
285
            return $this->setOAuthError('access_denied');
286
        }
287
288
        $this->data = $this->getSearchResults($f3, $this->getMapper());
289
    }
290
291
292
    /**
293
     * list objects (list is a reserved keyword)
294
     *
295
     * @param \Base $f3
296
     * @param array $params
297
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be array|boolean|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
298
     */
299
    public function listing(\Base $f3, array $params)
300
    {
301
        $isAdmin = $f3->get('isAdmin');
302
        $users_uuid = null;
303 View Code Duplication
        if (!$isAdmin && array_key_exists('id', $params)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
304
            $this->failure('authentication_error', "User does not have permission.", 401);
305
            return $this->setOAuthError('access_denied');
306
        } elseif ($isAdmin && array_key_exists('id', $params)) {
307
            $users_uuid = $params['id'];
308
        } elseif (!$isAdmin) {
309
            $users_uuid = $f3->get('uuid');
310
        }
311
312
        // return raw data for object?
313
        $adminView = $f3->get('isAdmin') && 'admin' == $f3->get('REQUEST.view');
314
315
        // set up paging limits
316
        $minPerPage = $f3->get('api.paging_min');
317
        $maxPerPage = $f3->get('api.paging_max');
318
        $perPage = (int) $f3->get('REQUEST.per_page');
319
        if ($perPage < $minPerPage) {
320
            $perPage = $minPerPage;
321
        }
322
        if ($perPage > $maxPerPage) {
323
            $perPage = $maxPerPage;
324
        }
325
326
        $page = $f3->get('REQUEST.page');
327
        if ($page < 1) {
328
            $page = 1;
329
        }
330
331
        // fetch data (paging is 0 based)
332
        $m = $this->getMapper();
333
334
        // validate order field
335
        $order = $f3->get('REQUEST.order');
336
        $orderClauses = empty($order) ? [] : preg_split("/[,]/", $order);
337
        $allFields = $m->fields();
338
        foreach ($orderClauses as $k => $field) {
339
            // split into field, asc/desc
340
            $field = preg_split("/[\s]+/", trim($field));
341
            if (!in_array($field[0], $allFields)) {
342
                // invalid field
343
                unset($orderClauses[$k]);
344
                continue;
345
            } elseif (count($field) == 1) {
346
                $field[1] = 'asc';
347
            } elseif (count($field) == 2) {
348
                if (!in_array($field[1], ['asc', 'desc'])) {
349
                    $field[1] = 'asc';
350
                }
351
            }
352
            $orderClauses[$k] = $field[0] . ' ' . $field[1];
353
        }
354
        $order = join(',', $orderClauses);
355
356
        // fields to return - validate
357
        $fields = $f3->get('REQUEST.fields');
358
        $fields = empty($fields) ? [] : preg_split("/[,]/", $fields);
359
        foreach ($fields as $k => $field) {
360
            if (!in_array($field, $allFields)) {
361
                unset($fields[$k]);
362
            }
363
        }
364
        $fields = join(',', $fields);
365
366
        // count rows
367
        if ($isAdmin) {
368
            $rows = $m->count();
369
        } else {
370
            $rows = $m->count(['users_uuid = ?', $users_uuid]);
371
        }
372
        if ($rows < 1) {
373
            $this->failure('sever_error', "No data available for request.", 404);
374
            $this->setOAuthError('server_error');
375
            return;
376
        }
377
378
        // if fewer results than per page, set per_page
379
        if ($page == 1 && $perPage > $rows) {
380
            $perPage = $rows;
381
        }
382
383
        $pagination = [];
384
        $pagination['count'] = ceil($rows / $perPage);
385
386
        // too high page number?
387
        if ($page > $pagination['count']) {
388
            $page = $pagination['count'];
389
        }
390
391
        // set up page URLs
392
        $url = $f3->get('PATH');
393
        $urlParams = [
394
            'per_page' => $perPage,
395
        ];
396
        if (!empty($order)) {
397
            $urlParams['order'] = $order;
398
        }
399
        if (!empty($adminView)) {
400
            $urlParams['view'] = 'admin';
401
        }
402
        if (!empty($fields)) {
403
            $urlParams['fields'] = $fields;
404
        }
405
        ksort($urlParams);
406
407
        // previous page url
408
        $pagination = [];
409
        $prevPage = (1 > $page - 1 ) ? null : $page - 1;
410
        $nextPage = (1 + $page> $pagination['count']) ? null : $page + 1;
411
412
        $resultsFrom = round($page * ($rows / $pagination['count'])) - $perPage + 1;
413
        $resultsTo = $resultsFrom + $perPage - 1;
414
415
        // return data
416
        $this->data['pagination'] = [
417
            'url_base' => $this->url($url, $urlParams),
418
            'url_current' => $this->url($url, $urlParams + ['page' => $page]),
419
            'url_first' => $this->url($url, $urlParams + ['page' => 1]),
420
            'url_last' => $this->url($url, $urlParams + ['page' => $pagination['count']]),
421
            'url_next' => (null == $nextPage) ? null : $this->url($url, $urlParams + ['page' => $nextPage]),
422
            'url_previous' => (null == $prevPage) ? null : $this->url($url, $urlParams + ['page' => $prevPage]),
423
            'results' => $rows,
424
            'results_from' => $resultsFrom,
425
            'results_to' => $resultsTo,
426
            'per_page' => $perPage,
427
            'pages' => $pagination['count'],
428
            'page' => $page,
429
            'object' => $m->table(),
430
            'fields' => preg_split("/[,]/", $fields)
431
        ];
432
433
        // fetch results
434
        if ($isAdmin && empty($users_uuid)) {
435
            $m->load('', [
436
                'order' => $order,
437
                'offset' => (1 == $page) ? 0 : ($page - 1) * $perPage,
438
                'limit' => $perPage
439
            ]);
440
        } else {
441
            $m->load(['users_uuid = ?', $users_uuid], [
442
                'order' => $order,
443
                'offset' => (1 == $page) ? 0 : ($page - 1) * $perPage,
444
                'limit' => $perPage
445
            ]);
446
        }
447
448
        do {
449
            $this->data['objects'][] = $adminView ? $m->castFields($fields) : $m->exportArray($fields);
450
        }
451
        while ($m->skip());
452
    }
453
454
455
    /**
456
     * search objects
457
     *
458
     * @param \Base $f3
459
     * @param array $params
460
     * @return void
0 ignored issues
show
Documentation introduced by
Should the return type not be array|boolean|null? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
461
     */
462
    public function search(\Base $f3, array $params)
463
    {
464
        $isAdmin = $f3->get('isAdmin');
465 View Code Duplication
        if (!$isAdmin && array_key_exists('id', $params)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
466
            $this->failure('authentication_error', "User does not have permission.", 401);
467
            return $this->setOAuthError('access_denied');
468
        } elseif ($isAdmin && array_key_exists('id', $params)) {
469
            $users_uuid = $params['id'];
470
        } elseif (!$isAdmin) {
471
            $users_uuid = $f3->get('uuid');
472
        }
473
474
        // return raw data for object?
475
        $adminView = $f3->get('isAdmin') && 'admin' == $f3->get('REQUEST.view');
476
477
        // set up paging limits
478
        $minPerPage = $f3->get('api.paging_min');
479
        $maxPerPage = $f3->get('api.paging_max');
480
        $perPage = (int) $f3->get('REQUEST.per_page');
481
        if ($perPage < $minPerPage) {
482
            $perPage = $minPerPage;
483
        }
484
        if ($perPage > $maxPerPage) {
485
            $perPage = $maxPerPage;
486
        }
487
488
        $page = $f3->get('REQUEST.page');
489
        if ($page < 1) {
490
            $page = 1;
491
        }
492
493
        // fetch data (paging is 0 based)
494
        $m = $this->getMapper();
495
        $allFields = $m->fields();
496
497
        // validate order field
498
        $order = $f3->get('REQUEST.order');
499 View Code Duplication
        if (!empty($order)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
500
            $orderClauses = empty($order) ? [] : preg_split("/[,]/", $order);
501
            foreach ($orderClauses as $k => $field) {
502
                // split into field, asc/desc
503
                $field = preg_split("/[\s]+/", trim($field));
504
                if (!in_array($field[0], $allFields)) {
505
                    // invalid field
506
                    unset($orderClauses[$k]);
507
                    continue;
508
                } elseif (count($field) == 1) {
509
                    $field[1] = 'asc';
510
                } elseif (count($field) == 2) {
511
                    if (!in_array($field[1], ['asc', 'desc'])) {
512
                        $field[1] = 'asc';
513
                    }
514
                }
515
                $orderClauses[$k] = $field[0] . ' ' . $field[1];
516
            }
517
            $order = join(',', $orderClauses);
518
        }
519
520
        // fields to return and fields to search - validate
521
        $validFields = [];
522 View Code Duplication
        foreach (['fields', 'search_fields'] as $fieldsList) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
523
            $fields = $f3->get('REQUEST.' . $fieldsList);
524
            if (empty($fields)) {
525
                continue;
526
            }
527
            $fields = empty($fields) ? [] : preg_split("/[,]/", $fields);
528
            foreach ($fields as $k => $field) {
529
                if (!in_array($field, $allFields)) {
530
                    unset($fields[$k]);
531
                }
532
            }
533
            $validFields[$fieldsList] = join(',', $fields);
534
        }
535
536
        // validated fields to return
537
        $fields = empty($validFields['fields']) ? join(',', $allFields) : $validFields['fields'];
538
539
        // validated fields to search in, use all if empty
540
        $searchFields = empty($searchFields['search_fields']) ? join(',', $allFields) : $validFields['search_fields'];
0 ignored issues
show
Bug introduced by
The variable $searchFields seems only to be defined at a later point. As such the call to empty() seems to always evaluate to true.

This check marks calls to isset(...) or empty(...) that are found before the variable itself is defined. These will always have the same result.

This is likely the result of code being shifted around. Consider removing these calls.

Loading history...
541
542
        // get search type
543
        $search = $f3->get('REQUEST.search');
544
        if (!empty($search)) {
545
            $search = trim(strtolower($search));
546
        }
547
        $search_type = $f3->get('REQUEST.search_type');
548
        if (empty($search_type)) {
549
            $search_type = 'exact';
550
        } elseif ($search_type !== 'exact') {
551
            $search_type = 'fuzzy';
552
        }
553
554
        // construct search query
555
        $db = \Registry::get('db');
556
        $sqlClauses = [];
557
        $searchFieldsArray = preg_split("/[,]/", $searchFields);
558 View Code Duplication
        foreach ($searchFieldsArray as $field) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
559
            $sqlClauses[] = 'LOWER(' . $db->quotekey($field) . ') = ' . $db->quote($search);
560
            if ($search_type == 'fuzzy') {
561
                $sqlClauses[] = 'LOWER(' . $db->quotekey($field) . ') LIKE ' . $db->quote('%' . $search . '%');
562
            }
563
        }
564
565
        // get total results
566
        $query = 'SELECT COUNT(*) AS results FROM ' . $db->quotekey($m->table()) . ' WHERE ';
567 View Code Duplication
        if (empty($users_uuid)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
568
             $query .= join(' OR ', $sqlClauses);
569
        } else {
570
             $query .= ' users_uuid = ' . $db->quote($users_uuid)  . ' AND ('.  join(' OR ', $sqlClauses) . ')';
571
        }
572
        $rows = $db->exec($query);
573
        $rows = (int) $rows[0]['results'];
574
        if ($rows < 1) {
575
            $this->failure('sever_error', "No data available for request.", 404);
576
            $this->setOAuthError('server_error');
577
            return;
578
        }
579
580
        // if fewer results than per page, set per_page
581
        if ($page == 1 && $perPage > $rows) {
582
            $perPage = $rows;
583
        }
584
585
        $pagination['count'] = ceil($rows / $perPage);
0 ignored issues
show
Coding Style Comprehensibility introduced by
$pagination was never initialized. Although not strictly required by PHP, it is generally a good practice to add $pagination = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
586
587
        // too high page number?
588
        if ($page > $pagination['count']) {
589
            $page = $pagination['count'];
590
        }
591
592
        // set up page URLs
593
        $url = $f3->get('PATH');
594
        $urlParams = [
595
            'per_page' => $perPage,
596
            'search' => $search,
597
            'search_type' => $search_type,
598
        ];
599
        if (!empty($order)) {
600
            $urlParams['order'] = $order;
601
        }
602
        if (!empty($adminView)) {
603
            $urlParams['view'] = 'admin';
604
        }
605
        if (!empty($fields)) {
606
            $urlParams['fields'] = $fields;
607
        }
608
        ksort($urlParams);
609
610
        // previous page url
611
        $prevPage = (1 > $page - 1 ) ? null : $page - 1;
612
        $nextPage = (1 + $page> $pagination['count']) ? null : $page + 1;
613
614
        $resultsFrom = 1 + ($page * $perPage) - $perPage;
615
        $resultsTo = $resultsFrom + $perPage - 1;
616
        if ($resultsTo > $rows) {
617
            $resultsTo = $rows;
618
        }
619
620
        // return data
621
        $this->data['pagination'] = [
622
            'url_base' => $this->url($url, $urlParams),
623
            'url_current' => $this->url($url, $urlParams + ['page' => $page]),
624
            'url_first' => $this->url($url, $urlParams + ['page' => 1]),
625
            'url_last' => $this->url($url, $urlParams + ['page' => $pagination['count']]),
626
            'url_next' => (null == $nextPage) ? null : $this->url($url, $urlParams + ['page' => $nextPage]),
627
            'url_previous' => (null == $prevPage) ? null : $this->url($url, $urlParams + ['page' => $prevPage]),
628
            'results' => $rows,
629
            'results_from' => $resultsFrom,
630
            'results_to' => $resultsTo,
631
            'per_page' => $perPage,
632
            'pages' => $pagination['count'],
633
            'page' => $page,
634
            'object' => $m->table(),
635
            'fields' => preg_split("/[,]/", $fields)
636
        ];
637
638
        // retrieve results
639
        $query = 'SELECT * FROM ' . $db->quotekey($m->table()) . ' WHERE ';
640 View Code Duplication
        if (empty($users_uuid)) {
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
641
             $query .= join(' OR ', $sqlClauses);
642
        } else {
643
             $query .= ' users_uuid = ' . $db->quote($users_uuid)  . ' AND ('.  join(' OR ', $sqlClauses) . ')';
644
        }
645
        $query .= sprintf(' LIMIT %d,%d', (1 == $page) ? 0 : ($page - 1) * $perPage, $perPage);
646
        $results = $db->exec($query);
647
        foreach ($results as $row) {
648
            $this->data['objects'][] = $adminView ? $m->castFields($fields, $row) : $m->exportArray($fields, $row);
649
        }
650
    }
651
652
}
653