Passed
Push — master ( 4b152c...2ff9b2 )
by Iman
03:51
created

ExecuteApi::handleListAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

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

543
        $data->skip(/** @scrutinizer ignore-type */ request('offset', 0));
Loading history...
544
        $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

544
        $data->take(/** @scrutinizer ignore-type */ request('limit', 20));
Loading history...
545
        $data = $this->responses($table, $data, $responses, $responsesFields); //End Responses
546
547
        $this->params($parameters, $posts, $data, $table);
548
549
        if (\Schema::hasColumn($table, 'deleted_at')) {
550
            $data->where($table.'.deleted_at', null);
551
        }
552
553
        return $data;
554
    }
555
556
    /**
557
     * @param $table
558
     * @param $data
559
     * @return mixed
560
     */
561
    private function sortRows($table, $data)
562
    {
563
        $orderBy = request('orderby', $table.'.id,desc');
564
565
        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

565
        list($orderByCol, $orderByVal) = explode(',', /** @scrutinizer ignore-type */ $orderBy);
Loading history...
566
567
        $rows = $data->orderby($orderByCol, $orderByVal)->get();
568
569
        return $rows;
570
    }
571
}