Yoast SEO - Version 15.0

Version Description

Release Date: September 29th, 2020

Today, were launching Yoast SEO 15.0. This release features some awesome new additions and enhancements. Weve added full support for Arabic and made the Yoast SEO block editor sidebar fully-featured. Read more about those changes in our release post!

Enhancements:

  • Introduces an advanced settings tab in the sidebar.
  • Introduces buttons in the sidebar to open the Facebook and Twitter Preview in a modal.
  • Changes the Google Preview modal styling to match the other new modals.
  • Always shows the Google Preview editor fields and as a result removes the 'Edit snippet' button.
  • Changes the styling of the Yoast SEO sidebar to match the standard Gutenberg styling.
  • Slightly rearranges the order of items in the Yoast SEO sidebar.
  • Adds a hover state styling to the items in the Metabox.
  • Improves the English transition word assessment by adding the following words to the transition word list: 'note that', 'not only', 'initially', 'as opposed to'.
  • Improves the keyphrase and prominent word recognition when words in the text occur with specific Arabic or Urdu punctuation marks.

Bugfixes:

  • Fixes a bug where the value of the schema @type could contain null.
  • Fixes a bug where the archive, imageindex and snippet robot values would be output when noindex was present as well.
  • Fixes a bug where the indexable permalinks could have an incorrect value when the term slug was changed.
  • Fixes a bug where parts of the content of a password protected post could be output in the schema.
  • Fixes a bug where the 'Stop counting' button in the text link counter modal wouldn't stop the counting of links.
  • Fixes a bug where indexable hierarchies were not being created during bulk indexing.

Other:

  • Adds the wpseo_sitemap_index_links filter to enable adding links to the sitemap index. Props to Joseph Paul.
Download this release

Release Info

Developer Yoast
Plugin Icon 128x128 Yoast SEO
Version 15.0
Comparing to
See all releases

Code changes from version 14.9 to 15.0

Files changed (137) hide show
  1. admin/class-admin-init.php +25 -24
  2. admin/class-admin.php +2 -2
  3. admin/class-bulk-description-editor-list-table.php +1 -1
  4. admin/class-bulk-editor-list-table.php +0 -1
  5. admin/class-gutenberg-compatibility.php +2 -2
  6. admin/class-meta-columns.php +6 -2
  7. admin/class-my-yoast-route.php +0 -317
  8. admin/class-premium-upsell-admin-block.php +4 -2
  9. admin/class-yoast-network-admin.php +6 -3
  10. admin/class-yoast-notification-center.php +3 -3
  11. admin/config-ui/class-configuration-structure.php +1 -0
  12. admin/config-ui/fields/class-field-tracking.php +4 -4
  13. admin/endpoints/class-endpoint-indexable.php +0 -102
  14. admin/endpoints/interface-endpoint-storable.php +0 -19
  15. admin/import/plugins/class-abstract-plugin-importer.php +3 -2
  16. admin/metabox/class-metabox.php +115 -161
  17. admin/roles/class-role-manager-wp.php +0 -1
  18. admin/services/class-indexable-post-provider.php +0 -232
  19. admin/services/class-indexable-provider.php +0 -38
  20. admin/services/class-indexable-term-provider.php +0 -168
  21. admin/services/class-indexable.php +0 -97
  22. admin/services/interface-indexable-provider.php +0 -42
  23. admin/taxonomy/class-taxonomy-content-fields.php +0 -66
  24. admin/taxonomy/class-taxonomy-fields.php +178 -25
  25. admin/taxonomy/class-taxonomy-metabox.php +78 -102
  26. admin/taxonomy/class-taxonomy-settings-fields.php +0 -51
  27. admin/taxonomy/class-taxonomy-social-fields.php +0 -152
  28. admin/tracking/class-tracking-server-data.php +0 -1
  29. admin/views/class-yoast-feature-toggle.php +7 -0
  30. admin/views/class-yoast-feature-toggles.php +21 -0
  31. admin/views/paper-collapsible.php +3 -4
  32. admin/views/partial-notifications-template.php +4 -4
  33. admin/views/sidebar.php +1 -1
  34. admin/views/tabs/dashboard/dashboard.php +4 -1
  35. admin/views/tabs/dashboard/features.php +4 -1
  36. admin/views/tabs/dashboard/webmaster-tools.php +3 -1
  37. admin/views/tabs/metas/archives.php +1 -0
  38. admin/views/tabs/metas/archives/help.php +3 -0
  39. admin/views/tabs/metas/breadcrumbs.php +2 -0
  40. admin/views/tabs/metas/general.php +1 -0
  41. admin/views/tabs/metas/media.php +1 -0
  42. admin/views/tabs/metas/paper-content/general/homepage.php +2 -0
  43. admin/views/tabs/metas/paper-content/general/knowledge-graph.php +8 -2
  44. admin/views/tabs/metas/paper-content/general/title-separator.php +7 -1
  45. admin/views/tabs/metas/paper-content/media-content.php +1 -0
  46. admin/views/tabs/metas/paper-content/post_type/post-type.php +1 -1
  47. admin/views/tabs/metas/paper-content/post_type/woocommerce-shop-page.php +1 -1
  48. admin/views/tabs/metas/paper-content/rss-content.php +2 -0
  49. admin/views/tabs/metas/post-types.php +1 -0
  50. admin/views/tabs/metas/rss.php +1 -0
  51. admin/views/tabs/metas/taxonomies.php +2 -0
  52. admin/views/tabs/network/features.php +0 -1
  53. admin/views/tabs/social/accounts.php +2 -0
  54. admin/views/tabs/social/facebook.php +9 -3
  55. admin/views/tool-bulk-editor.php +1 -1
  56. admin/views/tool-file-editor.php +0 -1
  57. css/dist/{admin-global-1490-rtl.css → admin-global-1500-rtl.css} +0 -0
  58. css/dist/{admin-global-1490.css → admin-global-1500.css} +0 -0
  59. css/dist/{adminbar-1490-rtl.css → adminbar-1500-rtl.css} +0 -0
  60. css/dist/{adminbar-1490.css → adminbar-1500.css} +0 -0
  61. css/dist/{alerts-1490-rtl.css → alerts-1500-rtl.css} +0 -0
  62. css/dist/{alerts-1490.css → alerts-1500.css} +0 -0
  63. css/dist/{dashboard-1490-rtl.css → dashboard-1500-rtl.css} +0 -0
  64. css/dist/{dashboard-1490.css → dashboard-1500.css} +0 -0
  65. css/dist/{edit-page-1490-rtl.css → edit-page-1500-rtl.css} +0 -0
  66. css/dist/{edit-page-1490.css → edit-page-1500.css} +0 -0
  67. css/dist/{featured-image-1490-rtl.css → featured-image-1500-rtl.css} +0 -0
  68. css/dist/{featured-image-1490.css → featured-image-1500.css} +0 -0
  69. css/dist/{filter-explanation-1490-rtl.css → filter-explanation-1500-rtl.css} +0 -0
  70. css/dist/{filter-explanation-1490.css → filter-explanation-1500.css} +0 -0
  71. css/dist/{icons-1490-rtl.css → icons-1500-rtl.css} +0 -0
  72. css/dist/{icons-1490.css → icons-1500.css} +0 -0
  73. css/dist/{inside-editor-1490-rtl.css → inside-editor-1500-rtl.css} +0 -0
  74. css/dist/{inside-editor-1490.css → inside-editor-1500.css} +0 -0
  75. css/dist/metabox-1490-rtl.css +0 -1
  76. css/dist/metabox-1500-rtl.css +1 -0
  77. css/dist/{metabox-1490.css → metabox-1500.css} +2 -2
  78. css/dist/{metabox-primary-category-1490-rtl.css → metabox-primary-category-1500-rtl.css} +0 -0
  79. css/dist/{metabox-primary-category-1490.css → metabox-primary-category-1500.css} +0 -0
  80. css/dist/modal-1490-rtl.css +0 -1
  81. css/dist/modal-1490.css +0 -1
  82. css/dist/modal-1500-rtl.css +1 -0
  83. css/dist/modal-1500.css +1 -0
  84. css/dist/monorepo-1490-rtl.css +0 -1
  85. css/dist/monorepo-1490.css +0 -1
  86. css/dist/monorepo-1500-rtl.css +1 -0
  87. css/dist/monorepo-1500.css +1 -0
  88. css/dist/{notifications-1490-rtl.css → notifications-1500-rtl.css} +0 -0
  89. css/dist/{notifications-1490.css → notifications-1500.css} +0 -0
  90. css/dist/{score_icon-1490-rtl.css → score_icon-1500-rtl.css} +0 -0
  91. css/dist/{score_icon-1490.css → score_icon-1500.css} +0 -0
  92. css/dist/{search-appearance-1490-rtl.css → search-appearance-1500-rtl.css} +0 -0
  93. css/dist/{search-appearance-1490.css → search-appearance-1500.css} +0 -0
  94. css/dist/{structured-data-blocks-1490-rtl.css → structured-data-blocks-1500-rtl.css} +0 -0
  95. css/dist/{structured-data-blocks-1490.css → structured-data-blocks-1500.css} +0 -0
  96. css/dist/{toggle-switch-1490-rtl.css → toggle-switch-1500-rtl.css} +0 -0
  97. css/dist/{toggle-switch-1490.css → toggle-switch-1500.css} +0 -0
  98. css/dist/{wpseo-dismissible-1490-rtl.css → wpseo-dismissible-1500-rtl.css} +0 -0
  99. css/dist/{wpseo-dismissible-1490.css → wpseo-dismissible-1500.css} +0 -0
  100. css/dist/{yoast-components-1490-rtl.css → yoast-components-1500-rtl.css} +0 -0
  101. css/dist/{yoast-components-1490.css → yoast-components-1500.css} +0 -0
  102. css/dist/{yoast-extensions-1490-rtl.css → yoast-extensions-1500-rtl.css} +0 -0
  103. css/dist/{yoast-extensions-1490.css → yoast-extensions-1500.css} +0 -0
  104. css/dist/{yst_plugin_tools-1490-rtl.css → yst_plugin_tools-1500-rtl.css} +0 -0
  105. css/dist/{yst_plugin_tools-1490.css → yst_plugin_tools-1500.css} +0 -0
  106. css/dist/{yst_seo_score-1490-rtl.css → yst_seo_score-1500-rtl.css} +0 -0
  107. css/dist/{yst_seo_score-1490.css → yst_seo_score-1500.css} +0 -0
  108. css/src/metabox.css +46 -0
  109. css/src/modal.css +97 -23
  110. inc/class-addon-manager.php +93 -7
  111. inc/class-my-yoast-api-request.php +1 -158
  112. inc/class-upgrade.php +2 -0
  113. inc/class-wpseo-image-utils.php +2 -2
  114. inc/class-wpseo-utils.php +25 -18
  115. inc/endpoints/class-myyoast-connect.php +0 -140
  116. inc/health-check-default-tagline.php +1 -2
  117. inc/indexables/class-indexable.php +0 -130
  118. inc/indexables/class-object-type.php +0 -113
  119. inc/indexables/class-post-indexable.php +0 -98
  120. inc/indexables/class-post-object-type.php +0 -31
  121. inc/indexables/class-term-indexable.php +0 -126
  122. inc/indexables/class-term-object-type.php +0 -31
  123. inc/indexables/validators/class-endpoint-validator.php +0 -21
  124. inc/indexables/validators/class-keyword-validator.php +0 -29
  125. inc/indexables/validators/class-link-validator.php +0 -29
  126. inc/indexables/validators/class-meta-values-validator.php +0 -51
  127. inc/indexables/validators/class-object-type-validator.php +0 -66
  128. inc/indexables/validators/class-opengraph-validator.php +0 -35
  129. inc/indexables/validators/class-robots-validator.php +0 -46
  130. inc/indexables/validators/class-twitter-validator.php +0 -35
  131. inc/options/class-wpseo-option-wpseo.php +1 -1
  132. inc/options/class-wpseo-option.php +22 -16
  133. inc/options/class-wpseo-options.php +2 -0
  134. inc/sitemaps/class-sitemaps.php +7 -0
  135. inc/wpseo-functions.php +64 -0
  136. js/dist/{admin-global-1490.js → admin-global-1500.js} +1 -1
  137. js/dist/analysis-1490.js +0 -21
admin/class-admin-init.php CHANGED
@@ -139,7 +139,7 @@ class WPSEO_Admin_Init {
139
  public function yoast_plugin_update_notification() {
140
  $notification_center = Yoast_Notification_Center::get();
141
  $current_minor_version = $this->get_major_minor_version( WPSEO_Options::get( 'version', WPSEO_VERSION ) );
142
- $file = plugin_dir_path( WPSEO_FILE ) . 'release-info.json';
143
 
144
  // Remove if file is not present.
145
  if ( ! file_exists( $file ) ) {
@@ -147,7 +147,9 @@ class WPSEO_Admin_Init {
147
  return;
148
  }
149
 
 
150
  $release_json = file_get_contents( $file );
 
151
  /**
152
  * Filter: 'wpseo_update_notice_content' - Allow filtering of the content
153
  * of the update notice read from the release-info.json file.
@@ -160,7 +162,7 @@ class WPSEO_Admin_Init {
160
  if ( is_null( $release_info )
161
  || empty( $release_info->version )
162
  || version_compare( $this->get_major_minor_version( $release_info->version ), $current_minor_version, '!=' )
163
- || empty( $release_info->release_description )
164
  ) {
165
  $notification_center->remove_notification_by_id( 'wpseo-plugin-updated' );
166
  return;
@@ -171,7 +173,7 @@ class WPSEO_Admin_Init {
171
  // Restore notification if it was dismissed in a previous minor version.
172
  $last_dismissed_version = get_user_option( $notification->get_dismissal_key() );
173
  if ( ! $last_dismissed_version
174
- || version_compare( $this->get_major_minor_version( $last_dismissed_version ), $current_minor_version, '<' )
175
  ) {
176
  Yoast_Notification_Center::restore_notification( $notification );
177
  }
@@ -197,25 +199,25 @@ class WPSEO_Admin_Init {
197
  * @return Yoast_Notification The notification for the present version
198
  */
199
  private function get_yoast_seo_update_notification( $release_info ) {
200
- $info_message = '<strong>' .
201
- sprintf(
202
- /* translators: %1$s expands to Yoast SEO, %2$s expands to the plugin version. */
203
- __( 'New in %1$s %2$s: ', 'wordpress-seo' ),
204
- 'Yoast SEO',
205
- $release_info->version
206
- ) .
207
- '</strong>' .
208
- $release_info->release_description;
209
 
210
  if ( ! empty( $release_info->shortlink ) ) {
211
- $link = esc_url( WPSEO_Shortlinker::get( $release_info->shortlink ) );
212
- $info_message .= ' <a href="' . esc_url( $link ) . '" target="_blank">' .
213
- sprintf(
214
- /* translators: %s expands to the plugin version. */
215
- __( 'Read all about version %s here', 'wordpress-seo' ),
216
- $release_info->version
217
- ) .
218
- '</a>';
219
  }
220
 
221
  return new Yoast_Notification(
@@ -463,13 +465,13 @@ class WPSEO_Admin_Init {
463
  // Show notice for each deprecated filter or action that has been registered.
464
  foreach ( $deprecated_notices as $deprecated_filter ) {
465
  $deprecation_info = $deprecated_filters[ $deprecated_filter ];
466
- // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- only uses the hardcoded values from above.
467
  _deprecated_hook(
468
  $deprecated_filter,
469
  'WPSEO ' . $deprecation_info['version'],
470
  $deprecation_info['alternative']
471
  );
472
- // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped.
473
  }
474
  }
475
 
@@ -636,8 +638,7 @@ class WPSEO_Admin_Init {
636
  $blog_description = get_bloginfo( 'description' );
637
  $default_blog_description = 'Just another WordPress site';
638
 
639
- // We are checking against the WordPress internal translation.
640
- // @codingStandardsIgnoreLine
641
  $translated_blog_description = __( 'Just another WordPress site', 'default' );
642
 
643
  return $translated_blog_description === $blog_description || $default_blog_description === $blog_description;
139
  public function yoast_plugin_update_notification() {
140
  $notification_center = Yoast_Notification_Center::get();
141
  $current_minor_version = $this->get_major_minor_version( WPSEO_Options::get( 'version', WPSEO_VERSION ) );
142
+ $file = plugin_dir_path( WPSEO_FILE ) . 'release-info.json';
143
 
144
  // Remove if file is not present.
145
  if ( ! file_exists( $file ) ) {
147
  return;
148
  }
149
 
150
+ // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Retrieving a local file.
151
  $release_json = file_get_contents( $file );
152
+
153
  /**
154
  * Filter: 'wpseo_update_notice_content' - Allow filtering of the content
155
  * of the update notice read from the release-info.json file.
162
  if ( is_null( $release_info )
163
  || empty( $release_info->version )
164
  || version_compare( $this->get_major_minor_version( $release_info->version ), $current_minor_version, '!=' )
165
+ || empty( $release_info->release_description )
166
  ) {
167
  $notification_center->remove_notification_by_id( 'wpseo-plugin-updated' );
168
  return;
173
  // Restore notification if it was dismissed in a previous minor version.
174
  $last_dismissed_version = get_user_option( $notification->get_dismissal_key() );
175
  if ( ! $last_dismissed_version
176
+ || version_compare( $this->get_major_minor_version( $last_dismissed_version ), $current_minor_version, '<' )
177
  ) {
178
  Yoast_Notification_Center::restore_notification( $notification );
179
  }
199
  * @return Yoast_Notification The notification for the present version
200
  */
201
  private function get_yoast_seo_update_notification( $release_info ) {
202
+ $info_message = '<strong>';
203
+ $info_message .= sprintf(
204
+ /* translators: %1$s expands to Yoast SEO, %2$s expands to the plugin version. */
205
+ __( 'New in %1$s %2$s: ', 'wordpress-seo' ),
206
+ 'Yoast SEO',
207
+ $release_info->version
208
+ );
209
+ $info_message .= '</strong>';
210
+ $info_message .= $release_info->release_description;
211
 
212
  if ( ! empty( $release_info->shortlink ) ) {
213
+ $link = esc_url( WPSEO_Shortlinker::get( $release_info->shortlink ) );
214
+ $info_message .= ' <a href="' . esc_url( $link ) . '" target="_blank">';
215
+ $info_message .= sprintf(
216
+ /* translators: %s expands to the plugin version. */
217
+ __( 'Read all about version %s here', 'wordpress-seo' ),
218
+ $release_info->version
219
+ );
220
+ $info_message .= '</a>';
221
  }
222
 
223
  return new Yoast_Notification(
465
  // Show notice for each deprecated filter or action that has been registered.
466
  foreach ( $deprecated_notices as $deprecated_filter ) {
467
  $deprecation_info = $deprecated_filters[ $deprecated_filter ];
468
+ // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Only uses the hardcoded values from above.
469
  _deprecated_hook(
470
  $deprecated_filter,
471
  'WPSEO ' . $deprecation_info['version'],
472
  $deprecation_info['alternative']
473
  );
474
+ // phpcs:enable
475
  }
476
  }
477
 
638
  $blog_description = get_bloginfo( 'description' );
639
  $default_blog_description = 'Just another WordPress site';
640
 
641
+ // We are using the WordPress internal translation.
 
642
  $translated_blog_description = __( 'Just another WordPress site', 'default' );
643
 
644
  return $translated_blog_description === $blog_description || $default_blog_description === $blog_description;
admin/class-admin.php CHANGED
@@ -104,7 +104,6 @@ class WPSEO_Admin {
104
  $integrations[] = new WPSEO_Admin_Gutenberg_Compatibility_Notification();
105
  $integrations[] = new WPSEO_Expose_Shortlinks();
106
  $integrations[] = new WPSEO_MyYoast_Proxy();
107
- $integrations[] = new WPSEO_MyYoast_Route();
108
  $integrations[] = new WPSEO_Schema_Person_Upgrade_Notification();
109
  $integrations[] = new WPSEO_Tracking( 'https://tracking.yoast.com/stats', ( WEEK_IN_SECONDS * 2 ) );
110
  $integrations[] = new WPSEO_Admin_Settings_Changed_Listener();
@@ -170,7 +169,8 @@ class WPSEO_Admin {
170
  * Maps the manage_options cap on saving an options page to wpseo_manage_options.
171
  */
172
  public function map_manage_options_cap() {
173
- $option_page = ! empty( $_POST['option_page'] ) ? $_POST['option_page'] : ''; // WPCS: CSRF ok.
 
174
 
175
  if ( strpos( $option_page, 'yoast_wpseo' ) === 0 ) {
176
  add_filter( 'option_page_capability_' . $option_page, [ $this, 'get_manage_options_cap' ] );
104
  $integrations[] = new WPSEO_Admin_Gutenberg_Compatibility_Notification();
105
  $integrations[] = new WPSEO_Expose_Shortlinks();
106
  $integrations[] = new WPSEO_MyYoast_Proxy();
 
107
  $integrations[] = new WPSEO_Schema_Person_Upgrade_Notification();
108
  $integrations[] = new WPSEO_Tracking( 'https://tracking.yoast.com/stats', ( WEEK_IN_SECONDS * 2 ) );
109
  $integrations[] = new WPSEO_Admin_Settings_Changed_Listener();
169
  * Maps the manage_options cap on saving an options page to wpseo_manage_options.
170
  */
171
  public function map_manage_options_cap() {
172
+ // phpcs:ignore WordPress.Security -- The variable is only used in strpos and thus safe to not unslash or sanitize.
173
+ $option_page = ! empty( $_POST['option_page'] ) ? $_POST['option_page'] : '';
174
 
175
  if ( strpos( $option_page, 'yoast_wpseo' ) === 0 ) {
176
  add_filter( 'option_page_capability_' . $option_page, [ $this, 'get_manage_options_cap' ] );
admin/class-bulk-description-editor-list-table.php CHANGED
@@ -72,7 +72,7 @@ class WPSEO_Bulk_Description_List_Table extends WPSEO_Bulk_List_Table {
72
  // @todo Inconsistent return/echo behavior R.
73
  // I traced the escaping of the attributes to WPSEO_Bulk_List_Table::column_attributes. Alexander.
74
  // The output of WPSEO_Bulk_List_Table::parse_meta_data_field is properly escaped.
75
- // phpcs:ignore WordPress.Security.EscapeOutput
76
  echo $this->parse_meta_data_field( $record->ID, $attributes );
77
  break;
78
  }
72
  // @todo Inconsistent return/echo behavior R.
73
  // I traced the escaping of the attributes to WPSEO_Bulk_List_Table::column_attributes. Alexander.
74
  // The output of WPSEO_Bulk_List_Table::parse_meta_data_field is properly escaped.
75
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
76
  echo $this->parse_meta_data_field( $record->ID, $attributes );
77
  break;
78
  }
admin/class-bulk-editor-list-table.php CHANGED
@@ -914,7 +914,6 @@ class WPSEO_Bulk_List_Table extends WP_List_Table {
914
  $id = "wpseo-existing-$record_id-$this->target_db_field";
915
 
916
  // $attributes correctly escaped, verified by Alexander. See WPSEO_Bulk_Description_List_Table::parse_page_specific_column.
917
- // phpcs:ignore WordPress.Security.EscapeOutput
918
  return sprintf( '<td %2$s id="%3$s">%1$s</td>', esc_html( $meta_value ), $attributes, esc_attr( $id ) );
919
  }
920
 
914
  $id = "wpseo-existing-$record_id-$this->target_db_field";
915
 
916
  // $attributes correctly escaped, verified by Alexander. See WPSEO_Bulk_Description_List_Table::parse_page_specific_column.
 
917
  return sprintf( '<td %2$s id="%3$s">%1$s</td>', esc_html( $meta_value ), $attributes, esc_attr( $id ) );
918
  }
919
 
admin/class-gutenberg-compatibility.php CHANGED
@@ -15,14 +15,14 @@ class WPSEO_Gutenberg_Compatibility {
15
  *
16
  * @var string
17
  */
18
- const CURRENT_RELEASE = '8.8.0';
19
 
20
  /**
21
  * The minimally supported version of Gutenberg by the plugin.
22
  *
23
  * @var string
24
  */
25
- const MINIMUM_SUPPORTED = '8.8.0';
26
 
27
  /**
28
  * Holds the current version.
15
  *
16
  * @var string
17
  */
18
+ const CURRENT_RELEASE = '9.0.0';
19
 
20
  /**
21
  * The minimally supported version of Gutenberg by the plugin.
22
  *
23
  * @var string
24
  */
25
+ const MINIMUM_SUPPORTED = '9.0.0';
26
 
27
  /**
28
  * Holds the current version.
admin/class-meta-columns.php CHANGED
@@ -117,12 +117,12 @@ class WPSEO_Meta_Columns {
117
 
118
  switch ( $column_name ) {
119
  case 'wpseo-score':
120
- // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
121
  echo $this->parse_column_score( $post_id );
122
  return;
123
 
124
  case 'wpseo-score-readability':
125
- // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
126
  echo $this->parse_column_score_readability( $post_id );
127
  return;
128
 
@@ -221,11 +221,13 @@ class WPSEO_Meta_Columns {
221
  echo '<label class="screen-reader-text" for="wpseo-filter">' . esc_html__( 'Filter by SEO Score', 'wordpress-seo' ) . '</label>';
222
  echo '<select name="seo_filter" id="wpseo-filter">';
223
 
 
224
  echo $this->generate_option( '', __( 'All SEO Scores', 'wordpress-seo' ) );
225
 
226
  foreach ( $ranks as $rank ) {
227
  $selected = selected( $this->get_current_seo_filter(), $rank->get_rank(), false );
228
 
 
229
  echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_label(), $selected );
230
  }
231
 
@@ -247,11 +249,13 @@ class WPSEO_Meta_Columns {
247
  echo '<label class="screen-reader-text" for="wpseo-readability-filter">' . esc_html__( 'Filter by Readability Score', 'wordpress-seo' ) . '</label>';
248
  echo '<select name="readability_filter" id="wpseo-readability-filter">';
249
 
 
250
  echo $this->generate_option( '', __( 'All Readability Scores', 'wordpress-seo' ) );
251
 
252
  foreach ( $ranks as $rank ) {
253
  $selected = selected( $this->get_current_readability_filter(), $rank->get_rank(), false );
254
 
 
255
  echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_readability_labels(), $selected );
256
  }
257
 
117
 
118
  switch ( $column_name ) {
119
  case 'wpseo-score':
120
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
121
  echo $this->parse_column_score( $post_id );
122
  return;
123
 
124
  case 'wpseo-score-readability':
125
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in render_score_indicator() method.
126
  echo $this->parse_column_score_readability( $post_id );
127
  return;
128
 
221
  echo '<label class="screen-reader-text" for="wpseo-filter">' . esc_html__( 'Filter by SEO Score', 'wordpress-seo' ) . '</label>';
222
  echo '<select name="seo_filter" id="wpseo-filter">';
223
 
224
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
225
  echo $this->generate_option( '', __( 'All SEO Scores', 'wordpress-seo' ) );
226
 
227
  foreach ( $ranks as $rank ) {
228
  $selected = selected( $this->get_current_seo_filter(), $rank->get_rank(), false );
229
 
230
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
231
  echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_label(), $selected );
232
  }
233
 
249
  echo '<label class="screen-reader-text" for="wpseo-readability-filter">' . esc_html__( 'Filter by Readability Score', 'wordpress-seo' ) . '</label>';
250
  echo '<select name="readability_filter" id="wpseo-readability-filter">';
251
 
252
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
253
  echo $this->generate_option( '', __( 'All Readability Scores', 'wordpress-seo' ) );
254
 
255
  foreach ( $ranks as $rank ) {
256
  $selected = selected( $this->get_current_readability_filter(), $rank->get_rank(), false );
257
 
258
+ // phpcs:ignore WordPress.Security.EscapeOutput -- Output is correctly escaped in the generate_option() method.
259
  echo $this->generate_option( $rank->get_rank(), $rank->get_drop_down_readability_labels(), $selected );
260
  }
261
 
admin/class-my-yoast-route.php DELETED
@@ -1,317 +0,0 @@
1
- <?php
2
- /**
3
- * WPSEO plugin file.
4
- *
5
- * @package WPSEO\Admin
6
- */
7
-
8
- /**
9
- * Represents the route for MyYoast.
10
- */
11
- class WPSEO_MyYoast_Route implements WPSEO_WordPress_Integration {
12
-
13
- /**
14
- * The identifier of the page in the My Yoast route.
15
- *
16
- * @var string
17
- */
18
- const PAGE_IDENTIFIER = 'wpseo_myyoast';
19
-
20
- /**
21
- * The instance of the MyYoast client.
22
- *
23
- * @var WPSEO_MyYoast_Client
24
- */
25
- protected $client;
26
-
27
- /**
28
- * The actions that are supported.
29
- *
30
- * Each action should have a method named equally to the action.
31
- *
32
- * For example:
33
- * The connect action is handled by a method named 'connect'.
34
- *
35
- * @var array
36
- */
37
- protected static $allowed_actions = [ 'connect', 'authorize', 'complete' ];
38
-
39
- /**
40
- * Sets the hooks when the user has enough rights and is on the right page.
41
- *
42
- * @return void
43
- */
44
- public function register_hooks() {
45
- $route = filter_input( INPUT_GET, 'page' );
46
- if ( ! ( $this->is_myyoast_route( $route ) && $this->can_access_route() ) ) {
47
- return;
48
- }
49
-
50
- if ( ! $this->is_valid_action( $this->get_action() ) ) {
51
- return;
52
- }
53
-
54
- add_action( 'admin_menu', [ $this, 'register_route' ] );
55
- add_action( 'admin_init', [ $this, 'handle_route' ] );
56
- }
57
-
58
- /**
59
- * Registers the page for the MyYoast route.
60
- *
61
- * @codeCoverageIgnore
62
- *
63
- * @return void
64
- */
65
- public function register_route() {
66
- add_dashboard_page(
67
- '', // Is empty because we don't render a page.
68
- '', // Is empty because we don't want a menu item.
69
- 'wpseo_manage_options',
70
- self::PAGE_IDENTIFIER
71
- );
72
- }
73
-
74
- /**
75
- * Abstracts the action from the URL and follows the appropriate route.
76
- *
77
- * @return void
78
- */
79
- public function handle_route() {
80
- $action = $this->get_action();
81
-
82
- if ( ! $this->is_valid_action( $action ) || ! method_exists( $this, $action ) ) {
83
- return;
84
- }
85
-
86
- // Dynamically call the method.
87
- $this->$action();
88
- }
89
-
90
- /**
91
- * Checks if the current page is the MyYoast route.
92
- *
93
- * @param string $route The MyYoast route.
94
- *
95
- * @return bool True when url is the MyYoast route.
96
- */
97
- protected function is_myyoast_route( $route ) {
98
- return ( $route === self::PAGE_IDENTIFIER );
99
- }
100
-
101
- /**
102
- * Compares an action to a list of allowed actions to see if it is valid.
103
- *
104
- * @param string $action The action to check.
105
- *
106
- * @return bool True if the action is valid.
107
- */
108
- protected function is_valid_action( $action ) {
109
- return in_array( $action, self::$allowed_actions, true );
110
- }
111
-
112
- /**
113
- * Connects to MyYoast and generates a new clientId.
114
- *
115
- * @return void
116
- */
117
- protected function connect() {
118
- $client_id = $this->generate_uuid();
119
-
120
- $this->save_client_id( $client_id );
121
-
122
- $this->redirect(
123
- 'https://my.yoast.com/connect',
124
- [
125
- 'url' => WPSEO_Utils::get_home_url(),
126
- 'client_id' => $client_id,
127
- 'extensions' => $this->get_extensions(),
128
- 'redirect_url' => admin_url( 'admin.php?page=' . self::PAGE_IDENTIFIER . '&action=complete' ),
129
- 'credentials_url' => rest_url( 'yoast/v1/myyoast/connect' ),
130
- 'type' => 'wordpress',
131
- ]
132
- );
133
- }
134
-
135
- /**
136
- * Redirects the user to the oAuth authorization page.
137
- *
138
- * @return void
139
- */
140
- protected function authorize() {
141
- $client = $this->get_client();
142
-
143
- if ( ! $client->has_configuration() ) {
144
- return;
145
- }
146
-
147
- $this->redirect(
148
- $client->get_provider()->getAuthorizationUrl()
149
- );
150
- }
151
-
152
- /**
153
- * Completes the oAuth connection flow.
154
- *
155
- * @return void
156
- */
157
- protected function complete() {
158
- $client = $this->get_client();
159
-
160
- if ( ! $client->has_configuration() ) {
161
- return;
162
- }
163
-
164
- try {
165
- $access_token = $client
166
- ->get_provider()
167
- ->getAccessToken(
168
- 'authorization_code',
169
- [
170
- 'code' => $this->get_authorization_code(),
171
- ]
172
- );
173
-
174
- $client->save_access_token(
175
- $this->get_current_user_id(),
176
- $access_token
177
- );
178
- }
179
- // @codingStandardsIgnoreLine Generic.CodeAnalysis.EmptyStatement.DetectedCATCH -- There is nothing to do.
180
- catch ( Exception $e ) {
181
- // Do nothing.
182
- }
183
-
184
- $this->redirect_to_premium_page();
185
- }
186
-
187
- /**
188
- * Saves the client id.
189
- *
190
- * @codeCoverageIgnore
191
- *
192
- * @param string $client_id The client id to save.
193
- *
194
- * @return void
195
- */
196
- protected function save_client_id( $client_id ) {
197
- $this->get_client()->save_configuration(
198
- [
199
- 'clientId' => $client_id,
200
- ]
201
- );
202
- }
203
-
204
- /**
205
- * Creates a new MyYoast Client instance.
206
- *
207
- * @codeCoverageIgnore
208
- *
209
- * @return WPSEO_MyYoast_Client Instance of the myyoast client.
210
- */
211
- protected function get_client() {
212
- if ( ! $this->client ) {
213
- $this->client = new WPSEO_MyYoast_Client();
214
- }
215
-
216
- return $this->client;
217
- }
218
-
219
- /**
220
- * Abstracts the action from the url.
221
- *
222
- * @codeCoverageIgnore
223
- *
224
- * @return string The action from the url.
225
- */
226
- protected function get_action() {
227
- return filter_input( INPUT_GET, 'action' );
228
- }
229
-
230
- /**
231
- * Abstracts the authorization code from the url.
232
- *
233
- * @codeCoverageIgnore
234
- *
235
- * @return string The action from the url.
236
- */
237
- protected function get_authorization_code() {
238
- return filter_input( INPUT_GET, 'code' );
239
- }
240
-
241
- /**
242
- * Retrieves a list of activated extensions slugs.
243
- *
244
- * @codeCoverageIgnore
245
- *
246
- * @return array The extensions slugs.
247
- */
248
- protected function get_extensions() {
249
- $addon_manager = new WPSEO_Addon_Manager();
250
-
251
- return array_keys( $addon_manager->get_subscriptions_for_active_addons() );
252
- }
253
-
254
- /**
255
- * Generates an URL-encoded query string, redirects there.
256
- *
257
- * @codeCoverageIgnore
258
- *
259
- * @param string $url The url to redirect to.
260
- * @param array $query_args The additional arguments to build the url from.
261
- *
262
- * @return void
263
- */
264
- protected function redirect( $url, $query_args = [] ) {
265
- if ( ! empty( $query_args ) ) {
266
- $url .= '?' . http_build_query( $query_args );
267
- }
268
-
269
- wp_redirect( $url );
270
- exit;
271
- }
272
-
273
- /**
274
- * Checks if current user is allowed to access the route.
275
- *
276
- * @codeCoverageIgnore
277
- *
278
- * @return bool True when current user has rights to manage options.
279
- */
280
- protected function can_access_route() {
281
- return WPSEO_Utils::has_access_token_support() && current_user_can( 'wpseo_manage_options' );
282
- }
283
-
284
- /**
285
- * Generates an unique user id.
286
- *
287
- * @codeCoverageIgnore
288
- *
289
- * @return string The generated unique user id.
290
- */
291
- protected function generate_uuid() {
292
- return wp_generate_uuid4();
293
- }
294
-
295
- /**
296
- * Retrieves the current user id.
297
- *
298
- * @codeCoverageIgnore
299
- *
300
- * @return int The user id.
301
- */
302
- protected function get_current_user_id() {
303
- return get_current_user_id();
304
- }
305
-
306
- /**
307
- * Redirects to the premium page.
308
- *
309
- * @codeCoverageIgnore
310
- *
311
- * @return void
312
- */
313
- protected function redirect_to_premium_page() {
314
- wp_safe_redirect( admin_url( 'admin.php?page=wpseo_licenses' ) );
315
- exit;
316
- }
317
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin/class-premium-upsell-admin-block.php CHANGED
@@ -64,7 +64,7 @@ class WPSEO_Premium_Upsell_Admin_Block {
64
  $class = $this->get_html_class();
65
 
66
  /* translators: %s expands to Yoast SEO Premium */
67
- $button_text = esc_html( sprintf( __( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' ) );
68
  $button_text .= '<span class="screen-reader-text">' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>' .
69
  '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>';
70
 
@@ -85,9 +85,11 @@ class WPSEO_Premium_Upsell_Admin_Block {
85
  'Yoast SEO Premium'
86
  ) .
87
  '</h2>';
 
 
88
  echo '<ul class="' . esc_attr( $class . '--motivation' ) . '">' . $arguments_html . '</ul>';
89
 
90
- // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in $upgrade_button and $button_text above.
91
  echo '<p>' . $upgrade_button . '</p>';
92
  echo '</div>';
93
 
64
  $class = $this->get_html_class();
65
 
66
  /* translators: %s expands to Yoast SEO Premium */
67
+ $button_text = sprintf( esc_html__( 'Get %s', 'wordpress-seo' ), 'Yoast SEO Premium' );
68
  $button_text .= '<span class="screen-reader-text">' . esc_html__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>' .
69
  '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>';
70
 
85
  'Yoast SEO Premium'
86
  ) .
87
  '</h2>';
88
+
89
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in $this->get_argument_html() method.
90
  echo '<ul class="' . esc_attr( $class . '--motivation' ) . '">' . $arguments_html . '</ul>';
91
 
92
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Correctly escaped in $upgrade_button and $button_text above.
93
  echo '<p>' . $upgrade_button . '</p>';
94
  echo '</div>';
95
 
admin/class-yoast-network-admin.php CHANGED
@@ -113,15 +113,17 @@ class Yoast_Network_Admin implements WPSEO_WordPress_Integration, WPSEO_WordPres
113
  return;
114
  }
115
 
 
116
  foreach ( $whitelist_options as $option_name ) {
117
  $value = null;
118
- if ( isset( $_POST[ $option_name ] ) ) { // WPCS: CSRF ok.
119
  // Adding sanitize_text_field around this will break the saving of settings because it expects a string: https://github.com/Yoast/wordpress-seo/issues/12440.
120
- $value = wp_unslash( $_POST[ $option_name ] ); // WPCS: CSRF ok.
121
  }
122
 
123
  WPSEO_Options::update_site_option( $option_name, $value );
124
  }
 
125
 
126
  $settings_errors = get_settings_errors();
127
  if ( empty( $settings_errors ) ) {
@@ -141,7 +143,8 @@ class Yoast_Network_Admin implements WPSEO_WordPress_Integration, WPSEO_WordPres
141
 
142
  $option_group = 'wpseo_ms';
143
 
144
- $site_id = ! empty( $_POST[ $option_group ]['site_id'] ) ? (int) $_POST[ $option_group ]['site_id'] : 0; // WPCS: CSRF ok.
 
145
  if ( ! $site_id ) {
146
  add_settings_error( $option_group, 'settings_updated', __( 'No site has been selected to restore.', 'wordpress-seo' ), 'error' );
147
 
113
  return;
114
  }
115
 
116
+ // phpcs:disable WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
117
  foreach ( $whitelist_options as $option_name ) {
118
  $value = null;
119
+ if ( isset( $_POST[ $option_name ] ) ) {
120
  // Adding sanitize_text_field around this will break the saving of settings because it expects a string: https://github.com/Yoast/wordpress-seo/issues/12440.
121
+ $value = wp_unslash( $_POST[ $option_name ] );
122
  }
123
 
124
  WPSEO_Options::update_site_option( $option_name, $value );
125
  }
126
+ // phpcs:enable WordPress.Security.NonceVerification
127
 
128
  $settings_errors = get_settings_errors();
129
  if ( empty( $settings_errors ) ) {
143
 
144
  $option_group = 'wpseo_ms';
145
 
146
+ // phpcs:ignore WordPress.Security.NonceVerification -- Nonce verified via `verify_request()` above.
147
+ $site_id = ! empty( $_POST[ $option_group ]['site_id'] ) ? (int) $_POST[ $option_group ]['site_id'] : 0;
148
  if ( ! $site_id ) {
149
  add_settings_error( $option_group, 'settings_updated', __( 'No site has been selected to restore.', 'wordpress-seo' ), 'error' );
150
 
admin/class-yoast-notification-center.php CHANGED
@@ -164,9 +164,9 @@ class Yoast_Notification_Center {
164
  return true;
165
  }
166
 
167
- $dismissal_key = $notification->get_dismissal_key();
168
- $notification_id = $notification->get_id();
169
- $notification_json = $notification->get_json();
170
 
171
  $is_dismissing = ( $dismissal_key === self::get_user_input( 'notification' ) );
172
  if ( ! $is_dismissing ) {
164
  return true;
165
  }
166
 
167
+ $dismissal_key = $notification->get_dismissal_key();
168
+ $notification_id = $notification->get_id();
169
+ $notification_json = $notification->get_json();
170
 
171
  $is_dismissing = ( $dismissal_key === self::get_user_input( 'notification' ) );
172
  if ( ! $is_dismissing ) {
admin/config-ui/class-configuration-structure.php CHANGED
@@ -88,6 +88,7 @@ class WPSEO_Configuration_Structure {
88
  );
89
 
90
  $this->add_step( 'title-template', __( 'Title settings', 'wordpress-seo' ), $this->fields['titleTemplate'] );
 
91
  $this->add_step( 'tracking', sprintf( __( 'Help us improve %s', 'wordpress-seo' ), 'Yoast SEO' ), $this->fields['tracking'] );
92
  $this->add_step( 'newsletter', __( 'Continue learning', 'wordpress-seo' ), $this->fields['newsletter'], true, true );
93
  $this->add_step( 'success', __( 'Success!', 'wordpress-seo' ), $this->fields['success'], true, true );
88
  );
89
 
90
  $this->add_step( 'title-template', __( 'Title settings', 'wordpress-seo' ), $this->fields['titleTemplate'] );
91
+ /* translators: %s expands to Yoast SEO */
92
  $this->add_step( 'tracking', sprintf( __( 'Help us improve %s', 'wordpress-seo' ), 'Yoast SEO' ), $this->fields['tracking'] );
93
  $this->add_step( 'newsletter', __( 'Continue learning', 'wordpress-seo' ), $this->fields['newsletter'], true, true );
94
  $this->add_step( 'success', __( 'Success!', 'wordpress-seo' ), $this->fields['success'], true, true );
admin/config-ui/fields/class-field-tracking.php CHANGED
@@ -18,8 +18,8 @@ class WPSEO_Config_Field_Tracking extends WPSEO_Config_Field_Choice {
18
 
19
  $this->set_property( 'label', __( 'Can we collect anonymous information about your website and its usage?', 'wordpress-seo' ) );
20
 
21
- $this->add_choice( '0', __( 'No, I don\'t want to allow you to track my site data.', 'wordpress-seo' ) );
22
- $this->add_choice( '1', __( 'Yes, you can track my site\'s data!', 'wordpress-seo' ) );
23
  }
24
 
25
  /**
@@ -43,9 +43,9 @@ class WPSEO_Config_Field_Tracking extends WPSEO_Config_Field_Choice {
43
  public function get_data() {
44
  $tracking = WPSEO_Options::get( 'tracking' );
45
  if ( $tracking ) {
46
- return '1';
47
  }
48
- return '0';
49
  }
50
 
51
  /**
18
 
19
  $this->set_property( 'label', __( 'Can we collect anonymous information about your website and its usage?', 'wordpress-seo' ) );
20
 
21
+ $this->add_choice( 'no', __( 'No, I don\'t want to allow you to track my site data.', 'wordpress-seo' ) );
22
+ $this->add_choice( 'yes', __( 'Yes, you can track my site\'s data!', 'wordpress-seo' ) );
23
  }
24
 
25
  /**
43
  public function get_data() {
44
  $tracking = WPSEO_Options::get( 'tracking' );
45
  if ( $tracking ) {
46
+ return 'yes';
47
  }
48
+ return 'no';
49
  }
50
 
51
  /**
admin/endpoints/class-endpoint-indexable.php DELETED
@@ -1,102 +0,0 @@
1
- <?php
2
- /**
3
- * WPSEO plugin file.
4
- *
5
- * @package WPSEO\Admin\Endpoints
6
- */
7
-
8
- /**
9
- * Represents an implementation of the WPSEO_Endpoint interface to register one or multiple endpoints.
10
- */
11
- class WPSEO_Endpoint_Indexable implements WPSEO_Endpoint, WPSEO_Endpoint_Storable {
12
-
13
- /**
14
- * The namespace of the REST route.
15
- *
16
- * @var string
17
- */
18
- const REST_NAMESPACE = 'yoast/v1';
19
-
20
- /**
21
- * The route of the endpoint to retrieve or patch the indexable.
22
- *
23
- * @var string
24
- */
25
- const ENDPOINT_SINGULAR = 'indexables/(?P<object_type>\w+)/(?P<object_id>\d+)';
26
-
27
- /**
28
- * The name of the capability needed to retrieve data using the endpoints.
29
- *
30
- * @var string
31
- */
32
- const CAPABILITY_RETRIEVE = 'manage_options';
33
-
34
- /**
35
- * The name of the capability needed to store data using the endpoints.
36
- *
37
- * @var string
38
- */
39
- const CAPABILITY_STORE = 'manage_options';
40
-
41
- /**
42
- * The indexable service.
43
- *
44
- * @var WPSEO_Indexable_Service
45
- */
46
- private $service;
47
-
48
- /**
49
- * Sets the service provider.
50
- *
51
- * @param WPSEO_Indexable_Service $service The service provider.
52
- */
53
- public function __construct( WPSEO_Indexable_Service $service ) {
54
- $this->service = $service;
55
- }
56
-
57
- /**
58
- * Registers the routes for the endpoints.
59
- *
60
- * @return void
61
- */
62
- public function register() {
63
- $endpoints = [];
64
-
65
- $endpoints[] = new WPSEO_Endpoint_Factory(
66
- self::REST_NAMESPACE,
67
- self::ENDPOINT_SINGULAR,
68
- [ $this->service, 'get_indexable' ],
69
- [ $this, 'can_retrieve_data' ]
70
- );
71
-
72
- $endpoints[] = new WPSEO_Endpoint_Factory(
73
- self::REST_NAMESPACE,
74
- self::ENDPOINT_SINGULAR,
75
- [ $this->service, 'patch_indexable' ],
76
- [ $this, 'can_store_data' ],
77
- 'PATCH'
78
- );
79
-
80
- foreach ( $endpoints as $endpoint ) {
81
- $endpoint->register();
82
- }
83
- }
84
-
85
- /**
86
- * Determines whether or not data can be retrieved for the registered endpoints.
87
- *
88
- * @return bool Whether or not data can be retrieved.
89
- */
90
- public function can_retrieve_data() {
91
- return current_user_can( self::CAPABILITY_RETRIEVE );
92
- }
93
-
94
- /**
95
- * Determines whether or not data can be stored for the registered endpoints.
96
- *
97
- * @return bool Whether or not data can be stored.
98
- */
99
- public function can_store_data() {
100
- return current_user_can( self::CAPABILITY_STORE );
101
- }
102
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin/endpoints/interface-endpoint-storable.php DELETED
@@ -1,19 +0,0 @@
1
- <?php
2
- /**
3
- * WPSEO plugin file.
4
- *
5
- * @package WPSEO\Admin\Endpoints
6
- */
7
-
8
- /**
9
- * Dictates the required methods for a storable implementation.
10
- */
11
- interface WPSEO_Endpoint_Storable {
12
-
13
- /**
14
- * Determines whether or not data can be stored for the registered endpoints.
15
- *
16
- * @return bool Whether or not data can be stored.
17
- */
18
- public function can_store_data();
19
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
admin/import/plugins/class-abstract-plugin-importer.php CHANGED
@@ -43,8 +43,7 @@ abstract class WPSEO_Plugin_Importer {
43
  /**
44
  * Class constructor.
45
  */
46
- public function __construct() {
47
- }
48
 
49
  /**
50
  * Returns the string for the plugin we're importing from.
@@ -188,6 +187,7 @@ abstract class WPSEO_Plugin_Importer {
188
  // First we create a temp table with all the values for meta_key.
189
  $result = $wpdb->query(
190
  $wpdb->prepare(
 
191
  "CREATE TEMPORARY TABLE tmp_meta_table SELECT * FROM {$wpdb->postmeta} WHERE meta_key = %s",
192
  $old_key
193
  )
@@ -225,6 +225,7 @@ abstract class WPSEO_Plugin_Importer {
225
  $wpdb->query( "INSERT INTO {$wpdb->postmeta} SELECT * FROM tmp_meta_table" );
226
 
227
  // Now we drop our temporary table.
 
228
  $wpdb->query( 'DROP TEMPORARY TABLE IF EXISTS tmp_meta_table' );
229
 
230
  return true;
43
  /**
44
  * Class constructor.
45
  */
46
+ public function __construct() {}
 
47
 
48
  /**
49
  * Returns the string for the plugin we're importing from.
187
  // First we create a temp table with all the values for meta_key.
188
  $result = $wpdb->query(
189
  $wpdb->prepare(
190
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- This is intentional + temporary.
191
  "CREATE TEMPORARY TABLE tmp_meta_table SELECT * FROM {$wpdb->postmeta} WHERE meta_key = %s",
192
  $old_key
193
  )
225
  $wpdb->query( "INSERT INTO {$wpdb->postmeta} SELECT * FROM tmp_meta_table" );
226
 
227
  // Now we drop our temporary table.
228
+ // phpcs:ignore WordPress.DB.DirectDatabaseQuery.SchemaChange -- This is intentional + a temporary table.
229
  $wpdb->query( 'DROP TEMPORARY TABLE IF EXISTS tmp_meta_table' );
230
 
231
  return true;
admin/metabox/class-metabox.php CHANGED
@@ -6,6 +6,7 @@
6
  */
7
 
8
  use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
 
9
 
10
  /**
11
  * This class generates the metabox on the edit post / page as well as contains all page analysis functionality.
@@ -13,25 +14,25 @@ use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
13
  class WPSEO_Metabox extends WPSEO_Meta {
14
 
15
  /**
16
- * Whether or not the social tab is enabled for this metabox.
17
  *
18
  * @var bool
19
  */
20
  private $social_is_enabled;
21
 
22
  /**
23
- * An instance of the Metabox Analysis SEO class.
24
  *
25
  * @var WPSEO_Metabox_Analysis_SEO
26
  */
27
- protected $analysis_seo;
28
 
29
  /**
30
- * An instance of the Metabox Analysis Readability class.
31
  *
32
  * @var WPSEO_Metabox_Analysis_Readability
33
  */
34
- protected $analysis_readability;
35
 
36
  /**
37
  * The metabox editor object.
@@ -40,6 +41,13 @@ class WPSEO_Metabox extends WPSEO_Meta {
40
  */
41
  protected $editor;
42
 
 
 
 
 
 
 
 
43
  /**
44
  * Whether or not the advanced metadata is enabled.
45
  *
@@ -69,8 +77,8 @@ class WPSEO_Metabox extends WPSEO_Meta {
69
  $this->social_is_enabled = WPSEO_Options::get( 'opengraph', false ) || WPSEO_Options::get( 'twitter', false );
70
  $this->is_advanced_metadata_enabled = WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || WPSEO_Options::get( 'disableadvanced_meta' ) === false;
71
 
72
- $this->analysis_seo = new WPSEO_Metabox_Analysis_SEO();
73
- $this->analysis_readability = new WPSEO_Metabox_Analysis_Readability();
74
  }
75
 
76
  /**
@@ -251,23 +259,22 @@ class WPSEO_Metabox extends WPSEO_Meta {
251
  * @return array
252
  */
253
  public function get_metabox_script_data() {
254
- $post = $this->get_metabox_post();
255
  $permalink = '';
256
 
257
- if ( is_object( $post ) ) {
258
- $permalink = get_sample_permalink( $post->ID );
259
  $permalink = $permalink[0];
260
  }
261
 
262
  $post_formatter = new WPSEO_Metabox_Formatter(
263
- new WPSEO_Post_Metabox_Formatter( $post, [], $permalink )
264
  );
265
 
266
  $values = $post_formatter->get_values();
267
 
268
  /** This filter is documented in admin/filters/class-cornerstone-filter.php. */
269
  $post_types = apply_filters( 'wpseo_cornerstone_post_types', WPSEO_Post_Type::get_accessible_post_types() );
270
- if ( $values['cornerstoneActive'] && ! in_array( $post->post_type, $post_types, true ) ) {
271
  $values['cornerstoneActive'] = false;
272
  }
273
 
@@ -292,9 +299,7 @@ class WPSEO_Metabox extends WPSEO_Meta {
292
  * @return string String describing the current scope.
293
  */
294
  private function determine_scope() {
295
- $post_type = get_post_type( $this->get_metabox_post() );
296
-
297
- if ( $post_type === 'page' ) {
298
  return 'page';
299
  }
300
 
@@ -303,156 +308,118 @@ class WPSEO_Metabox extends WPSEO_Meta {
303
 
304
  /**
305
  * Outputs the meta box.
306
- *
307
- * @param WP_Post $post The post.
308
  */
309
- public function meta_box( $post ) {
310
-
311
- $content_sections = $this->get_content_sections( $post->post_type );
312
-
313
- echo '<div class="wpseo-metabox-content">';
314
- // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $this->get_product_title is considered safe.
315
- printf( '<div class="wpseo-metabox-menu"><ul role="tablist" class="yoast-aria-tabs" aria-label="%s">', $this->get_product_title() );
316
-
317
- foreach ( $content_sections as $content_section ) {
318
- if ( $content_section->name === 'premium' ) {
319
- continue;
320
- }
321
-
322
- $content_section->display_link();
323
- }
324
-
325
- echo '</ul></div>';
326
-
327
- foreach ( $content_sections as $content_section ) {
328
- $content_section->display_content();
329
- }
330
-
331
- echo '</div>';
332
  }
333
 
334
  /**
335
- * Returns the relevant metabox sections for the current view.
336
- *
337
- * @param string $post_type The post type.
338
  *
339
- * @return WPSEO_Metabox_Section[]
340
  */
341
- private function get_content_sections( $post_type ) {
342
- $content_sections = [];
343
-
344
- $content_sections[] = $this->get_seo_meta_section();
345
 
346
- if ( $this->analysis_readability->is_enabled() ) {
347
- $content_sections[] = $this->get_readability_meta_section();
348
- }
349
 
350
  if ( $this->is_advanced_metadata_enabled ) {
351
- $content_sections[] = $this->get_schema_meta_section( $post_type );
352
  }
353
 
354
- // Whether social is enabled.
 
355
  if ( $this->social_is_enabled ) {
356
- $content_sections[] = $this->get_social_meta_section();
357
  }
358
 
359
- $content_sections = array_merge( $content_sections, $this->get_additional_meta_sections() );
360
-
361
- return $content_sections;
 
 
 
362
  }
363
 
364
  /**
365
- * Returns the social section for the social previews.
366
  *
367
- * @return WPSEO_Metabox_Section
368
  */
369
- private function get_social_meta_section() {
370
- $content = '';
 
 
 
 
371
 
372
- $content .= $this->get_tab_content( 'social' );
 
 
 
373
 
374
- // Add react target.
375
- $content .= '<div id="wpseo-section-social"></div>';
376
 
377
- $link_content = '<span class="dashicons dashicons-share"></span>' . __( 'Social', 'wordpress-seo' );
378
 
379
- return new WPSEO_Metabox_Section_React(
380
- 'social',
381
- $link_content,
382
- $content
383
- );
384
  }
385
 
386
  /**
387
- * Returns the metabox section for the seo analysis.
388
  *
389
- * @return WPSEO_Metabox_Section
390
  */
391
- private function get_seo_meta_section() {
392
- wp_nonce_field( 'yoast_free_metabox', 'yoast_free_metabox_nonce' );
393
-
394
- $content = $this->get_tab_content( 'general' );
395
 
396
  $label = __( 'SEO', 'wordpress-seo' );
397
- if ( $this->analysis_seo->is_enabled() ) {
398
  $label = '<span class="wpseo-score-icon-container" id="wpseo-seo-score-icon"></span>' . $label;
399
  }
 
400
 
401
- $html_after = '';
 
 
402
 
403
  if ( $this->is_advanced_metadata_enabled ) {
404
- $html_after = $this->get_tab_content( 'advanced' );
 
 
 
 
405
  }
406
 
407
- /**
408
- * Filter: 'wpseo_content_meta_section_content' - Allow filtering the metabox content before outputting.
409
- *
410
- * @api string $post_content The metabox content string.
411
- */
412
- $content = apply_filters( 'wpseo_content_meta_section_content', $content );
413
-
414
- return new WPSEO_Metabox_Section_React(
415
- 'content',
416
- $label,
417
- $content,
418
- [
419
- 'html_after' => $html_after,
420
- ]
421
- );
422
- }
423
 
424
- /**
425
- * Returns the metabox section for the schema tab.
426
- *
427
- * @param string $post_type The post type.
428
- *
429
- * @return WPSEO_Metabox_Section_React
430
- */
431
- private function get_schema_meta_section( $post_type ) {
432
- $content = $this->get_tab_content( 'schema', $post_type );
433
- return new WPSEO_Metabox_Section_React(
434
- 'schema',
435
- '<span class="wpseo-schema-icon"></span>' . __( 'Schema', 'wordpress-seo' ),
436
- $content
437
- );
438
- }
439
 
440
- /**
441
- * Returns the metabox section for the readability analysis.
442
- *
443
- * @return WPSEO_Metabox_Section
444
- */
445
- private function get_readability_meta_section() {
446
- return new WPSEO_Metabox_Section_Readability();
447
  }
448
 
449
  /**
450
- * Returns the metabox sections that have been added by other plugins.
451
  *
452
  * @return WPSEO_Metabox_Section_Additional[]
453
  */
454
- protected function get_additional_meta_sections() {
455
- $sections = [];
456
 
457
  /**
458
  * Private filter: 'yoast_free_additional_metabox_sections'.
@@ -461,10 +428,10 @@ class WPSEO_Metabox extends WPSEO_Meta {
461
  *
462
  * @since 11.9
463
  *
464
- * @param array[] $sections {
465
  * An array of arrays with tab specifications.
466
  *
467
- * @type array $section {
468
  * A tab specification.
469
  *
470
  * @type string $name The name of the tab. Used in the HTML IDs, href and aria properties.
@@ -479,38 +446,21 @@ class WPSEO_Metabox extends WPSEO_Meta {
479
  * }
480
  * }
481
  */
482
- $requested_sections = apply_filters( 'yoast_free_additional_metabox_sections', [] );
483
-
484
- foreach ( $requested_sections as $section ) {
485
- if ( is_array( $section ) && array_key_exists( 'name', $section ) && array_key_exists( 'link_content', $section ) && array_key_exists( 'content', $section ) ) {
486
- $options = array_key_exists( 'options', $section ) ? $section['options'] : [];
487
- $sections[] = new WPSEO_Metabox_Section_Additional(
488
- $section['name'],
489
- $section['link_content'],
490
- $section['content'],
491
  $options
492
  );
493
  }
494
  }
495
 
496
- return $sections;
497
- }
498
-
499
- /**
500
- * Retrieves the contents for the metabox tab.
501
- *
502
- * @param string $tab_name Tab for which to retrieve the field definitions.
503
- * @param string $post_type The post type. Defaults to post.
504
- *
505
- * @return string
506
- */
507
- private function get_tab_content( $tab_name, $post_type = 'post' ) {
508
- $content = '';
509
- foreach ( WPSEO_Meta::get_meta_field_defs( $tab_name, $post_type ) as $key => $meta_field ) {
510
- $content .= $this->do_meta_box( $meta_field, $key );
511
- }
512
-
513
- return $content;
514
  }
515
 
516
  /**
@@ -822,11 +772,11 @@ class WPSEO_Metabox extends WPSEO_Meta {
822
  * @return bool Whether the given meta value key is disabled.
823
  */
824
  public function is_meta_value_disabled( $key ) {
825
- if ( $key === 'linkdex' && ! $this->analysis_seo->is_enabled() ) {
826
  return true;
827
  }
828
 
829
- if ( $key === 'content_score' && ! $this->analysis_readability->is_enabled() ) {
830
  return true;
831
  }
832
 
@@ -872,7 +822,7 @@ class WPSEO_Metabox extends WPSEO_Meta {
872
  $asset_manager->enqueue_style( 'select2' );
873
  $asset_manager->enqueue_style( 'monorepo' );
874
 
875
- $is_block_editor = WP_Screen::get()->is_block_editor();
876
  $post_edit_handle = 'post-edit';
877
  if ( ! $is_block_editor ) {
878
  $post_edit_handle = 'post-edit-classic';
@@ -946,16 +896,23 @@ class WPSEO_Metabox extends WPSEO_Meta {
946
  * @returns WP_Post|array
947
  */
948
  protected function get_metabox_post() {
 
 
 
 
949
  $post = filter_input( INPUT_GET, 'post' );
950
  if ( ! empty( $post ) ) {
951
  $post_id = (int) WPSEO_Utils::validate_int( $post );
952
 
953
- return get_post( $post_id );
954
- }
955
 
 
 
956
 
957
  if ( isset( $GLOBALS['post'] ) ) {
958
- return $GLOBALS['post'];
 
 
959
  }
960
 
961
  return [];
@@ -982,8 +939,6 @@ class WPSEO_Metabox extends WPSEO_Meta {
982
  * @return array Replace vars.
983
  */
984
  private function get_replace_vars() {
985
- $post = $this->get_metabox_post();
986
-
987
  $cached_replacement_vars = [];
988
 
989
  $vars_to_cache = [
@@ -997,11 +952,11 @@ class WPSEO_Metabox extends WPSEO_Meta {
997
  ];
998
 
999
  foreach ( $vars_to_cache as $var ) {
1000
- $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $post );
1001
  }
1002
 
1003
  // Merge custom replace variables with the WordPress ones.
1004
- return array_merge( $cached_replacement_vars, $this->get_custom_replace_vars( $post ) );
1005
  }
1006
 
1007
  /**
@@ -1011,10 +966,9 @@ class WPSEO_Metabox extends WPSEO_Meta {
1011
  */
1012
  private function get_recommended_replace_vars() {
1013
  $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
1014
- $post = $this->get_metabox_post();
1015
 
1016
  // What is recommended depends on the current context.
1017
- $post_type = $recommended_replace_vars->determine_for_post( $post );
1018
 
1019
  return $recommended_replace_vars->get_recommended_replacevars_for( $post_type );
1020
  }
6
  */
7
 
8
  use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter;
9
+ use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter;
10
 
11
  /**
12
  * This class generates the metabox on the edit post / page as well as contains all page analysis functionality.
14
  class WPSEO_Metabox extends WPSEO_Meta {
15
 
16
  /**
17
+ * Whether or not the social tab is enabled.
18
  *
19
  * @var bool
20
  */
21
  private $social_is_enabled;
22
 
23
  /**
24
+ * Helper to determine whether or not the SEO analysis is enabled.
25
  *
26
  * @var WPSEO_Metabox_Analysis_SEO
27
  */
28
+ protected $seo_analysis;
29
 
30
  /**
31
+ * Helper to determine whether or not the readability analysis is enabled.
32
  *
33
  * @var WPSEO_Metabox_Analysis_Readability
34
  */
35
+ protected $readability_analysis;
36
 
37
  /**
38
  * The metabox editor object.
41
  */
42
  protected $editor;
43
 
44
+ /**
45
+ * The Metabox post.
46
+ *
47
+ * @var WP_Post
48
+ */
49
+ protected $post = null;
50
+
51
  /**
52
  * Whether or not the advanced metadata is enabled.
53
  *
77
  $this->social_is_enabled = WPSEO_Options::get( 'opengraph', false ) || WPSEO_Options::get( 'twitter', false );
78
  $this->is_advanced_metadata_enabled = WPSEO_Capability_Utils::current_user_can( 'wpseo_edit_advanced_metadata' ) || WPSEO_Options::get( 'disableadvanced_meta' ) === false;
79
 
80
+ $this->seo_analysis = new WPSEO_Metabox_Analysis_SEO();
81
+ $this->readability_analysis = new WPSEO_Metabox_Analysis_Readability();
82
  }
83
 
84
  /**
259
  * @return array
260
  */
261
  public function get_metabox_script_data() {
 
262
  $permalink = '';
263
 
264
+ if ( is_object( $this->get_metabox_post() ) ) {
265
+ $permalink = get_sample_permalink( $this->get_metabox_post()->ID );
266
  $permalink = $permalink[0];
267
  }
268
 
269
  $post_formatter = new WPSEO_Metabox_Formatter(
270
+ new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink )
271
  );
272
 
273
  $values = $post_formatter->get_values();
274
 
275
  /** This filter is documented in admin/filters/class-cornerstone-filter.php. */
276
  $post_types = apply_filters( 'wpseo_cornerstone_post_types', WPSEO_Post_Type::get_accessible_post_types() );
277
+ if ( $values['cornerstoneActive'] && ! in_array( $this->get_metabox_post()->post_type, $post_types, true ) ) {
278
  $values['cornerstoneActive'] = false;
279
  }
280
 
299
  * @return string String describing the current scope.
300
  */
301
  private function determine_scope() {
302
+ if ( $this->get_metabox_post()->post_type === 'page' ) {
 
 
303
  return 'page';
304
  }
305
 
308
 
309
  /**
310
  * Outputs the meta box.
 
 
311
  */
312
+ public function meta_box() {
313
+ $this->render_hidden_fields();
314
+ $this->render_tabs();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
315
  }
316
 
317
  /**
318
+ * Renders the metabox hidden fields.
 
 
319
  *
320
+ * @return void
321
  */
322
+ protected function render_hidden_fields() {
323
+ wp_nonce_field( 'yoast_free_metabox', 'yoast_free_metabox_nonce' );
 
 
324
 
325
+ echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'general' );
 
 
326
 
327
  if ( $this->is_advanced_metadata_enabled ) {
328
+ echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'advanced' );
329
  }
330
 
331
+ echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'schema', $this->get_metabox_post()->post_type );
332
+
333
  if ( $this->social_is_enabled ) {
334
+ echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' );
335
  }
336
 
337
+ /**
338
+ * Filter: 'wpseo_content_meta_section_content' - Allow filtering the metabox content before outputting.
339
+ *
340
+ * @api string $post_content The metabox content string.
341
+ */
342
+ echo apply_filters( 'wpseo_content_meta_section_content', '' );
343
  }
344
 
345
  /**
346
+ * Renders the metabox tabs.
347
  *
348
+ * @return void
349
  */
350
+ protected function render_tabs() {
351
+ echo '<div class="wpseo-metabox-content">';
352
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: $this->get_product_title() returns a hard-coded string.
353
+ printf( '<div class="wpseo-metabox-menu"><ul role="tablist" class="yoast-aria-tabs" aria-label="%s">', $this->get_product_title() );
354
+
355
+ $tabs = $this->get_tabs();
356
 
357
+ foreach ( $tabs as $tab ) {
358
+ if ( $tab->name === 'premium' ) {
359
+ continue;
360
+ }
361
 
362
+ $tab->display_link();
363
+ }
364
 
365
+ echo '</ul></div>';
366
 
367
+ foreach ( $tabs as $tab ) {
368
+ $tab->display_content();
369
+ }
370
+
371
+ echo '</div>';
372
  }
373
 
374
  /**
375
+ * Returns the relevant metabox tabs for the current view.
376
  *
377
+ * @return WPSEO_Metabox_Section[]
378
  */
379
+ private function get_tabs() {
380
+ $tabs = [];
 
 
381
 
382
  $label = __( 'SEO', 'wordpress-seo' );
383
+ if ( $this->seo_analysis->is_enabled() ) {
384
  $label = '<span class="wpseo-score-icon-container" id="wpseo-seo-score-icon"></span>' . $label;
385
  }
386
+ $tabs[] = new WPSEO_Metabox_Section_React( 'content', $label );
387
 
388
+ if ( $this->readability_analysis->is_enabled() ) {
389
+ $tabs[] = new WPSEO_Metabox_Section_Readability();
390
+ }
391
 
392
  if ( $this->is_advanced_metadata_enabled ) {
393
+ $tabs[] = new WPSEO_Metabox_Section_React(
394
+ 'schema',
395
+ '<span class="wpseo-schema-icon"></span>' . __( 'Schema', 'wordpress-seo' ),
396
+ ''
397
+ );
398
  }
399
 
400
+ if ( $this->social_is_enabled ) {
401
+ $tabs[] = new WPSEO_Metabox_Section_React(
402
+ 'social',
403
+ '<span class="dashicons dashicons-share"></span>' . __( 'Social', 'wordpress-seo' ),
404
+ '',
405
+ [
406
+ 'html_after' => '<div id="wpseo-section-social"></div>',
407
+ ]
408
+ );
409
+ }
 
 
 
 
 
 
410
 
411
+ $tabs = array_merge( $tabs, $this->get_additional_tabs() );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
+ return $tabs;
 
 
 
 
 
 
414
  }
415
 
416
  /**
417
+ * Returns the metabox tabs that have been added by other plugins.
418
  *
419
  * @return WPSEO_Metabox_Section_Additional[]
420
  */
421
+ protected function get_additional_tabs() {
422
+ $tabs = [];
423
 
424
  /**
425
  * Private filter: 'yoast_free_additional_metabox_sections'.
428
  *
429
  * @since 11.9
430
  *
431
+ * @param array[] $tabs {
432
  * An array of arrays with tab specifications.
433
  *
434
+ * @type array $tab {
435
  * A tab specification.
436
  *
437
  * @type string $name The name of the tab. Used in the HTML IDs, href and aria properties.
446
  * }
447
  * }
448
  */
449
+ $requested_tabs = apply_filters( 'yoast_free_additional_metabox_sections', [] );
450
+
451
+ foreach ( $requested_tabs as $tab ) {
452
+ if ( is_array( $tab ) && array_key_exists( 'name', $tab ) && array_key_exists( 'link_content', $tab ) && array_key_exists( 'content', $tab ) ) {
453
+ $options = array_key_exists( 'options', $tab ) ? $tab['options'] : [];
454
+ $tabs[] = new WPSEO_Metabox_Section_Additional(
455
+ $tab['name'],
456
+ $tab['link_content'],
457
+ $tab['content'],
458
  $options
459
  );
460
  }
461
  }
462
 
463
+ return $tabs;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
464
  }
465
 
466
  /**
772
  * @return bool Whether the given meta value key is disabled.
773
  */
774
  public function is_meta_value_disabled( $key ) {
775
+ if ( $key === 'linkdex' && ! $this->seo_analysis->is_enabled() ) {
776
  return true;
777
  }
778
 
779
+ if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) {
780
  return true;
781
  }
782
 
822
  $asset_manager->enqueue_style( 'select2' );
823
  $asset_manager->enqueue_style( 'monorepo' );
824
 
825
+ $is_block_editor = WP_Screen::get()->is_block_editor();
826
  $post_edit_handle = 'post-edit';
827
  if ( ! $is_block_editor ) {
828
  $post_edit_handle = 'post-edit-classic';
896
  * @returns WP_Post|array
897
  */
898
  protected function get_metabox_post() {
899
+ if ( $this->post !== null ) {
900
+ return $this->post;
901
+ }
902
+
903
  $post = filter_input( INPUT_GET, 'post' );
904
  if ( ! empty( $post ) ) {
905
  $post_id = (int) WPSEO_Utils::validate_int( $post );
906
 
907
+ $this->post = get_post( $post_id );
 
908
 
909
+ return $this->post;
910
+ }
911
 
912
  if ( isset( $GLOBALS['post'] ) ) {
913
+ $this->post = $GLOBALS['post'];
914
+
915
+ return $this->post;
916
  }
917
 
918
  return [];
939
  * @return array Replace vars.
940
  */
941
  private function get_replace_vars() {
 
 
942
  $cached_replacement_vars = [];
943
 
944
  $vars_to_cache = [
952
  ];
953
 
954
  foreach ( $vars_to_cache as $var ) {
955
+ $cached_replacement_vars[ $var ] = wpseo_replace_vars( '%%' . $var . '%%', $this->get_metabox_post() );
956
  }
957
 
958
  // Merge custom replace variables with the WordPress ones.
959
+ return array_merge( $cached_replacement_vars, $this->get_custom_replace_vars( $this->get_metabox_post() ) );
960
  }
961
 
962
  /**
966
  */
967
  private function get_recommended_replace_vars() {
968
  $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars();
 
969
 
970
  // What is recommended depends on the current context.
971
+ $post_type = $recommended_replace_vars->determine_for_post( $this->get_metabox_post() );
972
 
973
  return $recommended_replace_vars->get_recommended_replacevars_for( $post_type );
974
  }
admin/roles/class-role-manager-wp.php CHANGED
@@ -29,7 +29,6 @@ final class WPSEO_Role_Manager_WP extends WPSEO_Abstract_Role_Manager {
29
  return;
30
  }
31
 
32
- // @codingStandardsIgnoreLine
33
  add_role( $role, $display_name, $capabilities );
34
  }
35
 
29
  return;
30
  }
31
 
 
32
  add_role( $role, $display_name, $capabilities );
33
  }
34
 
admin/services/class-indexable-post-provider.php DELETED
@@ -1,232 +0,0 @@
1
- <?php
2
- /**
3
- * WPSEO plugin file.
4
- *
5
- * @package WPSEO\Admin\Services
6
- */
7
-
8
- /**
9
- * Represents the indexable post service.
10
- */
11
- class WPSEO_Indexable_Service_Post_Provider extends WPSEO_Indexable_Provider {
12
-
13
- /**
14
- * List of fields that need to be renamed.
15
- *
16
- * @var array
17
- */
18
- protected $renameable_fields = [
19
- 'description' => 'metadesc',
20
- 'breadcrumb_title' => 'bctitle',
21
- 'og_title' => 'opengraph-title',
22
- 'og_description' => 'opengraph-description',
23
- 'og_image' => 'opengraph-image',
24
- 'twitter_title' => 'twitter-title',
25
- 'twitter_description' => 'twitter-description',
26
- 'twitter_image' => 'twitter-image',
27
- 'is_robots_noindex' => 'meta-robots-noindex',
28
- 'is_robots_nofollow' => 'meta-robots-nofollow',
29
- 'primary_focus_keyword' => 'focuskw',
30
- 'primary_focus_keyword_score' => 'linkdex',
31
- 'readability_score' => 'content_score',
32
- ];
33
-
34
- /**
35
- * Returns an array with data for the target object.
36
- *
37
- * @param integer $object_id The target object id.
38
- * @param bool $as_object Optional. Whether or not to return the indexable
39
- * as an object. Defaults to false.
40
- *
41
- * @return array|WPSEO_Post_Indexable The retrieved data. Defaults to an array format.
42
- *
43
- * @throws WPSEO_Invalid_Argument_Exception The invalid argument exception.
44
- */
45
- public function get( $object_id, $as_object = false ) {
46
- if ( ! $this->is_indexable( $object_id ) ) {
47
- return [];
48
- }
49
-
50
- $indexable = WPSEO_Post_Indexable::from_object( $object_id );
51
-
52
- if ( $as_object === true ) {
53
- return $indexable;
54
- }
55
-
56
- return $indexable->to_array();
57
- }
58
-
59
- /**
60
- * Handles the patching of values for an existing indexable.
61
- *
62
- * @param int $object_id The ID of the object.
63
- * @param array $requestdata The request data to store.
64
- *
65
- * @return array The patched indexable.
66
- *
67
- * @throws WPSEO_Invalid_Indexable_Exception The invalid argument exception.
68
- * @throws WPSEO_REST_Request_Exception Exception that is thrown if patching the object has failed.
69
- */
70
- public function patch( $object_id, $requestdata ) {
71
- $indexable = $this->get( $object_id, true );
72
-
73
- if ( $indexable === [] ) {
74
- throw WPSEO_Invalid_Indexable_Exception::non_existing_indexable( $object_id );
75
- }
76
-
77
- $new_indexable = $indexable->update( $requestdata );
78
- $stored_indexable = $this->store_indexable( $new_indexable );
79
-
80
- if ( $stored_indexable === true ) {
81
- return $new_indexable->to_array();
82
- }
83
-
84
- throw WPSEO_REST_Request_Exception::patch( 'Post', $object_id );
85
- }
86
-
87
- /**
88
- * Stores the indexable object.
89
- *
90
- * @param WPSEO_Indexable $indexable The indexable object to store.
91
- *
92
- * @return bool True if saving was successful.
93
- */
94
- protected function store_indexable( WPSEO_Indexable $indexable ) {
95
- $values = $this->convert_indexable_data( $indexable->to_array() );
96
- $renamed_values = $this->rename_indexable_data( $values );
97
-
98
- foreach ( $renamed_values as $key => $item ) {
99
- WPSEO_Meta::set_value( $key, $item, $values['object_id'] );
100
- }
101
-
102
- return true;
103
- }
104
-
105
- /**
106
- * Checks if the given object id belongs to an indexable.
107
- *
108
- * @param int $object_id The object id.
109
- *
110
- * @return bool Whether the object id is indexable.
111
- */
112
- public function is_indexable( $object_id ) {
113
- if ( get_post( $object_id ) === null ) {
114
- return false;
115
- }
116
-
117
- if ( wp_is_post_autosave( $object_id ) ) {
118
- return false;
119
- }
120
-
121
- if ( wp_is_post_revision( $object_id ) ) {
122
- return false;
123
- }
124
-
125
- return true;
126
- }
127
-
128
- /**
129
- * Converts some of the indexable data to its database variant.
130
- *
131
- * @param array $indexable_data The indexable data to convert.
132
- *
133
- * @return array The converted indexable data.
134
- */
135
- protected function convert_indexable_data( $indexable_data ) {
136
- if ( WPSEO_Validator::key_exists( $indexable_data, 'is_robots_nofollow' ) ) {
137
- $indexable_data['is_robots_nofollow'] = $this->convert_nofollow( $indexable_data['is_robots_nofollow'] );
138
- }
139
-
140
- if ( WPSEO_Validator::key_exists( $indexable_data, 'is_robots_noindex' ) ) {
141
- $indexable_data['is_robots_noindex'] = $this->convert_noindex( $indexable_data['is_robots_noindex'] );
142
- }
143
-
144
- if ( WPSEO_Validator::key_exists( $indexable_data, 'is_cornerstone' ) ) {
145
- $indexable_data['is_cornerstone'] = $this->convert_cornerstone( $indexable_data['is_cornerstone'] );
146
- }
147
-
148
- $indexable_data['meta-robots-adv'] = $this->convert_advanced( $indexable_data );
149
-
150
- return $indexable_data;
151
- }
152
-
153
- /**
154
- * Converts the cornerstone value to its database variant.
155
- *
156
- * @param string $cornerstone_value The cornerstone value.
157
- *
158
- * @return string The converted indexable cornerstone value.
159
- */
160
- protected function convert_cornerstone( $cornerstone_value ) {
161
- if ( $cornerstone_value === 'true' ) {
162
- return '1';
163
- }
164
-
165
- return null;
166
- }
167
-
168
- /**
169
- * Converts the advanced meta settings to its database variant.
170
- *
171
- * @param array $indexable_data The indexable data to convert the advanced meta settings from.
172
- *
173
- * @return string The converted advanced meta settings.
174
- */
175
- protected function convert_advanced( &$indexable_data ) {
176
- $translated_advanced_data = [];
177
-
178
- if ( WPSEO_Validator::key_exists( $indexable_data, 'is_robots_nosnippet' ) && (bool) $indexable_data['is_robots_nosnippet'] === true ) {
179
- $translated_advanced_data[] = 'nosnippet';
180
-
181
- unset( $indexable_data['is_robots_nosnippet'] );
182
- }
183
-
184
- if ( WPSEO_Validator::key_exists( $indexable_data, 'is_robots_noarchive' ) && (bool) $indexable_data['is_robots_noarchive'] === true ) {
185
- $translated_advanced_data[] = 'noarchive';
186
-
187
- unset( $indexable_data['is_robots_noarchive'] );
188
- }
189
-
190
- if ( WPSEO_Validator::key_exists( $indexable_data, 'is_robots_noimageindex' ) && (bool) $indexable_data['is_robots_noimageindex'] === true ) {
191
- $translated_advanced_data[] = 'noimageindex';
192
-
193
- unset( $indexable_data['is_robots_noimageindex'] );
194
- }
195
-
196
- return implode( ',', $translated_advanced_data );
197
- }
198
-
199
- /**
200
- * Converts the nofollow value to a database compatible one.
201
- *
202
- * @param bool $nofollow The current nofollow value.
203
- *
204
- * @return string The converted value.
205
- */
206
- protected function convert_nofollow( $nofollow ) {
207
- if ( $nofollow === 'true' ) {
208
- return '1';
209
- }
210
-
211
- return '0';
212
- }
213
-
214
- /**
215
- * Converts the noindex value to a database compatible one.
216
- *
217
- * @param string $noindex The current noindex value.
218
- *
219
- * @return string|null The converted value.
220
- */
221
- protected function convert_noindex( $noindex ) {
222
- if ( $noindex === 'false' ) {
223
- return '2';
224
- }
225
-
226
- if ( $noindex === 'true' ) {
227
- return '1';
228
- }
229
-
230
- return null;
231
- }
232
- }