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

Search::getRowViews()   B
last analyzed

Complexity

Conditions 6
Paths 32

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 6
eloc 23
c 1
b 1
f 0
nc 32
nop 2
dl 0
loc 39
rs 8.9297
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(500, 'Missing column type when trying to apply search term.', ['developer-error-exception']);
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 ($searchLogic instanceof \Closure) {
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
                    break;
94
            }
95
        }
96
    }
97
98
    /**
99
     * Apply the datatables order to the crud query.
100
     */
101
    public function applyDatatableOrder()
102
    {
103
        if ($this->getRequest()->input('order')) {
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

103
        if ($this->/** @scrutinizer ignore-call */ getRequest()->input('order')) {
Loading history...
104
            // clear any past orderBy rules
105
            $this->query->getQuery()->orders = null;
106
            foreach ((array) $this->getRequest()->input('order') as $order) {
107
                $column_number = (int) $order['column'];
108
                $column_direction = (strtolower((string) $order['dir']) == 'asc' ? 'ASC' : 'DESC');
109
                $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

109
                /** @scrutinizer ignore-call */ 
110
                $column = $this->findColumnById($column_number);
Loading history...
110
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
        $groupBy = $this->query->toBase()->groups;
138
139
        $hasOrderByPrimaryKey = collect($orderBy)->some(function ($item) use ($key, $table) {
140
            return (isset($item['column']) && $item['column'] === $key)
141
                || (isset($item['sql']) && str_contains($item['sql'], "$table.$key"));
142
        });
143
144
        if (! $hasOrderByPrimaryKey && empty($groupBy)) {
145
            $this->orderByWithPrefix($key, 'DESC');
146
        }
147
    }
148
149
    // -------------------------
150
    // Responsive Table
151
    // -------------------------
152
153
    /**
154
     * Tell the list view to NOT show a reponsive DataTable.
155
     *
156
     * @param  bool  $value
157
     */
158
    public function setResponsiveTable($value = true)
159
    {
160
        $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

160
        $this->/** @scrutinizer ignore-call */ 
161
               setOperationSetting('responsiveTable', $value);
Loading history...
161
    }
162
163
    /**
164
     * Check if responsiveness is enabled for the table view.
165
     *
166
     * @return bool
167
     */
168
    public function getResponsiveTable()
169
    {
170
        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

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

267
        if ($this->/** @scrutinizer ignore-call */ buttons()->where('stack', 'line')->count()) {
Loading history...
268
            $row_items[] = \View::make('crud::inc.button_stack', ['stack' => 'line'])
269
                                ->with('crud', $this)
270
                                ->with('entry', $entry)
271
                                ->with('row_number', $rowNumber)
272
                                ->render();
273
        }
274
275
        // add the bulk actions checkbox to the first column
276
        if ($this->getOperationSetting('bulkActions')) {
277
            $bulk_actions_checkbox = \View::make('crud::columns.inc.bulk_actions_checkbox', ['entry' => $entry])->render();
278
            $row_items[0] = $bulk_actions_checkbox.$row_items[0];
279
        }
280
281
        // add the details_row button to the first column
282
        if ($this->getOperationSetting('detailsRow')) {
283
            $details_row_button = \View::make('crud::columns.inc.details_row_button')
284
                                           ->with('crud', $this)
285
                                           ->with('entry', $entry)
286
                                           ->with('row_number', $rowNumber)
287
                                           ->render();
288
            $row_items[0] = $details_row_button.$row_items[0];
289
        }
290
291
        if ($this->getResponsiveTable()) {
292
            $responsiveTableTrigger = '<div class="dtr-control d-none cursor-pointer"></div>';
293
            $row_items[0] = $responsiveTableTrigger.$row_items[0];
294
        }
295
296
        return $row_items;
297
    }
298
299
    /**
300
     * Get the HTML of a cell, using the column types.
301
     *
302
     * @param  array  $column
303
     * @param  \Illuminate\Database\Eloquent\Model  $entry  A db entry of the current entity;
304
     * @param  bool|int  $rowNumber  The number shown to the user as row number (index);
305
     * @return string
306
     */
307
    public function getCellView($column, $entry, $rowNumber = false)
308
    {
309
        return $this->renderCellView($this->getCellViewName($column), $column, $entry, $rowNumber);
310
    }
311
312
    /**
313
     * Get the name of the view to load for the cell.
314
     *
315
     * @param  array  $column
316
     * @return string
317
     */
318
    private function getCellViewName($column)
319
    {
320
        // return custom column if view_namespace attribute is set
321
        if (isset($column['view_namespace']) && isset($column['type'])) {
322
            return $column['view_namespace'].'.'.$column['type'];
323
        }
324
325
        if (isset($column['type'])) {
326
            // create a list of paths to column blade views
327
            // including the configured view_namespaces
328
            $columnPaths = array_map(function ($item) use ($column) {
329
                return $item.'.'.$column['type'];
330
            }, ViewNamespaces::getFor('columns'));
331
332
            // but always fall back to the stock 'text' column
333
            // if a view doesn't exist
334
            if (! in_array('crud::columns.text', $columnPaths)) {
335
                $columnPaths[] = 'crud::columns.text';
336
            }
337
338
            // return the first column blade file that exists
339
            foreach ($columnPaths as $path) {
340
                if (view()->exists($path)) {
341
                    return $path;
342
                }
343
            }
344
        }
345
346
        // fallback to text column
347
        return 'crud::columns.text';
348
    }
349
350
    /**
351
     * Return the column view HTML.
352
     *
353
     * @param  array  $column
354
     * @param  object  $entry
355
     * @return string
356
     */
357
    public function getTableCellHtml($column, $entry)
358
    {
359
        return $this->renderCellView($this->getCellViewName($column), $column, $entry);
360
    }
361
362
    /**
363
     * Render the given view.
364
     *
365
     * @param  string  $view
366
     * @param  array  $column
367
     * @param  object  $entry
368
     * @param  bool|int  $rowNumber  The number shown to the user as row number (index)
369
     * @return string
370
     */
371
    private function renderCellView($view, $column, $entry, $rowNumber = false)
372
    {
373
        if (! view()->exists($view)) {
374
            $view = 'crud::columns.text'; // fallback to text column
375
        }
376
377
        return \View::make($view)
378
            ->with('crud', $this)
379
            ->with('column', $column)
380
            ->with('entry', $entry)
381
            ->with('rowNumber', $rowNumber)
382
            ->render();
383
    }
384
385
    /**
386
     * Created the array to be fed to the data table.
387
     *
388
     * @param  array  $entries  Eloquent results.
389
     * @param  int  $totalRows
390
     * @param  int  $filteredRows
391
     * @param  bool|int  $startIndex
392
     * @return array
393
     */
394
    public function getEntriesAsJsonForDatatables($entries, $totalRows, $filteredRows, $startIndex = false)
395
    {
396
        $rows = [];
397
398
        foreach ($entries as $row) {
399
            $rows[] = $this->getRowViews($row, $startIndex === false ? false : ++$startIndex);
400
        }
401
402
        return [
403
            'draw' => (isset($this->getRequest()['draw']) ? (int) $this->getRequest()['draw'] : 0),
404
            'recordsTotal' => $totalRows,
405
            'recordsFiltered' => $filteredRows,
406
            'data' => $rows,
407
        ];
408
    }
409
410
    /**
411
     * Return the column attribute (column in database) prefixed with table to use in search.
412
     *
413
     * @param  \Illuminate\Database\Eloquent\Builder  $query
414
     * @param  string  $column
415
     * @return string
416
     */
417
    public function getColumnWithTableNamePrefixed($query, $column)
418
    {
419
        return $query->getModel()->getTable().'.'.$column;
420
    }
421
422
    private function isJsonColumnType(string $columnName)
423
    {
424
        return $this->model->getDbTableSchema()->getColumnType($columnName) === 'json';
425
    }
426
}
427