PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.7
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.7
28.5 28.4 28.3 28.2 28.1 28.0 27.9 27.8 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 All 129 releases
wordpress-seo / inc / class-wpseo-admin-bar-menu.php

class-wpseo-admin-bar-menu.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.7, at inc/class-wpseo-admin-bar-menu.php

681 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO
6 */
7
8 /**
9 * Class for the Yoast SEO admin bar menu.
10 */
11 class WPSEO_Admin_Bar_Menu implements WPSEO_WordPress_Integration {
12
13 /**
14 * The identifier used for the menu.
15 *
16 * @var string
17 */
18 const MENU_IDENTIFIER = 'wpseo-menu';
19
20 /**
21 * The identifier used for the Keyword Research submenu.
22 *
23 * @var string
24 */
25 const KEYWORD_RESEARCH_SUBMENU_IDENTIFIER = 'wpseo-kwresearch';
26
27 /**
28 * The identifier used for the Analysis submenu.
29 *
30 * @var string
31 */
32 const ANALYSIS_SUBMENU_IDENTIFIER = 'wpseo-analysis';
33
34 /**
35 * The identifier used for the Settings submenu.
36 *
37 * @var string
38 */
39 const SETTINGS_SUBMENU_IDENTIFIER = 'wpseo-settings';
40
41 /**
42 * The identifier used for the Network Settings submenu.
43 *
44 * @var string
45 */
46 const NETWORK_SETTINGS_SUBMENU_IDENTIFIER = 'wpseo-network-settings';
47
48 /**
49 * Asset manager instance.
50 *
51 * @var WPSEO_Admin_Asset_Manager
52 */
53 protected $asset_manager;
54
55 /**
56 * Constructor.
57 *
58 * Sets the asset manager to use.
59 *
60 * @param WPSEO_Admin_Asset_Manager|null $asset_manager Optional. Asset manager to use.
61 */
62 public function __construct( WPSEO_Admin_Asset_Manager $asset_manager = null ) {
63 if ( ! $asset_manager ) {
64 $asset_manager = new WPSEO_Admin_Asset_Manager();
65 }
66
67 $this->asset_manager = $asset_manager;
68 }
69
70 /**
71 * Adds the admin bar menu.
72 *
73 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
74 *
75 * @return void
76 */
77 public function add_menu( WP_Admin_Bar $wp_admin_bar ) {
78
79 // On block editor pages, the admin bar only shows on mobile, where having this menu icon is not very helpful.
80 if ( is_admin() ) {
81 $screen = get_current_screen();
82 if ( isset( $screen ) && $screen->is_block_editor() ) {
83 return;
84 }
85 }
86
87 // If the current user can't write posts, this is all of no use, so let's not output an admin menu.
88 if ( ! current_user_can( 'edit_posts' ) ) {
89 return;
90 }
91
92 $this->add_root_menu( $wp_admin_bar );
93 $this->add_keyword_research_submenu( $wp_admin_bar );
94
95 if ( ! is_admin() ) {
96 $this->add_analysis_submenu( $wp_admin_bar );
97 }
98
99 if ( ! is_admin() || is_blog_admin() ) {
100 $this->add_settings_submenu( $wp_admin_bar );
101 }
102 elseif ( is_network_admin() ) {
103 $this->add_network_settings_submenu( $wp_admin_bar );
104 }
105 }
106
107 /**
108 * Enqueues admin bar assets.
109 *
110 * @return void
111 */
112 public function enqueue_assets() {
113 if ( ! is_admin_bar_showing() ) {
114 return;
115 }
116
117 // If the current user can't write posts, this is all of no use, so let's not output an admin menu.
118 if ( ! current_user_can( 'edit_posts' ) ) {
119 return;
120 }
121
122 $this->asset_manager->register_assets();
123 $this->asset_manager->enqueue_style( 'adminbar' );
124 }
125
126 /**
127 * Registers the hooks.
128 *
129 * @return void
130 */
131 public function register_hooks() {
132 if ( ! $this->meets_requirements() ) {
133 return;
134 }
135
136 add_action( 'admin_bar_menu', [ $this, 'add_menu' ], 95 );
137
138 add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_assets' ] );
139 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
140 }
141
142 /**
143 * Checks whether the requirements to use this class are met.
144 *
145 * @return bool True if requirements are met, false otherwise.
146 */
147 public function meets_requirements() {
148 if ( is_network_admin() ) {
149 return WPSEO_Utils::is_plugin_network_active();
150 }
151
152 if ( WPSEO_Options::get( 'enable_admin_bar_menu' ) !== true ) {
153 return false;
154 }
155
156 return ! is_admin() || is_blog_admin();
157 }
158
159 /**
160 * Adds the admin bar root menu.
161 *
162 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
163 *
164 * @return void
165 */
166 protected function add_root_menu( WP_Admin_Bar $wp_admin_bar ) {
167 $title = $this->get_title();
168
169 $score = '';
170 $settings_url = '';
171 $counter = '';
172 $notification_popup = '';
173
174 $post = $this->get_singular_post();
175 if ( $post ) {
176 $score = $this->get_post_score( $post );
177 }
178
179 $term = $this->get_singular_term();
180 if ( $term ) {
181 $score = $this->get_term_score( $term );
182 }
183
184 $can_manage_options = $this->can_manage_options();
185
186 if ( $can_manage_options ) {
187 $settings_url = $this->get_settings_page_url();
188 }
189
190 if ( empty( $score ) && ! is_network_admin() && $can_manage_options ) {
191 $counter = $this->get_notification_counter();
192 $notification_popup = $this->get_notification_popup();
193 }
194
195 $admin_bar_menu_args = [
196 'id' => self::MENU_IDENTIFIER,
197 'title' => $title . $score . $counter . $notification_popup,
198 'href' => $settings_url,
199 'meta' => [ 'tabindex' => ! empty( $settings_url ) ? false : '0' ],
200 ];
201 $wp_admin_bar->add_menu( $admin_bar_menu_args );
202
203 if ( ! empty( $counter ) ) {
204 $admin_bar_menu_args = [
205 'parent' => self::MENU_IDENTIFIER,
206 'id' => 'wpseo-notifications',
207 'title' => __( 'Notifications', 'wordpress-seo' ) . $counter,
208 'href' => $settings_url,
209 'meta' => [ 'tabindex' => ! empty( $settings_url ) ? false : '0' ],
210 ];
211 $wp_admin_bar->add_menu( $admin_bar_menu_args );
212 }
213 }
214
215 /**
216 * Adds the admin bar keyword research submenu.
217 *
218 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
219 *
220 * @return void
221 */
222 protected function add_keyword_research_submenu( WP_Admin_Bar $wp_admin_bar ) {
223 $adwords_url = 'https://yoa.st/keywordplanner';
224 $trends_url = 'https://yoa.st/google-trends';
225
226 $post = $this->get_singular_post();
227 if ( $post ) {
228 $focus_keyword = $this->get_post_focus_keyword( $post );
229
230 if ( ! empty( $focus_keyword ) ) {
231 $trends_url .= '#q=' . urlencode( $focus_keyword );
232 }
233 }
234
235 $menu_args = [
236 'parent' => self::MENU_IDENTIFIER,
237 'id' => self::KEYWORD_RESEARCH_SUBMENU_IDENTIFIER,
238 'title' => __( 'Keyword Research', 'wordpress-seo' ),
239 'meta' => [ 'tabindex' => '0' ],
240 ];
241 $wp_admin_bar->add_menu( $menu_args );
242
243 $submenu_items = [
244 [
245 'id' => 'wpseo-kwresearchtraining',
246 'title' => __( 'Keyword research training', 'wordpress-seo' ),
247 'href' => WPSEO_Shortlinker::get( 'https://yoa.st/wp-admin-bar' ),
248 ],
249 [
250 'id' => 'wpseo-adwordsexternal',
251 'title' => __( 'Google Ads', 'wordpress-seo' ),
252 'href' => $adwords_url,
253 ],
254 [
255 'id' => 'wpseo-googleinsights',
256 'title' => __( 'Google Trends', 'wordpress-seo' ),
257 'href' => $trends_url,
258 ],
259 ];
260
261 foreach ( $submenu_items as $menu_item ) {
262 $menu_args = [
263 'parent' => self::KEYWORD_RESEARCH_SUBMENU_IDENTIFIER,
264 'id' => $menu_item['id'],
265 'title' => $menu_item['title'],
266 'href' => $menu_item['href'],
267 'meta' => [ 'target' => '_blank' ],
268 ];
269 $wp_admin_bar->add_menu( $menu_args );
270 }
271 }
272
273 /**
274 * Adds the admin bar analysis submenu.
275 *
276 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
277 *
278 * @return void
279 */
280 protected function add_analysis_submenu( WP_Admin_Bar $wp_admin_bar ) {
281 try {
282 $url = YoastSEO()->meta->for_current_page()->canonical;
283 } catch ( Exception $e ) {
284 // This is not the type of error we can handle here.
285 return;
286 }
287
288 $focus_keyword = '';
289
290 if ( ! $url ) {
291 return;
292 }
293
294 $post = $this->get_singular_post();
295 if ( $post ) {
296 $focus_keyword = $this->get_post_focus_keyword( $post );
297 }
298
299 $menu_args = [
300 'parent' => self::MENU_IDENTIFIER,
301 'id' => self::ANALYSIS_SUBMENU_IDENTIFIER,
302 'title' => __( 'Analyze this page', 'wordpress-seo' ),
303 'meta' => [ 'tabindex' => '0' ],
304 ];
305 $wp_admin_bar->add_menu( $menu_args );
306
307 $encoded_url = urlencode( $url );
308 $submenu_items = [
309 [
310 'id' => 'wpseo-inlinks',
311 'title' => __( 'Check links to this URL', 'wordpress-seo' ),
312 'href' => 'https://search.google.com/search-console/links/drilldown?resource_id=' . urlencode( get_option( 'siteurl' ) ) . '&type=EXTERNAL&target=' . $encoded_url . '&domain=',
313 ],
314 [
315 'id' => 'wpseo-kwdensity',
316 'title' => __( 'Check Keyphrase Density', 'wordpress-seo' ),
317 // HTTPS not available.
318 'href' => 'http://www.zippy.co.uk/keyworddensity/index.php?url=' . $encoded_url . '&keyword=' . urlencode( $focus_keyword ),
319 ],
320 [
321 'id' => 'wpseo-cache',
322 'title' => __( 'Check Google Cache', 'wordpress-seo' ),
323 'href' => '//webcache.googleusercontent.com/search?strip=1&q=cache:' . $encoded_url,
324 ],
325 [
326 'id' => 'wpseo-structureddata',
327 'title' => __( 'Google Rich Results Test', 'wordpress-seo' ),
328 'href' => 'https://search.google.com/test/rich-results?url=' . $encoded_url,
329 ],
330 [
331 'id' => 'wpseo-facebookdebug',
332 'title' => __( 'Facebook Debugger', 'wordpress-seo' ),
333 'href' => '//developers.facebook.com/tools/debug/?q=' . $encoded_url,
334 ],
335 [
336 'id' => 'wpseo-pinterestvalidator',
337 'title' => __( 'Pinterest Rich Pins Validator', 'wordpress-seo' ),
338 'href' => 'https://developers.pinterest.com/tools/url-debugger/?link=' . $encoded_url,
339 ],
340 [
341 'id' => 'wpseo-htmlvalidation',
342 'title' => __( 'HTML Validator', 'wordpress-seo' ),
343 'href' => '//validator.w3.org/check?uri=' . $encoded_url,
344 ],
345 [
346 'id' => 'wpseo-cssvalidation',
347 'title' => __( 'CSS Validator', 'wordpress-seo' ),
348 'href' => '//jigsaw.w3.org/css-validator/validator?uri=' . $encoded_url,
349 ],
350 [
351 'id' => 'wpseo-pagespeed',
352 'title' => __( 'Google Page Speed Test', 'wordpress-seo' ),
353 'href' => '//developers.google.com/speed/pagespeed/insights/?url=' . $encoded_url,
354 ],
355 [
356 'id' => 'wpseo-google-mobile-friendly',
357 'title' => __( 'Mobile-Friendly Test', 'wordpress-seo' ),
358 'href' => 'https://www.google.com/webmasters/tools/mobile-friendly/?url=' . $encoded_url,
359 ],
360 ];
361
362 foreach ( $submenu_items as $menu_item ) {
363 $menu_args = [
364 'parent' => self::ANALYSIS_SUBMENU_IDENTIFIER,
365 'id' => $menu_item['id'],
366 'title' => $menu_item['title'],
367 'href' => $menu_item['href'],
368 'meta' => [ 'target' => '_blank' ],
369 ];
370 $wp_admin_bar->add_menu( $menu_args );
371 }
372 }
373
374 /**
375 * Adds the admin bar settings submenu.
376 *
377 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
378 *
379 * @return void
380 */
381 protected function add_settings_submenu( WP_Admin_Bar $wp_admin_bar ) {
382 if ( ! $this->can_manage_options() ) {
383 return;
384 }
385
386 $admin_menu = new WPSEO_Admin_Menu( new WPSEO_Menu() );
387 $submenu_pages = $admin_menu->get_submenu_pages();
388
389 $menu_args = [
390 'parent' => self::MENU_IDENTIFIER,
391 'id' => self::SETTINGS_SUBMENU_IDENTIFIER,
392 'title' => __( 'SEO Settings', 'wordpress-seo' ),
393 'meta' => [ 'tabindex' => '0' ],
394 ];
395 $wp_admin_bar->add_menu( $menu_args );
396
397 foreach ( $submenu_pages as $submenu_page ) {
398 if ( ! current_user_can( $submenu_page[3] ) ) {
399 continue;
400 }
401
402 // Don't add the Google Search Console menu item.
403 if ( $submenu_page[4] === 'wpseo_search_console' ) {
404 continue;
405 }
406
407 $id = 'wpseo-' . str_replace( '_', '-', str_replace( 'wpseo_', '', $submenu_page[4] ) );
408 if ( $id === 'wpseo-dashboard' ) {
409 $id = 'wpseo-general';
410 }
411
412 $menu_args = [
413 'parent' => self::SETTINGS_SUBMENU_IDENTIFIER,
414 'id' => $id,
415 'title' => $submenu_page[2],
416 'href' => admin_url( 'admin.php?page=' . urlencode( $submenu_page[4] ) ),
417 ];
418 $wp_admin_bar->add_menu( $menu_args );
419 }
420 }
421
422 /**
423 * Adds the admin bar network settings submenu.
424 *
425 * @param WP_Admin_Bar $wp_admin_bar Admin bar instance to add the menu to.
426 *
427 * @return void
428 */
429 protected function add_network_settings_submenu( WP_Admin_Bar $wp_admin_bar ) {
430 if ( ! $this->can_manage_options() ) {
431 return;
432 }
433
434 $network_admin_menu = new WPSEO_Network_Admin_Menu( new WPSEO_Menu() );
435 $submenu_pages = $network_admin_menu->get_submenu_pages();
436
437 $menu_args = [
438 'parent' => self::MENU_IDENTIFIER,
439 'id' => self::NETWORK_SETTINGS_SUBMENU_IDENTIFIER,
440 'title' => __( 'SEO Settings', 'wordpress-seo' ),
441 'meta' => [ 'tabindex' => '0' ],
442 ];
443 $wp_admin_bar->add_menu( $menu_args );
444
445 foreach ( $submenu_pages as $submenu_page ) {
446 if ( ! current_user_can( $submenu_page[3] ) ) {
447 continue;
448 }
449
450 $id = 'wpseo-' . str_replace( '_', '-', str_replace( 'wpseo_', '', $submenu_page[4] ) );
451 if ( $id === 'wpseo-dashboard' ) {
452 $id = 'wpseo-general';
453 }
454
455 $menu_args = [
456 'parent' => self::NETWORK_SETTINGS_SUBMENU_IDENTIFIER,
457 'id' => $id,
458 'title' => $submenu_page[2],
459 'href' => network_admin_url( 'admin.php?page=' . urlencode( $submenu_page[4] ) ),
460 ];
461 $wp_admin_bar->add_menu( $menu_args );
462 }
463 }
464
465 /**
466 * Gets the menu title markup.
467 *
468 * @return string Admin bar title markup.
469 */
470 protected function get_title() {
471 return '<div id="yoast-ab-icon" class="ab-item yoast-logo svg"><span class="screen-reader-text">' . __( 'SEO', 'wordpress-seo' ) . '</span></div>';
472 }
473
474 /**
475 * Gets the current post if in a singular post context.
476 *
477 * @global string $pagenow Current page identifier.
478 * @global WP_Post|null $post Current post object, or null if none available.
479 *
480 * @return WP_Post|null Post object, or null if not in singular context.
481 */
482 protected function get_singular_post() {
483 global $pagenow, $post;
484
485 if ( ! is_singular() && ( ! is_blog_admin() || ! WPSEO_Metabox::is_post_edit( $pagenow ) ) ) {
486 return null;
487 }
488
489 if ( ! isset( $post ) || ! is_object( $post ) || ! $post instanceof WP_Post ) {
490 return null;
491 }
492
493 return $post;
494 }
495
496 /**
497 * Gets the focus keyword for a given post.
498 *
499 * @param WP_Post $post Post object to get its focus keyword.
500 *
501 * @return string Focus keyword, or empty string if none available.
502 */
503 protected function get_post_focus_keyword( $post ) {
504 if ( ! is_object( $post ) || ! property_exists( $post, 'ID' ) ) {
505 return '';
506 }
507
508 /**
509 * Filter: 'wpseo_use_page_analysis' Determines if the analysis should be enabled.
510 *
511 * @api bool Determines if the analysis should be enabled.
512 */
513 if ( apply_filters( 'wpseo_use_page_analysis', true ) !== true ) {
514 return '';
515 }
516
517 return WPSEO_Meta::get_value( 'focuskw', $post->ID );
518 }
519
520 /**
521 * Gets the score for a given post.
522 *
523 * @param WP_Post $post Post object to get its score.
524 *
525 * @return string Score markup, or empty string if none available.
526 */
527 protected function get_post_score( $post ) {
528 if ( ! is_object( $post ) || ! property_exists( $post, 'ID' ) ) {
529 return '';
530 }
531
532 if ( apply_filters( 'wpseo_use_page_analysis', true ) !== true ) {
533 return '';
534 }
535
536 $analysis_seo = new WPSEO_Metabox_Analysis_SEO();
537 $analysis_readability = new WPSEO_Metabox_Analysis_Readability();
538
539 if ( $analysis_seo->is_enabled() ) {
540 return $this->get_score( WPSEO_Meta::get_value( 'linkdex', $post->ID ) );
541 }
542
543 if ( $analysis_readability->is_enabled() ) {
544 return $this->get_score( WPSEO_Meta::get_value( 'content_score', $post->ID ) );
545 }
546
547 return '';
548 }
549
550 /**
551 * Gets the current term if in a singular term context.
552 *
553 * @global string $pagenow Current page identifier.
554 * @global WP_Query $wp_query Current query object.
555 * @global WP_Term|null $tag Current term object, or null if none available.
556 *
557 * @return WP_Term|null Term object, or null if not in singular context.
558 */
559 protected function get_singular_term() {
560 global $pagenow, $wp_query, $tag;
561
562 if ( is_category() || is_tag() || is_tax() ) {
563 return $wp_query->get_queried_object();
564 }
565
566 if ( WPSEO_Taxonomy::is_term_edit( $pagenow ) && ! WPSEO_Taxonomy::is_term_overview( $pagenow ) && isset( $tag ) && is_object( $tag ) && ! is_wp_error( $tag ) ) {
567 return get_term( $tag->term_id );
568 }
569
570 return null;
571 }
572
573 /**
574 * Gets the score for a given term.
575 *
576 * @param WP_Term $term Term object to get its score.
577 *
578 * @return string Score markup, or empty string if none available.
579 */
580 protected function get_term_score( $term ) {
581 if ( ! is_object( $term ) || ! property_exists( $term, 'term_id' ) || ! property_exists( $term, 'taxonomy' ) ) {
582 return '';
583 }
584
585 $analysis_seo = new WPSEO_Metabox_Analysis_SEO();
586 $analysis_readability = new WPSEO_Metabox_Analysis_Readability();
587
588 if ( $analysis_seo->is_enabled() ) {
589 return $this->get_score( WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $term->taxonomy, 'linkdex' ) );
590 }
591
592 if ( $analysis_readability->is_enabled() ) {
593 return $this->get_score( WPSEO_Taxonomy_Meta::get_term_meta( $term->term_id, $term->taxonomy, 'content_score' ) );
594 }
595
596 return '';
597 }
598
599 /**
600 * Takes the SEO score and makes the score icon for the admin bar for it.
601 *
602 * @param int $score The 0-100 rating of the score. Can be either SEO score or content score.
603 *
604 * @return string Score markup.
605 */
606 protected function get_score( $score ) {
607 $score_class = WPSEO_Utils::translate_score( $score );
608 $translated_score = WPSEO_Utils::translate_score( $score, false );
609 /* translators: %s expands to the SEO score. */
610 $screen_reader_text = sprintf( __( 'SEO score: %s', 'wordpress-seo' ), $translated_score );
611
612 $score_adminbar_element = '<div class="wpseo-score-icon adminbar-seo-score ' . $score_class . '"><span class="adminbar-seo-score-text screen-reader-text">' . $screen_reader_text . '</span></div>';
613
614 return $score_adminbar_element;
615 }
616
617 /**
618 * Gets the URL to the main admin settings page.
619 *
620 * @return string Admin settings page URL.
621 */
622 protected function get_settings_page_url() {
623 return self_admin_url( 'admin.php?page=' . WPSEO_Admin::PAGE_IDENTIFIER );
624 }
625
626 /**
627 * Gets the notification counter if in a valid context.
628 *
629 * @return string Notification counter markup, or empty string if not available.
630 */
631 protected function get_notification_counter() {
632 $notification_center = Yoast_Notification_Center::get();
633 $notification_count = $notification_center->get_notification_count();
634
635 if ( ! $notification_count ) {
636 return '';
637 }
638
639 /* translators: %s: number of notifications */
640 $counter_screen_reader_text = sprintf( _n( '%s notification', '%s notifications', $notification_count, 'wordpress-seo' ), number_format_i18n( $notification_count ) );
641
642 return sprintf( ' <div class="wp-core-ui wp-ui-notification yoast-issue-counter"><span aria-hidden="true">%d</span><span class="screen-reader-text">%s</span></div>', $notification_count, $counter_screen_reader_text );
643 }
644
645 /**
646 * Gets the notification popup if in a valid context.
647 *
648 * @return string Notification popup markup, or empty string if not available.
649 */
650 protected function get_notification_popup() {
651 $notification_center = Yoast_Notification_Center::get();
652 $new_notifications = $notification_center->get_new_notifications();
653 $new_notifications_count = count( $new_notifications );
654
655 if ( ! $new_notifications_count ) {
656 return '';
657 }
658
659 $notification = sprintf(
660 _n(
661 'There is a new notification.',
662 'There are new notifications.',
663 $new_notifications_count,
664 'wordpress-seo'
665 ),
666 $new_notifications_count
667 );
668
669 return '<div class="yoast-issue-added">' . $notification . '</div>';
670 }
671
672 /**
673 * Checks whether the current user can manage options in the current context.
674 *
675 * @return bool True if capabilities are sufficient, false otherwise.
676 */
677 protected function can_manage_options() {
678 return is_network_admin() && current_user_can( 'wpseo_manage_network_options' ) || ! is_network_admin() && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' );
679 }
680 }
681