Yoast SEO - Version 19.11

Version Description

Release date: November 29th, 2022

Yoast SEO 19.11 is out now. We're optimizing the Yoast SEO plugin to use fewer resources. This helps make your site faster and more efficient. In this release, we're doing this by streamlining your database. Find out more about what's new in Yoast SEO 19.11 in our release post!

Enhancements

  • Adds a WP-CLI command to clean up unused data from our custom database tables: wp yoast cleanup.
  • Performs a cleanup of indexables when a public post type (or taxonomy) becomes non-public.
  • Notifies users to run the SEO optimization when a non-public post type (or taxonomy) becomes public.

Bugfixes

  • Fixes a bug where a fatal error would be thrown when the SEO optimization was run after a post type had been manually excluded via a filter.
  • Fixes a bug where an entry would be added to our indexables table when saving, updating, or accessing a post (or term) for a non-public post type (or taxonomy).
  • Fixes a bug where duplicate indexable records would be created for the same object.
  • Fixes a bug where indexables for users would not get removed when a user did not have any publicly viewable posts anymore.
  • Fixes a bug where indexables for users would not get removed when author archives were disabled.
  • Fixes a bug where indexables would be created for users when author archives were disabled.
  • Fixes a bug where indexables would be created for users who did not have any publicly viewable posts.

Other

  • Introduces the wpseo_indexable_excluded_taxonomies filter, to allow manually excluding taxonomies from being indexed.
Download this release

Release Info

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

Code changes from version 19.10 to 19.11

Files changed (105) hide show
  1. admin/class-gutenberg-compatibility.php +2 -2
  2. admin/views/tabs/metas/post-types.php +3 -0
  3. css/dist/{admin-global-19100-rtl.css → admin-global-19110-rtl.css} +0 -0
  4. css/dist/{admin-global-19100.css → admin-global-19110.css} +0 -0
  5. css/dist/{adminbar-19100-rtl.css → adminbar-19110-rtl.css} +0 -0
  6. css/dist/{adminbar-19100.css → adminbar-19110.css} +0 -0
  7. css/dist/{alerts-19100-rtl.css → alerts-19110-rtl.css} +0 -0
  8. css/dist/{alerts-19100.css → alerts-19110.css} +0 -0
  9. css/dist/{dashboard-19100-rtl.css → dashboard-19110-rtl.css} +0 -0
  10. css/dist/{dashboard-19100.css → dashboard-19110.css} +0 -0
  11. css/dist/{edit-page-19100-rtl.css → edit-page-19110-rtl.css} +0 -0
  12. css/dist/{edit-page-19100.css → edit-page-19110.css} +0 -0
  13. css/dist/{elementor-19100-rtl.css → elementor-19110-rtl.css} +0 -0
  14. css/dist/{elementor-19100.css → elementor-19110.css} +0 -0
  15. css/dist/{featured-image-19100-rtl.css → featured-image-19110-rtl.css} +0 -0
  16. css/dist/{featured-image-19100.css → featured-image-19110.css} +0 -0
  17. css/dist/{filter-explanation-19100-rtl.css → filter-explanation-19110-rtl.css} +0 -0
  18. css/dist/{filter-explanation-19100.css → filter-explanation-19110.css} +0 -0
  19. css/dist/{icons-19100-rtl.css → icons-19110-rtl.css} +0 -0
  20. css/dist/{icons-19100.css → icons-19110.css} +0 -0
  21. css/dist/{inside-editor-19100-rtl.css → inside-editor-19110-rtl.css} +0 -0
  22. css/dist/{inside-editor-19100.css → inside-editor-19110.css} +0 -0
  23. css/dist/{metabox-19100-rtl.css → metabox-19110-rtl.css} +0 -0
  24. css/dist/{metabox-19100.css → metabox-19110.css} +0 -0
  25. css/dist/{metabox-primary-category-19100-rtl.css → metabox-primary-category-19110-rtl.css} +0 -0
  26. css/dist/{metabox-primary-category-19100.css → metabox-primary-category-19110.css} +0 -0
  27. css/dist/{modal-19100-rtl.css → modal-19110-rtl.css} +0 -0
  28. css/dist/{modal-19100.css → modal-19110.css} +0 -0
  29. css/dist/{monorepo-19100-rtl.css → monorepo-19110-rtl.css} +0 -0
  30. css/dist/{monorepo-19100.css → monorepo-19110.css} +0 -0
  31. css/dist/{new-settings-19100-rtl.css → new-settings-19110-rtl.css} +0 -0
  32. css/dist/{new-settings-19100.css → new-settings-19110.css} +0 -0
  33. css/dist/{notifications-19100-rtl.css → notifications-19110-rtl.css} +0 -0
  34. css/dist/{notifications-19100.css → notifications-19110.css} +0 -0
  35. css/dist/{notifications-new-19100-rtl.css → notifications-new-19110-rtl.css} +0 -0
  36. css/dist/{notifications-new-19100.css → notifications-new-19110.css} +0 -0
  37. css/dist/{schema-blocks-19100-rtl.css → schema-blocks-19110-rtl.css} +0 -0
  38. css/dist/{schema-blocks-19100.css → schema-blocks-19110.css} +0 -0
  39. css/dist/{score_icon-19100-rtl.css → score_icon-19110-rtl.css} +0 -0
  40. css/dist/{score_icon-19100.css → score_icon-19110.css} +0 -0
  41. css/dist/{search-appearance-19100-rtl.css → search-appearance-19110-rtl.css} +0 -0
  42. css/dist/{search-appearance-19100.css → search-appearance-19110.css} +0 -0
  43. css/dist/{structured-data-blocks-19100-rtl.css → structured-data-blocks-19110-rtl.css} +0 -0
  44. css/dist/{structured-data-blocks-19100.css → structured-data-blocks-19110.css} +0 -0
  45. css/dist/{tailwind-19100-rtl.css → tailwind-19110-rtl.css} +0 -0
  46. css/dist/{tailwind-19100.css → tailwind-19110.css} +0 -0
  47. css/dist/{toggle-switch-19100-rtl.css → toggle-switch-19110-rtl.css} +0 -0
  48. css/dist/{toggle-switch-19100.css → toggle-switch-19110.css} +0 -0
  49. css/dist/{tooltips-19100-rtl.css → tooltips-19110-rtl.css} +0 -0
  50. css/dist/{tooltips-19100.css → tooltips-19110.css} +0 -0
  51. css/dist/{workouts-19100-rtl.css → workouts-19110-rtl.css} +0 -0
  52. css/dist/{workouts-19100.css → workouts-19110.css} +0 -0
  53. css/dist/{wpseo-dismissible-19100-rtl.css → wpseo-dismissible-19110-rtl.css} +0 -0
  54. css/dist/{wpseo-dismissible-19100.css → wpseo-dismissible-19110.css} +0 -0
  55. css/dist/{yoast-components-19100-rtl.css → yoast-components-19110-rtl.css} +0 -0
  56. css/dist/{yoast-components-19100.css → yoast-components-19110.css} +0 -0
  57. css/dist/{yoast-extensions-19100-rtl.css → yoast-extensions-19110-rtl.css} +0 -0
  58. css/dist/{yoast-extensions-19100.css → yoast-extensions-19110.css} +0 -0
  59. css/dist/{yst_plugin_tools-19100-rtl.css → yst_plugin_tools-19110-rtl.css} +0 -0
  60. css/dist/{yst_plugin_tools-19100.css → yst_plugin_tools-19110.css} +0 -0
  61. css/dist/{yst_seo_score-19100-rtl.css → yst_seo_score-19110-rtl.css} +0 -0
  62. css/dist/{yst_seo_score-19100.css → yst_seo_score-19110.css} +0 -0
  63. inc/class-post-type.php +20 -0
  64. inc/class-upgrade.php +241 -3
  65. inc/options/class-wpseo-option-wpseo.php +4 -0
  66. inc/sitemaps/class-author-sitemap-provider.php +1 -3
  67. readme.txt +33 -33
  68. src/actions/indexing/indexable-post-indexation-action.php +2 -15
  69. src/actions/indexing/indexable-term-indexation-action.php +5 -4
  70. src/actions/indexing/post-link-indexing-action.php +2 -2
  71. src/actions/indexing/term-link-indexing-action.php +5 -4
  72. src/builders/indexable-author-builder.php +39 -0
  73. src/builders/indexable-builder.php +52 -23
  74. src/builders/indexable-post-builder.php +4 -2
  75. src/builders/indexable-term-builder.php +8 -1
  76. src/commands/cleanup-command.php +196 -0
  77. src/config/indexing-reasons.php +10 -0
  78. src/exceptions/indexable/author-not-built-exception.php +51 -0
  79. src/exceptions/indexable/not-built-exception.php +23 -0
  80. src/exceptions/indexable/post-not-built-exception.php +34 -0
  81. src/exceptions/indexable/term-not-built-exception.php +22 -0
  82. src/generated/container.php +71 -7
  83. src/generators/schema/breadcrumb.php +4 -0
  84. src/generators/schema/webpage.php +3 -0
  85. src/helpers/author-archive-helper.php +66 -0
  86. src/helpers/post-helper.php +22 -3
  87. src/helpers/post-type-helper.php +13 -0
  88. src/helpers/taxonomy-helper.php +47 -0
  89. src/integrations/admin/indexables-exclude-taxonomy-integration.php +53 -0
  90. src/integrations/cleanup-integration.php +207 -1
  91. src/integrations/watchers/indexable-author-archive-watcher.php +58 -0
  92. src/integrations/watchers/indexable-post-type-change-watcher.php +184 -0
  93. src/integrations/watchers/indexable-post-watcher.php +35 -4
  94. src/integrations/watchers/indexable-taxonomy-change-watcher.php +186 -0
  95. src/presentations/indexable-term-archive-presentation.php +5 -1
  96. src/presenters/admin/indexing-notification-presenter.php +6 -0
  97. src/routes/indexing-route.php +0 -2
  98. src/routes/yoast-head-rest-field.php +2 -2
  99. vendor/autoload.php +1 -1
  100. vendor/composer/autoload_classmap.php +9 -0
  101. vendor/composer/autoload_real.php +4 -4
  102. vendor/composer/autoload_static.php +13 -4
  103. vendor/composer/installed.php +2 -2
  104. wp-seo-main.php +2 -2
  105. wp-seo.php +1 -1
admin/class-gutenberg-compatibility.php CHANGED
@@ -15,14 +15,14 @@ class WPSEO_Gutenberg_Compatibility {
15
  *
16
  * @var string
17
  */
18
- const CURRENT_RELEASE = '14.4.0';
19
 
20
  /**
21
  * The minimally supported version of Gutenberg by the plugin.
22
  *
23
  * @var string
24
  */
25
- const MINIMUM_SUPPORTED = '14.4.0';
26
 
27
  /**
28
  * Holds the current version.
15
  *
16
  * @var string
17
  */
18
+ const CURRENT_RELEASE = '14.5.0';
19
 
20
  /**
21
  * The minimally supported version of Gutenberg by the plugin.
22
  *
23
  * @var string
24
  */
25
+ const MINIMUM_SUPPORTED = '14.5.0';
26
 
27
  /**
28
  * Holds the current version.
admin/views/tabs/metas/post-types.php CHANGED
@@ -11,6 +11,9 @@ if ( ! defined( 'WPSEO_VERSION' ) ) {
11
  exit();
12
  }
13
 
 
 
 
14
  /*
15
  * WPSEO_Post_Type::get_accessible_post_types() should *not* be used here.
16
  * Otherwise setting a post-type to `noindex` will remove it from the list,
11
  exit();
12
  }
13
 
14
+ WPSEO_Post_Type::remove_post_types_made_public_notification();
15
+ WPSEO_Post_Type::remove_taxonomies_made_public_notification();
16
+
17
  /*
18
  * WPSEO_Post_Type::get_accessible_post_types() should *not* be used here.
19
  * Otherwise setting a post-type to `noindex` will remove it from the list,
css/dist/{admin-global-19100-rtl.css → admin-global-19110-rtl.css} RENAMED
File without changes
css/dist/{admin-global-19100.css → admin-global-19110.css} RENAMED
File without changes
css/dist/{adminbar-19100-rtl.css → adminbar-19110-rtl.css} RENAMED
File without changes
css/dist/{adminbar-19100.css → adminbar-19110.css} RENAMED
File without changes
css/dist/{alerts-19100-rtl.css → alerts-19110-rtl.css} RENAMED
File without changes
css/dist/{alerts-19100.css → alerts-19110.css} RENAMED
File without changes
css/dist/{dashboard-19100-rtl.css → dashboard-19110-rtl.css} RENAMED
File without changes
css/dist/{dashboard-19100.css → dashboard-19110.css} RENAMED
File without changes
css/dist/{edit-page-19100-rtl.css → edit-page-19110-rtl.css} RENAMED
File without changes
css/dist/{edit-page-19100.css → edit-page-19110.css} RENAMED
File without changes
css/dist/{elementor-19100-rtl.css → elementor-19110-rtl.css} RENAMED
File without changes
css/dist/{elementor-19100.css → elementor-19110.css} RENAMED
File without changes
css/dist/{featured-image-19100-rtl.css → featured-image-19110-rtl.css} RENAMED
File without changes
css/dist/{featured-image-19100.css → featured-image-19110.css} RENAMED
File without changes
css/dist/{filter-explanation-19100-rtl.css → filter-explanation-19110-rtl.css} RENAMED
File without changes
css/dist/{filter-explanation-19100.css → filter-explanation-19110.css} RENAMED
File without changes
css/dist/{icons-19100-rtl.css → icons-19110-rtl.css} RENAMED
File without changes
css/dist/{icons-19100.css → icons-19110.css} RENAMED
File without changes
css/dist/{inside-editor-19100-rtl.css → inside-editor-19110-rtl.css} RENAMED
File without changes
css/dist/{inside-editor-19100.css → inside-editor-19110.css} RENAMED
File without changes
css/dist/{metabox-19100-rtl.css → metabox-19110-rtl.css} RENAMED
File without changes
css/dist/{metabox-19100.css → metabox-19110.css} RENAMED
File without changes
css/dist/{metabox-primary-category-19100-rtl.css → metabox-primary-category-19110-rtl.css} RENAMED
File without changes
css/dist/{metabox-primary-category-19100.css → metabox-primary-category-19110.css} RENAMED
File without changes
css/dist/{modal-19100-rtl.css → modal-19110-rtl.css} RENAMED
File without changes
css/dist/{modal-19100.css → modal-19110.css} RENAMED
File without changes
css/dist/{monorepo-19100-rtl.css → monorepo-19110-rtl.css} RENAMED
File without changes
css/dist/{monorepo-19100.css → monorepo-19110.css} RENAMED
File without changes
css/dist/{new-settings-19100-rtl.css → new-settings-19110-rtl.css} RENAMED
File without changes
css/dist/{new-settings-19100.css → new-settings-19110.css} RENAMED
File without changes
css/dist/{notifications-19100-rtl.css → notifications-19110-rtl.css} RENAMED
File without changes
css/dist/{notifications-19100.css → notifications-19110.css} RENAMED
File without changes
css/dist/{notifications-new-19100-rtl.css → notifications-new-19110-rtl.css} RENAMED
File without changes
css/dist/{notifications-new-19100.css → notifications-new-19110.css} RENAMED
File without changes
css/dist/{schema-blocks-19100-rtl.css → schema-blocks-19110-rtl.css} RENAMED
File without changes
css/dist/{schema-blocks-19100.css → schema-blocks-19110.css} RENAMED
File without changes
css/dist/{score_icon-19100-rtl.css → score_icon-19110-rtl.css} RENAMED
File without changes
css/dist/{score_icon-19100.css → score_icon-19110.css} RENAMED
File without changes
css/dist/{search-appearance-19100-rtl.css → search-appearance-19110-rtl.css} RENAMED
File without changes
css/dist/{search-appearance-19100.css → search-appearance-19110.css} RENAMED
File without changes
css/dist/{structured-data-blocks-19100-rtl.css → structured-data-blocks-19110-rtl.css} RENAMED
File without changes
css/dist/{structured-data-blocks-19100.css → structured-data-blocks-19110.css} RENAMED
File without changes
css/dist/{tailwind-19100-rtl.css → tailwind-19110-rtl.css} RENAMED
File without changes
css/dist/{tailwind-19100.css → tailwind-19110.css} RENAMED
File without changes
css/dist/{toggle-switch-19100-rtl.css → toggle-switch-19110-rtl.css} RENAMED
File without changes
css/dist/{toggle-switch-19100.css → toggle-switch-19110.css} RENAMED
File without changes
css/dist/{tooltips-19100-rtl.css → tooltips-19110-rtl.css} RENAMED
File without changes
css/dist/{tooltips-19100.css → tooltips-19110.css} RENAMED
File without changes
css/dist/{workouts-19100-rtl.css → workouts-19110-rtl.css} RENAMED
File without changes
css/dist/{workouts-19100.css → workouts-19110.css} RENAMED
File without changes
css/dist/{wpseo-dismissible-19100-rtl.css → wpseo-dismissible-19110-rtl.css} RENAMED
File without changes
css/dist/{wpseo-dismissible-19100.css → wpseo-dismissible-19110.css} RENAMED
File without changes
css/dist/{yoast-components-19100-rtl.css → yoast-components-19110-rtl.css} RENAMED
File without changes
css/dist/{yoast-components-19100.css → yoast-components-19110.css} RENAMED
File without changes
css/dist/{yoast-extensions-19100-rtl.css → yoast-extensions-19110-rtl.css} RENAMED
File without changes
css/dist/{yoast-extensions-19100.css → yoast-extensions-19110.css} RENAMED
File without changes
css/dist/{yst_plugin_tools-19100-rtl.css → yst_plugin_tools-19110-rtl.css} RENAMED
File without changes
css/dist/{yst_plugin_tools-19100.css → yst_plugin_tools-19110.css} RENAMED
File without changes
css/dist/{yst_seo_score-19100-rtl.css → yst_seo_score-19110-rtl.css} RENAMED
File without changes
css/dist/{yst_seo_score-19100.css → yst_seo_score-19110.css} RENAMED
File without changes
inc/class-post-type.php CHANGED
@@ -98,4 +98,24 @@ class WPSEO_Post_Type {
98
  public static function has_metabox_enabled( $post_type ) {
99
  return WPSEO_Options::get( 'display-metabox-pt-' . $post_type, false );
100
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  }
98
  public static function has_metabox_enabled( $post_type ) {
99
  return WPSEO_Options::get( 'display-metabox-pt-' . $post_type, false );
100
  }
101
+
102
+ /**
103
+ * Removes the notification related to the post types which have been made public.
104
+ *
105
+ * @return void
106
+ */
107
+ public static function remove_post_types_made_public_notification() {
108
+ $notification_center = Yoast_Notification_Center::get();
109
+ $notification_center->remove_notification_by_id( 'post-types-made-public' );
110
+ }
111
+
112
+ /**
113
+ * Removes the notification related to the taxonomies which have been made public.
114
+ *
115
+ * @return void
116
+ */
117
+ public static function remove_taxonomies_made_public_notification() {
118
+ $notification_center = Yoast_Notification_Center::get();
119
+ $notification_center->remove_notification_by_id( 'taxonomies-made-public' );
120
+ }
121
  }
inc/class-upgrade.php CHANGED
@@ -6,6 +6,12 @@
6
  */
7
 
8
  use Yoast\WP\Lib\Model;
 
 
 
 
 
 
9
 
10
  /**
11
  * This code handles the option upgrades.
@@ -82,10 +88,10 @@ class WPSEO_Upgrade {
82
  '19.1-RC0' => 'upgrade_191',
83
  '19.3-RC0' => 'upgrade_193',
84
  '19.6-RC0' => 'upgrade_196',
 
85
  ];
86
 
87
  array_walk( $routines, [ $this, 'run_upgrade_routine' ], $version );
88
-
89
  if ( version_compare( $version, '12.5-RC0', '<' ) ) {
90
  /*
91
  * We have to run this by hook, because otherwise:
@@ -847,8 +853,8 @@ class WPSEO_Upgrade {
847
  \wp_unschedule_hook( 'wpseo_cleanup_orphaned_indexables' );
848
  \wp_unschedule_hook( 'wpseo_cleanup_indexables' );
849
 
850
- if ( ! \wp_next_scheduled( \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK ) ) {
851
- \wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), \Yoast\WP\SEO\Integrations\Cleanup_Integration::START_HOOK );
852
  }
853
  }
854
 
@@ -948,6 +954,19 @@ class WPSEO_Upgrade {
948
  wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
949
  }
950
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951
  /**
952
  * Sets the home_url option for the 15.1 upgrade routine.
953
  *
@@ -1341,4 +1360,223 @@ class WPSEO_Upgrade {
1341
 
1342
  update_option( 'wpseo_titles', $wpseo_titles );
1343
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1344
  }
6
  */
7
 
8
  use Yoast\WP\Lib\Model;
9
+ use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
10
+ use Yoast\WP\SEO\Helpers\Options_Helper;
11
+ use Yoast\WP\SEO\Helpers\Post_Type_Helper;
12
+ use Yoast\WP\SEO\Helpers\String_Helper;
13
+ use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
14
+ use Yoast\WP\SEO\Integrations\Cleanup_Integration;
15
 
16
  /**
17
  * This code handles the option upgrades.
88
  '19.1-RC0' => 'upgrade_191',
89
  '19.3-RC0' => 'upgrade_193',
90
  '19.6-RC0' => 'upgrade_196',
91
+ '19.11-RC0' => 'upgrade_1911',
92
  ];
93
 
94
  array_walk( $routines, [ $this, 'run_upgrade_routine' ], $version );
 
95
  if ( version_compare( $version, '12.5-RC0', '<' ) ) {
96
  /*
97
  * We have to run this by hook, because otherwise:
853
  \wp_unschedule_hook( 'wpseo_cleanup_orphaned_indexables' );
854
  \wp_unschedule_hook( 'wpseo_cleanup_indexables' );
855
 
856
+ if ( ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
857
+ \wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
858
  }
859
  }
860
 
954
  wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
955
  }
956
 
957
+ /**
958
+ * Performs the 19.11 upgrade routine.
959
+ */
960
+ private function upgrade_1911() {
961
+ \add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_post_types' ] );
962
+ \add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_taxonomies' ] );
963
+ $this->deduplicate_unindexed_indexable_rows();
964
+ $this->remove_indexable_rows_for_disabled_authors_archive();
965
+ if ( ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
966
+ \wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
967
+ }
968
+ }
969
+
970
  /**
971
  * Sets the home_url option for the 15.1 upgrade routine.
972
  *
1360
 
1361
  update_option( 'wpseo_titles', $wpseo_titles );
1362
  }
1363
+
1364
+ /**
1365
+ * Removes all indexables for posts that are not publicly viewable.
1366
+ * This method should be called after init, because post_types can still be registered.
1367
+ *
1368
+ * @return void
1369
+ */
1370
+ public function remove_indexable_rows_for_non_public_post_types() {
1371
+ global $wpdb;
1372
+
1373
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1374
+ $show_errors = $wpdb->show_errors;
1375
+ $wpdb->show_errors = false;
1376
+
1377
+ $indexable_table = Model::get_table_name( 'Indexable' );
1378
+
1379
+ $included_post_types = \YoastSEO()->helpers->post_type->get_indexable_post_types();
1380
+
1381
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix.
1382
+ if ( empty( $included_post_types ) ) {
1383
+ $delete_query =
1384
+ "DELETE FROM $indexable_table
1385
+ WHERE object_type = 'post'
1386
+ AND object_sub_type IS NOT NULL";
1387
+ }
1388
+ else {
1389
+ $delete_query = $wpdb->prepare(
1390
+ "DELETE FROM $indexable_table
1391
+ WHERE object_type = 'post'
1392
+ AND object_sub_type IS NOT NULL
1393
+ AND object_sub_type NOT IN ( " . \implode( ', ', \array_fill( 0, \count( $included_post_types ), '%s' ) ) . ' )',
1394
+ $included_post_types
1395
+ );
1396
+ }
1397
+ // phpcs:enable
1398
+
1399
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1400
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1401
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
1402
+ $wpdb->query( $delete_query );
1403
+ // phpcs:enable
1404
+
1405
+ $wpdb->show_errors = $show_errors;
1406
+ }
1407
+
1408
+ /**
1409
+ * Removes all indexables for terms that are not publicly viewable.
1410
+ * This method should be called after init, because taxonomies can still be registered.
1411
+ *
1412
+ * @return void
1413
+ */
1414
+ public function remove_indexable_rows_for_non_public_taxonomies() {
1415
+ global $wpdb;
1416
+
1417
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1418
+ $show_errors = $wpdb->show_errors;
1419
+ $wpdb->show_errors = false;
1420
+
1421
+ $indexable_table = Model::get_table_name( 'Indexable' );
1422
+
1423
+ $included_taxonomies = \YoastSEO()->helpers->taxonomy->get_indexable_taxonomies();
1424
+
1425
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix.
1426
+ if ( empty( $included_taxonomies ) ) {
1427
+ $delete_query = "DELETE FROM $indexable_table
1428
+ WHERE object_type = 'term'
1429
+ AND object_sub_type IS NOT NULL";
1430
+ }
1431
+ else {
1432
+ $delete_query = $wpdb->prepare(
1433
+ "DELETE FROM $indexable_table
1434
+ WHERE object_type = 'term'
1435
+ AND object_sub_type IS NOT NULL
1436
+ AND object_sub_type NOT IN ( " . \implode( ', ', \array_fill( 0, \count( $included_taxonomies ), '%s' ) ) . ' )',
1437
+ $included_taxonomies
1438
+ );
1439
+ }
1440
+ // phpcs:enable
1441
+
1442
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1443
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1444
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
1445
+ $wpdb->query( $delete_query );
1446
+ // phpcs:enable
1447
+
1448
+ $wpdb->show_errors = $show_errors;
1449
+ }
1450
+
1451
+ /**
1452
+ * De-duplicates indexables that have more than one "unindexed" rows for the same object. Keeps the newest indexable.
1453
+ *
1454
+ * @return void
1455
+ */
1456
+ private function deduplicate_unindexed_indexable_rows() {
1457
+ global $wpdb;
1458
+
1459
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1460
+ $show_errors = $wpdb->show_errors;
1461
+ $wpdb->show_errors = false;
1462
+
1463
+ $indexable_table = Model::get_table_name( 'Indexable' );
1464
+
1465
+ $query =
1466
+ "SELECT
1467
+ MAX(id) as newest_id,
1468
+ object_id,
1469
+ object_type
1470
+ FROM
1471
+ $indexable_table
1472
+ WHERE
1473
+ post_status = 'unindexed'
1474
+ AND object_type IN ( 'term', 'post', 'user' )
1475
+ GROUP BY
1476
+ object_id,
1477
+ object_type
1478
+ HAVING
1479
+ count(*) > 1";
1480
+
1481
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1482
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1483
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
1484
+ $duplicates = $wpdb->get_results( $query, ARRAY_A );
1485
+ // phpcs:enable
1486
+
1487
+ if ( empty( $duplicates ) ) {
1488
+ $wpdb->show_errors = $show_errors;
1489
+
1490
+ return;
1491
+ }
1492
+
1493
+ // Users, terms and posts may share the same object_id. So delete them in separate, more performant, queries.
1494
+ $delete_queries = [
1495
+ $this->get_indexable_deduplication_query_for_type( 'post', $duplicates, $wpdb ),
1496
+ $this->get_indexable_deduplication_query_for_type( 'term', $duplicates, $wpdb ),
1497
+ $this->get_indexable_deduplication_query_for_type( 'user', $duplicates, $wpdb ),
1498
+ ];
1499
+
1500
+ foreach ( $delete_queries as $delete_query ) {
1501
+ if ( ! empty( $delete_query ) ) {
1502
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1503
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1504
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
1505
+ $wpdb->query( $delete_query );
1506
+ // phpcs:enable
1507
+ }
1508
+ }
1509
+
1510
+ $wpdb->show_errors = $show_errors;
1511
+ }
1512
+
1513
+ /**
1514
+ * Removes all user indexable rows when the author archive is disabled.
1515
+ *
1516
+ * @return void
1517
+ */
1518
+ private function remove_indexable_rows_for_disabled_authors_archive() {
1519
+ global $wpdb;
1520
+
1521
+ if ( ! \YoastSEO()->helpers->author_archive->are_disabled() ) {
1522
+ return;
1523
+ }
1524
+
1525
+ // If migrations haven't been completed successfully the following may give false errors. So suppress them.
1526
+ $show_errors = $wpdb->show_errors;
1527
+ $wpdb->show_errors = false;
1528
+
1529
+ $indexable_table = Model::get_table_name( 'Indexable' );
1530
+
1531
+ $delete_query = "DELETE FROM $indexable_table WHERE object_type = 'user'";
1532
+ // phpcs:enable
1533
+
1534
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
1535
+ // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
1536
+ // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already.
1537
+ $wpdb->query( $delete_query );
1538
+ // phpcs:enable
1539
+
1540
+ $wpdb->show_errors = $show_errors;
1541
+ }
1542
+
1543
+ /**
1544
+ * Creates a query for de-duplicating indexables for a particular type.
1545
+ *
1546
+ * @param string $object_type The object type to deduplicate.
1547
+ * @param array $duplicates The result of the duplicate query.
1548
+ * @param wpdb $wpdb The wpdb object.
1549
+ *
1550
+ * @return string The query that removes all but one duplicate for each object of the object type.
1551
+ */
1552
+ private function get_indexable_deduplication_query_for_type( $object_type, $duplicates, $wpdb ) {
1553
+ $indexable_table = Model::get_table_name( 'Indexable' );
1554
+
1555
+ $filtered_duplicates = \array_filter(
1556
+ $duplicates,
1557
+ static function ( $duplicate ) use ( $object_type ) {
1558
+ return $duplicate['object_type'] === $object_type;
1559
+ }
1560
+ );
1561
+
1562
+ if ( empty( $filtered_duplicates ) ) {
1563
+ return '';
1564
+ }
1565
+
1566
+ $object_ids = wp_list_pluck( $filtered_duplicates, 'object_id' );
1567
+ $newest_indexable_ids = wp_list_pluck( $filtered_duplicates, 'newest_id' );
1568
+
1569
+ // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix.
1570
+ // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Reason: we're passing an array instead.
1571
+ return $wpdb->prepare(
1572
+ "DELETE FROM
1573
+ $indexable_table
1574
+ WHERE
1575
+ object_id IN ( " . \implode( ', ', \array_fill( 0, \count( $filtered_duplicates ), '%d' ) ) . ' )
1576
+ AND id NOT IN ( ' . \implode( ', ', \array_fill( 0, \count( $filtered_duplicates ), '%d' ) ) . ' )
1577
+ AND object_type = %s',
1578
+ array_merge( array_values( $object_ids ), array_values( $newest_indexable_ids ), [ $object_type ] )
1579
+ );
1580
+ // phpcs:enable
1581
+ }
1582
  }
inc/options/class-wpseo-option-wpseo.php CHANGED
@@ -131,6 +131,8 @@ class WPSEO_Option_Wpseo extends WPSEO_Option {
131
  'least_linked_ignore_list' => [],
132
  'indexables_page_reading_list' => [ false, false, false, false, false ],
133
  'indexables_overview_state' => 'dashboard-not-visited',
 
 
134
  ];
135
 
136
  /**
@@ -400,6 +402,8 @@ class WPSEO_Option_Wpseo extends WPSEO_Option {
400
  case 'most_linked_ignore_list':
401
  case 'least_linked_ignore_list':
402
  case 'indexables_page_reading_list':
 
 
403
  $clean[ $key ] = $old[ $key ];
404
 
405
  if ( isset( $dirty[ $key ] ) ) {
131
  'least_linked_ignore_list' => [],
132
  'indexables_page_reading_list' => [ false, false, false, false, false ],
133
  'indexables_overview_state' => 'dashboard-not-visited',
134
+ 'last_known_public_post_types' => [],
135
+ 'last_known_public_taxonomies' => [],
136
  ];
137
 
138
  /**
402
  case 'most_linked_ignore_list':
403
  case 'least_linked_ignore_list':
404
  case 'indexables_page_reading_list':
405
+ case 'last_known_public_post_types':
406
+ case 'last_known_public_taxonomies':
407
  $clean[ $key ] = $old[ $key ];
408
 
409
  if ( isset( $dirty[ $key ] ) ) {
inc/sitemaps/class-author-sitemap-provider.php CHANGED
@@ -5,7 +5,6 @@
5
  * @package WPSEO\XML_Sitemaps
6
  */
7
 
8
- use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
9
  use Yoast\WP\SEO\Helpers\Wordpress_Helper;
10
 
11
  /**
@@ -131,8 +130,7 @@ class WPSEO_Author_Sitemap_Provider implements WPSEO_Sitemap_Provider {
131
 
132
  if ( WPSEO_Options::get( 'noindex-author-noposts-wpseo', true ) ) {
133
  unset( $defaults['who'], $defaults['capability'] ); // Otherwise it cancels out next argument.
134
- $author_archive = new Author_Archive_Helper();
135
- $defaults['has_published_posts'] = $author_archive->get_author_archive_post_types();
136
  }
137
 
138
  return get_users( array_merge( $defaults, $arguments ) );
5
  * @package WPSEO\XML_Sitemaps
6
  */
7
 
 
8
  use Yoast\WP\SEO\Helpers\Wordpress_Helper;
9
 
10
  /**
130
 
131
  if ( WPSEO_Options::get( 'noindex-author-noposts-wpseo', true ) ) {
132
  unset( $defaults['who'], $defaults['capability'] ); // Otherwise it cancels out next argument.
133
+ $defaults['has_published_posts'] = YoastSEO()->helpers->author_archive->get_author_archive_post_types();
 
134
  }
135
 
136
  return get_users( array_merge( $defaults, $arguments ) );
readme.txt CHANGED
@@ -5,7 +5,7 @@ License: GPLv3
5
  License URI: http://www.gnu.org/licenses/gpl.html
6
  Tags: SEO, XML sitemap, Content analysis, Readability, Schema
7
  Tested up to: 6.1
8
- Stable tag: 19.10
9
  Requires PHP: 5.6.20
10
 
11
  Improve your WordPress SEO: Write better content and have a fully optimized WordPress site using the Yoast SEO plugin.
@@ -245,53 +245,53 @@ Your question has most likely been answered on our help center: [yoast.com/help/
245
 
246
  == Changelog ==
247
 
248
- = 19.10 =
249
- Release Date: November 8th, 2022
250
 
251
- Yoast SEO 19.10 is out today. This release mostly consists of bug fixes and enhancements. In addition, we're getting our WordPress plugins ready for the upcoming High Performance Order Storage feature in WooCommerce 7.1+. Update now! Read more about what's new in Yoast SEO 19.9 in [our release post in English](https://yoa.st/release-8-11-22) or [our release post in Spanish](https://yoa.st/release-8-11-22-spanish)!
252
 
253
- Enhancements:
 
 
254
 
255
- * Improves the call-to-action feedback string of the _Flesch Reading Ease_ insight when the text is recognized as fairly difficult.
 
 
256
 
257
- Bugfixes:
258
 
259
- * Fixes a bug where a fatal error would be thrown in the classic editor in combination with certain plugins that misuse metabox hooks.
260
- * Fixes a bug where users with site-wide basic access authentication would be prompted to insert their credentials when saving a post in Elementor if they didn't have the `manage_options` capability.
261
- * Fixes a bug where Yoast SEO-related post meta data would not be saved if a user without the `manage_options` capability would save a post in Elementor.
 
 
 
 
262
 
263
- Other:
264
 
265
- * Deprecates the hooks used to add custom content to the Yoast SEO settings pages, in preparation for future releases. The following hooks have been deprecated: `wpseo_tools_overview_list_items`, `wpseo_settings_tab_crawl_cleanup`, `wpseo_settings_tab_site_analysis`, `Yoast\WP\SEO\admin_author_archives_meta`, `Yoast\WP\SEO\admin_date_archives_meta`, `Yoast\WP\SEO\admin_post_types_beforearchive`, `Yoast\WP\SEO\admin_post_types_archive`, `Yoast\WP\SEO\admin_taxonomies_meta`, `wpseo_admin_other_section`, `wpseo_admin_opengraph_section`, `wpseo_admin_pinterest_section`, `wpseo_admin_twitter_section`, `wpseo_import_other_plugins`.
266
- * Ensures compatibility with the _High Performance Order Storage_ feature in WooCommerce 7.1+.
267
- * Sets the WordPress tested up to version to 6.1.
268
 
269
- = 19.9 =
270
- Release Date: October 25th, 2022
271
 
272
- Yoast SEO 19.9 is out today. Yoast SEO already supports the Schema necessary for Google's Site Names update, but we've expanded support for it in this release. In addition, we give users more control over what names they can add, including an alternate title. Of course, there's a lot more, so check it out! Read more about what's new in Yoast SEO 19.9 in [our release post in English](https://yoa.st/release-25-10-22) or [our release post in Spanish](https://yoa.st/release-25-10-22-spanish)!
273
 
274
- Enhancements:
275
 
276
- * Adds input fields to overwrite the site name, as well as an extra input field for a (potentially shorter) alternate name. Google introduced new support for [site names in Google Search](https://developers.google.com/search/blog/2022/10/introducing-site-names-on-search). Yoast SEO already outputs this value correctly, using the WordPress site name. With these changes, we have increased the control site owners have over this value.
277
- * Improves the Schema output for Organization by no longer putting out an empty array if no social profiles have been added for it.
278
- * Adds immediate keyphrase tracking after connecting to Wincher.
279
 
280
- Bugfixes:
281
 
282
- * Fixes a bug where a fatal error would be thrown when using the `wpseo_breadcrumb_links` filter in the wrong way on PHP 8.0+.
283
- * Fixes a bug where social or canonical URLs containing `@` would lead to encoding issues. Props to [@stodorovic](https://github.com/stodorovic).
284
- * Fixes a bug where the buttons in the _FAQ_ and in the _how-to_ block would be hardly visible when using a dark theme.
285
- * Fixes a bug where the number of words would be counted incorrectly when using Cyrillic script. Props to [kudinovfedor](https://github.com/kudinovfedor).
286
- * Fixes a bug where the _previously used keyphrase_ assessment would also appear under the readability analysis tab when the cornerstone content toggle would be switched on.
287
- * Fixes a bug where the SEO optimization routine would give an error when an image file of an image linked in a post could not be retrieved.
288
- * Fixes a bug where the wrong canonical URL would be set on attachment pages.
289
 
290
- Other:
 
 
 
 
291
 
292
- * Adds taxonomy information to breadcrumbs of type "term" to be able to filter them better with the `wpseo_breadcrumb_links` filter. Props to [@svenvonarx](https://github.com/svenvonarx).
293
- * Adds a `wpseo_primary_category_admin_pages` filter to enable the use of the primary category in the post URL of additional admin pages besides the default ones. Props to [@ssvet](https://github.com/ssvet).
294
- * Reinstates the `wpseo_twitter_card_type` filter that was wrongly deprecated in 19.8.
295
 
296
  = Earlier versions =
297
  For the changelog of earlier versions, please refer to [the changelog on yoast.com](https://yoa.st/yoast-seo-changelog).
5
  License URI: http://www.gnu.org/licenses/gpl.html
6
  Tags: SEO, XML sitemap, Content analysis, Readability, Schema
7
  Tested up to: 6.1
8
+ Stable tag: 19.11
9
  Requires PHP: 5.6.20
10
 
11
  Improve your WordPress SEO: Write better content and have a fully optimized WordPress site using the Yoast SEO plugin.
245
 
246
  == Changelog ==
247
 
248
+ = 19.11 =
 
249
 
250
+ Release date: November 29th, 2022
251
 
252
+ Yoast SEO 19.11 is out now. We're optimizing the Yoast SEO plugin to use fewer resources. This helps make your site faster and more efficient. In this release, we're doing this by streamlining your database. Find out more about what's new in Yoast SEO 19.11 in [our release post](https://yoa.st/release-29-11-22)!
253
+
254
+ #### Enhancements
255
 
256
+ * Adds a WP-CLI command to clean up unused data from our custom database tables: `wp yoast cleanup`.
257
+ * Performs a cleanup of indexables when a public post type (or taxonomy) becomes non-public.
258
+ * Notifies users to run the SEO optimization when a non-public post type (or taxonomy) becomes public.
259
 
260
+ #### Bugfixes
261
 
262
+ * Fixes a bug where a fatal error would be thrown when the SEO optimization was run after a post type had been manually excluded via a filter.
263
+ * Fixes a bug where an entry would be added to our indexables table when saving, updating, or accessing a post (or term) for a non-public post type (or taxonomy).
264
+ * Fixes a bug where duplicate indexable records would be created for the same object.
265
+ * Fixes a bug where indexables for users would not get removed when a user did not have any publicly viewable posts anymore.
266
+ * Fixes a bug where indexables for users would not get removed when author archives were disabled.
267
+ * Fixes a bug where indexables would be created for users when author archives were disabled.
268
+ * Fixes a bug where indexables would be created for users who did not have any publicly viewable posts.
269
 
270
+ #### Other
271
 
272
+ * Introduces the `wpseo_indexable_excluded_taxonomies` filter, to allow manually excluding taxonomies from being indexed.
 
 
273
 
274
+ = 19.10 =
 
275
 
276
+ Release date: November 8th, 2022
277
 
278
+ Yoast SEO 19.10 is out today. This release mostly consists of bug fixes and enhancements. In addition, we're getting our WordPress plugins ready for the upcoming High Performance Order Storage feature in WooCommerce 7.1+. Update now! Read more about what's new in Yoast SEO 19.10 in [our release post in English](https://yoa.st/release-8-11-22) or [our release post in Spanish](https://yoa.st/release-8-11-22-spanish)!
279
 
280
+ #### Enhancements
 
 
281
 
282
+ * Improves the call-to-action feedback string of the Flesch Reading Ease insight when the text is recognized as fairly difficult.
283
 
284
+ #### Bugfixes
 
 
 
 
 
 
285
 
286
+ * Fixes a bug where a fatal error would be thrown in the classic editor in combination with certain plugins that misuse metabox hooks.
287
+ * Fixes a bug where users with site-wide basic access authentication would be prompted to insert their credentials when saving a post in Elementor if they didn’t have the manage_options capability.
288
+ * Fixes a bug where Yoast SEO-related post meta data would not be saved if a user without the manage_options capability would save a post in Elementor.
289
+
290
+ #### Other
291
 
292
+ * Deprecates the hooks used to add custom content to the Yoast SEO settings pages, in preparation for future releases. The following hooks have been deprecated: wpseo_tools_overview_list_items, wpseo_settings_tab_crawl_cleanup, wpseo_settings_tab_site_analysis, Yoast\WP\SEOdmin_author_archives_meta, Yoast\WP\SEOdmin_date_archives_meta, Yoast\WP\SEOdmin_post_types_beforearchive, Yoast\WP\SEOdmin_post_types_archive, Yoast\WP\SEOdmin_taxonomies_meta, wpseo_admin_other_section, wpseo_admin_opengraph_section, wpseo_admin_pinterest_section, wpseo_admin_twitter_section, wpseo_import_other_plugins.
293
+ * Ensures compatibility with the High Performance Order Storage feature in WooCommerce 7.1+.
294
+ * Sets the WordPress tested up to version to 6.1.
295
 
296
  = Earlier versions =
297
  For the changelog of earlier versions, please refer to [the changelog on yoast.com](https://yoa.st/yoast-seo-changelog).
src/actions/indexing/indexable-post-indexation-action.php CHANGED
@@ -139,7 +139,7 @@ class Indexable_Post_Indexation_Action extends Abstract_Indexing_Action {
139
  protected function get_count_query() {
140
  $indexable_table = Model::get_table_name( 'Indexable' );
141
 
142
- $post_types = $this->get_post_types();
143
  $excluded_post_statuses = $this->post_helper->get_excluded_post_statuses();
144
  $replacements = \array_merge(
145
  $post_types,
@@ -174,7 +174,7 @@ class Indexable_Post_Indexation_Action extends Abstract_Indexing_Action {
174
  protected function get_select_query( $limit = false ) {
175
  $indexable_table = Model::get_table_name( 'Indexable' );
176
 
177
- $post_types = $this->get_post_types();
178
  $excluded_post_statuses = $this->post_helper->get_excluded_post_statuses();
179
  $replacements = \array_merge(
180
  $post_types,
@@ -204,17 +204,4 @@ class Indexable_Post_Indexation_Action extends Abstract_Indexing_Action {
204
  $replacements
205
  );
206
  }
207
-
208
- /**
209
- * Returns the post types that should be indexed.
210
- *
211
- * @return array The post types that should be indexed.
212
- */
213
- protected function get_post_types() {
214
- $public_post_types = $this->post_type_helper->get_public_post_types();
215
- $excluded_post_types = $this->post_type_helper->get_excluded_post_types_for_indexables();
216
-
217
- // `array_values`, to make sure that the keys are reset.
218
- return \array_values( \array_diff( $public_post_types, $excluded_post_types ) );
219
- }
220
  }
139
  protected function get_count_query() {
140
  $indexable_table = Model::get_table_name( 'Indexable' );
141
 
142
+ $post_types = $this->post_type_helper->get_indexable_post_types();
143
  $excluded_post_statuses = $this->post_helper->get_excluded_post_statuses();
144
  $replacements = \array_merge(
145
  $post_types,
174
  protected function get_select_query( $limit = false ) {
175
  $indexable_table = Model::get_table_name( 'Indexable' );
176
 
177
+ $post_types = $this->post_type_helper->get_indexable_post_types();
178
  $excluded_post_statuses = $this->post_helper->get_excluded_post_statuses();
179
  $replacements = \array_merge(
180
  $post_types,
204
  $replacements
205
  );
206
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
207
  }
src/actions/indexing/indexable-term-indexation-action.php CHANGED
@@ -124,9 +124,10 @@ class Indexable_Term_Indexation_Action extends Abstract_Indexing_Action {
124
  * @return string The prepared query string.
125
  */
126
  protected function get_count_query() {
127
- $indexable_table = Model::get_table_name( 'Indexable' );
128
- $taxonomy_table = $this->wpdb->term_taxonomy;
129
- $public_taxonomies = \array_keys( $this->taxonomy->get_public_taxonomies() );
 
130
  $taxonomies_placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) );
131
 
132
  $replacements = [ $this->version ];
@@ -157,7 +158,7 @@ class Indexable_Term_Indexation_Action extends Abstract_Indexing_Action {
157
  protected function get_select_query( $limit = false ) {
158
  $indexable_table = Model::get_table_name( 'Indexable' );
159
  $taxonomy_table = $this->wpdb->term_taxonomy;
160
- $public_taxonomies = \array_keys( $this->taxonomy->get_public_taxonomies() );
161
  $placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) );
162
 
163
  $replacements = [ $this->version ];
124
  * @return string The prepared query string.
125
  */
126
  protected function get_count_query() {
127
+ $indexable_table = Model::get_table_name( 'Indexable' );
128
+ $taxonomy_table = $this->wpdb->term_taxonomy;
129
+ $public_taxonomies = $this->taxonomy->get_indexable_taxonomies();
130
+
131
  $taxonomies_placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) );
132
 
133
  $replacements = [ $this->version ];
158
  protected function get_select_query( $limit = false ) {
159
  $indexable_table = Model::get_table_name( 'Indexable' );
160
  $taxonomy_table = $this->wpdb->term_taxonomy;
161
+ $public_taxonomies = $this->taxonomy->get_indexable_taxonomies();
162
  $placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) );
163
 
164
  $replacements = [ $this->version ];
src/actions/indexing/post-link-indexing-action.php CHANGED
@@ -73,7 +73,7 @@ class Post_Link_Indexing_Action extends Abstract_Link_Indexing_Action {
73
  * @return string The prepared query string.
74
  */
75
  protected function get_count_query() {
76
- $public_post_types = $this->post_type_helper->get_accessible_post_types();
77
  $indexable_table = Model::get_table_name( 'Indexable' );
78
  $links_table = Model::get_table_name( 'SEO_Links' );
79
 
@@ -106,7 +106,7 @@ class Post_Link_Indexing_Action extends Abstract_Link_Indexing_Action {
106
  * @return string The prepared query string.
107
  */
108
  protected function get_select_query( $limit = false ) {
109
- $public_post_types = $this->post_type_helper->get_accessible_post_types();
110
  $indexable_table = Model::get_table_name( 'Indexable' );
111
  $links_table = Model::get_table_name( 'SEO_Links' );
112
  $replacements = $public_post_types;
73
  * @return string The prepared query string.
74
  */
75
  protected function get_count_query() {
76
+ $public_post_types = $this->post_type_helper->get_indexable_post_types();
77
  $indexable_table = Model::get_table_name( 'Indexable' );
78
  $links_table = Model::get_table_name( 'SEO_Links' );
79
 
106
  * @return string The prepared query string.
107
  */
108
  protected function get_select_query( $limit = false ) {
109
+ $public_post_types = $this->post_type_helper->get_indexable_post_types();
110
  $indexable_table = Model::get_table_name( 'Indexable' );
111
  $links_table = Model::get_table_name( 'SEO_Links' );
112
  $replacements = $public_post_types;
src/actions/indexing/term-link-indexing-action.php CHANGED
@@ -73,7 +73,7 @@ class Term_Link_Indexing_Action extends Abstract_Link_Indexing_Action {
73
  * @return string The prepared query string.
74
  */
75
  protected function get_count_query() {
76
- $public_taxonomies = $this->taxonomy_helper->get_public_taxonomies();
77
  $placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) );
78
  $indexable_table = Model::get_table_name( 'Indexable' );
79
 
@@ -100,9 +100,10 @@ class Term_Link_Indexing_Action extends Abstract_Link_Indexing_Action {
100
  * @return string The prepared query string.
101
  */
102
  protected function get_select_query( $limit = false ) {
103
- $public_taxonomies = $this->taxonomy_helper->get_public_taxonomies();
104
- $indexable_table = Model::get_table_name( 'Indexable' );
105
- $replacements = $public_taxonomies;
 
106
 
107
  $limit_query = '';
108
  if ( $limit ) {
73
  * @return string The prepared query string.
74
  */
75
  protected function get_count_query() {
76
+ $public_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies();
77
  $placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) );
78
  $indexable_table = Model::get_table_name( 'Indexable' );
79
 
100
  * @return string The prepared query string.
101
  */
102
  protected function get_select_query( $limit = false ) {
103
+ $public_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies();
104
+
105
+ $indexable_table = Model::get_table_name( 'Indexable' );
106
+ $replacements = $public_taxonomies;
107
 
108
  $limit_query = '';
109
  if ( $limit ) {
src/builders/indexable-author-builder.php CHANGED
@@ -3,6 +3,7 @@
3
  namespace Yoast\WP\SEO\Builders;
4
 
5
  use wpdb;
 
6
  use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
7
  use Yoast\WP\SEO\Helpers\Post_Helper;
8
  use Yoast\WP\SEO\Models\Indexable;
@@ -72,8 +73,15 @@ class Indexable_Author_Builder {
72
  * @param Indexable $indexable The indexable to format.
73
  *
74
  * @return Indexable The extended indexable.
 
 
75
  */
76
  public function build( $user_id, Indexable $indexable ) {
 
 
 
 
 
77
  $meta_data = $this->get_meta_data( $user_id );
78
 
79
  $indexable->object_id = $user_id;
@@ -190,4 +198,35 @@ class Indexable_Author_Builder {
190
  // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- We are using wpdb prepare.
191
  return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $replacements ) );
192
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
193
  }
3
  namespace Yoast\WP\SEO\Builders;
4
 
5
  use wpdb;
6
+ use Yoast\WP\SEO\Exceptions\Indexable\Author_Not_Built_Exception;
7
  use Yoast\WP\SEO\Helpers\Author_Archive_Helper;
8
  use Yoast\WP\SEO\Helpers\Post_Helper;
9
  use Yoast\WP\SEO\Models\Indexable;
73
  * @param Indexable $indexable The indexable to format.
74
  *
75
  * @return Indexable The extended indexable.
76
+ *
77
+ * @throws Author_Not_Built_Exception When author is not built.
78
  */
79
  public function build( $user_id, Indexable $indexable ) {
80
+ $exception = $this->check_if_user_should_be_indexed( $user_id );
81
+ if ( $exception ) {
82
+ throw $exception;
83
+ }
84
+
85
  $meta_data = $this->get_meta_data( $user_id );
86
 
87
  $indexable->object_id = $user_id;
198
  // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- We are using wpdb prepare.
199
  return $this->wpdb->get_row( $this->wpdb->prepare( $sql, $replacements ) );
200
  }
201
+
202
+ /**
203
+ * Checks if the user should be indexed.
204
+ * Returns an exception with an appropriate message if not.
205
+ *
206
+ * @param string $user_id The user id.
207
+ *
208
+ * @return Author_Not_Built_Exception|null The exception if it should not be indexed, or `null` if it should.
209
+ */
210
+ protected function check_if_user_should_be_indexed( $user_id ) {
211
+ $exception = null;
212
+
213
+ if ( $this->author_archive->are_disabled() ) {
214
+ $exception = Author_Not_Built_Exception::author_archives_are_disabled( $user_id );
215
+ }
216
+
217
+ // We will check if the author has public posts the WP way, instead of the indexable way, to make sure we get proper results even if SEO optimization is not run.
218
+ if ( $this->author_archive->author_has_public_posts_wp( $user_id ) === false ) {
219
+ $exception = Author_Not_Built_Exception::author_archives_are_not_indexed_for_users_without_posts( $user_id );
220
+ }
221
+
222
+ /**
223
+ * Filter: Include or exclude a user from being build and saved as an indexable.
224
+ * Return an `Author_Not_Built_Exception` when the indexable should not be build, with an appropriate message telling why it should not be built.
225
+ * Return `null` if the indexable should be build.
226
+ *
227
+ * @param Author_Not_Built_Exception|null $exception An exception if the indexable is not being built, `null` if the indexable should be built.
228
+ * @param string $user_id The ID of the user that should or should not be excluded.
229
+ */
230
+ return \apply_filters( 'wpseo_should_build_and_save_user_indexable', $exception, $user_id );
231
+ }
232
  }
src/builders/indexable-builder.php CHANGED
@@ -2,6 +2,7 @@
2
 
3
  namespace Yoast\WP\SEO\Builders;
4
 
 
5
  use Yoast\WP\SEO\Exceptions\Indexable\Source_Exception;
6
  use Yoast\WP\SEO\Helpers\Indexable_Helper;
7
  use Yoast\WP\SEO\Models\Indexable;
@@ -299,6 +300,32 @@ class Indexable_Builder {
299
  return $indexable;
300
  }
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  /**
303
  * Rebuilds an Indexable from scratch.
304
  *
@@ -315,6 +342,9 @@ class Indexable_Builder {
315
  $indexable = $this->ensure_indexable( $indexable, $defaults );
316
 
317
  try {
 
 
 
318
  switch ( $indexable->object_type ) {
319
 
320
  case 'post':
@@ -330,19 +360,11 @@ class Indexable_Builder {
330
  // Always rebuild the hierarchy; this needs the primary term to run correctly.
331
  $this->hierarchy_builder->build( $indexable );
332
 
333
- // Rebuild the author indexable only when necessary.
334
- $author_indexable = $this->indexable_repository->find_by_id_and_type(
335
- $indexable->author_id,
336
- 'user',
337
- false
338
- );
339
- if ( ! $author_indexable || $this->version_manager->indexable_needs_upgrade( $author_indexable ) ) {
340
- $author_defaults = [
341
- 'object_type' => 'user',
342
- 'object_id' => $indexable->author_id,
343
- ];
344
- $this->build( $author_indexable, $author_defaults );
345
- }
346
  break;
347
 
348
  case 'user':
@@ -351,6 +373,7 @@ class Indexable_Builder {
351
 
352
  case 'term':
353
  $indexable = $this->term_builder->build( $indexable->object_id, $indexable );
 
354
  $this->hierarchy_builder->build( $indexable );
355
  break;
356
 
@@ -380,20 +403,26 @@ class Indexable_Builder {
380
  *
381
  * @var Indexable $indexable
382
  */
383
- $indexable = $this->indexable_repository
384
- ->query()
385
- ->create(
386
- [
387
- 'object_id' => $indexable->object_id,
388
- 'object_type' => $indexable->object_type,
389
- 'post_status' => 'unindexed',
390
- 'version' => 0,
391
- ]
392
- );
 
393
  // Make sure that the indexing process doesn't get stuck in a loop on this broken indexable.
394
  $indexable = $this->version_manager->set_latest( $indexable );
395
 
396
  return $this->save_indexable( $indexable, $indexable_before );
397
  }
 
 
 
398
  }
 
 
399
  }
2
 
3
  namespace Yoast\WP\SEO\Builders;
4
 
5
+ use Yoast\WP\SEO\Exceptions\Indexable\Not_Built_Exception;
6
  use Yoast\WP\SEO\Exceptions\Indexable\Source_Exception;
7
  use Yoast\WP\SEO\Helpers\Indexable_Helper;
8
  use Yoast\WP\SEO\Models\Indexable;
300
  return $indexable;
301
  }
302
 
303
+ /**
304
+ * Build and author indexable from an author id if it does not exist yet, or if the author indexable needs to be upgraded.
305
+ *
306
+ * @param int $author_id The author id.
307
+ *
308
+ * @return Indexable|false The author indexable if it has been built, `false` if it could not be built.
309
+ */
310
+ protected function maybe_build_author_indexable( $author_id ) {
311
+ $author_indexable = $this->indexable_repository->find_by_id_and_type(
312
+ $author_id,
313
+ 'user',
314
+ false
315
+ );
316
+ if ( ! $author_indexable || $this->version_manager->indexable_needs_upgrade( $author_indexable ) ) {
317
+ // Try to build the author.
318
+ $author_defaults = [
319
+ 'object_type' => 'user',
320
+ 'object_id' => $author_id,
321
+ ];
322
+ $author_indexable = $this->build( $author_indexable, $author_defaults );
323
+ }
324
+ return $author_indexable;
325
+ }
326
+
327
+ // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing -- Exceptions are handled by the catch statement in the method.
328
+
329
  /**
330
  * Rebuilds an Indexable from scratch.
331
  *
342
  $indexable = $this->ensure_indexable( $indexable, $defaults );
343
 
344
  try {
345
+ if ( $indexable->object_id === 0 ) {
346
+ throw Not_Built_Exception::invalid_object_id( $indexable->object_id );
347
+ }
348
  switch ( $indexable->object_type ) {
349
 
350
  case 'post':
360
  // Always rebuild the hierarchy; this needs the primary term to run correctly.
361
  $this->hierarchy_builder->build( $indexable );
362
 
363
+ // Save indexable, to make sure that it can be queried when determining if an author has public posts.
364
+ $indexable = $this->save_indexable( $indexable, $indexable_before );
365
+
366
+ $this->maybe_build_author_indexable( $indexable->author_id );
367
+
 
 
 
 
 
 
 
 
368
  break;
369
 
370
  case 'user':
373
 
374
  case 'term':
375
  $indexable = $this->term_builder->build( $indexable->object_id, $indexable );
376
+
377
  $this->hierarchy_builder->build( $indexable );
378
  break;
379
 
403
  *
404
  * @var Indexable $indexable
405
  */
406
+ $indexable = $this->ensure_indexable(
407
+ $indexable,
408
+ [
409
+ 'object_id' => $indexable->object_id,
410
+ 'object_type' => $indexable->object_type,
411
+ 'post_status' => 'unindexed',
412
+ 'version' => 0,
413
+ ]
414
+ );
415
+ // If we already had an existing indexable, mark it as unindexed. We cannot rely on its validity anymore.
416
+ $indexable->post_status = 'unindexed';
417
  // Make sure that the indexing process doesn't get stuck in a loop on this broken indexable.
418
  $indexable = $this->version_manager->set_latest( $indexable );
419
 
420
  return $this->save_indexable( $indexable, $indexable_before );
421
  }
422
+ catch ( Not_Built_Exception $exception ) {
423
+ return false;
424
+ }
425
  }
426
+
427
+ // phpcs:enable
428
  }
src/builders/indexable-post-builder.php CHANGED
@@ -11,6 +11,7 @@ use Yoast\WP\SEO\Helpers\Post_Type_Helper;
11
  use Yoast\WP\SEO\Models\Indexable;
12
  use Yoast\WP\SEO\Repositories\Indexable_Repository;
13
  use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
 
14
 
15
  /**
16
  * Post Builder for the indexables.
@@ -96,10 +97,11 @@ class Indexable_Post_Builder {
96
  * @return bool|Indexable The extended indexable. False when unable to build.
97
  *
98
  * @throws Post_Not_Found_Exception When the post could not be found.
 
99
  */
100
  public function build( $post_id, $indexable ) {
101
  if ( ! $this->post_helper->is_post_indexable( $post_id ) ) {
102
- return false;
103
  }
104
 
105
  $post = $this->post_helper->get_post( $post_id );
@@ -109,7 +111,7 @@ class Indexable_Post_Builder {
109
  }
110
 
111
  if ( $this->should_exclude_post( $post ) ) {
112
- return false;
113
  }
114
 
115
  $indexable->object_id = $post_id;
11
  use Yoast\WP\SEO\Models\Indexable;
12
  use Yoast\WP\SEO\Repositories\Indexable_Repository;
13
  use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions;
14
+ use Yoast\WP\SEO\Exceptions\Indexable\Post_Not_Built_Exception;
15
 
16
  /**
17
  * Post Builder for the indexables.
97
  * @return bool|Indexable The extended indexable. False when unable to build.
98
  *
99
  * @throws Post_Not_Found_Exception When the post could not be found.
100
+ * @throws Post_Not_Built_Exception When the post should not be indexed.
101
  */
102
  public function build( $post_id, $indexable ) {
103
  if ( ! $this->post_helper->is_post_indexable( $post_id ) ) {
104
+ throw Post_Not_Built_Exception::because_not_indexable( $post_id );
105
  }
106
 
107
  $post = $this->post_helper->get_post( $post_id );
111
  }
112
 
113
  if ( $this->should_exclude_post( $post ) ) {
114
+ throw Post_Not_Built_Exception::because_post_type_excluded( $post_id );
115
  }
116
 
117
  $indexable->object_id = $post_id;
src/builders/indexable-term-builder.php CHANGED
@@ -5,6 +5,7 @@ namespace Yoast\WP\SEO\Builders;
5
  use wpdb;
6
  use Yoast\WP\SEO\Exceptions\Indexable\Invalid_Term_Exception;
7
  use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Found_Exception;
 
8
  use Yoast\WP\SEO\Helpers\Post_Helper;
9
  use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
10
  use Yoast\WP\SEO\Models\Indexable;
@@ -75,7 +76,8 @@ class Indexable_Term_Builder {
75
  *
76
  * @return bool|Indexable The extended indexable. False when unable to build.
77
  *
78
- * @throws Invalid_Term_Exception When the term is invalid.
 
79
  * @throws Term_Not_Found_Exception When the term is not found.
80
  */
81
  public function build( $term_id, $indexable ) {
@@ -89,6 +91,11 @@ class Indexable_Term_Builder {
89
  throw new Invalid_Term_Exception( $term->get_error_message() );
90
  }
91
 
 
 
 
 
 
92
  $term_link = \get_term_link( $term, $term->taxonomy );
93
 
94
  if ( \is_wp_error( $term_link ) ) {
5
  use wpdb;
6
  use Yoast\WP\SEO\Exceptions\Indexable\Invalid_Term_Exception;
7
  use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Found_Exception;
8
+ use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Built_Exception;
9
  use Yoast\WP\SEO\Helpers\Post_Helper;
10
  use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
11
  use Yoast\WP\SEO\Models\Indexable;
76
  *
77
  * @return bool|Indexable The extended indexable. False when unable to build.
78
  *
79
+ * @throws Invalid_Term_Exception When the term is invalid.
80
+ * @throws Term_Not_Built_Exception When the term is not viewable.
81
  * @throws Term_Not_Found_Exception When the term is not found.
82
  */
83
  public function build( $term_id, $indexable ) {
91
  throw new Invalid_Term_Exception( $term->get_error_message() );
92
  }
93
 
94
+ $indexable_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies();
95
+ if ( ! \in_array( $term->taxonomy, $indexable_taxonomies, true ) ) {
96
+ throw Term_Not_Built_Exception::because_not_indexable( $term_id );
97
+ }
98
+
99
  $term_link = \get_term_link( $term, $term->taxonomy );
100
 
101
  if ( \is_wp_error( $term_link ) ) {
src/commands/cleanup-command.php ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Yoast\WP\SEO\Commands;
4
+
5
+ use WP_CLI\ExitException;
6
+ use Yoast\WP\SEO\Integrations\Cleanup_Integration;
7
+ use Yoast\WP\SEO\Main;
8
+ use function get_sites;
9
+ use function WP_CLI\Utils\make_progress_bar;
10
+
11
+ /**
12
+ * A WP CLI command that helps with cleaning up unwanted records from our custom tables.
13
+ */
14
+ final class Cleanup_Command implements Command_Interface {
15
+
16
+ /**
17
+ * The integration that cleans up on cron.
18
+ *
19
+ * @var Cleanup_Integration
20
+ */
21
+ private $cleanup_integration;
22
+
23
+ /**
24
+ * The constructor.
25
+ *
26
+ * @param Cleanup_Integration $cleanup_integration The integration that cleans up on cron.
27
+ */
28
+ public function __construct( Cleanup_Integration $cleanup_integration ) {
29
+ $this->cleanup_integration = $cleanup_integration;
30
+ }
31
+
32
+ /**
33
+ * Returns the namespace of this command.
34
+ *
35
+ * @return string
36
+ */
37
+ public static function get_namespace() {
38
+ return Main::WP_CLI_NAMESPACE;
39
+ }
40
+
41
+ /**
42
+ * Performs a cleanup of custom Yoast tables.
43
+ *
44
+ * This removes unused, unwanted or orphaned database records, which ensures the best performance. Including:
45
+ * - Indexables
46
+ * - Indexable hierarchy
47
+ * - SEO links
48
+ *
49
+ * ## OPTIONS
50
+ *
51
+ * [--batch-size=<batch-size>]
52
+ * : The number of database records to clean up in a single sql query.
53
+ * ---
54
+ * default: 1000
55
+ * ---
56
+ *
57
+ * [--interval=<interval>]
58
+ * : The number of microseconds (millionths of a second) to wait between cleanup batches.
59
+ * ---
60
+ * default: 500000
61
+ * ---
62
+ *
63
+ * [--network]
64
+ * : Performs the cleanup on all sites within the network.
65
+ *
66
+ * ## EXAMPLES
67
+ *
68
+ * wp yoast cleanup
69
+ *
70
+ * @when after_wp_load
71
+ *
72
+ * @param array|null $args The arguments.
73
+ * @param array|null $assoc_args The associative arguments.
74
+ *
75
+ * @return void
76
+ *
77
+ * @throws ExitException When the input args are invalid.
78
+ */
79
+ public function cleanup( $args = null, $assoc_args = null ) {
80
+ if ( isset( $assoc_args['interval'] ) && (int) $assoc_args['interval'] < 0 ) {
81
+ \WP_CLI::error( __( 'The value for \'interval\' must be a positive integer.', 'wordpress-seo' ) );
82
+ }
83
+ if ( isset( $assoc_args['batch-size'] ) && (int) $assoc_args['batch-size'] < 1 ) {
84
+ \WP_CLI::error( __( 'The value for \'batch-size\' must be a positive integer higher than equal to 1.', 'wordpress-seo' ) );
85
+ }
86
+
87
+ if ( isset( $assoc_args['network'] ) && \is_multisite() ) {
88
+ $total_removed = $this->cleanup_network( $assoc_args );
89
+ }
90
+ else {
91
+ $total_removed = $this->cleanup_current_site( $assoc_args );
92
+ }
93
+
94
+ \WP_CLI::success(
95
+ \sprintf(
96
+ /* translators: %1$d is the number of records that are removed. */
97
+ \_n(
98
+ 'Cleaned up %1$d record.',
99
+ 'Cleaned up %1$d records.',
100
+ $total_removed,
101
+ 'wordpress-seo'
102
+ ),
103
+ $total_removed
104
+ )
105
+ );
106
+ }
107
+
108
+ /**
109
+ * Performs the cleanup for the entire network.
110
+ *
111
+ * @param array|null $assoc_args The associative arguments.
112
+ *
113
+ * @return int The number of cleaned up records.
114
+ */
115
+ private function cleanup_network( $assoc_args ) {
116
+ $criteria = [
117
+ 'fields' => 'ids',
118
+ 'spam' => 0,
119
+ 'deleted' => 0,
120
+ 'archived' => 0,
121
+ ];
122
+ $blog_ids = \get_sites( $criteria );
123
+ $total_removed = 0;
124
+ foreach ( $blog_ids as $blog_id ) {
125
+ \switch_to_blog( $blog_id );
126
+ $total_removed += $this->cleanup_current_site( $assoc_args );
127
+ \restore_current_blog();
128
+ }
129
+
130
+ return $total_removed;
131
+ }
132
+
133
+ /**
134
+ * Performs the cleanup for a single site.
135
+ *
136
+ * @param array|null $assoc_args The associative arguments.
137
+ *
138
+ * @return int The number of cleaned up records.
139
+ */
140
+ private function cleanup_current_site( $assoc_args ) {
141
+ $site_url = \site_url();
142
+ $total_removed = 0;
143
+
144
+ if ( ! \is_plugin_active( WPSEO_BASENAME ) ) {
145
+ /* translators: %1$s is the site url of the site that is skipped. %2$s is Yoast SEO. */
146
+ \WP_CLI::warning( \sprintf( \__( 'Skipping %1$s. %2$s is not active on this site.', 'wordpress-seo' ), $site_url, 'Yoast SEO' ) );
147
+
148
+ return $total_removed;
149
+ }
150
+
151
+ // Make sure the DB is up to date first.
152
+ \do_action( '_yoast_run_migrations' );
153
+
154
+ $tasks = $this->cleanup_integration->get_cleanup_tasks();
155
+ $limit = (int) $assoc_args['batch-size'];
156
+ $interval = (int) $assoc_args['interval'];
157
+
158
+ /* translators: %1$s is the site url of the site that is cleaned up. %2$s is the name of the cleanup task that is currently running. */
159
+ $progress_bar_title_format = \__( 'Cleaning up %1$s [%2$s]', 'wordpress-seo' );
160
+ $progress = make_progress_bar( \sprintf( $progress_bar_title_format, $site_url, \key( $tasks ) ), count( $tasks ) );
161
+
162
+ foreach ( $tasks as $task_name => $task ) {
163
+ // Update the progressbar title with the current task name.
164
+ $progress->tick( 0, \sprintf( $progress_bar_title_format, $site_url, $task_name ) );
165
+ do {
166
+ $items_cleaned = $task( $limit );
167
+ if ( \is_int( $items_cleaned ) ) {
168
+ $total_removed += $items_cleaned;
169
+ }
170
+ \usleep( $interval );
171
+
172
+ // Update the timer.
173
+ $progress->tick( 0 );
174
+ } while ( $items_cleaned !== false && $items_cleaned > 0 );
175
+ $progress->tick();
176
+ }
177
+ $progress->finish();
178
+
179
+ $this->cleanup_integration->reset_cleanup();
180
+ \WP_CLI::log(
181
+ \sprintf(
182
+ /* translators: %1$d is the number of records that were removed. %2$s is the site url. */
183
+ \_n(
184
+ 'Cleaned up %1$d record from %2$s.',
185
+ 'Cleaned up %1$d records from %2$s.',
186
+ $total_removed,
187
+ 'wordpress-seo'
188
+ ),
189
+ $total_removed,
190
+ $site_url
191
+ )
192
+ );
193
+
194
+ return $total_removed;
195
+ }
196
+ }
src/config/indexing-reasons.php CHANGED
@@ -31,4 +31,14 @@ class Indexing_Reasons {
31
  * Represents the reason that the home url option is changed.
32
  */
33
  const REASON_HOME_URL_OPTION = 'home_url_option_changed';
 
 
 
 
 
 
 
 
 
 
34
  }
31
  * Represents the reason that the home url option is changed.
32
  */
33
  const REASON_HOME_URL_OPTION = 'home_url_option_changed';
34
+
35
+ /**
36
+ * Represents the reason that a post type has been made public.
37
+ */
38
+ const REASON_POST_TYPE_MADE_PUBLIC = 'post_type_made_public';
39
+
40
+ /**
41
+ * Represents the reason that a post type has been made viewable.
42
+ */
43
+ const REASON_TAXONOMY_MADE_PUBLIC = 'taxonomy_made_public';
44
  }
src/exceptions/indexable/author-not-built-exception.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Yoast\WP\SEO\Exceptions\Indexable;
4
+
5
+ /**
6
+ * For when an author indexable is not being built.
7
+ */
8
+ class Author_Not_Built_Exception extends Not_Built_Exception {
9
+
10
+ /**
11
+ * Named constructor for creating an Author_Not_Built_Exception
12
+ * when author archives are disabled for users without posts.
13
+ *
14
+ * @param string $user_id The user id.
15
+ *
16
+ * @return Author_Not_Built_Exception The exception.
17
+ */
18
+ public static function author_archives_are_not_indexed_for_users_without_posts( $user_id ) {
19
+ return new Author_Not_Built_Exception(
20
+ 'Indexable for author with id ' . $user_id . ' is not being built, since author archives are not indexed for users without posts.'
21
+ );
22
+ }
23
+
24
+ /**
25
+ * Named constructor for creating an Author_Not_Built_Exception
26
+ * when author archives are disabled.
27
+ *
28
+ * @param string $user_id The user id.
29
+ *
30
+ * @return Author_Not_Built_Exception The exception.
31
+ */
32
+ public static function author_archives_are_disabled( $user_id ) {
33
+ return new Author_Not_Built_Exception(
34
+ 'Indexable for author with id ' . $user_id . ' is not being built, since author archives are disabled.'
35
+ );
36
+ }
37
+
38
+ /**
39
+ * Named constructor for creating an Author_Not_Build_Exception
40
+ * when an author is excluded because of the `'wpseo_should_build_and_save_user_indexable'` filter.
41
+ *
42
+ * @param string $user_id The user id.
43
+ *
44
+ * @return Author_Not_Built_Exception The exception.
45
+ */
46
+ public static function author_not_built_because_of_filter( $user_id ) {
47
+ return new Author_Not_Built_Exception(
48
+ 'Indexable for author with id ' . $user_id . ' is not being built, since it is excluded because of the \'wpseo_should_build_and_save_user_indexable\' filter.'
49
+ );
50
+ }
51
+ }
src/exceptions/indexable/not-built-exception.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Yoast\WP\SEO\Exceptions\Indexable;
4
+
5
+ /**
6
+ * Class Not_Built_Exception
7
+ */
8
+ class Not_Built_Exception extends Indexable_Exception {
9
+
10
+ /**
11
+ * Creates an exception that should be thrown when an indexable
12
+ * was not built because of an invalid object id.
13
+ *
14
+ * @param int $object_id The invalid object id.
15
+ *
16
+ * @return Not_Built_Exception The exception.
17
+ */
18
+ public static function invalid_object_id( $object_id ) {
19
+ return new Not_Built_Exception(
20
+ "Indexable was not built because it had an invalid object id of $object_id."
21
+ );
22
+ }
23
+ }
src/exceptions/indexable/post-not-built-exception.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Yoast\WP\SEO\Exceptions\Indexable;
4
+
5
+ /**
6
+ * Exception that is thrown whenever a post could not be built
7
+ * in the context of the indexables.
8
+ */
9
+ class Post_Not_Built_Exception extends Not_Built_Exception {
10
+
11
+ /**
12
+ * Throws an exception if the post is not indexable.
13
+ *
14
+ * @param int $post_id ID of the post.
15
+ *
16
+ * @throws Post_Not_Built_Exception When the post is not indexable.
17
+ */
18
+ public static function because_not_indexable( $post_id ) {
19
+ /* translators: %s: expands to the post id */
20
+ return new Post_Not_Built_Exception( sprintf( __( 'The post %s could not be indexed because it does not meet indexing requirements.', 'wordpress-seo' ), $post_id ) );
21
+ }
22
+
23
+ /**
24
+ * Throws an exception if the post type is excluded from indexing.
25
+ *
26
+ * @param int $post_id ID of the post.
27
+ *
28
+ * @throws Post_Not_Built_Exception When the post type is excluded.
29
+ */
30
+ public static function because_post_type_excluded( $post_id ) {
31
+ /* translators: %s: expands to the post id */
32
+ return new Post_Not_Built_Exception( sprintf( __( 'The post %s could not be indexed because it\'s post type is excluded from indexing.', 'wordpress-seo' ), $post_id ) );
33
+ }
34
+ }
src/exceptions/indexable/term-not-built-exception.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace Yoast\WP\SEO\Exceptions\Indexable;
4
+
5
+ /**
6
+ * Exception that is thrown whenever a term could not be built
7
+ * in the context of the indexables.
8
+ */
9
+ class Term_Not_Built_Exception extends Not_Built_Exception {
10
+
11
+ /**
12
+ * Throws an exception if the term is not indexable.
13
+ *
14
+ * @param int $term_id ID of the term.
15
+ *
16
+ * @throws Term_Not_Built_Exception When the term is not built.
17
+ */
18
+ public static function because_not_indexable( $term_id ) {
19
+ /* translators: %s: expands to the term id */
20
+ return new Term_Not_Built_Exception( sprintf( __( 'The term %s could not be built because it\'s not indexable.', 'wordpress-seo' ), $term_id ) );
21
+ }
22
+ }
src/generated/container.php CHANGED
@@ -75,6 +75,7 @@ class Cached_Container extends Container
75
  'yoast\\wp\\seo\\builders\\indexable_system_page_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_System_Page_Builder',
76
  'yoast\\wp\\seo\\builders\\indexable_term_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder',
77
  'yoast\\wp\\seo\\builders\\primary_term_builder' => 'Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder',
 
78
  'yoast\\wp\\seo\\commands\\index_command' => 'Yoast\\WP\\SEO\\Commands\\Index_Command',
79
  'yoast\\wp\\seo\\conditionals\\addon_installation_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional',
80
  'yoast\\wp\\seo\\conditionals\\admin\\doing_post_quick_edit_save_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Doing_Post_Quick_Edit_Save_Conditional',
@@ -256,6 +257,7 @@ class Cached_Container extends Container
256
  'yoast\\wp\\seo\\integrations\\admin\\health_check_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Health_Check_Integration',
257
  'yoast\\wp\\seo\\integrations\\admin\\helpscout_beacon' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\HelpScout_Beacon',
258
  'yoast\\wp\\seo\\integrations\\admin\\import_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Import_Integration',
 
259
  'yoast\\wp\\seo\\integrations\\admin\\indexables_page_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexables_Page_Integration',
260
  'yoast\\wp\\seo\\integrations\\admin\\indexing_notification_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Notification_Integration',
261
  'yoast\\wp\\seo\\integrations\\admin\\indexing_tool_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Tool_Integration',
@@ -321,6 +323,7 @@ class Cached_Container extends Container
321
  'yoast\\wp\\seo\\integrations\\watchers\\addon_update_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Addon_Update_Watcher',
322
  'yoast\\wp\\seo\\integrations\\watchers\\auto_update_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Auto_Update_Watcher',
323
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_ancestor_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Ancestor_Watcher',
 
324
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_author_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Watcher',
325
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_category_permalink_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Category_Permalink_Watcher',
326
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_date_archive_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Date_Archive_Watcher',
@@ -329,9 +332,11 @@ class Cached_Container extends Container
329
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_permalink_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Permalink_Watcher',
330
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_post_meta_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Meta_Watcher',
331
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_post_type_archive_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Archive_Watcher',
 
332
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_post_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher',
333
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_static_home_page_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Static_Home_Page_Watcher',
334
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_system_page_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_System_Page_Watcher',
 
335
  'yoast\\wp\\seo\\integrations\\watchers\\indexable_term_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Term_Watcher',
336
  'yoast\\wp\\seo\\integrations\\watchers\\option_titles_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Titles_Watcher',
337
  'yoast\\wp\\seo\\integrations\\watchers\\option_wpseo_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Wpseo_Watcher',
@@ -462,6 +467,7 @@ class Cached_Container extends Container
462
  'Yoast\\WP\\SEO\\Builders\\Indexable_System_Page_Builder' => 'getIndexableSystemPageBuilderService',
463
  'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder' => 'getIndexableTermBuilderService',
464
  'Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder' => 'getPrimaryTermBuilderService',
 
465
  'Yoast\\WP\\SEO\\Commands\\Index_Command' => 'getIndexCommandService',
466
  'Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional' => 'getAddonInstallationConditionalService',
467
  'Yoast\\WP\\SEO\\Conditionals\\Admin\\Doing_Post_Quick_Edit_Save_Conditional' => 'getDoingPostQuickEditSaveConditionalService',
@@ -643,6 +649,7 @@ class Cached_Container extends Container
643
  'Yoast\\WP\\SEO\\Integrations\\Admin\\Health_Check_Integration' => 'getHealthCheckIntegrationService',
644
  'Yoast\\WP\\SEO\\Integrations\\Admin\\HelpScout_Beacon' => 'getHelpScoutBeaconService',
645
  'Yoast\\WP\\SEO\\Integrations\\Admin\\Import_Integration' => 'getImportIntegrationService',
 
646
  'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexables_Page_Integration' => 'getIndexablesPageIntegrationService',
647
  'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Notification_Integration' => 'getIndexingNotificationIntegrationService',
648
  'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Tool_Integration' => 'getIndexingToolIntegrationService',
@@ -708,6 +715,7 @@ class Cached_Container extends Container
708
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Addon_Update_Watcher' => 'getAddonUpdateWatcherService',
709
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Auto_Update_Watcher' => 'getAutoUpdateWatcherService',
710
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Ancestor_Watcher' => 'getIndexableAncestorWatcherService',
 
711
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Watcher' => 'getIndexableAuthorWatcherService',
712
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Category_Permalink_Watcher' => 'getIndexableCategoryPermalinkWatcherService',
713
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Date_Archive_Watcher' => 'getIndexableDateArchiveWatcherService',
@@ -716,9 +724,11 @@ class Cached_Container extends Container
716
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Permalink_Watcher' => 'getIndexablePermalinkWatcherService',
717
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Meta_Watcher' => 'getIndexablePostMetaWatcherService',
718
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Archive_Watcher' => 'getIndexablePostTypeArchiveWatcherService',
 
719
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher' => 'getIndexablePostWatcherService',
720
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Static_Home_Page_Watcher' => 'getIndexableStaticHomePageWatcherService',
721
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_System_Page_Watcher' => 'getIndexableSystemPageWatcherService',
 
722
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Term_Watcher' => 'getIndexableTermWatcherService',
723
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Titles_Watcher' => 'getOptionTitlesWatcherService',
724
  'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Wpseo_Watcher' => 'getOptionWpseoWatcherService',
@@ -813,7 +823,6 @@ class Cached_Container extends Container
813
  'Psr\\Container\\ContainerInterface' => true,
814
  'YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
815
  'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true,
816
- 'Yoast\\WP\\SEO\\Commands\\Command_Interface' => true,
817
  'wpdb' => true,
818
  ];
819
  }
@@ -1343,7 +1352,7 @@ class Cached_Container extends Container
1343
  return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'];
1344
  }
1345
 
1346
- $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Author_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper'] = new \Yoast\WP\SEO\Helpers\Author_Archive_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}, $a, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'});
1347
 
1348
  $instance->set_social_image_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] : $this->getImageHelper2Service()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] : $this->getImageHelper4Service()) && false ?: '_'});
1349
 
@@ -1552,6 +1561,16 @@ class Cached_Container extends Container
1552
  return $this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder'] = new \Yoast\WP\SEO\Builders\Primary_Term_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] : ($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] = new \Yoast\WP\SEO\Repositories\Primary_Term_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper'] = new \Yoast\WP\SEO\Helpers\Primary_Term_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] = new \Yoast\WP\SEO\Helpers\Meta_Helper())) && false ?: '_'});
1553
  }
1554
 
 
 
 
 
 
 
 
 
 
 
1555
  /**
1556
  * Gets the public 'Yoast\WP\SEO\Commands\Index_Command' shared autowired service.
1557
  *
@@ -2673,7 +2692,7 @@ class Cached_Container extends Container
2673
  */
2674
  protected function getAuthorArchiveHelperService()
2675
  {