PluginProbe
Yoast SEO – Advanced SEO with real-time guidance and built-in AI / 28.1
Yoast SEO – Advanced SEO with real-time guidance and built-in AI v28.1
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 19.1 All 128 releases
wordpress-seo / admin / class-admin.php

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

386 lines 12.7 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 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 $this->initialize_cornerstone_content();
71
72 if ( WPSEO_Utils::is_plugin_network_active() ) {
73 $integrations[] = new Yoast_Network_Admin();
74 }
75
76 $this->admin_features = [
77 'dashboard_widget' => new Yoast_Dashboard_Widget(),
78 'wincher_dashboard_widget' => new Wincher_Dashboard_Widget(),
79 ];
80
81 if ( WPSEO_Metabox::is_post_overview( $pagenow ) || WPSEO_Metabox::is_post_edit( $pagenow ) ) {
82 $this->admin_features['primary_category'] = new WPSEO_Primary_Term_Admin();
83 }
84
85 $integrations[] = new WPSEO_Yoast_Columns();
86 $integrations[] = new WPSEO_Statistic_Integration();
87 $integrations[] = new WPSEO_Capability_Manager_Integration( WPSEO_Capability_Manager_Factory::get() );
88 $integrations[] = new WPSEO_Admin_Gutenberg_Compatibility_Notification();
89 $integrations[] = new WPSEO_Expose_Shortlinks();
90 $integrations[] = new WPSEO_MyYoast_Proxy();
91 $integrations[] = new WPSEO_Schema_Person_Upgrade_Notification();
92 $integrations[] = new WPSEO_Tracking( 'https://tracking.yoast.com/stats', ( WEEK_IN_SECONDS * 2 ) );
93 $integrations[] = new WPSEO_Admin_Settings_Changed_Listener();
94
95 $integrations = array_merge(
96 $integrations,
97 $this->get_admin_features(),
98 $this->initialize_cornerstone_content(),
99 );
100
101 foreach ( $integrations as $integration ) {
102 $integration->register_hooks();
103 }
104 }
105
106 /**
107 * Schedules a rewrite flush to happen at shutdown.
108 *
109 * @return void
110 */
111 public function schedule_rewrite_flush() {
112 if ( WPSEO_Options::get( 'stripcategorybase' ) !== true ) {
113 return;
114 }
115
116 // Bail if this is a multisite installation and the site has been switched.
117 if ( is_multisite() && ms_is_switched() ) {
118 return;
119 }
120
121 add_action( 'shutdown', 'flush_rewrite_rules' );
122 }
123
124 /**
125 * Returns all the classes for the admin features.
126 *
127 * @return array
128 */
129 public function get_admin_features() {
130 return $this->admin_features;
131 }
132
133 /**
134 * Register assets needed on admin pages.
135 *
136 * @deprecated 25.5
137 * @codeCoverageIgnore
138 *
139 * @return void
140 */
141 public function enqueue_assets() {
142 _deprecated_function( __METHOD__, 'Yoast SEO 25.5' );
143 }
144
145 /**
146 * Returns the manage_options capability.
147 *
148 * @return string The capability to use.
149 */
150 public function get_manage_options_cap() {
151 /**
152 * Filter: 'wpseo_manage_options_capability' - Allow changing the capability users need to view the settings pages.
153 *
154 * @param string $capability The capability.
155 */
156 return apply_filters( 'wpseo_manage_options_capability', 'wpseo_manage_options' );
157 }
158
159 /**
160 * Maps the manage_options cap on saving an options page to wpseo_manage_options.
161 *
162 * @return void
163 */
164 public function map_manage_options_cap() {
165 // phpcs:ignore WordPress.Security -- The variable is only used in strpos and thus safe to not unslash or sanitize.
166 $option_page = ! empty( $_POST['option_page'] ) ? $_POST['option_page'] : '';
167
168 if ( strpos( $option_page, 'yoast_wpseo' ) === 0 || strpos( $option_page, Settings_Integration::PAGE ) === 0 ) {
169 add_filter( 'option_page_capability_' . $option_page, [ $this, 'get_manage_options_cap' ] );
170 }
171 }
172
173 /**
174 * Adds the ability to choose how many posts are displayed per page
175 * on the bulk edit pages.
176 *
177 * @return void
178 */
179 public function bulk_edit_options() {
180 $option = 'per_page';
181 $args = [
182 'label' => __( 'Posts', 'wordpress-seo' ),
183 'default' => 10,
184 'option' => 'wpseo_posts_per_page',
185 ];
186 add_screen_option( $option, $args );
187 }
188
189 /**
190 * Saves the posts per page limit for bulk edit pages.
191 *
192 * @param int $status Status value to pass through.
193 * @param string $option Option name.
194 * @param int $value Count value to check.
195 *
196 * @return int
197 */
198 public function save_bulk_edit_options( $status, $option, $value ) {
199 if ( $option && ( $value > 0 && $value < 1000 ) === 'wpseo_posts_per_page' ) {
200 return $value;
201 }
202
203 return $status;
204 }
205
206 /**
207 * Adds links to Premium Support and FAQ under the plugin in the plugin overview page.
208 *
209 * @param array $links Array of links for the plugins, adapted when the current plugin is found.
210 * @param string $file The filename for the current plugin, which the filter loops through.
211 *
212 * @return array
213 */
214 public function add_action_link( $links, $file ) {
215 $first_time_configuration_notice_helper = YoastSEO()->helpers->first_time_configuration_notice;
216
217 if ( $file === WPSEO_BASENAME && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
218 if ( is_network_admin() ) {
219 $settings_url = network_admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER );
220 }
221 else {
222 $settings_url = admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER );
223 }
224 $settings_link = '<a href="' . esc_url( $settings_url ) . '">' . __( 'Settings', 'wordpress-seo' ) . '</a>';
225 array_unshift( $links, $settings_link );
226 }
227
228 // Add link to docs.
229 $faq_link = '<a href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/1yc' ) ) . '" target="_blank">' . __( 'FAQ', 'wordpress-seo' ) . '</a>';
230 array_unshift( $links, $faq_link );
231
232 if ( $first_time_configuration_notice_helper->first_time_configuration_not_finished() && ! is_network_admin() ) {
233 $configuration_title = ( ! $first_time_configuration_notice_helper->should_show_alternate_message() ) ? 'first-time configuration' : 'SEO configuration';
234 /* translators: CTA to finish the first time configuration. %s: Either first-time SEO configuration or SEO configuration. */
235 $message = sprintf( __( 'Finish your %s', 'wordpress-seo' ), $configuration_title );
236 $ftc_page = 'admin.php?page=wpseo_dashboard#/first-time-configuration';
237 $ftc_link = '<a href="' . esc_url( admin_url( $ftc_page ) ) . '" target="_blank">' . $message . '</a>';
238 array_unshift( $links, $ftc_link );
239 }
240
241 $addon_manager = new WPSEO_Addon_Manager();
242 if ( YoastSEO()->helpers->product->is_premium() ) {
243
244 // Remove Free 'deactivate' link if Premium is active as well. We don't want users to deactivate Free when Premium is active.
245 unset( $links['deactivate'] );
246 $no_deactivation_explanation = '<span style="color: #32373c">' . sprintf(
247 /* translators: %s expands to Yoast SEO Premium. */
248 __( 'Required by %s', 'wordpress-seo' ),
249 'Yoast SEO Premium',
250 ) . '</span>';
251
252 array_unshift( $links, $no_deactivation_explanation );
253
254 if ( $addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ) ) {
255 return $links;
256 }
257
258 // Add link to where premium can be activated.
259 $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>';
260 array_unshift( $links, $activation_link );
261
262 return $links;
263 }
264
265 // Add link to premium landing page.
266 $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>';
267 array_unshift( $links, $premium_link );
268
269 return $links;
270 }
271
272 /**
273 * Enqueues the (tiny) global JS needed for the plugin.
274 *
275 * @return void
276 */
277 public function config_page_scripts() {
278 $asset_manager = new WPSEO_Admin_Asset_Manager();
279 $asset_manager->enqueue_script( 'admin-global' );
280 $asset_manager->localize_script( 'admin-global', 'wpseoAdminGlobalL10n', $this->localize_admin_global_script() );
281 }
282
283 /**
284 * Enqueues the (tiny) global stylesheet needed for the plugin.
285 *
286 * @return void
287 */
288 public function enqueue_global_style() {
289 $asset_manager = new WPSEO_Admin_Asset_Manager();
290 $asset_manager->enqueue_style( 'admin-global' );
291 }
292
293 /**
294 * Filter the $contactmethods array and add a set of social profiles.
295 *
296 * These are used with the Facebook author, rel="author" and Twitter cards implementation.
297 *
298 * @deprecated 22.6
299 * @codeCoverageIgnore
300 *
301 * @param array<string, string> $contactmethods Currently set contactmethods.
302 *
303 * @return array<string, string> Contactmethods with added contactmethods.
304 */
305 public function update_contactmethods( $contactmethods ) {
306 _deprecated_function( __METHOD__, 'Yoast SEO 22.6' );
307
308 $contactmethods['facebook'] = __( 'Facebook profile URL', 'wordpress-seo' );
309 $contactmethods['instagram'] = __( 'Instagram profile URL', 'wordpress-seo' );
310 $contactmethods['linkedin'] = __( 'LinkedIn profile URL', 'wordpress-seo' );
311 $contactmethods['myspace'] = __( 'MySpace profile URL', 'wordpress-seo' );
312 $contactmethods['pinterest'] = __( 'Pinterest profile URL', 'wordpress-seo' );
313 $contactmethods['soundcloud'] = __( 'SoundCloud profile URL', 'wordpress-seo' );
314 $contactmethods['tumblr'] = __( 'Tumblr profile URL', 'wordpress-seo' );
315 $contactmethods['twitter'] = __( 'X username (without @)', 'wordpress-seo' );
316 $contactmethods['youtube'] = __( 'YouTube profile URL', 'wordpress-seo' );
317 $contactmethods['wikipedia'] = __( 'Wikipedia page about you', 'wordpress-seo' ) . '<br/><small>' . __( '(if one exists)', 'wordpress-seo' ) . '</small>';
318
319 return $contactmethods;
320 }
321
322 /**
323 * Log the updated timestamp for user profiles when theme is changed.
324 *
325 * @return void
326 */
327 public function switch_theme() {
328
329 $users = get_users( [ 'capability' => [ 'edit_posts' ] ] );
330
331 if ( is_array( $users ) && $users !== [] ) {
332 foreach ( $users as $user ) {
333 update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', time() );
334 }
335 }
336 }
337
338 /**
339 * Localization for the dismiss urls.
340 *
341 * @return array
342 */
343 private function localize_admin_global_script() {
344 return array_merge(
345 [
346 'isRtl' => is_rtl(),
347 'variable_warning' => sprintf(
348 /* 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' */
349 __( 'Warning: the variable %1$s cannot be used in this template. See the %2$s for more info.', 'wordpress-seo' ),
350 '<code>%s</code>',
351 'HelpScout beacon',
352 ),
353 /* translators: %s: expends to Yoast SEO */
354 'help_video_iframe_title' => sprintf( __( '%s video tutorial', 'wordpress-seo' ), 'Yoast SEO' ),
355 'scrollable_table_hint' => __( 'Scroll to see the table content.', 'wordpress-seo' ),
356 'wincher_is_logged_in' => WPSEO_Options::get( 'wincher_integration_active', true ) ? YoastSEO()->helpers->wincher->login_status() : false,
357 ],
358 YoastSEO()->helpers->wincher->get_admin_global_links(),
359 );
360 }
361
362 /**
363 * Whether we are on the admin dashboard page.
364 *
365 * @return bool
366 */
367 protected function on_dashboard_page() {
368 return $GLOBALS['pagenow'] === 'index.php';
369 }
370
371 /**
372 * Loads the cornerstone filter.
373 *
374 * @return WPSEO_WordPress_Integration[] The integrations to initialize.
375 */
376 protected function initialize_cornerstone_content() {
377 if ( ! WPSEO_Options::get( 'enable_cornerstone_content' ) ) {
378 return [];
379 }
380
381 return [
382 'cornerstone_filter' => new WPSEO_Cornerstone_Filter(),
383 ];
384 }
385 }
386