Scrutinizer GitHub App not installed

We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.

Install GitHub App

Passed
Push — test-upload ( 6059da )
by Pedro
29:51 queued 18:46
created

Search   F

Complexity

Total Complexity 66

Size/Duplication

Total Lines 410
Duplicated Lines 0 %

Importance

Changes 2
Bugs 2 Features 0
Metric Value
eloc 129
c 2
b 2
f 0
dl 0
loc 410
rs 3.12
wmc 66

20 Methods

Rating   Name   Duplication   Size   Complexity  
A applySearchTerm() 0 9 3
A disableResponsiveTable() 0 3 1
A getPersistentTableDuration() 0 7 2
B getCellViewName() 0 30 7
C applyDatatableOrder() 0 43 15
A getResponsiveTable() 0 7 2
A isJsonColumnType() 0 3 1
A setPersistentTable() 0 3 1
A getPersistentTable() 0 7 2
A getTableCellHtml() 0 3 1
A enablePersistentTable() 0 3 1
A disablePersistentTable() 0 3 1
C applySearchLogicForColumn() 0 56 14
A enableResponsiveTable() 0 3 1
A getRowViews() 0 34 5
A setResponsiveTable() 0 3 1
A renderCellView() 0 12 2
A getColumnWithTableNamePrefixed() 0 3 1
A getCellView() 0 3 1
A getEntriesAsJsonForDatatables() 0 13 4

How to fix   Complexity   

Complex Class

Complex classes like Search often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Search, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Backpack\CRUD\app\Library\CrudPanel\Traits;
4
5
use Backpack\CRUD\ViewNamespaces;
6
use Carbon\Carbon;
7
use Validator;
8
9
trait Search
10
{
11
    /*
12
    |--------------------------------------------------------------------------
13
    |                                   SEARCH
14
    |--------------------------------------------------------------------------
15
    */
16
17
    /**
18
     * Add conditions to the CRUD query for a particular search term.
19
     *
20
     * @param  string  $searchTerm  Whatever string the user types in the search bar.
21
     * @return \Illuminate\Database\Eloquent\Builder
22
     */
23
    public function applySearchTerm($searchTerm)
24
    {
25
        return $this->query->where(function ($query) use ($searchTerm) {
26
            foreach ($this->columns() as $column) {
0 ignored issues
show
Bug introduced by
It seems like columns() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

26
            foreach ($this->/** @scrutinizer ignore-call */ columns() as $column) {
Loading history...
27
                if (! isset($column['type'])) {
28
                    abort(400, 'Missing column type when trying to apply search term.');
29
                }
30
31
                $this->applySearchLogicForColumn($query, $column, $searchTerm);
32
            }
33
        });
34
    }
35
36
    /**
37
     * Apply the search logic for each CRUD column.
38
     */
39
    public function applySearchLogicForColumn($query, $column, $searchTerm)
40
    {
41
        $columnType = $column['type'];
42
43
        // if there's a particular search logic defined, apply that one
44
        if (isset($column['searchLogic'])) {
45
            $searchLogic = $column['searchLogic'];
46
47
            // if a closure was passed, execute it
48
            if (is_callable($searchLogic)) {
49
                return $searchLogic($query, $column, $searchTerm);
50
            }
51
52
            // if a string was passed, search like it was that column type
53
            if (is_string($searchLogic)) {
54
                $columnType = $searchLogic;
55
            }
56
57
            // if false was passed, don't search this column
58
            if ($searchLogic == false) {
59
                return;
60
            }
61
        }
62
63
        // sensible fallback search logic, if none was explicitly given
64
        if ($column['tableColumn']) {
65
            $searchOperator = config('backpack.operations.list.searchOperator', 'like');
66
67
            switch ($columnType) {
68
                case 'email':
69
                case 'text':
70
                case 'textarea':
71
                    $query->orWhere($this->getColumnWithTableNamePrefixed($query, $column['name']), $searchOperator, '%'.$searchTerm.'%');
72
                    break;
73
74
                case 'date':
75
                case 'datetime':
76
                    $validator = Validator::make(['value' => $searchTerm], ['value' => 'date']);
77
78
                    if ($validator->fails()) {
79
                        break;
80
                    }
81
82
                    $query->orWhereDate($this->getColumnWithTableNamePrefixed($query, $column['name']), Carbon::parse($searchTerm));
83
                    break;
84
85
                case 'select':
86
                case 'select_multiple':
87
                    $query->orWhereHas($column['entity'], function ($q) use ($column, $searchTerm, $searchOperator) {
88
                        $q->where($this->getColumnWithTableNamePrefixed($q, $column['attribute']), $searchOperator, '%'.$searchTerm.'%');
89
                    });
90
                    break;
91
92
                default:
93
                    return;
94
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
95
            }
96
        }
97
    }
98
99
    /**
100
     * Apply the datatables order to the crud query.
101
     */
102
    public function applyDatatableOrder()
103
    {
104
        if (request()->input('order')) {
105
            // clear any past orderBy rules
106
            $this->query->getQuery()->orders = null;
107
            foreach ((array) request()->input('order') as $order) {
108
                $column_number = (int) $order['column'];
109
                $column_direction = (strtolower((string) $order['dir']) == 'asc' ? 'ASC' : 'DESC');
110
                $column = $this->findColumnById($column_number);
0 ignored issues
show
Bug introduced by
It seems like findColumnById() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

110
                /** @scrutinizer ignore-call */ 
111
                $column = $this->findColumnById($column_number);
Loading history...
111
                if ($column['tableColumn'] && ! isset($column['orderLogic'])) {
112
                    if (method_exists($this->model, 'translationEnabled') &&
113
                        $this->model->translationEnabled() &&
114
                        $this->model->isTranslatableAttribute($column['name']) &&
115
                        $this->isJsonColumnType($column['name'])
116
                    ) {
117
                        $this->orderByWithPrefix($column['name'].'->'.app()->getLocale(), $column_direction);
0 ignored issues
show
Bug introduced by
It seems like orderByWithPrefix() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

117
                        $this->/** @scrutinizer ignore-call */ 
118
                               orderByWithPrefix($column['name'].'->'.app()->getLocale(), $column_direction);
Loading history...
introduced by
The method getLocale() does not exist on Illuminate\Container\Container. Are you sure you never get this type here, but always one of the subclasses? ( Ignorable by Annotation )

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

117
                        $this->orderByWithPrefix($column['name'].'->'.app()->/** @scrutinizer ignore-call */ getLocale(), $column_direction);
Loading history...
118
                    } else {
119
                        $this->orderByWithPrefix($column['name'], $column_direction);
120
                    }
121
                }
122
123
                // check for custom order logic in the column definition
124
                if (isset($column['orderLogic'])) {
125
                    $this->customOrderBy($column, $column_direction);
0 ignored issues
show
Bug introduced by
It seems like customOrderBy() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

125
                    $this->/** @scrutinizer ignore-call */ 
126
                           customOrderBy($column, $column_direction);
Loading history...
126
                }
127
            }
128
        }
129
130
        // show newest items first, by default (if no order has been set for the primary column)
131
        // if there was no order set, this will be the only one
132
        // if there was an order set, this will be the last one (after all others were applied)
133
        // Note to self: `toBase()` returns also the orders contained in global scopes, while `getQuery()` don't.
134
        $orderBy = $this->query->toBase()->orders;
135
        $table = $this->model->getTable();
136
        $key = $this->model->getKeyName();
137
138
        $hasOrderByPrimaryKey = collect($orderBy)->some(function ($item) use ($key, $table) {
139
            return (isset($item['column']) && $item['column'] === $key)
140
                || (isset($item['sql']) && str_contains($item['sql'], "$table.$key"));
141
        });
142
143
        if (! $hasOrderByPrimaryKey) {
144
            $this->orderByWithPrefix($key, 'DESC');
145
        }
146
    }
147
148
    // -------------------------
149
    // Responsive Table
150
    // -------------------------
151
152
    /**
153
     * Tell the list view to NOT show a reponsive DataTable.
154
     *
155
     * @param  bool  $value
156
     */
157
    public function setResponsiveTable($value = true)
158
    {
159
        $this->setOperationSetting('responsiveTable', $value);
0 ignored issues
show
Bug introduced by
It seems like setOperationSetting() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

159
        $this->/** @scrutinizer ignore-call */ 
160
               setOperationSetting('responsiveTable', $value);
Loading history...
160
    }
161
162
    /**
163
     * Check if responsiveness is enabled for the table view.
164
     *
165
     * @return bool
166
     */
167
    public function getResponsiveTable()
168
    {
169
        if ($this->getOperationSetting('responsiveTable') !== null) {
0 ignored issues
show
Bug introduced by
It seems like getOperationSetting() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

169
        if ($this->/** @scrutinizer ignore-call */ getOperationSetting('responsiveTable') !== null) {
Loading history...
170
            return $this->getOperationSetting('responsiveTable');
171
        }
172
173
        return config('backpack.crud.operations.list.responsiveTable');
174
    }
175
176
    /**
177
     * Remember to show a responsive table.
178
     */
179
    public function enableResponsiveTable()
180
    {
181
        $this->setResponsiveTable(true);
182
    }
183
184
    /**
185
     * Remember to show a table with horizontal scrolling.
186
     */
187
    public function disableResponsiveTable()
188
    {
189
        $this->setResponsiveTable(false);
190
    }
191
192
    // -------------------------
193
    // Persistent Table
194
    // -------------------------
195
196
    /**
197
     * Tell the list view to NOT store datatable information in local storage.
198
     *
199
     * @param  bool  $value
200
     */
201
    public function setPersistentTable($value = true)
202
    {
203
        return $this->setOperationSetting('persistentTable', $value);
204
    }
205
206
    /**
207
     * Check if saved state is enabled for the table view.
208
     *
209
     * @return bool
210
     */
211
    public function getPersistentTable()
212
    {
213
        if ($this->getOperationSetting('persistentTable') !== null) {
214
            return $this->getOperationSetting('persistentTable');
215
        }
216
217
        return config('backpack.crud.operations.list.persistentTable');
218
    }
219
220
    /**
221
     * Get duration for persistent table.
222
     *
223
     * @return bool
224
     */
225
    public function getPersistentTableDuration()
226
    {
227
        if ($this->getOperationSetting('persistentTableDuration') !== null) {
228
            return $this->getOperationSetting('persistentTableDuration');
229
        }
230
231
        return config('backpack.crud.operations.list.persistentTableDuration', false);
232
    }
233
234
    /**
235
     * Remember to show a persistent table.
236
     */
237
    public function enablePersistentTable()
238
    {
239
        return $this->setPersistentTable(true);
240
    }
241
242
    /**
243
     * Remember to show a table that doesn't store URLs and pagination in local storage.
244
     */
245
    public function disablePersistentTable()
246
    {
247
        return $this->setPersistentTable(false);
248
    }
249
250
    /**
251
     * Get the HTML of the cells in a table row, for a certain DB entry.
252
     *
253
     * @param  \Illuminate\Database\Eloquent\Model  $entry  A db entry of the current entity;
254
     * @param  bool|int  $rowNumber  The number shown to the user as row number (index);
255
     * @return array Array of HTML cell contents.
256
     */
257
    public function getRowViews($entry, $rowNumber = false)
258
    {
259
        $row_items = [];
260
261
        foreach ($this->columns() as $key => $column) {
262
            $row_items[] = $this->getCellView($column, $entry, $rowNumber);
263
        }
264
265
        // add the buttons as the last column
266
        if ($this->buttons()->where('stack', 'line')->count()) {
0 ignored issues
show
Bug introduced by
It seems like buttons() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

266
        if ($this->/** @scrutinizer ignore-call */ buttons()->where('stack', 'line')->count()) {
Loading history...
267
            $row_items[] = \View::make('crud::inc.button_stack', ['stack' => 'line'])
268
                                ->with('crud', $this)
269
                                ->with('entry', $entry)
270
                                ->with('row_number', $rowNumber)
271
                                ->render();
272
        }
273
274
        // add the bulk actions checkbox to the first column
275
        if ($this->getOperationSetting('bulkActions')) {
276
            $bulk_actions_checkbox = \View::make('crud::columns.inc.bulk_actions_checkbox', ['entry' => $entry])->render();
277
            $row_items[0] = $bulk_actions_checkbox.$row_items[0];
278
        }
279
280
        // add the details_row button to the first column
281
        if ($this->getOperationSetting('detailsRow')) {
282
            $details_row_button = \View::make('crud::columns.inc.details_row_button')
283
                                           ->with('crud', $this)
284
                                           ->with('entry', $entry)
285
                                           ->with('row_number', $rowNumber)
286
                                           ->render();
287
            $row_items[0] = $details_row_button.$row_items[0];
288
        }
289
290
        return $row_items;
291
    }
292
293
    /**
294
     * Get the HTML of a cell, using the column types.
295
     *
296
     * @param  array  $column
297
     * @param  \Illuminate\Database\Eloquent\Model  $entry  A db entry of the current entity;
298
     * @param  bool|int  $rowNumber  The number shown to the user as row number (index);
299
     * @return string
300
     */
301
    public function getCellView($column, $entry, $rowNumber = false)
302
    {
303
        return $this->renderCellView($this->getCellViewName($column), $column, $entry, $rowNumber);
304
    }
305
306
    /**
307
     * Get the name of the view to load for the cell.
308
     *
309
     * @param  array  $column
310
     * @return string
311
     */
312
    private function getCellViewName($column)
313
    {
314
        // return custom column if view_namespace attribute is set
315
        if (isset($column['view_namespace']) && isset($column['type'])) {
316
            return $column['view_namespace'].'.'.$column['type'];
317
        }
318
319
        if (isset($column['type'])) {
320
            // create a list of paths to column blade views
321
            // including the configured view_namespaces
322
            $columnPaths = array_map(function ($item) use ($column) {
323
                return $item.'.'.$column['type'];
324
            }, ViewNamespaces::getFor('columns'));
325
326
            // but always fall back to the stock 'text' column
327
            // if a view doesn't exist
328
            if (! in_array('crud::columns.text', $columnPaths)) {
329
                $columnPaths[] = 'crud::columns.text';
330
            }
331
332
            // return the first column blade file that exists
333
            foreach ($columnPaths as $path) {
334
                if (view()->exists($path)) {
335
                    return $path;
336
                }
337
            }
338
        }
339
340
        // fallback to text column
341
        return 'crud::columns.text';
342
    }
343
344
    /**
345
     * Return the column view HTML.
346
     *
347
     * @param  array  $column
348
     * @param  object  $entry
349
     * @return string
350
     */
351
    public function getTableCellHtml($column, $entry)
352
    {
353
        return $this->renderCellView($this->getCellViewName($column), $column, $entry);
354
    }
355
356
    /**
357
     * Render the given view.
358
     *
359
     * @param  string  $view
360
     * @param  array  $column
361
     * @param  object  $entry
362
     * @param  bool|int  $rowNumber  The number shown to the user as row number (index)
363
     * @return string
364
     */
365
    private function renderCellView($view, $column, $entry, $rowNumber = false)
366
    {
367
        if (! view()->exists($view)) {
368
            $view = 'crud::columns.text'; // fallback to text column
369
        }
370
371
        return \View::make($view)
372
            ->with('crud', $this)
373
            ->with('column', $column)
374
            ->with('entry', $entry)
375
            ->with('rowNumber', $rowNumber)
376
            ->render();
377
    }
378
379
    /**
380
     * Created the array to be fed to the data table.
381
     *
382
     * @param  array  $entries  Eloquent results.
383
     * @param  int  $totalRows
384
     * @param  int  $filteredRows
385
     * @param  bool|int  $startIndex
386
     * @return array
387
     */
388
    public function getEntriesAsJsonForDatatables($entries, $totalRows, $filteredRows, $startIndex = false)
389
    {
390
        $rows = [];
391
392
        foreach ($entries as $row) {
393
            $rows[] = $this->getRowViews($row, $startIndex === false ? false : ++$startIndex);
394
        }
395
396
        return [
397
            'draw' => (isset($this->getRequest()['draw']) ? (int) $this->getRequest()['draw'] : 0),
0 ignored issues
show
Bug introduced by
It seems like getRequest() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

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

397
            'draw' => (isset($this->/** @scrutinizer ignore-call */ getRequest()['draw']) ? (int) $this->getRequest()['draw'] : 0),
Loading history...
398
            'recordsTotal' => $totalRows,
399
            'recordsFiltered' => $filteredRows,
400
            'data' => $rows,
401
        ];
402
    }
403
404
    /**
405
     * Return the column attribute (column in database) prefixed with table to use in search.
406
     *
407
     * @param  \Illuminate\Database\Eloquent\Builder  $query
408
     * @param  string  $column
409
     * @return string
410
     */
411
    public function getColumnWithTableNamePrefixed($query, $column)
412
    {
413
        return $query->getModel()->getTable().'.'.$column;
414
    }
415
416
    private function isJsonColumnType(string $columnName)
417
    {
418
        return $this->model->getDbTableSchema()->getColumnType($columnName) === 'json';
419
    }
420
}
421