Passed
Push — master ( e55181...025733 )
by Henry
02:08
created

RecordsRequestHandler::handleBrowseRequest()   C

Complexity

Conditions 12
Paths 11

Size

Total Lines 57
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 33
nc 11
nop 4
dl 0
loc 57
rs 6.62
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Divergence\Controllers;
3
4
use Exception;
5
6
use Divergence\Helpers\JSON;
7
use Divergence\Helpers\JSONP;
8
use Divergence\Helpers\Util as Util;
9
use Divergence\IO\Database\MySQL as DB;
10
use Divergence\Models\ActiveRecord as ActiveRecord;
11
12
abstract class RecordsRequestHandler extends RequestHandler
13
{
14
    public static $config;
15
16
    // configurables
17
    public static $recordClass;
18
    public static $accountLevelRead = false;
19
    public static $accountLevelBrowse = 'Staff';
20
    public static $accountLevelWrite = 'Staff';
21
    public static $accountLevelAPI = false;
22
    public static $browseOrder = false;
23
    public static $browseConditions = false;
24
    public static $browseLimitDefault = false;
25
    public static $editableFields = false;
26
    public static $searchConditions = false;
27
    
28
    public static $calledClass = __CLASS__;
29
    public static $responseMode = 'dwoo';
30
    
31
    public static function handleRequest()
32
    {
33
        // save static class
34
        static::$calledClass = get_called_class();
35
    
36
        // handle JSON requests
37
        if (static::peekPath() == 'json') {
38
            // check access for API response modes
39
            static::$responseMode = static::shiftPath();
40
            if (in_array(static::$responseMode, ['json','jsonp'])) {
41
                if (!static::checkAPIAccess()) {
42
                    return static::throwAPIUnAuthorizedError();
43
                }
44
            }
45
        }
46
        
47
        return static::handleRecordsRequest();
48
    }
49
50
51
    public static function handleRecordsRequest($action = false)
52
    {
53
        switch ($action ? $action : $action = static::shiftPath()) {
54
            case 'save':
55
            {
56
                return static::handleMultiSaveRequest();
57
            }
58
            
59
            case 'destroy':
60
            {
61
                return static::handleMultiDestroyRequest();
62
            }
63
            
64
            case 'create':
65
            {
66
                return static::handleCreateRequest();
67
            }
68
            
69
            case '':
70
            case false:
71
            {
72
                return static::handleBrowseRequest();
73
            }
74
75
            default:
76
            {
77
                if ($Record = static::getRecordByHandle($action)) {
78
                    if (!static::checkReadAccess($Record)) {
79
                        return static::throwUnauthorizedError();
80
                    }
81
82
                    return static::handleRecordRequest($Record);
83
                } else {
84
                    return static::throwRecordNotFoundError();
85
                }
86
            }
87
        }
88
    }
89
    
90
    public static function getRecordByHandle($handle)
91
    {
92
        $className = static::$recordClass;
93
        
94
        if (method_exists($className, 'getByHandle')) {
95
            return $className::getByHandle($handle);
96
        }
97
    }
98
99
    public static function prepareBrowseConditions($conditions = [])
100
    {
101
        if (static::$browseConditions) {
102
            if (!is_array(static::$browseConditions)) {
103
                static::$browseConditions = [static::$browseConditions];
104
            }
105
            $conditions = array_merge(static::$browseConditions, $conditions);
106
        }
107
        return $conditions;
108
    }
109
110
    public static function prepareDefaultBrowseOptions()
111
    {
112
        if (empty($_REQUEST['offset']) && is_numeric($_REQUEST['start'])) {
113
            $_REQUEST['offset'] = $_REQUEST['start'];
114
        }
115
116
        $limit = !empty($_REQUEST['limit']) && is_numeric($_REQUEST['limit']) ? $_REQUEST['limit'] : static::$browseLimitDefault;
117
        $offset = !empty($_REQUEST['offset']) && is_numeric($_REQUEST['offset']) ? $_REQUEST['offset'] : false;
118
        
119
        $options = Util::prepareOptions($options, [
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $options seems to be never defined.
Loading history...
120
            'limit' =>  $limit,
121
            'offset' => $offset,
122
            'order' => static::$browseOrder,
123
        ]);
124
125
        return $options;
126
    }
127
128
    public static function handleBrowseRequest($options = [], $conditions = [], $responseID = null, $responseData = [])
129
    {
130
        if (!static::checkBrowseAccess(func_get_args())) {
131
            return static::throwUnauthorizedError();
132
        }
133
        
134
        $conditions = static::prepareBrowseConditions($conditions);
135
        
136
        $options = static::prepareDefaultBrowseOptions();
137
138
        // process sorter
139
        if (!empty($_REQUEST['sort'])) {
140
            $sort = json_decode($_REQUEST['sort'], true);
141
            if (!$sort || !is_array($sort)) {
142
                return static::respond('error', [
143
                    'success' => false,
144
                    'failed' => [
145
                        'errors'	=>	'Invalid sorter.',
146
                    ],
147
                ]);
148
            }
149
150
            if (is_array($sort)) {
1 ignored issue
show
introduced by
The condition is_array($sort) is always true.
Loading history...
151
                foreach ($sort as $field) {
152
                    $options['order'][$field['property']] = $field['direction'];
153
                }
154
            }
155
        }
156
        
157
        // process filter
158
        if (!empty($_REQUEST['filter'])) {
159
            $filter = json_decode($_REQUEST['filter'], true);
160
            if (!$filter || !is_array($filter)) {
161
                return static::respond('error', [
162
                    'success' => false,
163
                    'failed' => [
164
                        'errors'	=>	'Invalid filter.',
165
                    ],
166
                ]);
167
            }
168
169
            foreach ($filter as $field) {
170
                $conditions[$field['property']] = $field['value'];
171
            }
172
        }
173
174
        $className = static::$recordClass;
175
176
        return static::respond(
177
            isset($responseID) ? $responseID : static::getTemplateName($className::$pluralNoun),
178
            array_merge($responseData, [
179
                'success' => true,
180
                'data' => $className::getAllByWhere($conditions, $options),
181
                'conditions' => $conditions,
182
                'total' => DB::foundRows(),
183
                'limit' => $options['limit'],
184
                'offset' => $options['offset'],
185
            ])
186
        );
187
    }
188
189
190
    public static function handleRecordRequest(ActiveRecord $Record, $action = false)
191
    {
192
        switch ($action ? $action : $action = static::shiftPath()) {
193
            case '':
194
            case false:
195
            {
196
                $className = static::$recordClass;
197
                
198
                return static::respond(static::getTemplateName($className::$singularNoun), [
199
                    'success' => true,
200
                    'data' => $Record,
201
                ]);
202
            }
203
            
204
            case 'edit':
205
            {
206
                return static::handleEditRequest($Record);
207
            }
208
            
209
            case 'delete':
210
            {
211
                return static::handleDeleteRequest($Record);
212
            }
213
        
214
            default:
215
            {
216
                return static::onRecordRequestNotHandled($Record, $action);
217
            }
218
        }
219
    }
220
221
222
223
    public static function handleMultiSaveRequest()
224
    {
225
        $className = static::$recordClass;
226
    
227
        $PrimaryKey = $className::getPrimaryKey();
228
            
229
        if (static::$responseMode == 'json' && in_array($_SERVER['REQUEST_METHOD'], ['POST','PUT'])) {
230
            $JSONData = JSON::getRequestData();
231
            if (is_array($JSONData)) {
232
                $_REQUEST = $JSONData;
233
            }
234
        }
235
        
236
        if ($className::fieldExists(key($_REQUEST['data']))) {
237
            $_REQUEST['data'] = [$_REQUEST['data']];
238
        }
239
240
        if (empty($_REQUEST['data']) || !is_array($_REQUEST['data'])) {
241
            if (static::$responseMode == 'json') {
242
                return static::respond('error', [
243
                    'success' => false,
244
                    'failed' => [
245
                        'errors'	=>	'Save expects "data" field as array of records.',
246
                    ],
247
                ]);
248
            }
249
        }
250
        
251
        $results = [];
252
        $failed = [];
253
254
        foreach ($_REQUEST['data'] as $datum) {
255
            // get record
256
            if (empty($datum[$PrimaryKey])) {
257
                $Record = new $className::$defaultClass();
258
                static::onRecordCreated($Record, $datum);
259
            } else {
260
                if (!$Record = $className::getByID($datum[$PrimaryKey])) {
261
                    $failed[] = [
262
                        'record' => $datum,
263
                        'errors' => 'Record not found',
264
                    ];
265
                    continue;
266
                }
267
            }
268
            
269
            // check write access
270
            if (!static::checkWriteAccess($Record)) {
271
                $failed[] = [
272
                    'record' => $datum,
273
                    'errors' => 'Write access denied',
274
                ];
275
                continue;
276
            }
277
            
278
            // apply delta
279
            static::applyRecordDelta($Record, $datum);
280
281
            // call template function
282
            static::onBeforeRecordValidated($Record, $datum);
283
284
            // try to save record
285
            try {
286
                // call template function
287
                static::onBeforeRecordSaved($Record, $datum);
288
289
                $Record->save();
290
                $results[] = (!$Record::fieldExists('Class') || get_class($Record) == $Record->Class) ? $Record : $Record->changeClass();
291
                
292
                // call template function
293
                static::onRecordSaved($Record, $datum);
294
            } catch (Exception $e) {
295
                $failed[] = [
296
                    'record' => $Record->data,
297
                    'validationErrors' => $Record->validationErrors,
298
                ];
299
            }
300
        }
301
        
302
        
303
        return static::respond(static::getTemplateName($className::$pluralNoun).'Saved', [
304
            'success' => count($results) || !count($failed),
305
            'data' => $results,
306
            'failed' => $failed,
307
        ]);
308
    }
309
    
310
    
311
    public static function handleMultiDestroyRequest()
312
    {
313
        $className = static::$recordClass;
314
315
        $PrimaryKey = $className::getPrimaryKey();
316
            
317
        if (static::$responseMode == 'json' && in_array($_SERVER['REQUEST_METHOD'], ['POST','PUT','DELETE'])) {
318
            $JSONData = JSON::getRequestData();
319
            if (is_array($JSONData)) {
320
                $_REQUEST = $JSONData;
321
            }
322
        }
323
        
324
        if ($className::fieldExists(key($_REQUEST['data']))) {
325
            $_REQUEST['data'] = [$_REQUEST['data']];
326
        }
327
328
        if (empty($_REQUEST['data']) || !is_array($_REQUEST['data'])) {
329
            if (static::$responseMode == 'json') {
330
                return static::respond('error', [
331
                    'success' => false,
332
                    'failed' => [
333
                        'errors'	=>	'Save expects "data" field as array of records.',
334
                    ],
335
                ]);
336
            }
337
        }
338
339
340
        $results = [];
341
        $failed = [];
342
        
343
        foreach ($_REQUEST['data'] as $datum) {
344
            // get record
345
            if (is_numeric($datum)) {
346
                $recordID = $datum;
347
            } elseif (!empty($datum[$PrimaryKey]) && is_numeric($datum[$PrimaryKey])) {
348
                $recordID = $datum[$PrimaryKey];
349
            } else {
350
                $failed[] = [
351
                    'record' => $datum,
352
                    'errors' => $PrimaryKey.' missing',
353
                ];
354
                continue;
355
            }
356
357
            if (!$Record = $className::getByField($PrimaryKey, $recordID)) {
358
                $failed[] = [
359
                    'record' => $datum,
360
                    'errors' => $PrimaryKey.' not found',
361
                ];
362
                continue;
363
            }
364
            
365
            // check write access
366
            if (!static::checkWriteAccess($Record)) {
367
                $failed[] = [
368
                    'record' => $datum,
369
                    'errors' => 'Write access denied',
370
                ];
371
                continue;
372
            }
373
        
374
            // destroy record
375
            if ($Record->destroy()) {
376
                $results[] = $Record;
377
            }
378
        }
379
        
380
        return static::respond(static::getTemplateName($className::$pluralNoun).'Destroyed', [
381
            'success' => count($results) || !count($failed),
382
            'data' => $results,
383
            'failed' => $failed,
384
        ]);
385
    }
386
387
388
    public static function handleCreateRequest(ActiveRecord $Record = null)
389
    {
390
        // save static class
391
        static::$calledClass = get_called_class();
392
393
        if (!$Record) {
394
            $className = static::$recordClass;
395
            $Record = new $className::$defaultClass();
396
        }
397
        
398
        // call template function
399
        static::onRecordCreated($Record, $_REQUEST);
400
401
        return static::handleEditRequest($Record);
402
    }
403
404
    public static function handleEditRequest(ActiveRecord $Record)
405
    {
406
        $className = static::$recordClass;
407
408
        if (!static::checkWriteAccess($Record)) {
409
            return static::throwUnauthorizedError();
410
        }
411
412
        if (in_array($_SERVER['REQUEST_METHOD'], ['POST','PUT'])) {
413
            if (static::$responseMode == 'json') {
414
                $_REQUEST = JSON::getRequestData();
415
                if (is_array($_REQUEST['data'])) {
416
                    $_REQUEST = $_REQUEST['data'];
417
                }
418
            }
419
            $_REQUEST = $_REQUEST ? $_REQUEST : $_POST;
420
        
421
            // apply delta
422
            static::applyRecordDelta($Record, $_REQUEST);
423
            
424
            // call template function
425
            static::onBeforeRecordValidated($Record, $_REQUEST);
426
427
            // validate
428
            if ($Record->validate()) {
429
                // call template function
430
                static::onBeforeRecordSaved($Record, $_REQUEST);
431
                
432
                try {
433
                    // save session
434
                    $Record->save();
435
                } catch (Exception $e) {
436
                    return static::respond('Error', [
437
                        'success' => false,
438
                        'failed' => [
439
                            'errors'	=>	$e->getMessage(),
440
                        ],
441
                    ]);
442
                }
443
                
444
                // call template function
445
                static::onRecordSaved($Record, $_REQUEST);
446
        
447
                // fire created response
448
                $responseID = static::getTemplateName($className::$singularNoun).'Saved';
449
                $responseData = [
450
                    'success' => true,
451
                    'data' => $Record,
452
                ];
453
                return static::respond($responseID, $responseData);
454
            }
455
            
456
            // fall through back to form if validation failed
457
        }
458
        
459
        $responseID = static::getTemplateName($className::$singularNoun).'Edit';
460
        $responseData = [
461
            'success' => false,
462
            'data' => $Record,
463
        ];
464
    
465
        return static::respond($responseID, $responseData);
466
    }
467
468
469
    public static function handleDeleteRequest(ActiveRecord $Record)
470
    {
471
        $className = static::$recordClass;
472
473
        if (!static::checkWriteAccess($Record)) {
474
            return static::throwUnauthorizedError();
475
        }
476
    
477
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
478
            $data = $Record->data;
479
            $Record->destroy();
480
                    
481
            // call cleanup function after delete
482
            static::onRecordDeleted($Record, $data);
483
            
484
            // fire created response
485
            return static::respond(static::getTemplateName($className::$singularNoun).'Deleted', [
486
                'success' => true,
487
                'data' => $Record,
488
            ]);
489
        }
490
    
491
        return static::respond('confirm', [
492
            'question' => 'Are you sure you want to delete this '.$className::$singularNoun.'?',
493
            'data' => $Record,
494
        ]);
495
    }
496
    
497
    
498
    public static function respond($responseID, $responseData = [], $responseMode = false)
499
    {
500
        // default to static property
501
        if (!$responseMode) {
502
            $responseMode = static::$responseMode;
503
        }
504
    
505
        return parent::respond($responseID, $responseData, $responseMode);
506
    }
507
    
508
    // access control template functions
509
    public static function checkBrowseAccess($arguments)
510
    {
511
        return true;
512
    }
513
514
    public static function checkReadAccess(ActiveRecord $Record)
515
    {
516
        return true;
517
    }
518
    
519
    public static function checkWriteAccess(ActiveRecord $Record)
520
    {
521
        return true;
522
    }
523
    
524
    public static function checkAPIAccess()
525
    {
526
        return true;
527
    }
528
    
529
    public static function throwUnauthorizedError()
530
    {
531
        return static::respond('Unauthorized', [
532
            'success' => false,
533
            'failed' => [
534
                'errors'	=>	'Login required.',
535
            ],
536
        ]);
537
    }
538
539
    public static function throwAPIUnAuthorizedError()
540
    {
541
        return static::respond('Unauthorized', [
542
            'success' => false,
543
            'failed' => [
544
                'errors'	=>	'API access required.',
545
            ],
546
        ]);
547
    }
548
549
    public static function throwNotFoundError()
550
    {
551
        return static::respond('error', [
552
            'success' => false,
553
            'failed' => [
554
                'errors'	=>	'Record not found.',
555
            ],
556
        ]);
557
    }
558
    
559
    public static function onRecordRequestNotHandled(ActiveRecord $Record, $action)
560
    {
561
        return static::respond('error', [
562
            'success' => false,
563
            'failed' => [
564
                'errors'	=>	'Malformed request.',
565
            ],
566
        ]);
567
    }
568
    
569
570
571
    public static function getTemplateName($noun)
572
    {
573
        return preg_replace_callback('/\s+([a-zA-Z])/', function ($matches) {
574
            return strtoupper($matches[1]);
575
        }, $noun);
576
    }
577
    
578
    public static function applyRecordDelta(ActiveRecord $Record, $data)
579
    {
580
        if (is_array(static::$editableFields)) {
1 ignored issue
show
introduced by
The condition is_array(static::editableFields) is always false.
Loading history...
581
            $Record->setFields(array_intersect_key($data, array_flip(static::$editableFields)));
582
        } else {
583
            $Record->setFields($data);
584
        }
585
    }
586
    
587
    // event template functions
588
    protected static function onRecordCreated(ActiveRecord $Record, $data)
589
    {
590
    }
591
    protected static function onBeforeRecordValidated(ActiveRecord $Record, $data)
592
    {
593
    }
594
    protected static function onBeforeRecordSaved(ActiveRecord $Record, $data)
595
    {
596
    }
597
    protected static function onRecordDeleted(ActiveRecord $Record, $data)
598
    {
599
    }
600
    protected static function onRecordSaved(ActiveRecord $Record, $data)
601
    {
602
    }
603
    
604
    protected static function throwRecordNotFoundError()
605
    {
606
        return static::throwNotFoundError();
607
    }
608
}
609