Passed
Push — master ( 6268c8...c33117 )
by Paul
14:11 queued 01:11
created

ListTableController::isOrderbyWithIsNull()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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