PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 19.0
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v19.0
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 / wp-seo-main.php

wp-seo-main.php in Yoast SEO – Advanced SEO with real-time guidance and built-in AI 19.0, at wp-seo-main.php

650 lines 18.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\Main
6 */
7
8 use Yoast\WP\SEO\Helpers\Options_Helper;
9 use Yoast\WP\SEO\Integrations\Admin\Ryte_Integration;
10
11 if ( ! function_exists( 'add_filter' ) ) {
12 header( 'Status: 403 Forbidden' );
13 header( 'HTTP/1.1 403 Forbidden' );
14 exit();
15 }
16
17 /**
18 * {@internal Nobody should be able to overrule the real version number as this can cause
19 * serious issues with the options, so no if ( ! defined() ).}}
20 */
21 define( 'WPSEO_VERSION', '19.0' );
22
23
24 if ( ! defined( 'WPSEO_PATH' ) ) {
25 define( 'WPSEO_PATH', plugin_dir_path( WPSEO_FILE ) );
26 }
27
28 if ( ! defined( 'WPSEO_BASENAME' ) ) {
29 define( 'WPSEO_BASENAME', plugin_basename( WPSEO_FILE ) );
30 }
31
32 /*
33 * {@internal The prefix constants are used to build prefixed versions of dependencies.
34 * These should not be changed on run-time, thus missing the ! defined() check.}}
35 */
36 define( 'YOAST_VENDOR_NS_PREFIX', 'YoastSEO_Vendor' );
37 define( 'YOAST_VENDOR_DEFINE_PREFIX', 'YOASTSEO_VENDOR__' );
38 define( 'YOAST_VENDOR_PREFIX_DIRECTORY', 'vendor_prefixed' );
39
40 define( 'YOAST_SEO_PHP_REQUIRED', '5.6' );
41 define( 'YOAST_SEO_WP_TESTED', '6.0' );
42 define( 'YOAST_SEO_WP_REQUIRED', '5.8' );
43
44 if ( ! defined( 'WPSEO_NAMESPACES' ) ) {
45 define( 'WPSEO_NAMESPACES', true );
46 }
47
48
49 /* ***************************** CLASS AUTOLOADING *************************** */
50
51 /**
52 * Autoload our class files.
53 *
54 * @param string $class_name Class name.
55 *
56 * @return void
57 */
58 function wpseo_auto_load( $class_name ) {
59 static $classes = null;
60
61 if ( $classes === null ) {
62 $classes = [
63 'wp_list_table' => ABSPATH . 'wp-admin/includes/class-wp-list-table.php',
64 'walker_category' => ABSPATH . 'wp-includes/category-template.php',
65 ];
66 }
67
68 $cn = strtolower( $class_name );
69
70 if ( ! class_exists( $class_name ) && isset( $classes[ $cn ] ) ) {
71 require_once $classes[ $cn ];
72 }
73 }
74
75 $yoast_autoload_file = WPSEO_PATH . 'vendor/autoload.php';
76
77 if ( is_readable( $yoast_autoload_file ) ) {
78 $yoast_autoloader = require $yoast_autoload_file;
79 }
80 elseif ( ! class_exists( 'WPSEO_Options' ) ) { // Still checking since might be site-level autoload R.
81 add_action( 'admin_init', 'yoast_wpseo_missing_autoload', 1 );
82
83 return;
84 }
85
86 if ( function_exists( 'spl_autoload_register' ) ) {
87 spl_autoload_register( 'wpseo_auto_load' );
88 }
89 require_once WPSEO_PATH . 'src/functions.php';
90
91 /* ********************* DEFINES DEPENDING ON AUTOLOADED CODE ********************* */
92
93 /**
94 * Defaults to production, for safety.
95 */
96 if ( ! defined( 'YOAST_ENVIRONMENT' ) ) {
97 define( 'YOAST_ENVIRONMENT', 'production' );
98 }
99
100 if ( YOAST_ENVIRONMENT === 'development' && isset( $yoast_autoloader ) ) {
101 add_action(
102 'plugins_loaded',
103 /**
104 * Reregisters the autoloader so that Yoast SEO is at the front.
105 * This prevents conflicts with the development versions of our addons.
106 * An anonymous function is used so we can use the autoloader variable.
107 * As this is only loaded in development removing this action is not a concern.
108 *
109 * @return void
110 */
111 static function() use ( $yoast_autoloader ) {
112 $yoast_autoloader->unregister();
113 $yoast_autoloader->register( true );
114 },
115 1
116 );
117 }
118
119 /**
120 * Only use minified assets when we are in a production environment.
121 */
122 if ( ! defined( 'WPSEO_CSSJS_SUFFIX' ) ) {
123 define( 'WPSEO_CSSJS_SUFFIX', ( YOAST_ENVIRONMENT !== 'development' ) ? '.min' : '' );
124 }
125
126 /* ***************************** PLUGIN (DE-)ACTIVATION *************************** */
127
128 /**
129 * Run single site / network-wide activation of the plugin.
130 *
131 * @param bool $networkwide Whether the plugin is being activated network-wide.
132 */
133 function wpseo_activate( $networkwide = false ) {
134 if ( ! is_multisite() || ! $networkwide ) {
135 _wpseo_activate();
136 }
137 else {
138 /* Multi-site network activation - activate the plugin for all blogs. */
139 wpseo_network_activate_deactivate( true );
140 }
141
142 // This is done so that the 'uninstall_{$file}' is triggered.
143 register_uninstall_hook( WPSEO_FILE, '__return_false' );
144 }
145
146 /**
147 * Run single site / network-wide de-activation of the plugin.
148 *
149 * @param bool $networkwide Whether the plugin is being de-activated network-wide.
150 */
151 function wpseo_deactivate( $networkwide = false ) {
152 if ( ! is_multisite() || ! $networkwide ) {
153 _wpseo_deactivate();
154 }
155 else {
156 /* Multi-site network activation - de-activate the plugin for all blogs. */
157 wpseo_network_activate_deactivate( false );
158 }
159 }
160
161 /**
162 * Run network-wide (de-)activation of the plugin.
163 *
164 * @param bool $activate True for plugin activation, false for de-activation.
165 */
166 function wpseo_network_activate_deactivate( $activate = true ) {
167 global $wpdb;
168
169 $network_blogs = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = %d", $wpdb->siteid ) );
170
171 if ( is_array( $network_blogs ) && $network_blogs !== [] ) {
172 foreach ( $network_blogs as $blog_id ) {
173 switch_to_blog( $blog_id );
174
175 if ( $activate === true ) {
176 _wpseo_activate();
177 }
178 else {
179 _wpseo_deactivate();
180 }
181
182 restore_current_blog();
183 }
184 }
185 }
186
187 /**
188 * Runs on activation of the plugin.
189 */
190 function _wpseo_activate() {
191 require_once WPSEO_PATH . 'inc/wpseo-functions.php';
192 require_once WPSEO_PATH . 'inc/class-wpseo-installation.php';
193
194 wpseo_load_textdomain(); // Make sure we have our translations available for the defaults.
195
196 new WPSEO_Installation();
197
198 WPSEO_Options::get_instance();
199 if ( ! is_multisite() ) {
200 WPSEO_Options::initialize();
201 }
202 else {
203 WPSEO_Options::maybe_set_multisite_defaults( true );
204 }
205 WPSEO_Options::ensure_options_exist();
206
207 if ( is_multisite() && ms_is_switched() ) {
208 update_option( 'rewrite_rules', '' );
209 }
210 else {
211 if ( WPSEO_Options::get( 'stripcategorybase' ) === true ) {
212 // Constructor has side effects so this registers all hooks.
213 $GLOBALS['wpseo_rewrite'] = new WPSEO_Rewrite();
214 }
215 add_action( 'shutdown', 'flush_rewrite_rules' );
216 }
217
218 // Reset tracking to be disabled by default.
219 if ( ! YoastSEO()->helpers->product->is_premium() ) {
220 WPSEO_Options::set( 'tracking', false );
221 }
222
223 WPSEO_Options::set( 'indexing_reason', 'first_install' );
224 WPSEO_Options::set( 'first_time_install', true );
225 if ( ! defined( 'WP_CLI' ) || ! WP_CLI ) {
226 WPSEO_Options::set( 'should_redirect_after_install_free', true );
227 }
228 else {
229 WPSEO_Options::set( 'activation_redirect_timestamp_free', \time() );
230 }
231
232 do_action( 'wpseo_register_roles' );
233 WPSEO_Role_Manager_Factory::get()->add();
234
235 do_action( 'wpseo_register_capabilities' );
236 WPSEO_Capability_Manager_Factory::get()->add();
237
238 // Clear cache so the changes are obvious.
239 WPSEO_Utils::clear_cache();
240
241 // Schedule cronjob when it doesn't exists on activation.
242 $wpseo_ryte = YoastSEO()->classes->get( Ryte_Integration::class );
243 $wpseo_ryte->activate_hooks();
244
245 do_action( 'wpseo_activate' );
246 }
247
248 /**
249 * On deactivation, flush the rewrite rules so XML sitemaps stop working.
250 */
251 function _wpseo_deactivate() {
252 require_once WPSEO_PATH . 'inc/wpseo-functions.php';
253
254 if ( is_multisite() && ms_is_switched() ) {
255 update_option( 'rewrite_rules', '' );
256 }
257 else {
258 add_action( 'shutdown', 'flush_rewrite_rules' );
259 }
260
261 // Register capabilities, to make sure they are cleaned up.
262 do_action( 'wpseo_register_roles' );
263 do_action( 'wpseo_register_capabilities' );
264
265 // Clean up capabilities.
266 WPSEO_Role_Manager_Factory::get()->remove();
267 WPSEO_Capability_Manager_Factory::get()->remove();
268
269 // Clear cache so the changes are obvious.
270 WPSEO_Utils::clear_cache();
271
272 do_action( 'wpseo_deactivate' );
273 }
274
275 /**
276 * Run wpseo activation routine on creation / activation of a multisite blog if WPSEO is activated
277 * network-wide.
278 *
279 * Will only be called by multisite actions.
280 *
281 * {@internal Unfortunately will fail if the plugin is in the must-use directory.
282 * {@link https://core.trac.wordpress.org/ticket/24205} }}
283 *
284 * @param int|WP_Site $blog_id Blog ID.
285 */
286 function wpseo_on_activate_blog( $blog_id ) {
287 if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
288 require_once ABSPATH . 'wp-admin/includes/plugin.php';
289 }
290
291 if ( $blog_id instanceof WP_Site ) {
292 $blog_id = (int) $blog_id->blog_id;
293 }
294
295 if ( is_plugin_active_for_network( WPSEO_BASENAME ) ) {
296 switch_to_blog( $blog_id );
297 wpseo_activate( false );
298 restore_current_blog();
299 }
300 }
301
302 /* ***************************** PLUGIN LOADING *************************** */
303
304 /**
305 * Load translations.
306 */
307 function wpseo_load_textdomain() {
308 $wpseo_path = str_replace( '\\', '/', WPSEO_PATH );
309 $mu_path = str_replace( '\\', '/', WPMU_PLUGIN_DIR );
310
311 if ( stripos( $wpseo_path, $mu_path ) !== false ) {
312 load_muplugin_textdomain( 'wordpress-seo', dirname( WPSEO_BASENAME ) . '/languages/' );
313 }
314 else {
315 load_plugin_textdomain( 'wordpress-seo', false, dirname( WPSEO_BASENAME ) . '/languages/' );
316 }
317 }
318
319 add_action( 'plugins_loaded', 'wpseo_load_textdomain' );
320
321
322 /**
323 * On plugins_loaded: load the minimum amount of essential files for this plugin.
324 */
325 function wpseo_init() {
326 require_once WPSEO_PATH . 'inc/wpseo-functions.php';
327 require_once WPSEO_PATH . 'inc/wpseo-functions-deprecated.php';
328
329 // Make sure our option and meta value validation routines and default values are always registered and available.
330 WPSEO_Options::get_instance();
331 WPSEO_Meta::init();
332
333 if ( version_compare( WPSEO_Options::get( 'version', 1 ), WPSEO_VERSION, '<' ) ) {
334 if ( function_exists( 'opcache_reset' ) ) {
335 // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Prevent notices when opcache.restrict_api is set.
336 @opcache_reset();
337 }
338
339 new WPSEO_Upgrade();
340 // Get a cleaned up version of the $options.
341 }
342
343 if ( WPSEO_Options::get( 'stripcategorybase' ) === true ) {
344 $GLOBALS['wpseo_rewrite'] = new WPSEO_Rewrite();
345 }
346
347 if ( WPSEO_Options::get( 'enable_xml_sitemap' ) === true ) {
348 $GLOBALS['wpseo_sitemaps'] = new WPSEO_Sitemaps();
349 }
350
351 if ( ! wp_doing_ajax() ) {
352 require_once WPSEO_PATH . 'inc/wpseo-non-ajax-functions.php';
353 }
354
355 // Init it here because the filter must be present on the frontend as well or it won't work in the customizer.
356 new WPSEO_Customizer();
357
358 $integrations = [];
359 $integrations[] = new WPSEO_Slug_Change_Watcher();
360
361 foreach ( $integrations as $integration ) {
362 $integration->register_hooks();
363 }
364 }
365
366 /**
367 * Loads the rest api endpoints.
368 */
369 function wpseo_init_rest_api() {
370 // We can't do anything when requirements are not met.
371 if ( ! WPSEO_Utils::is_api_available() ) {
372 return;
373 }
374
375 // Boot up REST API.
376 $statistics_service = new WPSEO_Statistics_Service( new WPSEO_Statistics() );
377
378 $endpoints = [];
379 $endpoints[] = new WPSEO_Endpoint_File_Size( new WPSEO_File_Size_Service() );
380 $endpoints[] = new WPSEO_Endpoint_Statistics( $statistics_service );
381
382 foreach ( $endpoints as $endpoint ) {
383 $endpoint->register();
384 }
385 }
386
387 /**
388 * Used to load the required files on the plugins_loaded hook, instead of immediately.
389 */
390 function wpseo_admin_init() {
391 new WPSEO_Admin_Init();
392 }
393
394 /**
395 * Initialize the WP-CLI integration.
396 *
397 * The WP-CLI integration needs PHP 5.3 support, which should be automatically
398 * enforced by the check for the WP_CLI constant. As WP-CLI itself only runs
399 * on PHP 5.3+, the constant should only be set when requirements are met.
400 */
401 function wpseo_cli_init() {
402 if ( YoastSEO()->helpers->product->is_premium() ) {
403 WP_CLI::add_command(
404 'yoast redirect list',
405 'WPSEO_CLI_Redirect_List_Command',
406 [ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
407 );
408
409 WP_CLI::add_command(
410 'yoast redirect create',
411 'WPSEO_CLI_Redirect_Create_Command',
412 [ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
413 );
414
415 WP_CLI::add_command(
416 'yoast redirect update',
417 'WPSEO_CLI_Redirect_Update_Command',
418 [ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
419 );
420
421 WP_CLI::add_command(
422 'yoast redirect delete',
423 'WPSEO_CLI_Redirect_Delete_Command',
424 [ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
425 );
426
427 WP_CLI::add_command(
428 'yoast redirect has',
429 'WPSEO_CLI_Redirect_Has_Command',
430 [ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
431 );
432
433 WP_CLI::add_command(
434 'yoast redirect follow',
435 'WPSEO_CLI_Redirect_Follow_Command',
436 [ 'before_invoke' => 'WPSEO_CLI_Premium_Requirement::enforce' ]
437 );
438 }
439 }
440
441 /* ***************************** BOOTSTRAP / HOOK INTO WP *************************** */
442 $spl_autoload_exists = function_exists( 'spl_autoload_register' );
443 $filter_exists = function_exists( 'filter_input' );
444
445 if ( ! $spl_autoload_exists ) {
446 add_action( 'admin_init', 'yoast_wpseo_missing_spl', 1 );
447 }
448
449 if ( ! $filter_exists ) {
450 add_action( 'admin_init', 'yoast_wpseo_missing_filter', 1 );
451 }
452
453 if ( ! wp_installing() && ( $spl_autoload_exists && $filter_exists ) ) {
454 add_action( 'plugins_loaded', 'wpseo_init', 14 );
455 add_action( 'rest_api_init', 'wpseo_init_rest_api' );
456
457 if ( is_admin() ) {
458
459 new Yoast_Notifications();
460
461 $yoast_addon_manager = new WPSEO_Addon_Manager();
462 $yoast_addon_manager->register_hooks();
463
464 if ( wp_doing_ajax() ) {
465 require_once WPSEO_PATH . 'admin/ajax.php';
466
467 // Plugin conflict ajax hooks.
468 new Yoast_Plugin_Conflict_Ajax();
469
470 if ( filter_input( INPUT_POST, 'action' ) === 'inline-save' ) {
471 add_action( 'plugins_loaded', 'wpseo_admin_init', 15 );
472 }
473 }
474 else {
475 add_action( 'plugins_loaded', 'wpseo_admin_init', 15 );
476 }
477 }
478
479 add_action( 'plugins_loaded', 'load_yoast_notifications' );
480
481 if ( defined( 'WP_CLI' ) && WP_CLI ) {
482 add_action( 'plugins_loaded', 'wpseo_cli_init', 20 );
483 }
484
485 add_action( 'init', [ 'WPSEO_Replace_Vars', 'setup_statics_once' ] );
486
487 // Initializes the Yoast indexables for the first time.
488 YoastSEO();
489
490 /**
491 * Action called when the Yoast SEO plugin file has loaded.
492 */
493 do_action( 'wpseo_loaded' );
494 }
495
496 // Activation and deactivation hook.
497 register_activation_hook( WPSEO_FILE, 'wpseo_activate' );
498 register_deactivation_hook( WPSEO_FILE, 'wpseo_deactivate' );
499
500 // Wpmu_new_blog has been deprecated in 5.1 and replaced by wp_insert_site.
501 global $wp_version;
502 if ( version_compare( $wp_version, '5.1', '<' ) ) {
503 add_action( 'wpmu_new_blog', 'wpseo_on_activate_blog' );
504 }
505 else {
506 add_action( 'wp_initialize_site', 'wpseo_on_activate_blog', 99 );
507 }
508
509 add_action( 'activate_blog', 'wpseo_on_activate_blog' );
510
511 // Registers SEO capabilities.
512 $wpseo_register_capabilities = new WPSEO_Register_Capabilities();
513 $wpseo_register_capabilities->register_hooks();
514
515 // Registers SEO roles.
516 $wpseo_register_capabilities = new WPSEO_Register_Roles();
517 $wpseo_register_capabilities->register_hooks();
518
519 /**
520 * Wraps for notifications center class.
521 */
522 function load_yoast_notifications() {
523 // Init Yoast_Notification_Center class.
524 Yoast_Notification_Center::get();
525 }
526
527
528 /**
529 * Throw an error if the PHP SPL extension is disabled (prevent white screens) and self-deactivate plugin.
530 *
531 * @since 1.5.4
532 *
533 * @return void
534 */
535 function yoast_wpseo_missing_spl() {
536 if ( is_admin() ) {
537 add_action( 'admin_notices', 'yoast_wpseo_missing_spl_notice' );
538
539 yoast_wpseo_self_deactivate();
540 }
541 }
542
543 /**
544 * Returns the notice in case of missing spl extension.
545 */
546 function yoast_wpseo_missing_spl_notice() {
547 $message = esc_html__( 'The Standard PHP Library (SPL) extension seem to be unavailable. Please ask your web host to enable it.', 'wordpress-seo' );
548 yoast_wpseo_activation_failed_notice( $message );
549 }
550
551 /**
552 * Throw an error if the Composer autoload is missing and self-deactivate plugin.
553 *
554 * @return void
555 */
556 function yoast_wpseo_missing_autoload() {
557 if ( is_admin() ) {
558 add_action( 'admin_notices', 'yoast_wpseo_missing_autoload_notice' );
559
560 yoast_wpseo_self_deactivate();
561 }
562 }
563
564 /**
565 * Returns the notice in case of missing Composer autoload.
566 */
567 function yoast_wpseo_missing_autoload_notice() {
568 /* translators: %1$s expands to Yoast SEO, %2$s / %3$s: links to the installation manual in the Readme for the Yoast SEO code repository on GitHub */
569 $message = esc_html__( 'The %1$s plugin installation is incomplete. Please refer to %2$sinstallation instructions%3$s.', 'wordpress-seo' );
570 $message = sprintf( $message, 'Yoast SEO', '<a href="https://github.com/Yoast/wordpress-seo#installation">', '</a>' );
571 yoast_wpseo_activation_failed_notice( $message );
572 }
573
574 /**
575 * Throw an error if the filter extension is disabled (prevent white screens) and self-deactivate plugin.
576 *
577 * @since 2.0
578 *
579 * @return void
580 */
581 function yoast_wpseo_missing_filter() {
582 if ( is_admin() ) {
583 add_action( 'admin_notices', 'yoast_wpseo_missing_filter_notice' );
584
585 yoast_wpseo_self_deactivate();
586 }
587 }
588
589 /**
590 * Returns the notice in case of missing filter extension.
591 */
592 function yoast_wpseo_missing_filter_notice() {
593 $message = esc_html__( 'The filter extension seem to be unavailable. Please ask your web host to enable it.', 'wordpress-seo' );
594 yoast_wpseo_activation_failed_notice( $message );
595 }
596
597 /**
598 * Echo's the Activation failed notice with any given message.
599 *
600 * @param string $message Message string.
601 */
602 function yoast_wpseo_activation_failed_notice( $message ) {
603 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- This function is only called in 3 places that are safe.
604 echo '<div class="error"><p>' . esc_html__( 'Activation failed:', 'wordpress-seo' ) . ' ' . strip_tags( $message, '<a>' ) . '</p></div>';
605 }
606
607 /**
608 * The method will deactivate the plugin, but only once, done by the static $is_deactivated.
609 */
610 function yoast_wpseo_self_deactivate() {
611 static $is_deactivated;
612
613 if ( $is_deactivated === null ) {
614 $is_deactivated = true;
615 deactivate_plugins( WPSEO_BASENAME );
616 if ( isset( $_GET['activate'] ) ) {
617 unset( $_GET['activate'] );
618 }
619 }
620 }
621
622 /* ********************* DEPRECATED METHODS ********************* */
623
624 /**
625 * Instantiate the different social classes on the frontend.
626 *
627 * @deprecated 14.0
628 * @codeCoverageIgnore
629 */
630 function wpseo_frontend_head_init() {
631 _deprecated_function( __METHOD__, 'WPSEO 14.0' );
632 }
633
634 /**
635 * Used to load the required files on the plugins_loaded hook, instead of immediately.
636 *
637 * @deprecated 14.0
638 * @codeCoverageIgnore
639 */
640 function wpseo_frontend_init() {
641 _deprecated_function( __METHOD__, 'WPSEO 14.0' );
642 }
643
644 /**
645 * Aliasses added in order to keep compatibility with Yoast SEO: Local.
646 */
647 class_alias( '\Yoast\WP\SEO\Initializers\Initializer_Interface', '\Yoast\WP\SEO\WordPress\Initializer' );
648 class_alias( '\Yoast\WP\SEO\Loadable_Interface', '\Yoast\WP\SEO\WordPress\Loadable' );
649 class_alias( '\Yoast\WP\SEO\Integrations\Integration_Interface', '\Yoast\WP\SEO\WordPress\Integration' );
650