PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 18.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v18.1
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 / admin / class-admin-init.php

class-admin-init.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 18.1, at admin/class-admin-init.php

498 lines 14.2 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\Admin
6 */
7
8 /**
9 * Performs the load on admin side.
10 */
11 class WPSEO_Admin_Init {
12
13 /**
14 * Holds the global `$pagenow` variable's value.
15 *
16 * @var string
17 */
18 private $pagenow;
19
20 /**
21 * Holds the asset manager.
22 *
23 * @var WPSEO_Admin_Asset_Manager
24 */
25 private $asset_manager;
26
27 /**
28 * Class constructor.
29 */
30 public function __construct() {
31 $GLOBALS['wpseo_admin'] = new WPSEO_Admin();
32
33 $this->pagenow = $GLOBALS['pagenow'];
34
35 $this->asset_manager = new WPSEO_Admin_Asset_Manager();
36
37 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_dismissible' ] );
38 add_action( 'admin_init', [ $this, 'unsupported_php_notice' ], 15 );
39 add_action( 'admin_init', [ $this->asset_manager, 'register_assets' ] );
40 add_action( 'admin_init', [ $this, 'show_hook_deprecation_warnings' ] );
41 add_action( 'admin_init', [ 'WPSEO_Plugin_Conflict', 'hook_check_for_plugin_conflicts' ] );
42 add_action( 'admin_notices', [ $this, 'permalink_settings_notice' ] );
43 add_action( 'post_submitbox_misc_actions', [ $this, 'add_publish_box_section' ] );
44
45 /*
46 * The `admin_notices` hook fires on single site admin pages vs.
47 * `network_admin_notices` which fires on multisite admin pages and
48 * `user_admin_notices` which fires on multisite user admin pagss.
49 */
50 add_action( 'admin_notices', [ $this, 'search_engines_discouraged_notice' ] );
51
52 $health_checks = [
53 new WPSEO_Health_Check_Page_Comments(),
54 new WPSEO_Health_Check_Ryte(),
55 new WPSEO_Health_Check_Postname_Permalink(),
56 new WPSEO_Health_Check_Curl_Version(),
57 new WPSEO_Health_Check_Link_Table_Not_Accessible(),
58 ];
59
60 foreach ( $health_checks as $health_check ) {
61 $health_check->register_test();
62 }
63
64 $this->load_meta_boxes();
65 $this->load_taxonomy_class();
66 $this->load_admin_page_class();
67 $this->load_admin_user_class();
68 $this->load_xml_sitemaps_admin();
69 $this->load_plugin_suggestions();
70 }
71
72 /**
73 * Enqueue our styling for dismissible yoast notifications.
74 */
75 public function enqueue_dismissible() {
76 $this->asset_manager->enqueue_style( 'dismissible' );
77 }
78
79 /**
80 * Creates an unsupported PHP version notification in the notification center.
81 *
82 * @return void
83 */
84 public function unsupported_php_notice() {
85 $notification_center = Yoast_Notification_Center::get();
86 $notification_center->remove_notification_by_id( 'wpseo-dismiss-unsupported-php' );
87 }
88
89 /**
90 * Gets the latest released major WordPress version from the WordPress stable-check api.
91 *
92 * @return float|int The latest released major WordPress version. 0 when the stable-check API doesn't respond.
93 */
94 private function get_latest_major_wordpress_version() {
95 $core_updates = get_core_updates( [ 'dismissed' => true ] );
96
97 if ( $core_updates === false ) {
98 return 0;
99 }
100
101 $wp_version_latest = get_bloginfo( 'version' );
102 foreach ( $core_updates as $update ) {
103 if ( $update->response === 'upgrade' && version_compare( $update->version, $wp_version_latest, '>' ) ) {
104 $wp_version_latest = $update->version;
105 }
106 }
107
108 // Strip the patch version and convert to a float.
109 return (float) $wp_version_latest;
110 }
111
112 /**
113 * Helper to verify if the user is currently visiting one of our admin pages.
114 *
115 * @return bool
116 */
117 private function on_wpseo_admin_page() {
118 return $this->pagenow === 'admin.php' && strpos( filter_input( INPUT_GET, 'page' ), 'wpseo' ) === 0;
119 }
120
121 /**
122 * Determine whether we should load the meta box class and if so, load it.
123 */
124 private function load_meta_boxes() {
125
126 $is_editor = WPSEO_Metabox::is_post_overview( $this->pagenow ) || WPSEO_Metabox::is_post_edit( $this->pagenow );
127 $is_inline_save = filter_input( INPUT_POST, 'action' ) === 'inline-save';
128
129 /**
130 * Filter: 'wpseo_always_register_metaboxes_on_admin' - Allow developers to change whether
131 * the WPSEO metaboxes are only registered on the typical pages (lean loading) or always
132 * registered when in admin.
133 *
134 * @api bool Whether to always register the metaboxes or not. Defaults to false.
135 */
136 if ( $is_editor || $is_inline_save || apply_filters( 'wpseo_always_register_metaboxes_on_admin', false )
137 ) {
138 $GLOBALS['wpseo_metabox'] = new WPSEO_Metabox();
139 $GLOBALS['wpseo_meta_columns'] = new WPSEO_Meta_Columns();
140 }
141 }
142
143 /**
144 * Determine if we should load our taxonomy edit class and if so, load it.
145 */
146 private function load_taxonomy_class() {
147 if (
148 WPSEO_Taxonomy::is_term_edit( $this->pagenow )
149 || WPSEO_Taxonomy::is_term_overview( $this->pagenow )
150 ) {
151 new WPSEO_Taxonomy();
152 }
153 }
154
155 /**
156 * Determine if we should load our admin pages class and if so, load it.
157 *
158 * Loads admin page class for all admin pages starting with `wpseo_`.
159 */
160 private function load_admin_user_class() {
161 if ( in_array( $this->pagenow, [ 'user-edit.php', 'profile.php' ], true )
162 && current_user_can( 'edit_users' )
163 ) {
164 new WPSEO_Admin_User_Profile();
165 }
166 }
167
168 /**
169 * Determine if we should load our admin pages class and if so, load it.
170 *
171 * Loads admin page class for all admin pages starting with `wpseo_`.
172 */
173 private function load_admin_page_class() {
174
175 if ( $this->on_wpseo_admin_page() ) {
176 // For backwards compatabilty, this still needs a global, for now...
177 $GLOBALS['wpseo_admin_pages'] = new WPSEO_Admin_Pages();
178
179 $page = filter_input( INPUT_GET, 'page' );
180 // Only register the yoast i18n when the page is a Yoast SEO page.
181 if ( WPSEO_Utils::is_yoast_seo_free_page( $page ) ) {
182 $this->register_i18n_promo_class();
183 if ( $page !== 'wpseo_titles' ) {
184 $this->register_premium_upsell_admin_block();
185 }
186 }
187 }
188 }
189
190 /**
191 * Loads the plugin suggestions.
192 */
193 private function load_plugin_suggestions() {
194 $suggestions = new WPSEO_Suggested_Plugins( new WPSEO_Plugin_Availability(), Yoast_Notification_Center::get() );
195 $suggestions->register_hooks();
196 }
197
198 /**
199 * Registers the Premium Upsell Admin Block.
200 *
201 * @return void
202 */
203 private function register_premium_upsell_admin_block() {
204 if ( ! YoastSEO()->helpers->product->is_premium() ) {
205 $upsell_block = new WPSEO_Premium_Upsell_Admin_Block( 'wpseo_admin_promo_footer' );
206 $upsell_block->register_hooks();
207 }
208 }
209
210 /**
211 * Registers the promotion class for our GlotPress instance, then creates a notification with the i18n promo.
212 *
213 * @link https://github.com/Yoast/i18n-module
214 */
215 private function register_i18n_promo_class() {
216 // BC, because an older version of the i18n-module didn't have this class.
217 $i18n_module = new Yoast_I18n_WordPressOrg_v3(
218 [
219 'textdomain' => 'wordpress-seo',
220 'plugin_name' => 'Yoast SEO',
221 'hook' => 'wpseo_admin_promo_footer',
222 ],
223 false
224 );
225
226 $message = $i18n_module->get_promo_message();
227
228 if ( $message !== '' ) {
229 $message .= $i18n_module->get_dismiss_i18n_message_button();
230 }
231
232 $notification_center = Yoast_Notification_Center::get();
233
234 $notification = new Yoast_Notification(
235 $message,
236 [
237 'type' => Yoast_Notification::WARNING,
238 'id' => 'i18nModuleTranslationAssistance',
239 ]
240 );
241
242 if ( $message ) {
243 $notification_center->add_notification( $notification );
244
245 return;
246 }
247
248 $notification_center->remove_notification( $notification );
249 }
250
251 /**
252 * See if we should start our XML Sitemaps Admin class.
253 */
254 private function load_xml_sitemaps_admin() {
255 if ( WPSEO_Options::get( 'enable_xml_sitemap', false ) ) {
256 new WPSEO_Sitemaps_Admin();
257 }
258 }
259
260 /**
261 * Checks whether search engines are discouraged from indexing the site.
262 *
263 * @return bool Whether search engines are discouraged from indexing the site.
264 */
265 private function are_search_engines_discouraged() {
266 return (string) get_option( 'blog_public' ) === '0';
267 }
268
269 /**
270 * Shows deprecation warnings to the user if a plugin has registered a filter we have deprecated.
271 */
272 public function show_hook_deprecation_warnings() {
273 global $wp_filter;
274
275 if ( wp_doing_ajax() ) {
276 return;
277 }
278
279 // WordPress hooks that have been deprecated since a Yoast SEO version.
280 $deprecated_filters = [
281 'wpseo_genesis_force_adjacent_rel_home' => [
282 'version' => '9.4',
283 'alternative' => null,
284 ],
285 'wpseo_opengraph' => [
286 'version' => '14.0',
287 'alternative' => null,
288 ],
289 'wpseo_twitter' => [
290 'version' => '14.0',
291 'alternative' => null,
292 ],
293 'wpseo_twitter_taxonomy_image' => [
294 'version' => '14.0',
295 'alternative' => null,
296 ],
297 'wpseo_twitter_metatag_key' => [
298 'version' => '14.0',
299 'alternative' => null,
300 ],
301 'wp_seo_get_bc_ancestors' => [
302 'version' => '14.0',
303 'alternative' => 'wpseo_breadcrumb_links',
304 ],
305 'validate_facebook_app_id_api_response_code' => [
306 'version' => '15.5',
307 'alternative' => null,
308 ],
309 'validate_facebook_app_id_api_response_body' => [
310 'version' => '15.5',
311 'alternative' => null,
312 ],
313 ];
314
315 // Determine which filters have been registered.
316 $deprecated_notices = array_intersect(
317 array_keys( $deprecated_filters ),
318 array_keys( $wp_filter )
319 );
320
321 // Show notice for each deprecated filter or action that has been registered.
322 foreach ( $deprecated_notices as $deprecated_filter ) {
323 $deprecation_info = $deprecated_filters[ $deprecated_filter ];
324 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Only uses the hardcoded values from above.
325 _deprecated_hook(
326 $deprecated_filter,
327 'WPSEO ' . $deprecation_info['version'],
328 $deprecation_info['alternative']
329 );
330 // phpcs:enable
331 }
332 }
333
334 /**
335 * Check if the permalink uses %postname%.
336 *
337 * @return bool
338 */
339 private function has_postname_in_permalink() {
340 return ( strpos( get_option( 'permalink_structure' ), '%postname%' ) !== false );
341 }
342
343 /**
344 * Shows a notice on the permalink settings page.
345 */
346 public function permalink_settings_notice() {
347 global $pagenow;
348
349 if ( $pagenow === 'options-permalink.php' ) {
350 printf(
351 '<div class="notice notice-warning"><p><strong>%1$s</strong><br>%2$s<br><a href="%3$s" target="_blank">%4$s</a></p></div>',
352 esc_html__( 'WARNING:', 'wordpress-seo' ),
353 sprintf(
354 /* translators: %1$s and %2$s expand to <em> items to emphasize the word in the middle. */
355 esc_html__( 'Changing your permalinks settings can seriously impact your search engine visibility. It should almost %1$s never %2$s be done on a live website.', 'wordpress-seo' ),
356 '<em>',
357 '</em>'
358 ),
359 esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/why-permalinks/' ) ),
360 // The link's content.
361 esc_html__( 'Learn about why permalinks are important for SEO.', 'wordpress-seo' )
362 );
363 }
364 }
365
366 /**
367 * Determines whether and where the "search engines discouraged" admin notice should be displayed.
368 *
369 * @return bool Whether the "search engines discouraged" admin notice should be displayed.
370 */
371 private function should_display_search_engines_discouraged_notice() {
372 $discouraged_pages = [
373 'index.php',
374 'plugins.php',
375 'update-core.php',
376 ];
377
378 return (
379 $this->are_search_engines_discouraged()
380 && WPSEO_Capability_Utils::current_user_can( 'manage_options' )
381 && WPSEO_Options::get( 'ignore_search_engines_discouraged_notice', false ) === false
382 && (
383 $this->on_wpseo_admin_page()
384 || in_array( $this->pagenow, $discouraged_pages, true )
385 )
386 );
387 }
388
389 /**
390 * Displays an admin notice when WordPress is set to discourage search engines from indexing the site.
391 *
392 * @return void
393 */
394 public function search_engines_discouraged_notice() {
395 if ( ! $this->should_display_search_engines_discouraged_notice() ) {
396 return;
397 }
398
399 printf(
400 '<div id="robotsmessage" class="notice notice-error"><p><strong>%1$s</strong> %2$s <button type="button" id="robotsmessage-dismiss-button" class="button-link hide-if-no-js" data-nonce="%3$s">%4$s</button></p></div>',
401 esc_html__( 'Huge SEO Issue: You\'re blocking access to robots.', 'wordpress-seo' ),
402 sprintf(
403 /* translators: 1: Link start tag to the WordPress Reading Settings page, 2: Link closing tag. */
404 esc_html__( 'If you want search engines to show this site in their results, you must %1$sgo to your Reading Settings%2$s and uncheck the box for Search Engine Visibility.', 'wordpress-seo' ),
405 '<a href="' . esc_url( admin_url( 'options-reading.php' ) ) . '">',
406 '</a>'
407 ),
408 esc_js( wp_create_nonce( 'wpseo-ignore' ) ),
409 esc_html__( 'I don\'t want this site to show in the search results.', 'wordpress-seo' )
410 );
411 }
412
413 /**
414 * Adds a custom Yoast section within the Classic Editor publish box.
415 *
416 * @param \WP_Post $post The current post object.
417 *
418 * @return void
419 */
420 public function add_publish_box_section( $post ) {
421 if ( in_array( $this->pagenow, [ 'post.php', 'post-new.php' ], true ) ) {
422 ?>
423 <div id="yoast-seo-publishbox-section"></div>
424 <?php
425 /**
426 * Fires after the post time/date setting in the Publish meta box.
427 *
428 * @api \WP_Post The current post object.
429 */
430 do_action( 'wpseo_publishbox_misc_actions', $post );
431 }
432 }
433
434 /* ********************* DEPRECATED METHODS ********************* */
435
436 /**
437 * Notify about the default tagline if the user hasn't changed it.
438 *
439 * @deprecated 13.2
440 * @codeCoverageIgnore
441 */
442 public function tagline_notice() {
443 _deprecated_function( __METHOD__, 'WPSEO 13.2' );
444 }
445
446 /**
447 * Returns whether or not the site has the default tagline.
448 *
449 * @deprecated 13.2
450 * @codeCoverageIgnore
451 *
452 * @return bool
453 */
454 public function has_default_tagline() {
455 _deprecated_function( __METHOD__, 'WPSEO 13.2' );
456
457 $blog_description = get_bloginfo( 'description' );
458 $default_blog_description = 'Just another WordPress site';
459
460 // We are using the WordPress internal translation.
461 $translated_blog_description = __( 'Just another WordPress site', 'default' );
462
463 return $translated_blog_description === $blog_description || $default_blog_description === $blog_description;
464 }
465
466 /**
467 * Shows an alert when the permalink doesn't contain %postname%.
468 *
469 * @deprecated 13.2
470 * @codeCoverageIgnore
471 */
472 public function permalink_notice() {
473 _deprecated_function( __METHOD__, 'WPSEO 13.2' );
474 }
475
476 /**
477 * Add an alert if the blog is not publicly visible.
478 *
479 * @deprecated 14.1
480 * @codeCoverageIgnore
481 */
482 public function blog_public_notice() {
483 _deprecated_function( __METHOD__, 'WPSEO 14.1' );
484 }
485
486 /**
487 * Handles the notifiers for the dashboard page.
488 *
489 * @deprecated 14.1
490 * @codeCoverageIgnore
491 *
492 * @return void
493 */
494 public function handle_notifications() {
495 _deprecated_function( __METHOD__, 'WPSEO 14.1' );
496 }
497 }
498