Passed
Pull Request — master (#1139)
by Iman
03:41
created

ExecuteApi::prepareResponses()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
nc 3
nop 1
dl 0
loc 10
c 0
b 0
f 0
cc 3
rs 9.4285
1
<?php
2
3
namespace crocodicstudio\crudbooster\controllers\ApiController;
4
5
use crocodicstudio\crudbooster\helpers\DbInspector;
6
use crocodicstudio\crudbooster\Modules\ModuleGenerator\ControllerGenerator\FieldDetector;
7
use crocodicstudio\crudbooster\helpers\CRUDBooster, CB;
8
use Illuminate\Support\Facades\Schema;
9
10
class ExecuteApi
11
{
12
    private $ctrl;
13
14
    /**
15
     * ExecuteApi constructor.
16
     *
17
     * @param $ctrl
18
     */
19
    public function __construct($ctrl)
20
    {
21
        $this->ctrl = $ctrl;
22
    }
23
24
    public function execute()
25
    {
26
        $result = [];
27
        $posts = request()->all();
28
        //$posts_keys = array_keys($posts);
29
        //$posts_values = array_values($posts);
30
31
        $row_api = DB::table('cms_apicustom')->where('permalink', $this->ctrl->permalink)->first();
0 ignored issues
show
Bug introduced by
The type crocodicstudio\crudboost...ollers\ApiController\DB was not found. Did you mean DB? If so, make sure to prefix the type with \.
Loading history...
32
33
        $actionType = $row_api->aksi;
34
        $table = $row_api->tabel;
35
36
        $debugModeMessage = 'You are in debug mode !';
37
38
        /* Do some custome pre-checking for posted data, if failed discard API execution */
39
        $this->doCustomePrecheck($posts, $result, $debugModeMessage);
40
41
        /* Method Type validation */
42
        $this->validateMethodType($row_api, $result, $debugModeMessage, $posts);
43
44
        /* Check the row is exists or not */
45
        $this->checkApiDefined($row_api, $result, $debugModeMessage, $posts);
46
47
        @$parameters = unserialize($row_api->parameters);
48
        @$responses = unserialize($row_api->responses);
49
50
        /*
51
        | ----------------------------------------------
52
        | User Data Validation
53
        | ----------------------------------------------
54
        |
55
        */
56
        if ($parameters) {
57
            $type_except = ['password', 'ref', 'base64_file', 'custom', 'search'];
58
            $input_validator = [];
59
            $data_validation = [];
60
61
            foreach ($parameters as $param) {
62
                $name = $param['name'];
63
                $value = $posts[$name];
64
                $used = $param['used'];
65
66
                if ($used == 0) {
67
                    continue;
68
                }
69
                if ($param['config'] && substr($param['config'], 0, 1) != '*') {
70
                    continue;
71
                }
72
73
                $input_validator[$name] = $value;
74
                $data_validation[$name] = app(ValidationRules::class)->make($param, $type_except, $table);
75
            }
76
77
            $result = $this->doValidation($input_validator, $data_validation, $result, $debugModeMessage, $posts);
78
        }
79
80
        $responses_fields = $this->prepareResponses($responses);
81
82
        $this->ctrl->hookBefore($posts);
83
84
        $limit = ($posts['limit']) ?: 20;
85
        $offset = ($posts['offset']) ?: 0;
86
        $orderby = ($posts['orderby']) ?: $table.'.id,desc';
87
88
        unset($posts['limit'], $posts['offset'], $posts['orderby']);
89
90
        if (in_array($actionType, ['list', 'detail', 'delete'])) {
91
            $data = DB::table($table);
92
            $data->skip($offset);
93
            $data->take($limit);
94
            $data = $this->responses($table, $data, $responses, $responses_fields); //End Responses
95
96
            $this->params($parameters, $posts, $data, $table);
97
98
            if (\Schema::hasColumn($table, 'deleted_at')) {
99
                $data->where($table.'.deleted_at', null);
100
            }
101
102
            $this->filterRows($data, $parameters, $posts, $table, $type_except);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type_except does not seem to be defined for all execution paths leading up to this point.
Loading history...
103
104
            //IF SQL WHERE IS NOT NULL
105
            if ($row_api->sql_where) {
106
                $data->whereraw($row_api->sql_where);
107
            }
108
109
            $this->ctrl->hookQuery($data);
110
            if ($actionType == 'list') {
111
                $result = $this->handleListAction($table, $orderby, $data, $result, $debugModeMessage, $responses_fields);
112
            }
113
            $result = $this->handleDetailsAction($actionType, $result, $debugModeMessage, $data, $parameters, $posts, $responses_fields);
114
            if ($actionType == 'delete') {
115
                $result = $this->handleDeleteAction($actionType, $table, $data, $result, $debugModeMessage);
0 ignored issues
show
Unused Code introduced by
The call to crocodicstudio\crudboost...i::handleDeleteAction() has too many arguments starting with $debugModeMessage. ( Ignorable by Annotation )

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

115
                /** @scrutinizer ignore-call */ 
116
                $result = $this->handleDeleteAction($actionType, $table, $data, $result, $debugModeMessage);

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...
116
            }
117
        }
118
119
        if (in_array($actionType, ['save_add', 'save_edit'])) {
120
            $rowAssign = array_filter($input_validator, function ($column) use ($table) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $input_validator does not seem to be defined for all execution paths leading up to this point.
Loading history...
121
                return Schema::hasColumn($table, $column);
122
            }, ARRAY_FILTER_USE_KEY);
123
124
            $this->handleAddEdit($parameters, $posts, $rowAssign);
125
        }
126
127
        $this->show($result, $debugModeMessage, $posts);
128
    }
129
130
    /**
131
     * @param $result
132
     * @param $debugModeMessage
133
     * @param $posts
134
     * @return mixed
135
     */
136
    private function show($result, $debugModeMessage, $posts)
137
    {
138
        $result['api_status'] = $this->ctrl->hook_api_status ?: $result['api_status'];
139
        $result['api_message'] = $this->ctrl->hook_api_message ?: $result['api_message'];
140
141
        if (cbGetsetting('api_debug_mode') == 'true') {
142
            $result['api_authorization'] = $debugModeMessage;
143
        }
144
145
        $this->ctrl->hookAfter($posts, $result);
146
147
        sendAndTerminate(response()->json($result));
0 ignored issues
show
Bug introduced by
The method json() does not exist on Symfony\Component\HttpFoundation\Response. It seems like you code against a sub-type of Symfony\Component\HttpFoundation\Response such as Illuminate\Http\Response or Illuminate\Http\JsonResponse or Illuminate\Http\RedirectResponse. ( Ignorable by Annotation )

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

147
        sendAndTerminate(response()->/** @scrutinizer ignore-call */ json($result));
Loading history...
148
    }
149
150
    /**
151
     * @param $responses
152
     * @return array
153
     */
154
    private function prepareResponses($responses)
155
    {
156
        $responsesFields = [];
157
        foreach ($responses as $r) {
158
            if ($r['used']) {
159
                $responsesFields[] = $r['name'];
160
            }
161
        }
162
163
        return $responsesFields;
164
    }
165
166
    /**
167
     * @param $table
168
     * @param $orderby
169
     * @param $data
170
     * @param $result
171
     * @param $debugModeMessage
172
     * @param $responsesFields
173
     * @return array
174
     */
175
    private function handleListAction($table, $orderby, $data, $result, $debugModeMessage, $responsesFields)
176
    {
177
        $orderbyCol = $table.'.id';
178
        $orderbyVal = 'desc';
179
180
        if ($orderby) {
181
            list($orderbyCol, $orderbyVal) = explode(',', $orderby);
182
        }
183
184
        $rows = $data->orderby($orderbyCol, $orderbyVal)->get();
185
186
        $result['api_status'] = 0;
187
        $result['api_message'] = 'There is no data found !';
188
        if (cbGetsetting('api_debug_mode') == 'true') {
189
            $result['api_authorization'] = $debugModeMessage;
190
        }
191
        $result['data'] = [];
192
        if ($rows) {
193
            $result = $this->handleRows($result, $debugModeMessage, $responsesFields, $rows);
194
        }
195
196
        return $result;
197
    }
198
199
    /**
200
     * @param $table
201
     * @param $data
202
     * @param $result
203
     * @param $debugModeMessage
204
     * @return mixed
205
     */
206
    private function handleDeleteAction($table, $data, $result, $debugModeMessage)
207
    {
208
        if (\Schema::hasColumn($table, 'deleted_at')) {
209
            $delete = $data->update(['deleted_at' => date('Y-m-d H:i:s')]);
210
        } else {
211
            $delete = $data->delete();
212
        }
213
214
        $result['api_status'] = ($delete) ? 1 : 0;
215
        $result['api_message'] = ($delete) ? "success" : "failed";
216
        if (cbGetsetting('api_debug_mode') == 'true') {
217
            $result['api_authorization'] = $debugModeMessage;
218
        }
219
220
        return $result;
221
    }
222
223
    /**
224
     * @param $data
225
     * @param $parameters
226
     * @param $posts
227
     * @param $table
228
     * @param $typeExcept
229
     */
230
    private function filterRows($data, $parameters, $posts, $table, $typeExcept)
231
    {
232
        $data->where(function ($w) use ($parameters, $posts, $table, $typeExcept) {
233
            foreach ($parameters as $param) {
234
                $name = $param['name'];
235
                $type = $param['type'];
236
                $value = $posts[$name];
237
                $used = $param['used'];
238
                $required = $param['required'];
239
240
                if (in_array($type, $typeExcept)) {
241
                    continue;
242
                }
243
244
                if ($param['config'] != '' && substr($param['config'], 0, 1) != '*') {
245
                    $value = $param['config'];
246
                }
247
248
                if ($required == '1') {
249
                    $this->applyWhere($w, $table, $name, $value);
250
                } else {
251
                    if ($used && $value) {
252
                        $this->applyWhere($w, $table, $name, $value);
253
                    }
254
                }
255
            }
256
        });
257
    }
258
259
    /**
260
     * @param $w
261
     * @param $table
262
     * @param $name
263
     * @param $value
264
     */
265
    private function applyWhere($w, $table, $name, $value)
266
    {
267
        if (\Schema::hasColumn($table, $name)) {
268
            $w->where($table.'.'.$name, $value);
269
        } else {
270
            $w->having($name, '=', $value);
271
        }
272
    }
273
274
    /**
275
     * @param $parameters
276
     * @param $posts
277
     * @param $data
278
     * @param $table
279
     * @return null
280
     */
281
    private function params($parameters, $posts, $data, $table)
282
    {
283
        foreach ($parameters as $param) {
284
            $name = $param['name'];
285
            $type = $param['type'];
286
            $value = $posts[$name];
287
            $used = $param['used'];
288
            $required = $param['required'];
289
            $config = $param['config'];
290
291
            if ($type == 'password') {
292
                $data->addselect($table.'.'.$name);
293
            }
294
295
            if ($type !== 'search') {
296
                continue;
297
            }
298
            $search_in = explode(',', $config);
299
300
            if ($required == '1' || ($used && $value)) {
301
                $this->applyLike($data, $search_in, $value);
302
            }
303
        }
304
    }
305
306
    /**
307
     * @param $result
308
     * @param $debugModeMessage
309
     * @param $responsesFields
310
     * @param $rows
311
     * @return array
312
     */
313
    private function handleRows($result, $debugModeMessage, $responsesFields, $rows)
314
    {
315
        $uploadsFormatCandidate = explode(',', cbConfig("UPLOAD_TYPES"));
316
        foreach ($rows as &$row) {
317
            foreach ($row as $k => $v) {
318
                $ext = \File::extension($v);
319
                if (in_array($ext, $uploadsFormatCandidate)) {
320
                    $row->$k = asset($v);
321
                }
322
323
                if (! in_array($k, $responsesFields)) {
324
                    unset($row[$k]);
325
                }
326
            }
327
        }
328
329
        $result['api_status'] = 1;
330
        $result['api_message'] = 'success';
331
        if (cbGetsetting('api_debug_mode') == 'true') {
332
            $result['api_authorization'] = $debugModeMessage;
333
        }
334
        $result['data'] = $rows;
335
336
        return $result;
337
    }
338
339
    /**
340
     * @param $parameters
341
     * @param $posts
342
     * @param $rowAssign
343
     */
344
    private function handleAddEdit($parameters, $posts, $rowAssign)
345
    {
346
        foreach ($parameters as $param) {
347
            $name = $param['name'];
348
            $used = $param['used'];
349
            $value = $posts[$name];
350
            if ($used == '1' && $value == '') {
351
                unset($rowAssign[$name]);
352
            }
353
        }
354
    }
355
356
    /**
357
     * @param $result
358
     * @param $debugModeMessage
359
     * @param $posts
360
     * @return mixed
361
     */
362
    private function passwordError($result, $debugModeMessage, $posts)
363
    {
364
        $result['api_status'] = 0;
365
        $result['api_message'] = cbTrans('alert_password_wrong');
366
        if (cbGetsetting('api_debug_mode') == 'true') {
367
            $result['api_authorization'] = $debugModeMessage;
368
        }
369
370
        $this->show($result, $debugModeMessage, $posts);
371
    }
372
373
    /**
374
     * @param $rows
375
     * @param $responsesFields
376
     * @param $row
377
     */
378
    private function handleFile($rows, $responsesFields, $row)
379
    {
380
        foreach ($rows as $k => $v) {
381
            $ext = \File::extension($v);
382
            if (FieldDetector::isUploadField($ext)) {
383
                $rows->$k = asset($v);
384
            }
385
386
            if (! in_array($k, $responsesFields)) {
387
                unset($row[$k]);
388
            }
389
        }
390
    }
391
392
    /**
393
     * @param $table
394
     * @param $data
395
     * @param $responses
396
     *
397
     * @param $responsesFields
398
     * @return array
399
     */
400
    private function responses($table, $data, $responses, $responsesFields)
401
    {
402
        $name_tmp = [];
403
404
        $responses = $this->filterRedundantResp($responses);
405
        
406
        foreach ($responses as $resp) {
407
            $name = $resp['name'];
408
            $subquery = $resp['subquery'];
409
            $used = intval($resp['used']);
410
411
            if (in_array($name, $name_tmp)) {
412
                continue;
413
            }
414
415
            if ($subquery) {
416
                $data->addSelect(DB::raw('('.$subquery.') as '.$name));
417
                $name_tmp[] = $name;
418
                continue;
419
            }
420
421
            if ($used) {
422
                $data->addSelect($table.'.'.$name);
423
            }
424
425
            $name_tmp[] = $name;
426
            $name_tmp = $this->joinRelatedTables($table, $responsesFields, $name, $data, $name_tmp);
427
        }
428
429
        return $data;
430
    }
431
432
    /**
433
     * @param $result
434
     * @param $debugModeMessage
435
     * @param $rows
436
     * @return array
437
     */
438
    private function success($result, $debugModeMessage, $rows)
439
    {
440
        $result['api_status'] = 1;
441
        $result['api_message'] = 'success';
442
        if (cbGetsetting('api_debug_mode') == 'true') {
443
            $result['api_authorization'] = $debugModeMessage;
444
        }
445
        $rows = (array) $rows;
446
        $result = array_merge($result, $rows);
447
448
        return $result;
449
    }
450
451
    /**
452
     * @param $data
453
     * @param $search_in
454
     * @param $value
455
     */
456
    private function applyLike($data, $search_in, $value)
457
    {
458
        $data->where(function ($w) use ($search_in, $value) {
459
            foreach ($search_in as $k => $field) {
460
                if ($k == 0) {
461
                    $w->where($field, "like", "%$value%");
462
                } else {
463
                    $w->orWhere($field, "like", "%$value%");
464
                }
465
            }
466
        });
467
    }
468
469
    /**
470
     * @param $table
471
     * @param $responsesFields
472
     * @param $name
473
     * @param $data
474
     * @param $nameTmp
475
     * @return array
476
     */
477
    private function joinRelatedTables($table, $responsesFields, $name, $data, $nameTmp)
478
    {
479
        if (! DbInspector::isForeignKey($name)) {
480
            return $nameTmp;
481
        }
482
        $jointable = DbInspector::getTableForeignKey($name);
483
        $jointable_field = DbInspector::getTableCols($jointable);
484
485
        $data->leftjoin($jointable, $jointable.'.id', '=', $table.'.'.$name);
486
        foreach ($jointable_field as $jf) {
487
            $jfAlias = $jointable.'_'.$jf;
488
            if (in_array($jfAlias, $responsesFields)) {
489
                $data->addselect($jointable.'.'.$jf.' as '.$jfAlias);
490
                $nameTmp[] = $jfAlias;
491
            }
492
        }
493
        return $nameTmp;
494
    }
495
496
    /**
497
     * @param $row_api
498
     * @param $result
499
     * @param $debugModeMessage
500
     * @param $posts
501
     * @return mixed
502
     */
503
    private function validateMethodType($row_api, $result, $debugModeMessage, $posts)
504
    {
505
        $methodType = $row_api->method_type;
506
        if (! $methodType || ! Request::isMethod($methodType)) {
0 ignored issues
show
Bug introduced by
The type crocodicstudio\crudboost...s\ApiController\Request was not found. Did you mean Request? If so, make sure to prefix the type with \.
Loading history...
507
            $result['api_status'] = 0;
508
            $result['api_message'] = "The request method is not allowed !";
509
510
            $this->show($result, $debugModeMessage, $posts);
511
        }
512
513
        return $result;
514
    }
515
516
    /**
517
     * @param $posts
518
     * @param $result
519
     * @param $debugModeMessage
520
     * @return mixed
521
     */
522
    private function doCustomePrecheck($posts, $result, $debugModeMessage)
523
    {
524
        $this->ctrl->hookValidate($posts);
525
        if ($this->ctrl->validate) { // hook have to return true
526
            $result['api_status'] = 0;
527
            $result['api_message'] = "Failed to execute API !";
528
529
            $this->show($result, $debugModeMessage, $posts);
530
        }
531
532
        return $result;
533
    }
534
535
    /**
536
     * @param $row_api
537
     * @param $result
538
     * @param $debugModeMessage
539
     * @param $posts
540
     * @return mixed
541
     */
542
    private function checkApiDefined($row_api, $result, $debugModeMessage, $posts)
543
    {
544
        if (! $row_api) {
545
            $result['api_status'] = 0;
546
            $result['api_message'] = 'Sorry this API is no longer available, maybe has changed by admin, or please make sure api url is correct.';
547
548
            $this->show($result, $debugModeMessage, $posts);
549
        }
550
551
        return $result;
552
    }
553
554
    /**
555
     * @param $input_validator
556
     * @param $data_validation
557
     * @param $result
558
     * @param $debugModeMessage
559
     * @param $posts
560
     * @return mixed
561
     */
562
    private function doValidation($input_validator, $data_validation, $result, $debugModeMessage, $posts)
563
    {
564
        $validator = Validator::make($input_validator, $data_validation);
0 ignored issues
show
Bug introduced by
The type crocodicstudio\crudboost...ApiController\Validator was not found. Did you mean Validator? If so, make sure to prefix the type with \.
Loading history...
565
        if ($validator->fails()) {
566
            $message = $validator->errors()->all();
567
            $message = implode(', ', $message);
568
            $result['api_status'] = 0;
569
            $result['api_message'] = $message;
570
571
            $this->show($result, $debugModeMessage, $posts);
572
        }
573
574
        return $result;
575
    }
576
577
    /**
578
     * @param $action_type
579
     * @param $result
580
     * @param $debugModeMessage
581
     * @param $data
582
     * @param $parameters
583
     * @param $posts
584
     * @param $responses_fields
585
     * @return array
586
     */
587
    private function handleDetailsAction($action_type, $result, $debugModeMessage, $data, $parameters, $posts, $responses_fields)
588
    {
589
        if ($action_type != 'detail') {
590
            return $result;
591
        }
592
593
        $result['api_status'] = 0;
594
        $result['api_message'] = 'There is no data found !';
595
596
        if (cbGetsetting('api_debug_mode') == 'true') {
597
            $result['api_authorization'] = $debugModeMessage;
598
        }
599
600
        $row = $data->first();
601
602
        if ($row) {
603
            foreach ($parameters as $param) {
604
                $name = $param['name'];
605
                $type = $param['type'];
606
                $value = $posts[$name];
607
                $used = $param['used'];
608
                $required = $param['required'];
609
610
                if ($param['config'] != '' && substr($param['config'], 0, 1) != '*') {
611
                    $value = $param['config'];
612
                }
613
614
                if ($required && $type == 'password' && ! Hash::check($value, $row->{$name})) {
0 ignored issues
show
Bug introduced by
The type crocodicstudio\crudboost...lers\ApiController\Hash was not found. Did you mean Hash? If so, make sure to prefix the type with \.
Loading history...
615
                    $this->passwordError($result, $debugModeMessage, $posts);
616
                }
617
618
                if (! $required && $used && $value && ! Hash::check($value, $row->{$name})) {
619
                    $this->passwordError($result, $debugModeMessage, $posts);
620
                }
621
            }
622
623
            $this->handleFile($row, $responses_fields, $row);
624
625
            $result = $this->success($result, $debugModeMessage, $row);
626
        }
627
        return $result;
628
    }
629
630
    /**
631
     * @param $responses
632
     * @return array
633
     */
634
    private function filterRedundantResp($responses)
635
    {
636
        $responses = array_filter($responses, function ($resp) {
637
            return ! ($resp['name'] == 'ref_id' || $resp['type'] == 'custom');
638
        });
639
640
        $responses = array_filter($responses, function ($resp) {
641
            return (intval($resp['used']) != 0 || DbInspector::isForeignKey($resp['name']));
642
        });
643
644
        return $responses;
645
    }
646
}