PluginProbe ʕ •ᴥ•ʔ
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 22.0
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v22.0
27.7 27.6 27.5 trunk 18.0 18.1 18.2 18.3 18.4 18.4.1 18.5 18.5.1 18.6 18.7 18.8 18.9 19.0 19.1 19.10 19.11 19.12 19.13 19.14 19.2 19.3 19.4 19.5 19.5.1 19.6 19.6.1 19.7 19.7.1 19.7.2 19.8 19.9 20.0 20.1 20.10 20.11 20.12 20.13 20.2 20.2.1 20.3 20.4 20.5 20.6 20.7 20.8 20.9 21.0 21.1 21.2 21.3 21.4 21.5 21.6 21.7 21.8 21.8.1 21.9 21.9.1 22.0 22.1 22.2 22.3 22.4 22.5 22.6 22.7 22.8 22.9 23.0 23.1 23.2 23.3 23.4 23.5 23.6 23.7 23.8 23.9 24.0 24.1 24.2 24.3 24.4 24.5 24.6 24.7 24.8 24.8.1 24.9 25.0 25.1 25.2 25.3 25.3.1 25.4 25.5 25.6 25.7 25.8 25.9 26.0 26.1 26.1.1 26.2 26.3 26.4 26.5 26.6 26.7 26.8 26.9 27.0 27.1 27.1.1 27.2 27.3 27.4
wordpress-seo / admin / class-meta-columns.php
wordpress-seo / admin Last commit date
ajax 2 years ago capabilities 2 years ago endpoints 2 years ago exceptions 7 years ago filters 2 years ago formatter 2 years ago google_search_console 2 years ago import 2 years ago listeners 8 years ago menu 2 years ago metabox 2 years ago notifiers 3 years ago pages 2 years ago roles 2 years ago services 5 years ago statistics 2 years ago taxonomy 2 years ago tracking 2 years ago views 2 years ago watchers 2 years ago admin-settings-changed-listener.php 2 years ago ajax.php 2 years ago class-admin-asset-analysis-worker-location.php 5 years ago class-admin-asset-dev-server-location.php 2 years ago class-admin-asset-location.php 8 years ago class-admin-asset-manager.php 2 years ago class-admin-asset-seo-location.php 4 years ago class-admin-editor-specific-replace-vars.php 2 years ago class-admin-gutenberg-compatibility-notification.php 2 years ago class-admin-help-panel.php 5 years ago class-admin-init.php 2 years ago class-admin-recommended-replace-vars.php 2 years ago class-admin-user-profile.php 2 years ago class-admin-utils.php 2 years ago class-admin.php 2 years ago class-asset.php 2 years ago class-bulk-description-editor-list-table.php 5 years ago class-bulk-editor-list-table.php 2 years ago class-bulk-title-editor-list-table.php 6 years ago class-collector.php 2 years ago class-config.php 2 years ago class-customizer.php 2 years ago class-database-proxy.php 2 years ago class-export.php 2 years ago class-expose-shortlinks.php 2 years ago class-gutenberg-compatibility.php 2 years ago class-meta-columns.php 2 years ago class-my-yoast-proxy.php 2 years ago class-option-tab.php 4 years ago class-option-tabs-formatter.php 2 years ago class-option-tabs.php 2 years ago class-paper-presenter.php 5 years ago class-plugin-availability.php 2 years ago class-plugin-conflict.php 2 years ago class-premium-popup.php 2 years ago class-premium-upsell-admin-block.php 2 years ago class-primary-term-admin.php 2 years ago class-product-upsell-notice.php 2 years ago class-remote-request.php 2 years ago class-schema-person-upgrade-notification.php 2 years ago class-suggested-plugins.php 2 years ago class-wincher-dashboard-widget.php 2 years ago class-yoast-columns.php 2 years ago class-yoast-dashboard-widget.php 2 years ago class-yoast-form.php 2 years ago class-yoast-input-validation.php 2 years ago class-yoast-network-admin.php 2 years ago class-yoast-network-settings-api.php 4 years ago class-yoast-notification-center.php 2 years ago class-yoast-notification.php 2 years ago class-yoast-notifications.php 2 years ago class-yoast-plugin-conflict.php 2 years ago index.php 10 years ago interface-collection.php 7 years ago interface-installable.php 8 years ago
class-meta-columns.php
840 lines
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 ) {
95 if ( $this->display_metabox() === false ) {
96 return $columns;
97 }
98
99 $added_columns = [];
100
101 if ( $this->analysis_seo->is_enabled() ) {
102 $added_columns['wpseo-score'] = '<span class="yoast-column-seo-score yoast-column-header-has-tooltip" data-tooltip-text="'
103 . esc_attr__( 'SEO score', 'wordpress-seo' )
104 . '"><span class="screen-reader-text">'
105 . __( 'SEO score', 'wordpress-seo' )
106 . '</span></span></span>';
107 }
108
109 if ( $this->analysis_readability->is_enabled() ) {
110 $added_columns['wpseo-score-readability'] = '<span class="yoast-column-readability yoast-column-header-has-tooltip" data-tooltip-text="'
111 . esc_attr__( 'Readability score', 'wordpress-seo' )
112 . '"><span class="screen-reader-text">'
113 . __( 'Readability score', 'wordpress-seo' )
114 . '</span></span></span>';
115 }
116
117 $added_columns['wpseo-title'] = __( 'SEO Title', 'wordpress-seo' );
118 $added_columns['wpseo-metadesc'] = __( 'Meta Desc.', 'wordpress-seo' );
119
120 if ( $this->analysis_seo->is_enabled() ) {
121 $added_columns['wpseo-focuskw'] = __( 'Keyphrase', 'wordpress-seo' );
122 }
123
124 return array_merge( $columns, $added_columns );
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 ) {
232 if ( ! is_array( $hidden ) ) {
233 $hidden = [];
234 }
235
236 array_push( $hidden, 'wpseo-title', 'wpseo-metadesc' );
237
238 if ( $this->analysis_seo->is_enabled() ) {
239 $hidden[] = 'wpseo-focuskw';
240 }
241
242 return $hidden;
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 ) {
337 if ( $seo_filter === WPSEO_Rank::NO_FOCUS ) {
338 return $this->create_no_focus_keyword_filter();
339 }
340
341 if ( $seo_filter === WPSEO_Rank::NO_INDEX ) {
342 return $this->create_no_index_filter();
343 }
344
345 $rank = new WPSEO_Rank( $seo_filter );
346
347 return $this->create_seo_score_filter( $rank->get_starting_score(), $rank->get_end_score() );
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 ) {
358 $rank = new WPSEO_Rank( $readability_filter );
359
360 return $this->create_readability_score_filter( $rank->get_starting_score(), $rank->get_end_score() );
361 }
362
363 /**
364 * Creates a keyword filter for the meta query, based on the passed Keyword filter.
365 *
366 * @param string $keyword_filter The keyword filter to use.
367 *
368 * @return array The keyword filter.
369 */
370 protected function get_keyword_filter( $keyword_filter ) {
371 return [
372 'post_type' => get_query_var( 'post_type', 'post' ),
373 'key' => WPSEO_Meta::$meta_prefix . 'focuskw',
374 'value' => sanitize_text_field( $keyword_filter ),
375 ];
376 }
377
378 /**
379 * Determines whether the passed filter is considered to be valid.
380 *
381 * @param mixed $filter The filter to check against.
382 *
383 * @return bool Whether the filter is considered valid.
384 */
385 protected function is_valid_filter( $filter ) {
386 return ! empty( $filter ) && is_string( $filter );
387 }
388
389 /**
390 * Collects the filters and merges them into a single array.
391 *
392 * @return array Array containing all the applicable filters.
393 */
394 protected function collect_filters() {
395 $active_filters = [];
396
397 $seo_filter = $this->get_current_seo_filter();
398 $readability_filter = $this->get_current_readability_filter();
399 $current_keyword_filter = $this->get_current_keyword_filter();
400
401 if ( $this->is_valid_filter( $seo_filter ) ) {
402 $active_filters = array_merge(
403 $active_filters,
404 $this->determine_seo_filters( $seo_filter )
405 );
406 }
407
408 if ( $this->is_valid_filter( $readability_filter ) ) {
409 $active_filters = array_merge(
410 $active_filters,
411 $this->determine_readability_filters( $readability_filter )
412 );
413 }
414
415 if ( $this->is_valid_filter( $current_keyword_filter ) ) {
416 /**
417 * Adapt the meta query used to filter the post overview on keyphrase.
418 *
419 * @internal
420 *
421 * @param array $keyphrase The keyphrase used in the filter.
422 * @param array $keyword_filter The current keyword filter.
423 */
424 $keyphrase_filter = apply_filters(
425 'wpseo_change_keyphrase_filter_in_request',
426 $this->get_keyword_filter( $current_keyword_filter ),
427 $current_keyword_filter
428 );
429
430 if ( is_array( $keyphrase_filter ) ) {
431 $active_filters = array_merge(
432 $active_filters,
433 [ $keyphrase_filter ]
434 );
435 }
436 }
437
438 /**
439 * Adapt the active applicable filters on the posts overview.
440 *
441 * @internal
442 *
443 * @param array $active_filters The current applicable filters.
444 */
445 return apply_filters( 'wpseo_change_applicable_filters', $active_filters );
446 }
447
448 /**
449 * Modify the query based on the filters that are being passed.
450 *
451 * @param array $vars Query variables that need to be modified based on the filters.
452 *
453 * @return array Array containing the meta query to use for filtering the posts overview.
454 */
455 public function column_sort_orderby( $vars ) {
456 $collected_filters = $this->collect_filters();
457
458 $order_by_column = $vars['orderby'];
459 if ( isset( $order_by_column ) ) {
460 // Based on the selected column, create a meta query.
461 $order_by = $this->filter_order_by( $order_by_column );
462
463 /**
464 * Adapt the order by part of the query on the posts overview.
465 *
466 * @internal
467 *
468 * @param array $order_by The current order by.
469 * @param string $order_by_column The current order by column.
470 */
471 $order_by = apply_filters( 'wpseo_change_order_by', $order_by, $order_by_column );
472
473 $vars = array_merge( $vars, $order_by );
474 }
475
476 return $this->build_filter_query( $vars, $collected_filters );
477 }
478
479 /**
480 * Retrieves the meta robots query values to be used within the meta query.
481 *
482 * @return array Array containing the query parameters regarding meta robots.
483 */
484 protected function get_meta_robots_query_values() {
485 return [
486 'relation' => 'OR',
487 [
488 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
489 'compare' => 'NOT EXISTS',
490 ],
491 [
492 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
493 'value' => '1',
494 'compare' => '!=',
495 ],
496 ];
497 }
498
499 /**
500 * Determines the score filters to be used. If more than one is passed, it created an AND statement for the query.
501 *
502 * @param array $score_filters Array containing the score filters.
503 *
504 * @return array Array containing the score filters that need to be applied to the meta query.
505 */
506 protected function determine_score_filters( $score_filters ) {
507 if ( count( $score_filters ) > 1 ) {
508 return array_merge( [ 'relation' => 'AND' ], $score_filters );
509 }
510
511 return $score_filters;
512 }
513
514 /**
515 * Retrieves the post type from the $_GET variable.
516 *
517 * @return string|null The sanitized current post type or null when the variable is not set in $_GET.
518 */
519 public function get_current_post_type() {
520 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
521 if ( isset( $_GET['post_type'] ) && is_string( $_GET['post_type'] ) ) {
522 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
523 return sanitize_text_field( wp_unslash( $_GET['post_type'] ) );
524 }
525 return null;
526 }
527
528 /**
529 * Retrieves the SEO filter from the $_GET variable.
530 *
531 * @return string|null The sanitized seo filter or null when the variable is not set in $_GET.
532 */
533 public function get_current_seo_filter() {
534 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
535 if ( isset( $_GET['seo_filter'] ) && is_string( $_GET['seo_filter'] ) ) {
536 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
537 return sanitize_text_field( wp_unslash( $_GET['seo_filter'] ) );
538 }
539 return null;
540 }
541
542 /**
543 * Retrieves the Readability filter from the $_GET variable.
544 *
545 * @return string|null The sanitized readability filter or null when the variable is not set in $_GET.
546 */
547 public function get_current_readability_filter() {
548 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
549 if ( isset( $_GET['readability_filter'] ) && is_string( $_GET['readability_filter'] ) ) {
550 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
551 return sanitize_text_field( wp_unslash( $_GET['readability_filter'] ) );
552 }
553 return null;
554 }
555
556 /**
557 * Retrieves the keyword filter from the $_GET variable.
558 *
559 * @return string|null The sanitized seo keyword filter or null when the variable is not set in $_GET.
560 */
561 public function get_current_keyword_filter() {
562 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
563 if ( isset( $_GET['seo_kw_filter'] ) && is_string( $_GET['seo_kw_filter'] ) ) {
564 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information.
565 return sanitize_text_field( wp_unslash( $_GET['seo_kw_filter'] ) );
566 }
567 return null;
568 }
569
570 /**
571 * Uses the vars to create a complete filter query that can later be executed to filter out posts.
572 *
573 * @param array $vars Array containing the variables that will be used in the meta query.
574 * @param array $filters Array containing the filters that we need to apply in the meta query.
575 *
576 * @return array Array containing the complete filter query.
577 */
578 protected function build_filter_query( $vars, $filters ) {
579 // If no filters were applied, just return everything.
580 if ( count( $filters ) === 0 ) {
581 return $vars;
582 }
583
584 $result = [ 'meta_query' => [] ];
585 $result['meta_query'] = array_merge( $result['meta_query'], [ $this->determine_score_filters( $filters ) ] );
586
587 $current_seo_filter = $this->get_current_seo_filter();
588
589 // This only applies for the SEO score filter because it can because the SEO score can be altered by the no-index option.
590 if ( $this->is_valid_filter( $current_seo_filter ) && ! in_array( $current_seo_filter, [ WPSEO_Rank::NO_INDEX, WPSEO_Rank::NO_FOCUS ], true ) ) {
591 $result['meta_query'] = array_merge( $result['meta_query'], [ $this->get_meta_robots_query_values() ] );
592 }
593
594 return array_merge( $vars, $result );
595 }
596
597 /**
598 * Creates a Readability score filter.
599 *
600 * @param number $low The lower boundary of the score.
601 * @param number $high The higher boundary of the score.
602 *
603 * @return array The Readability Score filter.
604 */
605 protected function create_readability_score_filter( $low, $high ) {
606 return [
607 [
608 'key' => WPSEO_Meta::$meta_prefix . 'content_score',
609 'value' => [ $low, $high ],
610 'type' => 'numeric',
611 'compare' => 'BETWEEN',
612 ],
613 ];
614 }
615
616 /**
617 * Creates an SEO score filter.
618 *
619 * @param number $low The lower boundary of the score.
620 * @param number $high The higher boundary of the score.
621 *
622 * @return array The SEO score filter.
623 */
624 protected function create_seo_score_filter( $low, $high ) {
625 return [
626 [
627 'key' => WPSEO_Meta::$meta_prefix . 'linkdex',
628 'value' => [ $low, $high ],
629 'type' => 'numeric',
630 'compare' => 'BETWEEN',
631 ],
632 ];
633 }
634
635 /**
636 * Creates a filter to retrieve posts that were set to no-index.
637 *
638 * @return array Array containin the no-index filter.
639 */
640 protected function create_no_index_filter() {
641 return [
642 [
643 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
644 'value' => '1',
645 'compare' => '=',
646 ],
647 ];
648 }
649
650 /**
651 * Creates a filter to retrieve posts that have no keyword set.
652 *
653 * @return array Array containing the no focus keyword filter.
654 */
655 protected function create_no_focus_keyword_filter() {
656 return [
657 [
658 'key' => WPSEO_Meta::$meta_prefix . 'meta-robots-noindex',
659 'value' => 'needs-a-value-anyway',
660 'compare' => 'NOT EXISTS',
661 ],
662 [
663 'key' => WPSEO_Meta::$meta_prefix . 'linkdex',
664 'value' => 'needs-a-value-anyway',
665 'compare' => 'NOT EXISTS',
666 ],
667 ];
668 }
669
670 /**
671 * Determines whether a particular post_id is of an indexable post type.
672 *
673 * @param string $post_id The post ID to check.
674 *
675 * @return bool Whether or not it is indexable.
676 */
677 protected function is_indexable( $post_id ) {
678 if ( ! empty( $post_id ) && ! $this->uses_default_indexing( $post_id ) ) {
679 return WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '2';
680 }
681
682 $post = get_post( $post_id );
683
684 if ( is_object( $post ) ) {
685 // If the option is false, this means we want to index it.
686 return WPSEO_Options::get( 'noindex-' . $post->post_type, false ) === false;
687 }
688
689 return true;
690 }
691
692 /**
693 * Determines whether the given post ID uses the default indexing settings.
694 *
695 * @param int $post_id The post ID to check.
696 *
697 * @return bool Whether or not the default indexing is being used for the post.
698 */
699 protected function uses_default_indexing( $post_id ) {
700 return WPSEO_Meta::get_value( 'meta-robots-noindex', $post_id ) === '0';
701 }
702
703 /**
704 * Returns filters when $order_by is matched in the if-statement.
705 *
706 * @param string $order_by The ID of the column by which to order the posts.
707 *
708 * @return array Array containing the order filters.
709 */
710 private function filter_order_by( $order_by ) {
711 switch ( $order_by ) {
712 case 'wpseo-metadesc':
713 return [
714 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
715 'meta_key' => WPSEO_Meta::$meta_prefix . 'metadesc',
716 'orderby' => 'meta_value',
717 ];
718
719 case 'wpseo-focuskw':
720 return [
721 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
722 'meta_key' => WPSEO_Meta::$meta_prefix . 'focuskw',
723 'orderby' => 'meta_value',
724 ];
725
726 case 'wpseo-score':
727 return [
728 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
729 'meta_key' => WPSEO_Meta::$meta_prefix . 'linkdex',
730 'orderby' => 'meta_value_num',
731 ];
732
733 case 'wpseo-score-readability':
734 return [
735 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- Reason: Only used when user requests sorting.
736 'meta_key' => WPSEO_Meta::$meta_prefix . 'content_score',
737 'orderby' => 'meta_value_num',
738 ];
739 }
740
741 return [];
742 }
743
744 /**
745 * Parses the score column.
746 *
747 * @param int $post_id The ID of the post for which to show the score.
748 *
749 * @return string The HTML for the SEO score indicator.
750 */
751 private function parse_column_score( $post_id ) {
752 $meta = $this->get_meta( $post_id );
753
754 if ( $meta ) {
755 return $this->score_icon_helper->for_seo( $meta->indexable, '', __( 'Post is set to noindex.', 'wordpress-seo' ) );
756 }
757 }
758
759 /**
760 * Parsing the readability score column.
761 *
762 * @param int $post_id The ID of the post for which to show the readability score.
763 *
764 * @return string The HTML for the readability score indicator.
765 */
766 private function parse_column_score_readability( $post_id ) {
767 $meta = $this->get_meta( $post_id );
768 if ( $meta ) {
769 return $this->score_icon_helper->for_readability( $meta->indexable->readability_score );
770 }
771 }
772
773 /**
774 * Sets up the hooks for the post_types.
775 *
776 * @return void
777 */
778 private function set_post_type_hooks() {
779 $post_types = WPSEO_Post_Type::get_accessible_post_types();
780
781 if ( ! is_array( $post_types ) || $post_types === [] ) {
782 return;
783 }
784
785 foreach ( $post_types as $post_type ) {
786 if ( $this->display_metabox( $post_type ) === false ) {
787 continue;
788 }
789
790 add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'column_heading' ], 10, 1 );
791 add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 );
792 add_action( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ], 10, 2 );
793 }
794
795 unset( $post_type );
796 }
797
798 /**
799 * Wraps the WPSEO_Metabox check to determine whether the metabox should be displayed either by
800 * choice of the admin or because the post type is not a public post type.
801 *
802 * @since 7.0
803 *
804 * @param string|null $post_type Optional. The post type to test, defaults to the current post post_type.
805 *
806 * @return bool Whether or not the meta box (and associated columns etc) should be hidden.
807 */
808 private function display_metabox( $post_type = null ) {
809 $current_post_type = $this->get_current_post_type();
810
811 if ( ! isset( $post_type ) && ! empty( $current_post_type ) ) {
812 $post_type = $current_post_type;
813 }
814
815 return WPSEO_Utils::is_metabox_active( $post_type, 'post_type' );
816 }
817
818 /**
819 * Determines whether or not filter dropdowns should be displayed.
820 *
821 * @return bool Whether or the current page can display the filter drop downs.
822 */
823 public function can_display_filter() {
824 if ( $GLOBALS['pagenow'] === 'upload.php' ) {
825 return false;
826 }
827
828 if ( $this->display_metabox() === false ) {
829 return false;
830 }
831
832 $screen = get_current_screen();
833 if ( $screen === null ) {
834 return false;
835 }
836
837 return WPSEO_Post_Type::is_post_type_accessible( $screen->post_type );
838 }
839 }
840