Passed
Push — master ( 4a70a7...ec1703 )
by Iman
04:09
created

ExecuteApi::handleAddEdit()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
nc 3
nop 3
dl 0
loc 8
c 0
b 0
f 0
cc 4
rs 9.2
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 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
27
        //$posts_keys = array_keys($posts);
28
        //$posts_values = array_values($posts);
29
30
        $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...
31
32
        $actionType = $row_api->aksi;
33
        $table = $row_api->tabel;
34
35
        $debugModeMessage = 'You are in debug mode !';
0 ignored issues
show
Unused Code introduced by
The assignment to $debugModeMessage is dead and can be removed.
Loading history...
36
37
        /* Do some custome pre-checking for posted data, if failed discard API execution */
38
        $this->doCustomePrecheck();
39
40
        /* Method Type validation */
41
        $methodType = $row_api->method_type;
42
        $this->validateMethodType($methodType);
43
44
        /* Check the row is exists or not */
45
        $this->checkApiDefined($row_api);
46
        @$parameters = unserialize($row_api->parameters);
47
        @$responses = unserialize($row_api->responses);
48
49
        /*
50
        | ----------------------------------------------
51
        | User Data Validation
52
        | ----------------------------------------------
53
        |
54
        */
55
        $posts = request()->all();
56
        list($type_except, $input_validator) = $this->validateParams($parameters, $posts, $table);
57
58
59
        $responses_fields = $this->prepareResponses($responses);
60
61
        $this->ctrl->hookBefore($posts);
62
63
        $limit = ($posts['limit']) ?: 20;
64
        $offset = ($posts['offset']) ?: 0;
65
        $orderby = ($posts['orderby']) ?: $table.'.id,desc';
66
67
        unset($posts['limit'], $posts['offset'], $posts['orderby']);
68
69
        if (in_array($actionType, ['list', 'detail', 'delete'])) {
70
            $data = $this->fetchDataFromDB($table, $offset, $limit, $responses, $responses_fields, $parameters, $posts);
71
72
            $this->filterRows($data, $parameters, $posts, $table, $type_except);
73
74
            //IF SQL WHERE IS NOT NULL
75
            if ($row_api->sql_where) {
76
                $data->whereraw($row_api->sql_where);
77
            }
78
79
            $this->ctrl->hookQuery($data);
80
81
            $result = [];
82
            if ($actionType == 'list') {
83
                $result = $this->handleListAction($table, $orderby, $data, $responses_fields);
84
            }elseif ($actionType == 'detail') {
85
                $result = $this->handleDetailsAction($data, $parameters, $posts, $responses_fields);
86
            }elseif ($actionType == 'delete') {
87
                $result = $this->handleDeleteAction($table, $data);
88
            }
89
        }elseif (in_array($actionType, ['save_add', 'save_edit'])) {
90
            $rowAssign = array_filter($input_validator, function ($column) use ($table) {
91
                return Schema::hasColumn($table, $column);
92
            }, ARRAY_FILTER_USE_KEY);
93
94
            $this->handleAddEdit($parameters, $posts, $rowAssign);
95
        }
96
97
        $this->show($result, $posts);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
98
    }
99
100
    /**
101
     * @param $result
102
     * @param $posts
103
     * @return mixed
104
     */
105
    private function show($result, $posts)
106
    {
107
        $result['api_status'] = $this->ctrl->hook_api_status ?: $result['api_status'];
108
        $result['api_message'] = $this->ctrl->hook_api_message ?: $result['api_message'];
109
110
        if (cbGetsetting('api_debug_mode') == 'true') {
111
            $result['api_authorization'] = 'You are in debug mode !';
112
        }
113
114
        $this->ctrl->hookAfter($posts, $result);
115
116
        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

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