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