class-admin.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. <?php
  2. /**
  3. * WPSEO plugin file.
  4. *
  5. * @package WPSEO\Admin
  6. */
  7. /**
  8. * Class that holds most of the admin functionality for Yoast SEO.
  9. */
  10. class WPSEO_Admin {
  11. /** The page identifier used in WordPress to register the admin page !DO NOT CHANGE THIS! */
  12. const PAGE_IDENTIFIER = 'wpseo_dashboard';
  13. /**
  14. * Array of classes that add admin functionality.
  15. *
  16. * @var array
  17. */
  18. protected $admin_features;
  19. /**
  20. * Class constructor.
  21. */
  22. public function __construct() {
  23. $integrations = array();
  24. global $pagenow;
  25. $wpseo_menu = new WPSEO_Menu();
  26. $wpseo_menu->register_hooks();
  27. if ( is_multisite() ) {
  28. WPSEO_Options::maybe_set_multisite_defaults( false );
  29. }
  30. if ( WPSEO_Options::get( 'stripcategorybase' ) === true ) {
  31. add_action( 'created_category', array( $this, 'schedule_rewrite_flush' ) );
  32. add_action( 'edited_category', array( $this, 'schedule_rewrite_flush' ) );
  33. add_action( 'delete_category', array( $this, 'schedule_rewrite_flush' ) );
  34. }
  35. $this->admin_features = array(
  36. // Google Search Console.
  37. 'google_search_console' => new WPSEO_GSC(),
  38. 'dashboard_widget' => new Yoast_Dashboard_Widget(),
  39. );
  40. if ( WPSEO_Metabox::is_post_overview( $pagenow ) || WPSEO_Metabox::is_post_edit( $pagenow ) ) {
  41. $this->admin_features['primary_category'] = new WPSEO_Primary_Term_Admin();
  42. }
  43. if ( filter_input( INPUT_GET, 'page' ) === 'wpseo_tools' && filter_input( INPUT_GET, 'tool' ) === null ) {
  44. new WPSEO_Recalculate_Scores();
  45. }
  46. add_filter( 'plugin_action_links_' . WPSEO_BASENAME, array( $this, 'add_action_link' ), 10, 2 );
  47. add_action( 'admin_enqueue_scripts', array( $this, 'config_page_scripts' ) );
  48. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_global_style' ) );
  49. add_filter( 'user_contactmethods', array( $this, 'update_contactmethods' ), 10, 1 );
  50. add_action( 'after_switch_theme', array( $this, 'switch_theme' ) );
  51. add_action( 'switch_theme', array( $this, 'switch_theme' ) );
  52. add_filter( 'set-screen-option', array( $this, 'save_bulk_edit_options' ), 10, 3 );
  53. add_action( 'admin_init', array( 'WPSEO_Plugin_Conflict', 'hook_check_for_plugin_conflicts' ), 10, 1 );
  54. add_action( 'admin_init', array( $this, 'map_manage_options_cap' ) );
  55. WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'wpseo' );
  56. WPSEO_Sitemaps_Cache::register_clear_on_option_update( 'home' );
  57. if ( WPSEO_Utils::is_yoast_seo_page() ) {
  58. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
  59. }
  60. if ( WPSEO_Utils::is_api_available() ) {
  61. $configuration = new WPSEO_Configuration_Page();
  62. $configuration->set_hooks();
  63. $configuration->catch_configuration_request();
  64. }
  65. $this->set_upsell_notice();
  66. $this->initialize_cornerstone_content();
  67. new Yoast_Modal();
  68. if ( WPSEO_Utils::is_plugin_network_active() ) {
  69. $integrations[] = new Yoast_Network_Admin();
  70. }
  71. $integrations[] = new WPSEO_Yoast_Columns();
  72. $integrations[] = new WPSEO_License_Page_Manager();
  73. $integrations[] = new WPSEO_Statistic_Integration();
  74. $integrations[] = new WPSEO_Capability_Manager_Integration( WPSEO_Capability_Manager_Factory::get() );
  75. $integrations[] = new WPSEO_Admin_Media_Purge_Notification();
  76. $integrations[] = new WPSEO_Admin_Gutenberg_Compatibility_Notification();
  77. $integrations[] = new WPSEO_Expose_Shortlinks();
  78. $integrations = array_merge( $integrations, $this->initialize_seo_links() );
  79. /** @var WPSEO_WordPress_Integration $integration */
  80. foreach ( $integrations as $integration ) {
  81. $integration->register_hooks();
  82. }
  83. }
  84. /**
  85. * Schedules a rewrite flush to happen at shutdown.
  86. */
  87. public function schedule_rewrite_flush() {
  88. add_action( 'shutdown', 'flush_rewrite_rules' );
  89. }
  90. /**
  91. * Returns all the classes for the admin features.
  92. *
  93. * @return array
  94. */
  95. public function get_admin_features() {
  96. return $this->admin_features;
  97. }
  98. /**
  99. * Register assets needed on admin pages.
  100. */
  101. public function enqueue_assets() {
  102. if ( 'wpseo_licenses' === filter_input( INPUT_GET, 'page' ) ) {
  103. $asset_manager = new WPSEO_Admin_Asset_Manager();
  104. $asset_manager->enqueue_style( 'extensions' );
  105. }
  106. }
  107. /**
  108. * Returns the manage_options capability.
  109. *
  110. * @return string The capability to use.
  111. */
  112. public function get_manage_options_cap() {
  113. /**
  114. * Filter: 'wpseo_manage_options_capability' - Allow changing the capability users need to view the settings pages.
  115. *
  116. * @api string unsigned The capability.
  117. */
  118. return apply_filters( 'wpseo_manage_options_capability', 'wpseo_manage_options' );
  119. }
  120. /**
  121. * Maps the manage_options cap on saving an options page to wpseo_manage_options.
  122. */
  123. public function map_manage_options_cap() {
  124. $option_page = ! empty( $_POST['option_page'] ) ? $_POST['option_page'] : ''; // WPCS: CSRF ok.
  125. if ( strpos( $option_page, 'yoast_wpseo' ) === 0 ) {
  126. add_filter( 'option_page_capability_' . $option_page, array( $this, 'get_manage_options_cap' ) );
  127. }
  128. }
  129. /**
  130. * Adds the ability to choose how many posts are displayed per page
  131. * on the bulk edit pages.
  132. */
  133. public function bulk_edit_options() {
  134. $option = 'per_page';
  135. $args = array(
  136. 'label' => __( 'Posts', 'wordpress-seo' ),
  137. 'default' => 10,
  138. 'option' => 'wpseo_posts_per_page',
  139. );
  140. add_screen_option( $option, $args );
  141. }
  142. /**
  143. * Saves the posts per page limit for bulk edit pages.
  144. *
  145. * @param int $status Status value to pass through.
  146. * @param string $option Option name.
  147. * @param int $value Count value to check.
  148. *
  149. * @return int
  150. */
  151. public function save_bulk_edit_options( $status, $option, $value ) {
  152. if ( 'wpseo_posts_per_page' === $option && ( $value > 0 && $value < 1000 ) ) {
  153. return $value;
  154. }
  155. return $status;
  156. }
  157. /**
  158. * Adds links to Premium Support and FAQ under the plugin in the plugin overview page.
  159. *
  160. * @staticvar string $this_plugin Holds the directory & filename for the plugin.
  161. *
  162. * @param array $links Array of links for the plugins, adapted when the current plugin is found.
  163. * @param string $file The filename for the current plugin, which the filter loops through.
  164. *
  165. * @return array $links
  166. */
  167. public function add_action_link( $links, $file ) {
  168. if ( WPSEO_BASENAME === $file && WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
  169. $settings_link = '<a href="' . esc_url( admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER ) ) . '">' . __( 'Settings', 'wordpress-seo' ) . '</a>';
  170. array_unshift( $links, $settings_link );
  171. }
  172. if ( class_exists( 'WPSEO_Product_Premium' ) ) {
  173. $product_premium = new WPSEO_Product_Premium();
  174. $extension_manager = new WPSEO_Extension_Manager();
  175. if ( $extension_manager->is_activated( $product_premium->get_slug() ) ) {
  176. return $links;
  177. }
  178. }
  179. // Add link to premium support landing page.
  180. $premium_link = '<a href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/1yb' ) ) . '">' . __( 'Premium Support', 'wordpress-seo' ) . '</a>';
  181. array_unshift( $links, $premium_link );
  182. // Add link to docs.
  183. $faq_link = '<a href="' . esc_url( WPSEO_Shortlinker::get( 'https://yoa.st/1yc' ) ) . '">' . __( 'FAQ', 'wordpress-seo' ) . '</a>';
  184. array_unshift( $links, $faq_link );
  185. return $links;
  186. }
  187. /**
  188. * Enqueues the (tiny) global JS needed for the plugin.
  189. */
  190. public function config_page_scripts() {
  191. $asset_manager = new WPSEO_Admin_Asset_Manager();
  192. $asset_manager->enqueue_script( 'admin-global-script' );
  193. wp_localize_script( WPSEO_Admin_Asset_Manager::PREFIX . 'admin-global-script', 'wpseoAdminGlobalL10n', $this->localize_admin_global_script() );
  194. }
  195. /**
  196. * Enqueues the (tiny) global stylesheet needed for the plugin.
  197. */
  198. public function enqueue_global_style() {
  199. $asset_manager = new WPSEO_Admin_Asset_Manager();
  200. $asset_manager->enqueue_style( 'admin-global' );
  201. }
  202. /**
  203. * Filter the $contactmethods array and add Facebook, Google+ and Twitter.
  204. *
  205. * These are used with the Facebook author, rel="author" and Twitter cards implementation.
  206. *
  207. * @param array $contactmethods Currently set contactmethods.
  208. *
  209. * @return array $contactmethods with added contactmethods.
  210. */
  211. public function update_contactmethods( $contactmethods ) {
  212. // Add Google+.
  213. $contactmethods['googleplus'] = __( 'Google+', 'wordpress-seo' );
  214. // Add Twitter.
  215. $contactmethods['twitter'] = __( 'Twitter username (without @)', 'wordpress-seo' );
  216. // Add Facebook.
  217. $contactmethods['facebook'] = __( 'Facebook profile URL', 'wordpress-seo' );
  218. return $contactmethods;
  219. }
  220. /**
  221. * Log the updated timestamp for user profiles when theme is changed.
  222. */
  223. public function switch_theme() {
  224. $users = get_users( array( 'who' => 'authors' ) );
  225. if ( is_array( $users ) && $users !== array() ) {
  226. foreach ( $users as $user ) {
  227. update_user_meta( $user->ID, '_yoast_wpseo_profile_updated', time() );
  228. }
  229. }
  230. }
  231. /**
  232. * Localization for the dismiss urls.
  233. *
  234. * @return array
  235. */
  236. private function localize_admin_global_script() {
  237. return array(
  238. /* translators: %s: '%%term_title%%' variable used in titles and meta's template that's not compatible with the given template */
  239. 'variable_warning' => sprintf( __( 'Warning: the variable %s cannot be used in this template. See the help center for more info.', 'wordpress-seo' ), '<code>%s</code>' ),
  240. 'dismiss_about_url' => $this->get_dismiss_url( 'wpseo-dismiss-about' ),
  241. 'dismiss_tagline_url' => $this->get_dismiss_url( 'wpseo-dismiss-tagline-notice' ),
  242. 'help_video_iframe_title' => __( 'Yoast SEO video tutorial', 'wordpress-seo' ),
  243. 'scrollable_table_hint' => __( 'Scroll to see the table content.', 'wordpress-seo' ),
  244. );
  245. }
  246. /**
  247. * Extending the current page URL with two params to be able to ignore the notice.
  248. *
  249. * @param string $dismiss_param The param used to dismiss the notification.
  250. *
  251. * @return string
  252. */
  253. private function get_dismiss_url( $dismiss_param ) {
  254. $arr_params = array(
  255. $dismiss_param => '1',
  256. 'nonce' => wp_create_nonce( $dismiss_param ),
  257. );
  258. return esc_url( add_query_arg( $arr_params ) );
  259. }
  260. /**
  261. * Sets the upsell notice.
  262. */
  263. protected function set_upsell_notice() {
  264. $upsell = new WPSEO_Product_Upsell_Notice();
  265. $upsell->dismiss_notice_listener();
  266. $upsell->initialize();
  267. }
  268. /**
  269. * Initializes Whip to show a notice for outdated PHP versions.
  270. *
  271. * @deprecated 8.1
  272. *
  273. * @return void
  274. */
  275. public function check_php_version() {
  276. // Intentionally left empty.
  277. }
  278. /**
  279. * Whether we are on the admin dashboard page.
  280. *
  281. * @returns bool
  282. */
  283. protected function on_dashboard_page() {
  284. return 'index.php' === $GLOBALS['pagenow'];
  285. }
  286. /**
  287. * Loads the cornerstone filter.
  288. */
  289. protected function initialize_cornerstone_content() {
  290. if ( ! WPSEO_Options::get( 'enable_cornerstone_content' ) ) {
  291. return;
  292. }
  293. $cornerstone = new WPSEO_Cornerstone();
  294. $cornerstone->register_hooks();
  295. $cornerstone_filter = new WPSEO_Cornerstone_Filter();
  296. $cornerstone_filter->register_hooks();
  297. }
  298. /**
  299. * Initializes the seo link watcher.
  300. *
  301. * @returns WPSEO_WordPress_Integration[]
  302. */
  303. protected function initialize_seo_links() {
  304. $integrations = array();
  305. $link_table_compatibility_notifier = new WPSEO_Link_Compatibility_Notifier();
  306. $link_table_accessible_notifier = new WPSEO_Link_Table_Accessible_Notifier();
  307. if ( ! WPSEO_Options::get( 'enable_text_link_counter' ) ) {
  308. $link_table_compatibility_notifier->remove_notification();
  309. return $integrations;
  310. }
  311. $integrations[] = new WPSEO_Link_Cleanup_Transient();
  312. // Only use the link module for PHP 5.3 and higher and show a notice when version is wrong.
  313. if ( version_compare( phpversion(), '5.3', '<' ) ) {
  314. $link_table_compatibility_notifier->add_notification();
  315. return $integrations;
  316. }
  317. $link_table_compatibility_notifier->remove_notification();
  318. // When the table doesn't exists, just add the notification and return early.
  319. if ( ! WPSEO_Link_Table_Accessible::is_accessible() ) {
  320. WPSEO_Link_Table_Accessible::cleanup();
  321. }
  322. if ( ! WPSEO_Meta_Table_Accessible::is_accessible() ) {
  323. WPSEO_Meta_Table_Accessible::cleanup();
  324. }
  325. if ( ! WPSEO_Link_Table_Accessible::is_accessible() || ! WPSEO_Meta_Table_Accessible::is_accessible() ) {
  326. $link_table_accessible_notifier->add_notification();
  327. return $integrations;
  328. }
  329. $link_table_accessible_notifier->remove_notification();
  330. $integrations[] = new WPSEO_Link_Columns( new WPSEO_Meta_Storage() );
  331. $integrations[] = new WPSEO_Link_Reindex_Dashboard();
  332. $integrations[] = new WPSEO_Link_Notifier();
  333. // Adds a filter to exclude the attachments from the link count.
  334. add_filter( 'wpseo_link_count_post_types', array( 'WPSEO_Post_Type', 'filter_attachment_post_type' ) );
  335. return $integrations;
  336. }
  337. /********************** DEPRECATED METHODS **********************/
  338. // @codeCoverageIgnoreStart
  339. /**
  340. * Register the menu item and its sub menu's.
  341. *
  342. * @deprecated 5.5
  343. */
  344. public function register_settings_page() {
  345. _deprecated_function( __METHOD__, 'WPSEO 5.5.0' );
  346. }
  347. /**
  348. * Register the settings page for the Network settings.
  349. *
  350. * @deprecated 5.5
  351. */
  352. public function register_network_settings_page() {
  353. _deprecated_function( __METHOD__, 'WPSEO 5.5.0' );
  354. }
  355. /**
  356. * Load the form for a WPSEO admin page.
  357. *
  358. * @deprecated 5.5
  359. */
  360. public function load_page() {
  361. _deprecated_function( __METHOD__, 'WPSEO 5.5.0' );
  362. }
  363. /**
  364. * Loads the form for the network configuration page.
  365. *
  366. * @deprecated 5.5
  367. */
  368. public function network_config_page() {
  369. _deprecated_function( __METHOD__, 'WPSEO 5.5.0' );
  370. }
  371. /**
  372. * Filters all advanced settings pages from the given pages.
  373. *
  374. * @deprecated 5.5
  375. * @param array $pages The pages to filter.
  376. */
  377. public function filter_settings_pages( array $pages ) {
  378. _deprecated_function( __METHOD__, 'WPSEO 5.5.0' );
  379. }
  380. /**
  381. * Cleans stopwords out of the slug, if the slug hasn't been set yet.
  382. *
  383. * @deprecated 7.0
  384. *
  385. * @return void
  386. */
  387. public function remove_stopwords_from_slug() {
  388. _deprecated_function( __METHOD__, 'WPSEO 7.0' );
  389. }
  390. /**
  391. * Filter the stopwords from the slug.
  392. *
  393. * @deprecated 7.0
  394. *
  395. * @return void
  396. */
  397. public function filter_stopwords_from_slug() {
  398. _deprecated_function( __METHOD__, 'WPSEO 7.0' );
  399. }
  400. /**
  401. * Adds contextual help to the titles & metas page.
  402. *
  403. * @deprecated 5.6.0
  404. */
  405. public function title_metas_help_tab() {
  406. _deprecated_function( __METHOD__, '5.6.0' );
  407. $screen = get_current_screen();
  408. $screen->set_help_sidebar( '
  409. <p><strong>' . __( 'For more information:', 'wordpress-seo' ) . '</strong></p>
  410. <p><a target="_blank" href="https://yoast.com/wordpress-seo/#titles">' . __( 'Title optimization', 'wordpress-seo' ) . '</a></p>
  411. <p><a target="_blank" href="https://yoast.com/google-page-title/">' . __( 'Why Google won\'t display the right page title', 'wordpress-seo' ) . '</a></p>'
  412. );
  413. $screen->add_help_tab(
  414. array(
  415. 'id' => 'basic-help',
  416. 'title' => __( 'Template explanation', 'wordpress-seo' ),
  417. 'content' => "\n\t\t<h2>" . __( 'Template explanation', 'wordpress-seo' ) . "</h2>\n\t\t" . '<p>' .
  418. sprintf(
  419. /* translators: %1$s expands to Yoast SEO. */
  420. __( 'The title &amp; metas settings for %1$s are made up of variables that are replaced by specific values from the page when the page is displayed. The tabs on the left explain the available variables.', 'wordpress-seo' ),
  421. 'Yoast SEO'
  422. ) .
  423. '</p><p>' . __( 'Note that not all variables can be used in every template.', 'wordpress-seo' ) . '</p>',
  424. )
  425. );
  426. $screen->add_help_tab(
  427. array(
  428. 'id' => 'title-vars',
  429. 'title' => __( 'Basic Variables', 'wordpress-seo' ),
  430. 'content' => "\n\t\t<h2>" . __( 'Basic Variables', 'wordpress-seo' ) . "</h2>\n\t\t" . WPSEO_Replace_Vars::get_basic_help_texts(),
  431. )
  432. );
  433. $screen->add_help_tab(
  434. array(
  435. 'id' => 'title-vars-advanced',
  436. 'title' => __( 'Advanced Variables', 'wordpress-seo' ),
  437. 'content' => "\n\t\t<h2>" . __( 'Advanced Variables', 'wordpress-seo' ) . "</h2>\n\t\t" . WPSEO_Replace_Vars::get_advanced_help_texts(),
  438. )
  439. );
  440. }
  441. // @codeCoverageIgnoreEnd
  442. }