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 / admin / class-admin.php
wordpress-seo / admin Last commit date
ajax 2 years ago capabilities 1 year ago endpoints 2 years ago exceptions 7 years ago filters 1 year ago formatter 1 year ago google_search_console 2 years ago import 2 years ago listeners 8 years ago menu 1 year ago metabox 1 year ago notifiers 3 years ago pages 1 year ago roles 2 years ago services 5 years ago statistics 2 years ago taxonomy 1 year ago tracking 1 year ago views 1 year ago watchers 2 years ago admin-settings-changed-listener.php 2 years ago ajax.php 2 years ago class-admin-asset-analysis-worker-location.php 5 years ago class-admin-asset-dev-server-location.php 2 years ago class-admin-asset-location.php 8 years ago class-admin-asset-manager.php 1 year ago class-admin-asset-seo-location.php 4 years ago class-admin-editor-specific-replace-vars.php 2 years ago class-admin-gutenberg-compatibility-notification.php 2 years ago class-admin-help-panel.php 5 years ago class-admin-init.php 1 year ago class-admin-recommended-replace-vars.php 2 years ago class-admin-user-profile.php 1 year ago class-admin-utils.php 2 years ago class-admin.php 1 year ago class-asset.php 1 year ago class-bulk-description-editor-list-table.php 5 years ago class-bulk-editor-list-table.php 2 years ago class-bulk-title-editor-list-table.php 6 years ago class-collector.php 2 years ago class-config.php 1 year ago class-database-proxy.php 2 years ago class-export.php 2 years ago class-expose-shortlinks.php 1 year ago class-gutenberg-compatibility.php 1 year ago class-meta-columns.php 1 year ago class-my-yoast-proxy.php 2 years ago class-option-tab.php 4 years ago class-option-tabs-formatter.php 2 years ago class-option-tabs.php 2 years ago class-paper-presenter.php 5 years ago class-plugin-availability.php 1 year ago class-plugin-conflict.php 2 years ago class-premium-popup.php 2 years ago class-premium-upsell-admin-block.php 1 year ago class-primary-term-admin.php 2 years ago class-product-upsell-notice.php 2 years ago class-remote-request.php 2 years ago class-schema-person-upgrade-notification.php 2 years ago class-suggested-plugins.php 2 years ago class-wincher-dashboard-widget.php 2 years ago class-yoast-columns.php 2 years ago class-yoast-dashboard-widget.php 2 years ago class-yoast-form.php 1 year ago class-yoast-input-validation.php 1 year ago class-yoast-network-admin.php 2 years ago class-yoast-network-settings-api.php 4 years ago class-yoast-notification-center.php 1 year ago class-yoast-notification.php 1 year ago class-yoast-notifications.php 2 years ago class-yoast-plugin-conflict.php 2 years ago index.php 10 years ago interface-collection.php 7 years ago interface-installable.php 8 years ago
class-admin.php
392 lines
1 <?php
2 /**
3 * WPSEO plugin file.
4 *
5 * @package WPSEO\Admin
6 */
7
8 use Yoast\WP\SEO\Integrations\Settings_Integration;
9
10 /**
11 * Class that holds most of the admin functionality for Yoast SEO.
12 */
13 class WPSEO_Admin {
14
15 /**
16 * The page identifier used in WordPress to register the admin page.
17 *
18 * !DO NOT CHANGE THIS!
19 *
20 * @var string
21 */
22 public const PAGE_IDENTIFIER = 'wpseo_dashboard';
23
24 /**
25 * Array of classes that add admin functionality.
26 *
27 * @var array
28 */
29 protected $admin_features;
30
31 /**
32 * Class constructor.
33 */
34 public function __construct() {
35 $integrations = [];
36
37 global $pagenow;
38
39 $wpseo_menu = new WPSEO_Menu();
40 $wpseo_menu->register_hooks();
41
42 if ( is_multisite() ) {
43 WPSEO_Options::maybe_set_multisite_defaults( false );
44 }
45
46 add_action( 'created_category', [ $this, 'schedule_rewrite_flush' ] );
47 add_action( 'edited_category', [ $this, 'schedule_rewrite_flush' ] );
48 add_action( 'delete_category', [ $this, 'schedule_rewrite_flush' ] );
49
50 add_filter( 'wpseo_accessible_post_types', [ 'WPSEO_Post_Type', 'filter_attachment_post_type' ] );
51
52 add_filter( 'plugin_action_links_' . WPSEO_BASENAME, [ $this, 'add_action_link' ], 10, 2 );
53 add_filter( 'network_admin_plugin_action_links_' . WPSEO_BASENAME, [ $this, 'add_action_link' ], 10, 2 );
54
55 add_action( 'admin_enqueue_scripts', [ $this, 'config_page_scripts' ] );
56 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_global_style' ] );
57
58 add_action( 'after_switch_theme', [ $this, 'switch_theme' ] );
59 add_action( 'switch_theme', [ $this, 'switch_theme' ] );
60
61 add_filter( 'set-screen-option', [ $this, 'save_bulk_edit_options' ], 10, 3 );
62
63 add_action( 'admin_init', [ 'WPSEO_Plugin_Conflict', 'hook_check_for_plugin_conflicts' ], 10, 1 );
64
65 add_action( 'admin_init', [ $this, 'map_manage_options_cap' ] );
66
67 WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'wpseo' );
68 WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'home' );
69
70 if ( YoastSEO()->helpers->current_page->is_yoast_seo_page() ) {
71 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
72 }
73
74 $this->initialize_cornerstone_content();
75
76 if ( WPSEO_Utils::is_plugin_network_active() ) {
77 $integrations[] = new Yoast_Network_Admin();
78 }
79
80 $this->admin_features = [
81 'dashboard_widget' => new Yoast_Dashboard_Widget(),
82 'wincher_dashboard_widget' => new Wincher_Dashboard_Widget(),
83 ];
84
85 if ( WPSEO_Metabox::is_post_overview( $pagenow ) || WPSEO_Metabox::is_post_edit( $pagenow ) ) {
86 $this->admin_features['primary_category'] = new WPSEO_Primary_Term_Admin();
87 }
88
89 $integrations[] = new WPSEO_Yoast_Columns();
90 $integrations[] = new WPSEO_Statistic_Integration();
91 $integrations[] = new WPSEO_Capability_Manager_Integration( WPSEO_Capability_Manager_Factory::get() );
92 $integrations[] = new WPSEO_Admin_Gutenberg_Compatibility_Notification();
93 $integrations[] = new WPSEO_Expose_Shortlinks();
94 $integrations[] = new WPSEO_MyYoast_Proxy();
95 $integrations[] = new WPSEO_Schema_Person_Upgrade_Notification();
96 $integrations[] = new WPSEO_Tracking( 'https://tracking.yoast.com/stats', ( WEEK_IN_SECONDS * 2 ) );
97 $integrations[] = new WPSEO_Admin_Settings_Changed_Listener();
98
99 $integrations = array_merge(
100 $integrations,
101 $this->get_admin_features(),
102 $this->initialize_cornerstone_content()
103 );
104
105 foreach ( $integrations as $integration ) {
106 $integration->register_hooks();
107 }
108 }
109
110 /**
111 * Schedules a rewrite flush to happen at shutdown.
112 *
113 * @return void
114 */
115 public function schedule_rewrite_flush() {
116 if ( WPSEO_Options::get( 'stripcategorybase' ) !== true ) {
117 return;
118 }
119
120 // Bail if this is a multisite installation and the site has been switched.
121 if ( is_multisite() && ms_is_switched() ) {
122 return;
123 }
124
125 add_action( 'shutdown', 'flush_rewrite_rules' );
126 }
127
128 /**
129 * Returns all the classes for the admin features.
130 *
131 * @return array
132 */
133 public function get_admin_features() {
134 return $this->admin_features;
135 }
136
137 /**
138 * Register assets needed on admin pages.
139 *
140 * @return void
141 */
142 public function enqueue_assets() {
143 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form data.
144 $page = isset( $_GET['page'] ) && is_string( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
145 if ( $page === 'wpseo_licenses' ) {
146 $asset_manager = new WPSEO_Admin_Asset_Manager();
147 $asset_manager->enqueue_style( 'extensions' );
148 }
149 }
150
151 /**
152 * Returns the manage_options capability.
153 *
154 * @return string The capability to use.
155 */
156 public function get_manage_options_cap() {
157 /**
158 * Filter: 'wpseo_manage_options_capability' - Allow changing the capability users need to view the settings pages.
159 *
160 * @param string $capability The capability.
161 */
162 return apply_filters( 'wpseo_manage_options_capability', 'wpseo_manage_options' );
163 }
164
165 /**
166 * Maps the manage_options cap on saving an options page to wpseo_manage_options.
167 *
168 * @return void
169 */
170 public function map_manage_options_cap() {
171 // phpcs:ignore WordPress.Security -- The variable is only used in strpos and thus safe to not unslash or sanitize.
172 $option_page = ! empty( $_POST['option_page'] ) ? $_POST['option_page'] : '';
173
174 if ( strpos( $option_page, 'yoast_wpseo' ) === 0 || strpos( $option_page, Settings_Integration::PAGE ) === 0 ) {
175 add_filter( 'option_page_capability_' . $option_page, [ $this, 'get_manage_options_cap' ] );
176 }
177 }
178
179 /**
180 * Adds the ability to choose how many posts are displayed per page
181 * on the bulk edit pages.
182 *
183 * @return void
184 */
185 public function bulk_edit_options() {
186 $option = 'per_page';
187 $args = [
188 'label' => __( 'Posts', 'wordpress-seo' ),
189 'default' => 10,
190 'option' => 'wpseo_posts_per_page',
191 ];
192 add_screen_option( $option, $args );
193 }
194
195 /**
196 * Saves the posts per page limit for bulk edit pages.
197 *
198 * @param int $status Status value to pass through.
199 * @param string $option Option name.
200 * @param int $value Count value to check.
201 *
202 * @return int
203 */
204 public function save_bulk_edit_options( $status, $option, $value ) {
205 if ( $option && ( $value > 0 && $value < 1000 ) === 'wpseo_posts_per_page' ) {
206 return $value;
207 }
208
209 return $status;
210 }
211
212 /**
213 * Adds links to Premium Support and FAQ under the plugin in the plugin overview page.
214 *
215 * @param array $links Array of links for the plugins, adapted when the current plugin is found.
216 * @param string $file The filename for the current plugin, which the filter loops through.
217 *
218 * @return array
219 */
220 public function add_action_link( $links, $file ) {
221 $first_time_configuration_notice_helper = YoastSEO()->helpers->first_time_configuration_notice;
222
223 if ( $file === WPSEO_BASENAME && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
224 if ( is_network_admin() ) {
225 $settings_url = network_admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER );
226 }
227 else {
228 $settings_url = admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER );
229 }
230 $settings_link = '<a href="' . esc_url( $settings_url ) . '">' . __( 'Settings', 'wordpress-seo' ) . '</a>';
231 array_unshift( $links, $settings_link );
232 }
233
234 // Add link to docs.
235 $faq_link = '<a href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/1yc' ) ) . '" target="_blank">' . __( 'FAQ', 'wordpress-seo' ) . '</a>';
236 array_unshift( $links, $faq_link );
237
238 if ( $first_time_configuration_notice_helper->first_time_configuration_not_finished() && ! is_network_admin() ) {
239 $configuration_title = ( ! $first_time_configuration_notice_helper->should_show_alternate_message() ) ? 'first-time configuration' : 'SEO configuration';
240 /* translators: CTA to finish the first time configuration. %s: Either first-time SEO configuration or SEO configuration. */
241 $message = sprintf( __( 'Finish your %s', 'wordpress-seo' ), $configuration_title );
242 $ftc_page = 'admin.php?page=wpseo_dashboard#/first-time-configuration';
243 $ftc_link = '<a href="' . esc_url( admin_url( $ftc_page ) ) . '" target="_blank">' . $message . '</a>';
244 array_unshift( $links, $ftc_link );
245 }
246
247 $addon_manager = new WPSEO_Addon_Manager();
248 if ( YoastSEO()->helpers->product->is_premium() ) {
249
250 // Remove Free 'deactivate' link if Premium is active as well. We don't want users to deactivate Free when Premium is active.
251 unset( $links['deactivate'] );
252 $no_deactivation_explanation = '<span style="color: #32373c">' . sprintf(
253 /* translators: %s expands to Yoast SEO Premium. */
254 __( 'Required by %s', 'wordpress-seo' ),
255 'Yoast SEO Premium'
256 ) . '</span>';
257
258 array_unshift( $links, $no_deactivation_explanation );
259
260 if ( $addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ) ) {
261 return $links;
262 }
263
264 // Add link to where premium can be activated.
265 $activation_link = '<a style="font-weight: bold;" href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/activate-my-yoast' ) ) . '" target="_blank">' . __( 'Activate your subscription', 'wordpress-seo' ) . '</a>';
266 array_unshift( $links, $activation_link );
267
268 return $links;
269 }
270
271 // Add link to premium landing page.
272 $premium_link = '<a style="font-weight: bold;" href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/1yb' ) ) . '" target="_blank" data-action="load-nfd-ctb" data-ctb-id="f6a84663-465f-4cb5-8ba5-f7a6d72224b2">' . __( 'Get Premium', 'wordpress-seo' ) . '</a>';
273 array_unshift( $links, $premium_link );
274
275 return $links;
276 }
277
278 /**
279 * Enqueues the (tiny) global JS needed for the plugin.
280 *
281 * @return void
282 */
283 public function config_page_scripts() {
284 $asset_manager = new WPSEO_Admin_Asset_Manager();
285 $asset_manager->enqueue_script( 'admin-global' );
286 $asset_manager->localize_script( 'admin-global', 'wpseoAdminGlobalL10n', $this->localize_admin_global_script() );
287 }
288
289 /**
290 * Enqueues the (tiny) global stylesheet needed for the plugin.
291 *
292 * @return void
293 */
294 public function enqueue_global_style() {
295 $asset_manager = new WPSEO_Admin_Asset_Manager();
296 $asset_manager->enqueue_style( 'admin-global' );
297 }
298
299 /**
300 * Filter the $contactmethods array and add a set of social profiles.
301 *
302 * These are used with the Facebook author, rel="author" and Twitter cards implementation.
303 *
304 * @deprecated 22.6
305 * @codeCoverageIgnore
306 *
307 * @param array<string, string> $contactmethods Currently set contactmethods.
308 *
309 * @return array<string, string> Contactmethods with added contactmethods.
310 */
311 public function update_contactmethods( $contactmethods ) {
312 _deprecated_function( __METHOD__, 'Yoast SEO 22.6' );
313
314 $contactmethods['facebook'] = __( 'Facebook profile URL', 'wordpress-seo' );
315 $contactmethods['instagram'] = __( 'Instagram profile URL', 'wordpress-seo' );
316 $contactmethods['linkedin'] = __( 'LinkedIn profile URL', 'wordpress-seo' );
317 $contactmethods['myspace'] = __( 'MySpace profile URL', 'wordpress-seo' );
318 $contactmethods['pinterest'] = __( 'Pinterest profile URL', 'wordpress-seo' );
319 $contactmethods['soundcloud'] = __( 'SoundCloud profile URL', 'wordpress-seo' );
320 $contactmethods['tumblr'] = __( 'Tumblr profile URL', 'wordpress-seo' );
321 $contactmethods['twitter'] = __( 'X username (without @)', 'wordpress-seo' );
322 $contactmethods['youtube'] = __( 'YouTube profile URL', 'wordpress-seo' );
323 $contactmethods['wikipedia'] = __( 'Wikipedia page about you', 'wordpress-seo' ) . '<br/><small>' . __( '(if one exists)', 'wordpress-seo' ) . '</small>';
324
325 return $contactmethods;
326 }
327
328 /**
329 * Log the updated timestamp for user profiles when theme is changed.
330 *
331 * @return void
332 */
333 public function switch_theme() {
334
335 $users = get_users( [ 'capability' => [ 'edit_posts' ] ] );
336
337 if ( is_array( $users ) && $users !== [] ) {
338 foreach ( $users as $user ) {
339 update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', time() );
340 }
341 }
342 }
343
344 /**
345 * Localization for the dismiss urls.
346 *
347 * @return array
348 */
349 private function localize_admin_global_script() {
350 return array_merge(
351 [
352 'isRtl' => is_rtl(),
353 'variable_warning' => sprintf(
354 /* translators: %1$s: '%%term_title%%' variable used in titles and meta's template that's not compatible with the given template, %2$s: expands to 'HelpScout beacon' */
355 __( 'Warning: the variable %1$s cannot be used in this template. See the %2$s for more info.', 'wordpress-seo' ),
356 '<code>%s</code>',
357 'HelpScout beacon'
358 ),
359 /* translators: %s: expends to Yoast SEO */
360 'help_video_iframe_title' => sprintf( __( '%s video tutorial', 'wordpress-seo' ), 'Yoast SEO' ),
361 'scrollable_table_hint' => __( 'Scroll to see the table content.', 'wordpress-seo' ),
362 'wincher_is_logged_in' => WPSEO_Options::get( 'wincher_integration_active', true ) ? YoastSEO()->helpers->wincher->login_status() : false,
363 ],
364 YoastSEO()->helpers->wincher->get_admin_global_links()
365 );
366 }
367
368 /**
369 * Whether we are on the admin dashboard page.
370 *
371 * @return bool
372 */
373 protected function on_dashboard_page() {
374 return $GLOBALS['pagenow'] === 'index.php';
375 }
376
377 /**
378 * Loads the cornerstone filter.
379 *
380 * @return WPSEO_WordPress_Integration[] The integrations to initialize.
381 */
382 protected function initialize_cornerstone_content() {
383 if ( ! WPSEO_Options::get( 'enable_cornerstone_content' ) ) {
384 return [];
385 }
386
387 return [
388 'cornerstone_filter' => new WPSEO_Cornerstone_Filter(),
389 ];
390 }
391 }
392