: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
private function upgrade_189() {
// Make old users not get the Installation Success page after upgrading.
WPSEO_Options::set( 'should_redirect_after_install_free', false );
// We're adding a hardcoded time here, so that in the future we can be able to identify whether the user did see the Installation Success page or not.
// If they did, they wouldn't have this hardcoded value in that option, but rather (roughly) the timestamp of the moment they saw it.
WPSEO_Options::set( 'activation_redirect_timestamp_free', 1652258756 );
// Transfer the Social URLs.
$other[] = WPSEO_Options::get( 'instagram_url' );
$other[] = WPSEO_Options::get( 'linkedin_url' );
$other[] = WPSEO_Options::get( 'myspace_url' );
$other[] = WPSEO_Options::get( 'pinterest_url' );
$other[] = WPSEO_Options::get( 'youtube_url' );
$other[] = WPSEO_Options::get( 'wikipedia_url' );
WPSEO_Options::set( 'other_social_urls', array_values( array_unique( array_filter( $other ) ) ) );
// Transfer the progress of the old Configuration Workout.
$workout_data = WPSEO_Options::get( 'workouts_data' );
$old_conf_progress = ( $workout_data['configuration']['finishedSteps'] ?? [] );
if ( in_array( 'optimizeSeoData', $old_conf_progress, true ) && in_array( 'siteRepresentation', $old_conf_progress, true ) ) {
// If completed ‘SEO optimization’ and ‘Site representation’ step, we assume the workout was completed.
$configuration_finished_steps = [
WPSEO_Options::set( 'configuration_finished_steps', $configuration_finished_steps );
* Performs the 19.1 upgrade routine.
private function upgrade_191() {
WPSEO_Options::set( 'allow_remove_feed_post_comments', true );
* Performs the 19.3 upgrade routine.
private function upgrade_193() {
if ( empty( get_option( 'wpseo_premium', [] ) ) ) {
WPSEO_Options::set( 'enable_index_now', true );
WPSEO_Options::set( 'enable_link_suggestions', true );
* Performs the 19.6 upgrade routine.
private function upgrade_196() {
WPSEO_Options::set( 'ryte_indexability', false );
WPSEO_Options::set( 'allow_ryte_indexability', false );
wp_clear_scheduled_hook( 'wpseo_ryte_fetch' );
* Performs the 19.11 upgrade routine.
private function upgrade_1911() {
add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_post_types' ] );
add_action( 'shutdown', [ $this, 'remove_indexable_rows_for_non_public_taxonomies' ] );
$this->deduplicate_unindexed_indexable_rows();
$this->remove_indexable_rows_for_disabled_authors_archive();
if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
* Performs the 20.2 upgrade routine.
private function upgrade_202() {
if ( WPSEO_Options::get( 'disable-attachment', true ) ) {
$attachment_cleanup_helper = YoastSEO()->helpers->attachment_cleanup;
$attachment_cleanup_helper->remove_attachment_indexables( true );
$attachment_cleanup_helper->clean_attachment_links_from_target_indexable_ids( true );
$this->clean_unindexed_indexable_rows_with_no_object_id();
if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
// This schedules the cleanup routine cron again, since in combination of premium cleans up the prominent words table. We also want to cleanup possible orphaned hierarchies from the above cleanups.
wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
* Performs the 20.5 upgrade routine.
private function upgrade_205() {
if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
* Performs the 20.7 upgrade routine.
* Removes the metadata related to the settings page introduction modal for all the users.
* Also, schedules another cleanup scheduled action.
private function upgrade_207() {
add_action( 'shutdown', [ $this, 'delete_user_introduction_meta' ] );
* Performs the 20.8 upgrade routine.
* Schedules another cleanup scheduled action.
private function upgrade_208() {
if ( ! wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) {
wp_schedule_single_event( ( time() + ( MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK );
* Performs the 22.6 upgrade routine.
* Schedules another cleanup scheduled action, but starting from the last cleanup action we just added (if there aren't any running cleanups already).
private function upgrade_226() {
if ( get_option( Cleanup_Integration::CURRENT_TASK_OPTION ) === false ) {
$cleanup_integration = YoastSEO()->classes->get( Cleanup_Integration::class );
$cleanup_integration->start_cron_job( 'clean_selected_empty_usermeta', DAY_IN_SECONDS );
* Sets the home_url option for the 15.1 upgrade routine.
protected function set_home_url_for_151() {
$home_url = WPSEO_Options::get( 'home_url' );
if ( empty( $home_url ) ) {
WPSEO_Options::set( 'home_url', get_home_url() );
* Moves the `indexables_indexation_reason` option to the
* renamed `indexing_reason` option.
protected function move_indexables_indexation_reason_for_151() {
$reason = WPSEO_Options::get( 'indexables_indexation_reason', '' );
WPSEO_Options::set( 'indexing_reason', $reason );
* Checks if the indexable indexation is completed.
* If so, sets the `indexables_indexation_completed` option to `true`,
public function set_indexation_completed_option_for_145() {
WPSEO_Options::set( 'indexables_indexation_completed', YoastSEO()->helpers->indexing->get_limited_filtered_unindexed_count( 1 ) === 0 );
* Cleans up the private taxonomies from the indexables table for the upgrade routine to 14.1.
public function clean_up_private_taxonomies_for_141() {
// If migrations haven't been completed successfully the following may give false errors. So suppress them.
$show_errors = $wpdb->show_errors;
$wpdb->show_errors = false;
// Clean up indexables of private taxonomies.
$private_taxonomies = get_taxonomies( [ 'public' => false ], 'names' );
if ( empty( $private_taxonomies ) ) {
$replacements = array_merge( [ Model::get_table_name( 'Indexable' ), 'object_type', 'object_sub_type' ], $private_taxonomies );
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
. implode( ', ', array_fill( 0, count( $private_taxonomies ), '%s' ) )
$wpdb->show_errors = $show_errors;
* Resets the permalinks of attachments to `null` in the indexable table for the upgrade routine to 14.1.
private function reset_permalinks_of_attachments_for_141() {
// If migrations haven't been completed succesfully the following may give false errors. So suppress them.
$show_errors = $wpdb->show_errors;
$wpdb->show_errors = false;
// Reset the permalinks of the attachments in the indexable table.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
"UPDATE %i SET %i = NULL WHERE %i = 'post' AND %i = 'attachment'",
[ Model::get_table_name( 'Indexable' ), 'permalink', 'object_type', 'object_sub_type' ]
$wpdb->show_errors = $show_errors;
* Removes notifications from the Notification center for the 14.1 upgrade.
public function remove_notifications_for_141() {
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-recalculate' );
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-dismiss-blog-public-notice' );
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-links-table-not-accessible' );
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-post-type-archive-notification' );
* Removes the wpseo-suggested-plugin-yoast-acf-analysis notification from the Notification center for the 14.2 upgrade.
public function remove_acf_notification_for_142() {
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-suggested-plugin-yoast-acf-analysis' );
* Removes the wpseo-plugin-updated notification from the Notification center for the 15.7 upgrade.
public function remove_plugin_updated_notification_for_157() {
Yoast_Notification_Center::get()->remove_notification_by_id( 'wpseo-plugin-updated' );
* Removes all notifications saved in the database under 'wp_yoast_notifications'.
private function clean_all_notifications() {
delete_metadata( 'user', 0, $wpdb->get_blog_prefix() . Yoast_Notification_Center::STORAGE_KEY, '', true );
* Removes the post meta fields for a given meta key.
* @param string $meta_key The meta key.
private function delete_post_meta( $meta_key ) {
$deleted = $wpdb->delete( $wpdb->postmeta, [ 'meta_key' => $meta_key ], [ '%s' ] );
wp_cache_set( 'last_changed', microtime(), 'posts' );
* Removes all sitemap validators.
* This should be executed on every upgrade routine until we have removed the sitemap caching in the database.
private function remove_sitemap_validators() {
// Remove all sitemap validators.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
'DELETE FROM %i WHERE %i LIKE %s',
[ $wpdb->options, 'option_name', 'wpseo_sitemap%validator%' ]
* Retrieves the option value directly from the database.
* @param string $option_name Option to retrieve.
* @return int|string|bool|float|array<string|int|bool|float> The content of the option if exists, otherwise an empty array.
protected function get_option_from_database( $option_name ) {
// Load option directly from the database, to avoid filtering and sanitization.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches.
// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way.
$results = $wpdb->get_results(
'SELECT %i FROM %i WHERE %i = %s',
[ 'option_value', $wpdb->options, 'option_name', $option_name ]
if ( ! empty( $results ) ) {
return maybe_unserialize( $results[0]['option_value'] );
* Cleans the option to make sure only relevant settings are there.
* @param string $option_name Option name save.
protected function cleanup_option_data( $option_name ) {
$data = get_option( $option_name, [] );
if ( ! is_array( $data ) || $data === [] ) {
* Clean up the option by re-saving it.
* The option framework will remove any settings that are not configured
* for this option, removing any migrated settings.
update_option( $option_name, $data );
* Saves an option setting to where it should be stored.
* @param int|string|bool|float|array<string|int|bool|float> $source_data The option containing the value to be migrated.
* @param string $source_setting Name of the key in the "from" option.
* @param string|null $target_setting Name of the key in the "to" option.
protected function save_option_setting( $source_data, $source_setting, $target_setting = null ) {
if ( $target_setting === null ) {
$target_setting = $source_setting;
if ( isset( $source_data[ $source_setting ] ) ) {
WPSEO_Options::set( $target_setting, $source_data[ $source_setting ] );
* Migrates WooCommerce archive settings to the WooCommerce Shop page meta-data settings.
* If no Shop page is defined, nothing will be migrated.
private function migrate_woocommerce_archive_setting_to_shop_page() {
$shop_page_id = wc_get_page_id( 'shop' );
if ( $shop_page_id === -1 ) {
$title = WPSEO_Meta::get_value( 'title', $shop_page_id );
$option_title = WPSEO_Options::get( 'title-ptarchive-product' );
WPSEO_Options::set( 'title-ptarchive-product', '' );
$meta_description = WPSEO_Meta::get_value( 'metadesc', $shop_page_id );
if ( empty( $meta_description ) ) {
$option_metadesc = WPSEO_Options::get( 'metadesc-ptarchive-product' );
WPSEO_Options::set( 'metadesc-ptarchive-product', '' );
$bc_title = WPSEO_Meta::get_value( 'bctitle', $shop_page_id );
if ( empty( $bc_title ) ) {
$option_bctitle = WPSEO_Options::get( 'bctitle-ptarchive-product' );
WPSEO_Options::set( 'bctitle-ptarchive-product', '' );
$noindex = WPSEO_Meta::get_value( 'meta-robots-noindex', $shop_page_id );
if ( $noindex === '0' ) {
$option_noindex = WPSEO_Options::get( 'noindex-ptarchive-product' );
WPSEO_Options::set( 'noindex-ptarchive-product', false );
* Stores the initial `permalink_structure` option.
public function set_permalink_structure_option_for_151() {
WPSEO_Options::set( 'permalink_structure', get_option( 'permalink_structure' ) );
* Stores the initial slugs of custom taxonomies.
public function store_custom_taxonomy_slugs_for_151() {
$taxonomies = $this->taxonomy_helper->get_custom_taxonomies();
foreach ( $taxonomies as $taxonomy ) {
$slug = $this->taxonomy_helper->get_taxonomy_slug( $taxonomy );
$custom_taxonomies[ $taxonomy ] = $slug;
WPSEO_Options::set( 'custom_taxonomy_slugs', $custom_taxonomies );
* Copies the frontpage social settings to the titles options.
public function copy_og_settings_from_social_to_titles() {