• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Yoast / wordpress-seo / 53bef1b757b696c397145116de9d0118268cb1fb

02 Dec 2024 12:44PM UTC coverage: 54.094% (-0.02%) from 54.109%
53bef1b757b696c397145116de9d0118268cb1fb

Pull #21885

github

leonidasmi
Fix integration tests
Pull Request #21885: Feature/dash phase 1 fixes

7603 of 13695 branches covered (55.52%)

Branch coverage included in aggregate %.

0 of 29 new or added lines in 8 files covered. (0.0%)

1 existing line in 1 file now uncovered.

29774 of 55402 relevant lines covered (53.74%)

41425.85 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.0
/admin/class-meta-columns.php
1
<?php
2
/**
3
 * WPSEO plugin file.
4
 *
5
 * @package WPSEO\Admin
6
 */
7

8
use Yoast\WP\SEO\Context\Meta_Tags_Context;
9
use Yoast\WP\SEO\Helpers\Score_Icon_Helper;
10
use Yoast\WP\SEO\Integrations\Admin\Admin_Columns_Cache_Integration;
11
use Yoast\WP\SEO\Surfaces\Values\Meta;
12

13
/**
14
 * Class WPSEO_Meta_Columns.
15
 */
16
class WPSEO_Meta_Columns {
17

18
        /**
19
         * Holds the context objects for each indexable.
20
         *
21
         * @var Meta_Tags_Context[]
22
         */
23
        protected $context = [];
24

25
        /**
26
         * Holds the SEO analysis.
27
         *
28
         * @var WPSEO_Metabox_Analysis_SEO
29
         */
30
        private $analysis_seo;
31

32
        /**
33
         * Holds the readability analysis.
34
         *
35
         * @var WPSEO_Metabox_Analysis_Readability
36
         */
37
        private $analysis_readability;
38

39
        /**
40
         * Admin columns cache.
41
         *
42
         * @var Admin_Columns_Cache_Integration
43
         */
44
        private $admin_columns_cache;
45

46
        /**
47
         * Holds the Score_Icon_Helper.
48
         *
49
         * @var Score_Icon_Helper
50
         */
51
        private $score_icon_helper;
52

53
        /**
54
         * When page analysis is enabled, just initialize the hooks.
55
         */
56
        public function __construct() {
×
57
                if ( apply_filters( 'wpseo_use_page_analysis', true ) === true ) {
×
58
                        add_action( 'admin_init', [ $this, 'setup_hooks' ] );
×
59
                }
60

61
                $this->analysis_seo         = new WPSEO_Metabox_Analysis_SEO();
×
62
                $this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
×
63
                $this->admin_columns_cache  = YoastSEO()->classes->get( Admin_Columns_Cache_Integration::class );
×
64
                $this->score_icon_helper    = YoastSEO()->helpers->score_icon;
×
65
        }
66

67
        /**
68
         * Sets up up the hooks.
69
         *
70
         * @return void
71
         */
72
        public function setup_hooks() {
×
73
                $this->set_post_type_hooks();
×
74

75
                if ( $this->analysis_seo->is_enabled() ) {
×
76
                        add_action( 'restrict_manage_posts', [ $this, 'posts_filter_dropdown' ] );
×
77
                }
78

79
                if ( $this->analysis_readability->is_enabled() ) {
×
80
                        add_action( 'restrict_manage_posts', [ $this, 'posts_filter_dropdown_readability' ] );
×
81
                }
82

83
                add_filter( 'request', [ $this, 'column_sort_orderby' ] );
×
84
                add_filter( 'default_hidden_columns', [ $this, 'column_hidden' ], 10, 1 );
×
85
        }
86

87
        /**
88
         * Adds the column headings for the SEO plugin for edit posts / pages overview.
89
         *
90
         * @param array $columns Already existing columns.
91
         *
92
         * @return array Array containing the column headings.
93
         */
94
        public function column_heading( $columns ) {
12✔
95
                if ( $this->display_metabox() === false ) {
12✔
96
                        return $columns;
×
97
                }
98

99
                $added_columns = [];
12✔
100

101
                if ( $this->analysis_seo->is_enabled() ) {
12✔
102
                        $added_columns['wpseo-score'] = '<span class="yoast-column-seo-score yoast-column-header-has-tooltip" data-tooltip-text="'
12✔
103
                                                                                        . esc_attr__( 'SEO score', 'wordpress-seo' )
12✔
104
                                                                                        . '"><span class="screen-reader-text">'
12✔
105
                                                                                        . __( 'SEO score', 'wordpress-seo' )
12✔
106
                                                                                        . '</span></span></span>';
12✔
107
                }
108

109
                if ( $this->analysis_readability->is_enabled() ) {
12✔
110
                        $added_columns['wpseo-score-readability'] = '<span class="yoast-column-readability yoast-column-header-has-tooltip" data-tooltip-text="'
12✔
111
                                                                                                                . esc_attr__( 'Readability score', 'wordpress-seo' )
12✔
112
                                                                                                                . '"><span class="screen-reader-text">'
12✔
113
                                                                                                                . __( 'Readability score', 'wordpress-seo' )
12✔
114
                                                                                                                . '</span></span></span>';
12✔
115
                }
116

117
                $added_columns['wpseo-title']    = __( 'SEO Title', 'wordpress-seo' );
12✔
118
                $added_columns['wpseo-metadesc'] = __( 'Meta Desc.', 'wordpress-seo' );
12✔
119

120
                if ( $this->analysis_seo->is_enabled() ) {
12✔
121
                        $added_columns['wpseo-focuskw'] = __( 'Keyphrase', 'wordpress-seo' );
12✔
122
                }
123

124
                return array_merge( $columns, $added_columns );
12✔
125
        }
126

127
        /**
128
         * Displays the column content for the given column.
129
         *
130
         * @param string $column_name Column to display the content for.
131
         * @param int    $post_id     Post to display the column content for.
132
         *
133
         * @return void
134
         */
135
        public function column_content( $column_name, $post_id ) {
×
136
                if ( $this->display_metabox() === false ) {
×
137
                        return;
×
138
                }
139

140
                switch ( $column_name ) {
141
                        case 'wpseo-score':
×
142
                                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
143
                                echo $this->parse_column_score( $post_id );
×
144

145
                                return;
×
146

147
                        case 'wpseo-score-readability':
×
148
                                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
149
                                echo $this->parse_column_score_readability( $post_id );
×
150

151
                                return;
×
152

153
                        case 'wpseo-title':
×
154
                                $meta = $this->get_meta( $post_id );
×
155
                                if ( $meta ) {
×
156
                                        echo esc_html( $meta->title );
×
157
                                }
158

159
                                return;
×
160

161
                        case 'wpseo-metadesc':
×
162
                                $metadesc_val = '';
×
163
                                $meta         = $this->get_meta( $post_id );
×
164
                                if ( $meta ) {
×
165
                                        $metadesc_val = $meta->meta_description;
×
166
                                }
167
                                if ( $metadesc_val === '' ) {
×
168
                                        echo '<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">',
×
169
                                        /* translators: Hidden accessibility text. */
170
                                        esc_html__( 'Meta description not set.', 'wordpress-seo' ),
×
171
                                        '</span>';
×
172

173
                                        return;
×
174
                                }
175

176
                                echo esc_html( $metadesc_val );
×
177

178
                                return;
×
179

180
                        case 'wpseo-focuskw':
×
181
                                $focuskw_val = WPSEO_Meta::get_value( 'focuskw', $post_id );
×
182

183
                                if ( $focuskw_val === '' ) {
×
184
                                        echo '<span aria-hidden="true">&#8212;</span><span class="screen-reader-text">',
×
185
                                        /* translators: Hidden accessibility text. */
186
                                        esc_html__( 'Focus keyphrase not set.', 'wordpress-seo' ),
×
187
                                        '</span>';
×
188

189
                                        return;
×
190
                                }
191

192
                                echo esc_html( $focuskw_val );
×
193

194
                                return;
×
195
                }
196
        }
197

198
        /**
199
         * Indicates which of the SEO columns are sortable.
200
         *
201
         * @param array $columns Appended with their orderby variable.
202
         *
203
         * @return array Array containing the sortable columns.
204
         */
205
        public function column_sort( $columns ) {
×
206
                if ( $this->display_metabox() === false ) {
×
207
                        return $columns;
×
208
                }
209

210
                $columns['wpseo-metadesc'] = 'wpseo-metadesc';
×
211

212
                if ( $this->analysis_seo->is_enabled() ) {
×
213
                        $columns['wpseo-focuskw'] = 'wpseo-focuskw';
×
214
                        $columns['wpseo-score']   = 'wpseo-score';
×
215
                }
216

217
                if ( $this->analysis_readability->is_enabled() ) {
×
218
                        $columns['wpseo-score-readability'] = 'wpseo-score-readability';
×
219
                }
220

221
                return $columns;
×
222
        }
223

224
        /**
225
         * Hides the SEO title, meta description and focus keyword columns if the user hasn't chosen which columns to hide.
226
         *
227
         * @param array $hidden The hidden columns.
228
         *
229
         * @return array Array containing the columns to hide.
230
         */
231
        public function column_hidden( $hidden ) {
12✔
232
                if ( ! is_array( $hidden ) ) {
12✔
233
                        $hidden = [];
4✔
234
                }
235

236
                array_push( $hidden, 'wpseo-title', 'wpseo-metadesc' );
12✔
237

238
                if ( $this->analysis_seo->is_enabled() ) {
12✔
239
                        $hidden[] = 'wpseo-focuskw';
12✔
240
                }
241

242
                return $hidden;
12✔
243
        }
244

245
        /**
246
         * Adds a dropdown that allows filtering on the posts SEO Quality.
247
         *
248
         * @return void
249
         */
250
        public function posts_filter_dropdown() {
×
251
                if ( ! $this->can_display_filter() ) {
×
252
                        return;
×
253
                }
254

255
                $ranks = WPSEO_Rank::get_all_ranks();
×
256

257
                /* translators: Hidden accessibility text. */
258
                echo '<label class="screen-reader-text" for="wpseo-filter">' . esc_html__( 'Filter by SEO Score', 'wordpress-seo' ) . '</label>';
×
259
                echo '<select name="seo_filter" id="wpseo-filter">';
×
260

261
                // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
262
                echo $this->generate_option( '', __( 'All SEO Scores', 'wordpress-seo' ) );
×
263

264
                foreach ( $ranks as $rank ) {
×
265
                        $selected = selected( $this->get_current_seo_filter(), $rank->get_rank(), false );
×
266

267
                        // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
268
                        echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_label(), $selected );
×
269
                }
270

271
                echo '</select>';
×
272
        }
273

274
        /**
275
         * Adds a dropdown that allows filtering on the posts Readability Quality.
276
         *
277
         * @return void
278
         */
279
        public function posts_filter_dropdown_readability() {
×
280
                if ( ! $this->can_display_filter() ) {
×
281
                        return;
×
282
                }
283

284
                $ranks = WPSEO_Rank::get_all_readability_ranks();
×
285

286
                /* translators: Hidden accessibility text. */
287
                echo '<label class="screen-reader-text" for="wpseo-readability-filter">' . esc_html__( 'Filter by Readability Score', 'wordpress-seo' ) . '</label>';
×
288
                echo '<select name="readability_filter" id="wpseo-readability-filter">';
×
289

290
                // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
291
                echo $this->generate_option( '', __( 'All Readability Scores', 'wordpress-seo' ) );
×
292

293
                foreach ( $ranks as $rank ) {
×
294
                        $selected = selected( $this->get_current_readability_filter(), $rank->get_rank(), false );
×
295

296
                        // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
297
                        echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_readability_labels(), $selected );
×
298
                }
299

300
                echo '</select>';
×
301
        }
302

303
        /**
304
         * Generates an <option> element.
305
         *
306
         * @param string $value    The option's value.
307
         * @param string $label    The option's label.
308
         * @param string $selected HTML selected attribute for an option.
309
         *
310
         * @return string The generated <option> element.
311
         */
312
        protected function generate_option( $value, $label, $selected = '' ) {
×
313
                return '<option ' . $selected . ' value="' . esc_attr( $value ) . '">' . esc_html( $label ) . '</option>';
×
314
        }
315

316
        /**
317
         * Returns the meta object for a given post ID.
318
         *
319
         * @param int $post_id The post ID.
320
         *
321
         * @return Meta The meta object.
322
         */
323
        protected function get_meta( $post_id ) {
×
324
                $indexable = $this->admin_columns_cache->get_indexable( $post_id );
×
325

326
                return YoastSEO()->meta->for_indexable( $indexable, 'Post_Type' );
×
327
        }
328

329
        /**
330
         * Determines the SEO score filter to be later used in the meta query, based on the passed SEO filter.
331
         *
332
         * @param string $seo_filter The SEO filter to use to determine what further filter to apply.
333
         *
334
         * @return array The SEO score filter.
335
         */
336
        protected function determine_seo_filters( $seo_filter ) {
24✔
337
                if ( $seo_filter === WPSEO_Rank::NO_FOCUS ) {
24✔
338
                        return $this->create_no_focus_keyword_filter();
4✔
339
                }
340

341
                if ( $seo_filter === WPSEO_Rank::NO_INDEX ) {
20✔
342
                        return $this->create_no_index_filter();
4✔
343
                }
344

345
                $rank = new WPSEO_Rank( $seo_filter );
16✔
346

347
                return $this->create_seo_score_filter( $rank->get_starting_score(), $rank->get_end_score() );
16✔
348
        }
349

350
        /**
351
         * Determines the Readability score filter to the meta query, based on the passed Readability filter.
352
         *
353
         * @param string $readability_filter The Readability filter to use to determine what further filter to apply.
354
         *
355
         * @return array The Readability score filter.
356
         */
357
        protected function determine_readability_filters( $readability_filter ) {
16✔
358
                if ( $readability_filter === WPSEO_Rank::NO_FOCUS ) {
16✔
359
                        return $this->create_no_readability_scores_filter();
4✔
360
                }
361
                if ( $readability_filter === WPSEO_Rank::BAD ) {
12✔
362
                        return $this->create_bad_readability_scores_filter();
4✔
363
                }
364
                $rank = new WPSEO_Rank( $readability_filter );
8✔
365

366
                return $this->create_readability_score_filter( $rank->get_starting_score(), $rank->get_end_score() );
8✔
367
        }
368

369
        /**
370
         * Creates a keyword filter for the meta query, based on the passed Keyword filter.
371
         *
372
         * @param string $keyword_filter The keyword filter to use.
373
         *
374
         * @return array The keyword filter.
375
         */
376
        protected function get_keyword_filter( $keyword_filter ) {
×
377
                return [
378
                        'post_type' => get_query_var( 'post_type', 'post' ),
×
379
                        'key'       => WPSEO_Meta::$meta_prefix . 'focuskw',
×
380
                        'value'     => sanitize_text_field( $keyword_filter ),
×
381
                ];
382
        }
383

384
        /**
385
         * Determines whether the passed filter is considered to be valid.
386
         *
387
         * @param mixed $filter The filter to check against.
388
         *
389
         * @return bool Whether the filter is considered valid.
390
         */
391
        protected function is_valid_filter( $filter ) {
8✔
392
                return ! empty( $filter ) && is_string( $filter );
8✔
393
        }
394

395
        /**
396
         * Collects the filters and merges them into a single array.
397
         *
398
         * @return array Array containing all the applicable filters.
399
         */
400
        protected function collect_filters() {
×
401
                $active_filters = [];
×
402

403
                $seo_filter             = $this->get_current_seo_filter();
×
404
                $readability_filter     = $this->get_current_readability_filter();
×
405
                $current_keyword_filter = $this->get_current_keyword_filter();
×
406

407
                if ( $this->is_valid_filter( $seo_filter ) ) {
×
408
                        $active_filters = array_merge(
×
409
                                $active_filters,
×
410
                                $this->determine_seo_filters( $seo_filter )
×
411
                        );
412
                }
413

414
                if ( $this->is_valid_filter( $readability_filter ) ) {
×
415
                        $active_filters = array_merge(
×
416
                                $active_filters,
×
417
                                $this->determine_readability_filters( $readability_filter )
×
418
                        );
419
                }
420

421
                if ( $this->is_valid_filter( $current_keyword_filter ) ) {
×
422
                        /**
423
                         * Adapt the meta query used to filter the post overview on keyphrase.
424
                         *
425
                         * @internal
426
                         *
427
                         * @param array $keyphrase      The keyphrase used in the filter.
428
                         * @param array $keyword_filter The current keyword filter.
429
                         */
430
                        $keyphrase_filter = apply_filters(
×
431
                                'wpseo_change_keyphrase_filter_in_request',
×
432
                                $this->get_keyword_filter( $current_keyword_filter ),
×
433
                                $current_keyword_filter
×
434
                        );
435

436
                        if ( is_array( $keyphrase_filter ) ) {
×
437
                                $active_filters = array_merge(
×
438
                                        $active_filters,
×
439
                                        [ $keyphrase_filter ]
×
440
                                );
441
                        }
442
                }
443

444
                /**
445
                 * Adapt the active applicable filters on the posts overview.
446
                 *
447
                 * @internal
448
                 *
449
                 * @param array $active_filters The current applicable filters.
450
                 */
451
                return apply_filters( 'wpseo_change_applicable_filters', $active_filters );
×
452
        }
453

454
        /**
455
         * Modify the query based on the filters that are being passed.
456
         *
457
         * @param array $vars Query variables that need to be modified based on the filters.
458
         *
459
         * @return array Array containing the meta query to use for filtering the posts overview.
460
         */
461
        public function column_sort_orderby( $vars ) {
×
462
                $collected_filters = $this->collect_filters();
×
463

464
                $order_by_column = $vars['orderby'];
×
465
                if ( isset( $order_by_column ) ) {
×
466
                        // Based on the selected column, create a meta query.
467
                        $order_by = $this->filter_order_by( $order_by_column );
×
468

469
                        /**
470
                         * Adapt the order by part of the query on the posts overview.
471
                         *
472
                         * @internal
473
                         *
474
                         * @param array  $order_by        The current order by.
475
                         * @param string $order_by_column The current order by column.
476
                         */
477
                        $order_by = apply_filters( 'wpseo_change_order_by', $order_by, $order_by_column );
×
478

479
                        $vars = array_merge( $vars, $order_by );
×
480
                }
481

482
                return $this->build_filter_query( $vars, $collected_filters );
×
483
        }
484

485
        /**
486
         * Retrieves the meta robots query values to be used within the meta query.
487
         *
488
         * @return array Array containing the query parameters regarding meta robots.
489
         */
490
        protected function get_meta_robots_query_values() {
×
491
                return [
492
                        'relation' => 'OR',
×
493
                        [
494
                                'key'     => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
×
495
                                'compare' => 'NOT EXISTS',
×
496
                        ],
497
                        [
498
                                'key'     => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
×
499
                                'value'   => '1',
×
500
                                'compare' => '!=',
×
501
                        ],
502
                ];
503
        }
504

505
        /**
506
         * Determines the score filters to be used. If more than one is passed, it created an AND statement for the query.
507
         *
508
         * @param array $score_filters Array containing the score filters.
509
         *
510
         * @return array Array containing the score filters that need to be applied to the meta query.
511
         */
512
        protected function determine_score_filters( $score_filters ) {
×
513
                if ( count( $score_filters ) > 1 ) {
×
514
                        return array_merge( [ 'relation' => 'AND' ], $score_filters );
×
515
                }
516

517
                return $score_filters;
×
518
        }
519

520
        /**
521
         * Retrieves the post type from the $_GET variable.
522
         *
523
         * @return string|null The sanitized current post type or null when the variable is not set in $_GET.
524
         */
525
        public function get_current_post_type() {
12✔
526
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
527
                if ( isset( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ) {
12✔
528
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
529
                        return sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
4✔
530
                }
531
                return null;
8✔
532
        }
533

534
        /**
535
         * Retrieves the SEO filter from the $_GET variable.
536
         *
537
         * @return string|null The sanitized seo filter or null when the variable is not set in $_GET.
538
         */
539
        public function get_current_seo_filter() {
12✔
540
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
541
                if ( isset( $_GET['seo_filter'] ) && is_string( $_GET['seo_filter'] ) ) {
12✔
542
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
543
                        return sanitize_text_field( wp_unslash( $_GET['seo_filter'] ) );
4✔
544
                }
545
                return null;
8✔
546
        }
547

548
        /**
549
         * Retrieves the Readability filter from the $_GET variable.
550
         *
551
         * @return string|null The sanitized readability filter or null when the variable is not set in $_GET.
552
         */
553
        public function get_current_readability_filter() {
12✔
554
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
555
                if ( isset( $_GET['readability_filter'] ) && is_string( $_GET['readability_filter'] ) ) {
12✔
556
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
557
                        return sanitize_text_field( wp_unslash( $_GET['readability_filter'] ) );
4✔
558
                }
559
                return null;
8✔
560
        }
561

562
        /**
563
         * Retrieves the keyword filter from the $_GET variable.
564
         *
565
         * @return string|null The sanitized seo keyword filter or null when the variable is not set in $_GET.
566
         */
567
        public function get_current_keyword_filter() {
12✔
568
                // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
569
                if ( isset( $_GET['seo_kw_filter'] ) && is_string( $_GET['seo_kw_filter'] ) ) {
12✔
570
                        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
571
                        return sanitize_text_field( wp_unslash( $_GET['seo_kw_filter'] ) );
4✔
572
                }
573
                return null;
8✔
574
        }
575

576
        /**
577
         * Uses the vars to create a complete filter query that can later be executed to filter out posts.
578
         *
579
         * @param array $vars    Array containing the variables that will be used in the meta query.
580
         * @param array $filters Array containing the filters that we need to apply in the meta query.
581
         *
582
         * @return array Array containing the complete filter query.
583
         */
584
        protected function build_filter_query( $vars, $filters ) {
16✔
585
                // If no filters were applied, just return everything.
586
                if ( count( $filters ) === 0 ) {
16✔
587
                        return $vars;
4✔
588
                }
589

590
                $result               = [ 'meta_query' => [] ];
12✔
591
                $result['meta_query'] = array_merge( $result['meta_query'], [ $this->determine_score_filters( $filters ) ] );
12✔
592

593
                $current_seo_filter = $this->get_current_seo_filter();
12✔
594

595
                // This only applies for the SEO score filter because it can because the SEO score can be altered by the no-index option.
596
                if ( $this->is_valid_filter( $current_seo_filter ) && ! in_array( $current_seo_filter, [ WPSEO_Rank::NO_INDEX, WPSEO_Rank::NO_FOCUS ], true ) ) {
12✔
597
                        $result['meta_query'] = array_merge( $result['meta_query'], [ $this->get_meta_robots_query_values() ] );
×
598
                }
599

600
                return array_merge( $vars, $result );
12✔
601
        }
602

603
        /**
604
         * Creates a Readability score filter.
605
         *
606
         * @param number $low  The lower boundary of the score.
607
         * @param number $high The higher boundary of the score.
608
         *
609
         * @return array<array<string>> The Readability Score filter.
610
         */
611
        protected function create_readability_score_filter( $low, $high ) {
×
612
                return [
613
                        [
614
                                'key'     => WPSEO_Meta::$meta_prefix . 'content_score',
×
615
                                'value'   => [ $low, $high ],
×
616
                                'type'    => 'numeric',
×
617
                                'compare' => 'BETWEEN',
×
618
                        ],
619
                ];
620
        }
621

622
        /**
623
         * Creates an SEO score filter.
624
         *
625
         * @param number $low  The lower boundary of the score.
626
         * @param number $high The higher boundary of the score.
627
         *
628
         * @return array<array<string>> The SEO score filter.
629
         */
630
        protected function create_seo_score_filter( $low, $high ) {
×
631
                return [
632
                        [
633
                                'key'     => WPSEO_Meta::$meta_prefix . 'linkdex',
×
634
                                'value'   => [ $low, $high ],
×
635
                                'type'    => 'numeric',
×
636
                                'compare' => 'BETWEEN',
×
637
                        ],
638
                ];
639
        }
640

641
        /**
642
         * Creates a filter to retrieve posts that were set to no-index.
643
         *
644
         * @return array<array<string>> Array containin the no-index filter.
645
         */
646
        protected function create_no_index_filter() {
×
647
                return [
648
                        [
649
                                'key'     => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
×
650
                                'value'   => '1',
×
651
                                'compare' => '=',
×
652
                        ],
653
                ];
654
        }
655

656
        /**
657
         * Creates a filter to retrieve posts that have no keyword set.
658
         *
659
         * @return array<array<string>> Array containing the no focus keyword filter.
660
         */
661
        protected function create_no_focus_keyword_filter() {
×
662
                return [
663
                        [
664
                                'key'     => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
×
665
                                'value'   => 'needs-a-value-anyway',
×
666
                                'compare' => 'NOT EXISTS',
×
667
                        ],
668
                        [
669
                                'key'     => WPSEO_Meta::$meta_prefix . 'linkdex',
×
670
                                'value'   => 'needs-a-value-anyway',
×
671
                                'compare' => 'NOT EXISTS',
×
672
                        ],
673
                ];
674
        }
675

676
        /**
677
         * Creates a filter to retrieve posts that have not been analyzed for readability yet.
678
         *
679
         * @return array<array<string>> Array containing the no readability filter.
680
         */
681
        protected function create_no_readability_scores_filter() {
×
682
                // We check the existence of the Estimated Reading Time, because readability scores of posts that haven't been manually saved while Yoast SEO is active, don't exist, which is also the case for posts with not enough content.
683
                // Meanwhile, the ERT is a solid indicator of whether a post has ever been saved (aka, analyzed), so we're using that.
NEW
684
                $rank = new WPSEO_Rank( WPSEO_Rank::BAD );
×
685
                return [
686
                        [
687
                                'key'     => WPSEO_Meta::$meta_prefix . 'estimated-reading-time-minutes',
×
688
                                'value'   => 'needs-a-value-anyway',
×
689
                                'compare' => 'NOT EXISTS',
×
690
                        ],
691
                        [
NEW
692
                                'relation' => 'OR',
×
693
                                [
NEW
694
                                        'key'     => WPSEO_Meta::$meta_prefix . 'content_score',
×
NEW
695
                                        'value'   => $rank->get_end_score(),
×
NEW
696
                                        'type'    => 'numeric',
×
NEW
697
                                        'compare' => '<=',
×
698
                                ],
699
                                [
NEW
700
                                        'key'     => WPSEO_Meta::$meta_prefix . 'content_score',
×
NEW
701
                                        'value'   => 'needs-a-value-anyway',
×
NEW
702
                                        'compare' => 'NOT EXISTS',
×
703
                                ],
704
                        ],
705
                ];
706
        }
707

708
        /**
709
         * Creates a filter to retrieve posts that have bad readability scores, including those that have not enough content to have one.
710
         *
711
         * @return array<array<string>> Array containing the bad readability filter.
712
         */
713
        protected function create_bad_readability_scores_filter() {
×
714
                $rank = new WPSEO_Rank( WPSEO_Rank::BAD );
×
715
                return [
716
                        [
717
                                'key'     => WPSEO_Meta::$meta_prefix . 'estimated-reading-time-minutes',
×
718
                                'compare' => 'EXISTS',
×
719
                        ],
720
                        [
721
                                'relation' => 'OR',
×
722
                                [
723
                                        'key'     => WPSEO_Meta::$meta_prefix . 'content_score',
×
724
                                        'value'   => $rank->get_end_score(),
×
725
                                        'type'    => 'numeric',
×
726
                                        'compare' => '<=',
×
727
                                ],
728
                                [
729
                                        'key'     => WPSEO_Meta::$meta_prefix . 'content_score',
×
730
                                        'value'   => 'needs-a-value-anyway',
×
731
                                        'compare' => 'NOT EXISTS',
×
732
                                ],
733
                        ],
734
                ];
735
        }
736

737
        /**
738
         * Determines whether a particular post_id is of an indexable post type.
739
         *
740
         * @param string $post_id The post ID to check.
741
         *
742
         * @return bool Whether or not it is indexable.
743
         */
744
        protected function is_indexable( $post_id ) {
16✔
745
                if ( ! empty( $post_id ) && ! $this->uses_default_indexing( $post_id ) ) {
16✔
746
                        return WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '2';
4✔
747
                }
748

749
                $post = get_post( $post_id );
12✔
750

751
                if ( is_object( $post ) ) {
12✔
752
                        // If the option is false, this means we want to index it.
753
                        return WPSEO_Options::get( 'noindex-' . $post->post_type, false ) === false;
8✔
754
                }
755

756
                return true;
4✔
757
        }
758

759
        /**
760
         * Determines whether the given post ID uses the default indexing settings.
761
         *
762
         * @param int $post_id The post ID to check.
763
         *
764
         * @return bool Whether or not the default indexing is being used for the post.
765
         */
766
        protected function uses_default_indexing( $post_id ) {
8✔
767
                return WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '0';
8✔
768
        }
769

770
        /**
771
         * Returns filters when $order_by is matched in the if-statement.
772
         *
773
         * @param string $order_by The ID of the column by which to order the posts.
774
         *
775
         * @return array<string> Array containing the order filters.
776
         */
777
        private function filter_order_by( $order_by ) {
×
778
                switch ( $order_by ) {
779
                        case 'wpseo-metadesc':
×
780
                                return [
781
                                        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
782
                                        'meta_key' => WPSEO_Meta::$meta_prefix . 'metadesc',
×
783
                                        'orderby'  => 'meta_value',
×
784
                                ];
785

786
                        case 'wpseo-focuskw':
×
787
                                return [
788
                                        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
789
                                        'meta_key' => WPSEO_Meta::$meta_prefix . 'focuskw',
×
790
                                        'orderby'  => 'meta_value',
×
791
                                ];
792

793
                        case 'wpseo-score':
×
794
                                return [
795
                                        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
796
                                        'meta_key' => WPSEO_Meta::$meta_prefix . 'linkdex',
×
797
                                        'orderby'  => 'meta_value_num',
×
798
                                ];
799

800
                        case 'wpseo-score-readability':
×
801
                                return [
802
                                        // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
803
                                        'meta_key' => WPSEO_Meta::$meta_prefix . 'content_score',
×
804
                                        'orderby'  => 'meta_value_num',
×
805
                                ];
806
                }
807

808
                return [];
×
809
        }
810

811
        /**
812
         * Parses the score column.
813
         *
814
         * @param int $post_id The ID of the post for which to show the score.
815
         *
816
         * @return string The HTML for the SEO score indicator.
817
         */
818
        private function parse_column_score( $post_id ) {
×
819
                $meta = $this->get_meta( $post_id );
×
820

821
                if ( $meta ) {
×
822
                        return $this->score_icon_helper->for_seo( $meta->indexable, '', __( 'Post is set to noindex.', 'wordpress-seo' ) );
×
823
                }
824
        }
825

826
        /**
827
         * Parsing the readability score column.
828
         *
829
         * @param int $post_id The ID of the post for which to show the readability score.
830
         *
831
         * @return string The HTML for the readability score indicator.
832
         */
833
        private function parse_column_score_readability( $post_id ) {
×
834
                $meta = $this->get_meta( $post_id );
×
835
                if ( $meta ) {
×
836
                        return $this->score_icon_helper->for_readability( $meta->indexable->readability_score );
×
837
                }
838
        }
839

840
        /**
841
         * Sets up the hooks for the post_types.
842
         *
843
         * @return void
844
         */
845
        private function set_post_type_hooks() {
×
846
                $post_types = WPSEO_Post_Type::get_accessible_post_types();
×
847

848
                if ( ! is_array( $post_types ) || $post_types === [] ) {
×
849
                        return;
×
850
                }
851

852
                foreach ( $post_types as $post_type ) {
×
853
                        if ( $this->display_metabox( $post_type ) === false ) {
×
854
                                continue;
×
855
                        }
856

857
                        add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'column_heading' ], 10, 1 );
×
858
                        add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 );
×
859
                        add_action( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ], 10, 2 );
×
860
                }
861

862
                unset( $post_type );
×
863
        }
864

865
        /**
866
         * Wraps the WPSEO_Metabox check to determine whether the metabox should be displayed either by
867
         * choice of the admin or because the post type is not a public post type.
868
         *
869
         * @since 7.0
870
         *
871
         * @param string|null $post_type Optional. The post type to test, defaults to the current post post_type.
872
         *
873
         * @return bool Whether or not the meta box (and associated columns etc) should be hidden.
874
         */
875
        private function display_metabox( $post_type = null ) {
×
876
                $current_post_type = $this->get_current_post_type();
×
877

878
                if ( ! isset( $post_type ) && ! empty( $current_post_type ) ) {
×
879
                        $post_type = $current_post_type;
×
880
                }
881

882
                return WPSEO_Utils::is_metabox_active( $post_type, 'post_type' );
×
883
        }
884

885
        /**
886
         * Determines whether or not filter dropdowns should be displayed.
887
         *
888
         * @return bool Whether or the current page can display the filter drop downs.
889
         */
890
        public function can_display_filter() {
×
891
                if ( $GLOBALS['pagenow'] === 'upload.php' ) {
×
892
                        return false;
×
893
                }
894

895
                if ( $this->display_metabox() === false ) {
×
896
                        return false;
×
897
                }
898

899
                $screen = get_current_screen();
×
900
                if ( $screen === null ) {
×
901
                        return false;
×
902
                }
903

904
                return WPSEO_Post_Type::is_post_type_accessible( $screen->post_type );
×
905
        }
906
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc