Passed
Push — main ( db602c...8b7c2e )
by Paul
06:19
created

ListTableController   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 435
Duplicated Lines 0 %

Test Coverage

Coverage 1.32%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 76
eloc 200
c 1
b 0
f 0
dl 0
loc 435
ccs 3
cts 228
cp 0.0132
rs 2.32

21 Methods

Rating   Name   Duplication   Size   Complexity  
A filterPostClauses() 0 13 6
A filterColumnsForPostType() 0 10 4
A filterScreenFilters() 0 25 5
A modifyClauseJoin() 0 17 4
A filterSearchQuery() 0 11 4
A isListFiltered() 0 3 1
A modifyClauseOrderby() 0 11 3
A overrideInlineSaveAjax() 0 30 5
A filterDateColumnStatus() 0 4 1
A filterByValues() 0 5 1
A isListOrdered() 0 5 1
A isOrderbyWithIsNull() 0 7 1
B filterCheckLockedReviews() 0 26 8
A filterDefaultHiddenColumns() 0 7 2
A renderColumnValues() 0 12 2
A overridePostsListTable() 0 13 3
A setQueryForTable() 0 18 5
A filterSortableColumns() 0 11 4
A renderColumnFilters() 0 9 3
B modifyClauseWhere() 0 22 7
B filterRowActions() 0 33 6

How to fix   Complexity   

Complex Class

Complex classes like ListTableController 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 ListTableController, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace GeminiLabs\SiteReviews\Controllers;
4
5
use GeminiLabs\SiteReviews\Database\Query;
6
use GeminiLabs\SiteReviews\Database\ReviewManager;
7
use GeminiLabs\SiteReviews\Defaults\ColumnFilterbyDefaults;
8
use GeminiLabs\SiteReviews\Defaults\ColumnOrderbyDefaults;
9
use GeminiLabs\SiteReviews\Defaults\ListtableFiltersDefaults;
10
use GeminiLabs\SiteReviews\Helper;
11
use GeminiLabs\SiteReviews\Helpers\Arr;
12
use GeminiLabs\SiteReviews\Helpers\Cast;
13
use GeminiLabs\SiteReviews\Helpers\Str;
14
use GeminiLabs\SiteReviews\Modules\Html\Builder;
15
use GeminiLabs\SiteReviews\Modules\Migrate;
16
use GeminiLabs\SiteReviews\Modules\Notice;
17
use GeminiLabs\SiteReviews\Modules\Sanitizer;
18
use GeminiLabs\SiteReviews\Overrides\ReviewsListTable;
19
20
class ListTableController extends Controller
21
{
22
    /**
23
     * @param array $response
24
     * @param array $data
25
     * @param string $screenId
26
     * @return array
27
     * @filter heartbeat_received
28
     */
29
    public function filterCheckLockedReviews($response, $data, $screenId)
30
    {
31
        $checked = [];
32
        if (!is_array(Arr::get($data, 'wp-check-locked-posts'))) {
33
            return $response;
34
        }
35
        foreach ($data['wp-check-locked-posts'] as $key) {
36
            $postId = absint(substr($key, 5));
37
            $userId = (int) wp_check_post_lock($postId);
38
            $user = get_userdata($userId);
39
            if ($user && !glsr()->can('edit_post', $postId) && glsr()->can('respond_to_post', $postId)) {
40
                $displayName = glsr(Sanitizer::class)->sanitizeUserName($user->display_name);
41
                $send = [
42
                    'text' => sprintf(_x('%s is currently editing', 'admin-text', 'site-reviews'), $displayName),
43
                ];
44
                if (get_option('show_avatars')) {
45
                    $send['avatar_src'] = get_avatar_url($user->ID, ['size' => 18]);
46
                    $send['avatar_src_2x'] = get_avatar_url($user->ID, ['size' => 36]);
47
                }
48
                $checked[$key] = $send;
49
            }
50
        }
51
        if (!empty($checked)) {
52
            $response['wp-check-locked-posts'] = $checked;
53
        }
54
        return $response;
55
    }
56
57
    /**
58
     * @param array $columns
59
     * @return array
60
     * @filter manage_{glsr()->post_type}_posts_columns
61
     */
62
    public function filterColumnsForPostType($columns)
63
    {
64
        $columns = Arr::consolidate($columns);
65
        $postTypeColumns = glsr()->retrieveAs('array', 'columns.'.glsr()->post_type, []);
66
        foreach ($postTypeColumns as $key => &$value) {
67
            if (array_key_exists($key, $columns) && empty($value)) {
68
                $value = $columns[$key];
69
            }
70
        }
71
        return array_filter($postTypeColumns, 'strlen'); // @phpstan-ignore-line
72
    }
73
74
    /**
75
     * @param string $status
76
     * @param \WP_Post $post
77
     * @return string
78
     * @filter post_date_column_status
79
     */
80
    public function filterDateColumnStatus($status, $post)
81
    {
82
        $isReview = glsr()->post_type === Arr::get($post, 'post_type');
83
        return Helper::ifTrue(!$isReview, $status, _x('Submitted', 'admin-text', 'site-reviews'));
84
    }
85
86
    /**
87
     * @param array $hidden
88
     * @param \WP_Screen $screen
89
     * @return array
90
     * @filter default_hidden_columns
91
     */
92
    public function filterDefaultHiddenColumns($hidden, $screen)
93
    {
94
        if (Arr::get($screen, 'id') === 'edit-'.glsr()->post_type) {
95
            $hiddenColumns = glsr()->retrieveAs('array', 'columns_hidden.'.glsr()->post_type, []);
96
            return array_unique(array_merge(Arr::consolidate($hidden), $hiddenColumns));
97
        }
98
        return $hidden;
99
    }
100
101
    /**
102
     * @return array
103
     * @filter posts_clauses
104
     */
105
    public function filterPostClauses(array $clauses, \WP_Query $query)
106
    {
107
        if (!$this->hasQueryPermission($query) || (!$this->isListFiltered() && !$this->isListOrdered())) {
108
            return $clauses;
109
        }
110
        $table = glsr(Query::class)->table('ratings');
111
        foreach ($clauses as $key => &$clause) {
112
            $method = Helper::buildMethodName($key, 'modifyClause');
113
            if (method_exists($this, $method)) {
114
                $clause = call_user_func([$this, $method], $clause, $table, $query);
115
            }
116
        }
117
        return glsr()->filterArray('review-table/clauses', $clauses, $table, $query);
118
    }
119
120
    /**
121
     * @param array $actions
122
     * @param \WP_Post $post
123
     * @return array
124
     * @filter post_row_actions
125
     */
126
    public function filterRowActions($actions, $post)
127
    {
128
        if (glsr()->post_type !== Arr::get($post, 'post_type') || 'trash' === $post->post_status) {
129
            return $actions;
130
        }
131
        unset($actions['inline hide-if-no-js']);
132
        $newActions = ['id' => sprintf('<span>ID: %d</span>', $post->ID)];
133
        if (glsr()->can('publish_post', $post->ID)) {
134
            $rowActions = [
135
                'approve' => _x('Approve', 'admin-text', 'site-reviews'),
136
                'unapprove' => _x('Unapprove', 'admin-text', 'site-reviews'),
137
            ];
138
            foreach ($rowActions as $key => $text) {
139
                $newActions[$key] = glsr(Builder::class)->a($text, [
140
                    'aria-label' => esc_attr(sprintf(_x('%s this review', 'Approve the review (admin-text)', 'site-reviews'), $text)),
141
                    'class' => 'glsr-toggle-status',
142
                    'href' => wp_nonce_url(
143
                        admin_url('post.php?post='.$post->ID.'&action='.$key.'&plugin='.glsr()->id),
144
                        $key.'-review_'.$post->ID
145
                    ),
146
                ]);
147
            }
148
        }
149
        if (glsr()->can('respond_to_post', $post->ID)) {
150
            $newActions['respond hide-if-no-js'] = glsr(Builder::class)->button([
151
                'aria-expanded' => false,
152
                'aria-label' => esc_attr(sprintf(_x('Respond inline to &#8220;%s&#8221;', 'admin-text', 'site-reviews'), _draft_or_post_title())),
153
                'class' => 'button-link editinline',
154
                'text' => _x('Respond', 'admin-text', 'site-reviews'),
155
                'type' => 'button',
156
            ]);
157
        }
158
        return $newActions + Arr::consolidate($actions);
159
    }
160
161
    /**
162
     * @param \WP_Screen $screen
163
     * @return string
164
     * @filter screen_settings
165
     */
166
    public function filterScreenFilters($settings, $screen)
167
    {
168
        if ('edit-'.glsr()->post_type === $screen->id) {
169
            $userId = get_current_user_id();
170
            $filters = glsr(ListtableFiltersDefaults::class)->defaults();
171
            if (count(glsr()->retrieveAs('array', 'review_types')) < 2) {
172
                unset($filters['type']);
173
            }
174
            foreach ($filters as $key => &$value) {
175
                $value = glsr($value)->title();
176
            }
177
            ksort($filters);
178
            $setting = 'edit_'.glsr()->post_type.'_filters';
179
            $enabled = get_user_meta($userId, $setting, true);
180
            if (!is_array($enabled)) {
181
                $enabled = ['rating']; // the default enabled filters
182
                update_user_meta($userId, $setting, $enabled);
183
            }
184
            $settings .= glsr()->build('partials/screen/filters', [
185
                'enabled' => $enabled,
186
                'filters' => $filters,
187
                'setting' => $setting,
188
            ]);
189
        }
190
        return $settings;
191
    }
192
193
    /**
194
     * @param string $search
195
     * @return string
196
     * @action posts_search
197
     */
198
    public function filterSearchQuery($search, \WP_Query $query)
199
    {
200
        if (!$this->hasQueryPermission($query)) {
201
            return $search;
202
        }
203
        if (!is_numeric($query->get('s')) || empty($search)) {
204
            return $search;
205
        }
206
        global $wpdb;
207
        $replace = $wpdb->prepare("{$wpdb->posts}.ID = %d", $query->get('s'));
208
        return str_replace('AND (((', "AND ((({$replace}) OR (", $search);
209
    }
210
211
    /**
212
     * @param array $columns
213
     * @return array
214
     * @filter manage_edit-{glsr()->post_type}_sortable_columns
215
     */
216
    public function filterSortableColumns($columns)
217
    {
218
        $columns = Arr::consolidate($columns);
219
        $postTypeColumns = glsr()->retrieveAs('array', 'columns.'.glsr()->post_type, []);
220
        unset($postTypeColumns['cb']);
221
        foreach ($postTypeColumns as $key => $value) {
222
            if (!Str::startsWith($key, 'assigned') && !Str::startsWith($key, 'taxonomy')) {
223
                $columns[$key] = $key;
224
            }
225
        }
226
        return $columns;
227
    }
228
229
    /**
230
     * @return void
231
     * @action wp_ajax_inline_save
232
     */
233
    public function overrideInlineSaveAjax()
234
    {
235
        $screen = filter_input(INPUT_POST, 'screen');
236
        if ('edit-'.glsr()->post_type !== $screen) {
237
            return; // don't override
238
        }
239
        global $mode;
240
        check_ajax_referer('inlineeditnonce', '_inline_edit');
241
        if (empty($postId = filter_input(INPUT_POST, 'post_ID', FILTER_VALIDATE_INT))) {
242
            wp_die();
243
        }
244
        if (!glsr()->can('respond_to_post', $postId)) {
245
            wp_die(_x('Sorry, you are not allowed to respond to this review.', 'admin-text', 'site-reviews'));
246
        }
247
        if ($last = wp_check_post_lock($postId)) {
248
            $user = get_userdata($last);
249
            $displayName = Arr::get($user, 'display_name', _x('Someone', 'admin-text', 'site-reviews'));
250
            $displayName = glsr(Sanitizer::class)->sanitizeUserName($displayName);
251
            $message = esc_html_x('Saving is disabled: %s is currently editing this review.', 'admin-text', 'site-reviews');
252
            printf($message, $displayName);
253
            wp_die();
254
        }
255
        glsr(ReviewManager::class)->updateResponse($postId, [
256
            'response' => filter_input(INPUT_POST, '_response'),
257
        ]);
258
        glsr()->action('cache/flush', glsr_get_review($postId));
259
        $mode = Str::restrictTo(['excerpt', 'list'], filter_input(INPUT_POST, 'post_view'), 'list');
260
        $table = new ReviewsListTable(['screen' => convert_to_screen($screen)]);
261
        $table->display_rows([get_post($postId)], 0);
262
        wp_die();
263
    }
264
265
    /**
266
     * @return void
267
     * @action load-edit.php
268
     */
269
    public function overridePostsListTable()
270
    {
271
        if ('edit-'.glsr()->post_type === glsr_current_screen()->id
272
            && glsr()->can('respond_to_posts')) {
273
            $table = new ReviewsListTable();
274
            $table->prepare_items();
275
            add_filter('views_edit-'.glsr()->post_type, function ($views) use ($table) {
276
                global $wp_list_table;
277
                $wp_list_table = clone $table;
278
                echo glsr(Builder::class)->div(glsr(Notice::class)->get(), [
279
                    'id' => 'glsr-notices',
280
                ]);
281
                return $views;
282
            });
283
        }
284
    }
285
286
    /**
287
     * @param string $postType
288
     * @return void
289
     * @action restrict_manage_posts
290
     */
291
    public function renderColumnFilters($postType)
292
    {
293
        if (glsr()->post_type === $postType) {
294
            $filters = glsr(ListtableFiltersDefaults::class)->defaults();
295
            $enabledFilters = Arr::consolidate(
296
                get_user_meta(get_current_user_id(), 'edit_'.glsr()->post_type.'_filters', true)
297
            );
298
            foreach ($filters as $filter) {
299
                echo Cast::toString(glsr()->runIf($filter, $enabledFilters));
300
            }
301
        }
302
    }
303
304
    /**
305
     * @param string $column
306
     * @param int $postId
307
     * @return void
308
     * @action manage_{glsr()->post_type}_posts_custom_column
309
     */
310
    public function renderColumnValues($column, $postId)
311
    {
312
        $review = glsr(Query::class)->review($postId);
313
        if (!$review->isValid()) {
314
            glsr(Migrate::class)->reset(); // looks like a migration is needed!
315
            return;
316
        }
317
        $className = Helper::buildClassName(['ColumnValue', $column], 'Controllers\ListTableColumns');
318
        $className = glsr()->filterString('column/'.$column, $className);
319
        $value = glsr()->runIf($className, $review);
320
        $value = glsr()->filterString('columns/'.$column, $value, $postId);
321
        echo Helper::ifEmpty($value, '&mdash;');
322
    }
323
324
    /**
325
     * @return void
326
     * @action pre_get_posts
327
     */
328 28
    public function setQueryForTable(\WP_Query $query)
329
    {
330 28
        if (!$this->hasQueryPermission($query)) {
331 28
            return;
332
        }
333
        $orderby = $query->get('orderby');
334
        if ('response' === $orderby) {
335
            $query->set('meta_key', Str::prefix($orderby, '_'));
336
            $query->set('orderby', 'meta_value');
337
        }
338
        if ($termId = filter_input(INPUT_GET, 'category', FILTER_SANITIZE_NUMBER_INT)) {
339
            $taxQuery = ['taxonomy' => glsr()->taxonomy];
340
            if (-1 === Cast::toInt($termId)) {
341
                $taxQuery['operator'] = 'NOT EXISTS';
342
            } else {
343
                $taxQuery['terms'] = $termId;
344
            }
345
            $query->set('tax_query', [$taxQuery]);
346
        }
347
    }
348
349
    /**
350
     * @return array
351
     * */
352
    protected function filterByValues()
353
    {
354
        $filterBy = glsr(ColumnFilterbyDefaults::class)->defaults();
355
        $filterBy = filter_input_array(INPUT_GET, $filterBy);
356
        return Arr::removeEmptyValues(Arr::consolidate($filterBy));
357
    }
358
359
    /**
360
     * @return bool
361
     */
362
    protected function isListFiltered()
363
    {
364
        return !empty($this->filterByValues());
365
    }
366
367
    /**
368
     * @return bool
369
     */
370
    protected function isListOrdered()
371
    {
372
        $columns = glsr(ColumnOrderbyDefaults::class)->defaults();
373
        $column = Cast::toString(get_query_var('orderby')); // get_query_var output is unpredictable
374
        return array_key_exists($column, $columns);
375
    }
376
377
    /**
378
     * @return bool
379
     */
380
    protected function isOrderbyWithIsNull($column)
381
    {
382
        $columns = [
383
            'email', 'name', 'ip_address', 'type',
384
        ];
385
        $columns = glsr()->filterArray('columns/orderby-is-null', $columns);
386
        return in_array($column, $columns);
387
    }
388
389
    /**
390
     * @param string $join
391
     * @return string
392
     */
393
    protected function modifyClauseJoin($join, $table, \WP_Query $query)
394
    {
395
        global $wpdb;
396
        $join .= " INNER JOIN {$table} ON {$table}.review_id = {$wpdb->posts}.ID ";
397
        foreach ($this->filterByValues() as $key => $value) {
398
            if (!in_array($key, ['assigned_post', 'assigned_user'])) {
399
                continue;
400
            }
401
            $assignedTable = glsr(Query::class)->table($key.'s');
402
            $value = Cast::toInt($value);
403
            if (0 === $value) {
404
                $join .= " LEFT JOIN {$assignedTable} ON {$assignedTable}.rating_id = {$table}.ID ";
405
            } else {
406
                $join .= " INNER JOIN {$assignedTable} ON {$assignedTable}.rating_id = {$table}.ID ";
407
            }
408
        }
409
        return $join;
410
    }
411
412
    /**
413
     * @param string $orderby
414
     * @return string
415
     */
416
    protected function modifyClauseOrderby($orderby, $table, \WP_Query $query)
417
    {
418
        $columns = glsr(ColumnOrderbyDefaults::class)->defaults();
419
        if ($column = Arr::get($columns, $query->get('orderby'))) {
420
            $order = $query->get('order');
421
            $orderby = "{$table}.{$column} {$order}";
422
            if ($this->isOrderbyWithIsNull($column)) {
423
                $orderby = "NULLIF({$table}.{$column}, '') IS NULL, {$orderby}";
424
            }
425
        }
426
        return $orderby;
427
    }
428
429
    /**
430
     * @param string $where
431
     * @return string
432
     */
433
    protected function modifyClauseWhere($where, $table, \WP_Query $query)
434
    {
435
        global $wpdb;
436
        foreach ($this->filterByValues() as $key => $value) {
437
            if (in_array($key, ['assigned_post', 'assigned_user'])) {
438
                $assignedTable = glsr(Query::class)->table($key.'s');
439
                $column = Str::suffix(Str::removePrefix($key, 'assigned_'), '_id');
440
                $value = Cast::toInt($value);
441
                if (0 === $value) {
442
                    $where .= " AND {$assignedTable}.{$column} IS NULL ";
443
                } else {
444
                    $where .= " AND {$assignedTable}.{$column} = {$value} ";
445
                }
446
            } elseif (in_array($key, ['rating', 'terms', 'type'])) {
447
                $where .= " AND {$table}.{$key} = '{$value}' ";
448
            } elseif ('author' === $key && '0' === $value) {
449
                // Filtering by the "author" URL parameter is automatically done
450
                // by WordPress when the value is not empty
451
                $where .= " AND {$wpdb->posts}.post_author IN (0) ";
452
            }
453
        }
454
        return $where;
455
    }
456
}
457