Passed
Push — master ( 12c3ac...b3cb09 )
by Iman
03:57
created

ExecuteApi::sortRows()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

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

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

561
        $data->take(/** @scrutinizer ignore-type */ request('limit', 20));
Loading history...
562
        $data = $this->responses($table, $data, $responses, $responsesFields); //End Responses
563
564
        $this->params($parameters, $posts, $data, $table);
565
566
        if (\Schema::hasColumn($table, 'deleted_at')) {
567
            $data->where($table.'.deleted_at', null);
568
        }
569
570
        return $data;
571
    }
572
573
    /**
574
     * @param $status
575
     * @param $msg
576
     * @return array
577
     */
578
    private function makeResult($status, $msg)
579
    {
580
        $result = [
581
            'api_status'=> $status,
582
            'api_message'=> $msg,
583
        ];
584
585
        if (cbGetsetting('api_debug_mode') == 'true') {
586
            $result['api_authorization'] = 'You are in debug mode !';
587
        }
588
589
        return $result;
590
    }
591
592
    /**
593
     * @param $table
594
     * @param $data
595
     * @return mixed
596
     */
597
    private function sortRows($table, $data)
598
    {
599
        $orderBy = request('orderby', $table.'.id,desc');
600
601
        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

601
        list($orderByCol, $orderByVal) = explode(',', /** @scrutinizer ignore-type */ $orderBy);
Loading history...
602
603
        $rows = $data->orderby($orderByCol, $orderByVal)->get();
604
605
        return $rows;
606
    }
607
}