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