Passed
Push — master ( c837ae...12c3ac )
by Iman
04:05
created

ExecuteApi::execute()   C

Complexity

Conditions 7
Paths 10

Size

Total Lines 50
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 32
nc 10
nop 0
dl 0
loc 50
rs 6.7272
c 0
b 0
f 0
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 Illuminate\Support\Facades\DB;
8
use Illuminate\Support\Facades\Hash;
9
use Illuminate\Support\Facades\Schema;
10
use Illuminate\Support\Facades\Validator;
11
12
class ExecuteApi
13
{
14
    private $ctrl;
15
16
    /**
17
     * ExecuteApi constructor.
18
     *
19
     * @param $ctrl
20
     */
21
    public function __construct($ctrl)
22
    {
23
        $this->ctrl = $ctrl;
24
    }
25
26
    public function execute()
27
    {
28
        $rowApi = DB::table('cms_apicustom')->where('permalink', $this->ctrl->permalink)->first();
29
        /* Check the row is exists or not */
30
        $this->checkApiDefined($rowApi);
31
32
        /* Method Type validation */
33
        $this->validateMethodType($rowApi->method_type);
34
35
        /* Do some custome pre-checking for posted data, if failed discard API execution */
36
        $this->doCustomePrecheck();
37
38
        $table = $rowApi->tabel;
39
40
        @$parameters = unserialize($rowApi->parameters);
41
        list($type_except, $input_validator) = $this->validateParams($parameters, $table);
42
43
        $posts = request()->all();
44
        $this->ctrl->hookBefore($posts);
45
46
47
        unset($posts['limit'], $posts['offset'], $posts['orderby']);
48
        $actionType = $rowApi->aksi;
49
        if (in_array($actionType, ['list', 'detail', 'delete'])) {
50
            @$responses = unserialize($rowApi->responses);
51
            $responses_fields = $this->prepareResponses($responses);
52
            $data = $this->fetchDataFromDB($table, $responses, $responses_fields, $parameters, $posts);
53
54
            $this->filterRows($data, $parameters, $posts, $table, $type_except);
55
56
            if (!is_null($rowApi->sql_where)) {
57
                $data->whereraw($rowApi->sql_where);
58
            }
59
60
            $this->ctrl->hookQuery($data);
61
            $result = [];
62
            if ($actionType == 'list') {
63
                $result = $this->handleListAction($table, $data, $responses_fields);
64
            } elseif ($actionType == 'detail') {
65
                $result = $this->handleDetailsAction($data, $parameters, $posts, $responses_fields);
66
            } elseif ($actionType == 'delete') {
67
                $result = $this->handleDeleteAction($table, $data);
68
            }
69
            $this->show($result, $posts);
70
        } elseif (in_array($actionType, ['save_add', 'save_edit'])) {
71
            $rowAssign = array_filter($input_validator, function ($column) use ($table) {
72
                return Schema::hasColumn($table, $column);
73
            }, ARRAY_FILTER_USE_KEY);
74
75
            $this->handleAddEdit($parameters, $posts, $rowAssign);
76
        }
77
78
    }
79
80
    /**
81
     * @param $result
82
     * @param $posts
83
     * @return mixed
84
     */
85
    private function show($result, $posts)
86
    {
87
        $this->ctrl->hookAfter($posts, $result);
88
        $result['api_status'] = $this->ctrl->hook_api_status ?: $result['api_status'];
89
        $result['api_message'] = $this->ctrl->hook_api_message ?: $result['api_message'];
90
91
        if (cbGetsetting('api_debug_mode') == 'true') {
92
            $result['api_authorization'] = 'You are in debug mode !';
93
        }
94
        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

94
        sendAndTerminate(response()->/** @scrutinizer ignore-call */ json($result));
Loading history...
95
    }
96
97
    /**
98
     * @param $responses
99
     * @return array
100
     */
101
    private function prepareResponses($responses)
102
    {
103
        $responsesFields = [];
104
        foreach ($responses as $r) {
105
            if ($r['used']) {
106
                $responsesFields[] = $r['name'];
107
            }
108
        }
109
110
        return $responsesFields;
111
    }
112
113
    /**
114
     * @param $table
115
     * @param $data
116
     * @param $responsesFields
117
     * @return array
118
     */
119
    private function handleListAction($table, $data, $responsesFields)
120
    {
121
        $orderBy = request('orderby', $table.'.id,desc');
122
123
        list($orderByCol, $orderByVal) = explode(',', $orderBy);
0 ignored issues
show
Bug introduced by
It seems like $orderBy can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

578
        $data->skip(/** @scrutinizer ignore-type */ request('offset', 0));
Loading history...
579
        $data->take(request('limit', 20));
0 ignored issues
show
Bug introduced by
request('limit', 20) of type Illuminate\Http\Request|string|array is incompatible with the type integer expected by parameter $value of Illuminate\Database\Query\Builder::take(). ( Ignorable by Annotation )

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

579
        $data->take(/** @scrutinizer ignore-type */ request('limit', 20));
Loading history...
580
        $data = $this->responses($table, $data, $responses, $responsesFields); //End Responses
581
582
        $this->params($parameters, $posts, $data, $table);
583
584
        if (\Schema::hasColumn($table, 'deleted_at')) {
585
            $data->where($table.'.deleted_at', null);
586
        }
587
588
        return $data;
589
    }
590
591
    /**
592
     * @param $status
593
     * @param $msg
594
     * @return array
595
     */
596
    private function makeResult($status, $msg)
597
    {
598
        $result = [
599
            'api_status'=> $status,
600
            'api_message'=> $msg,
601
        ];
602
603
        if (cbGetsetting('api_debug_mode') == 'true') {
604
            $result['api_authorization'] = 'You are in debug mode !';
605
        }
606
607
        return $result;
608
    }
609
}