: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
namespace Yoast\WP\SEO\Integrations;
use WPSEO_Admin_Asset_Manager;
use WPSEO_Admin_Editor_Specific_Replace_Vars;
use WPSEO_Admin_Recommended_Replace_Vars;
use WPSEO_Plugin_Availability;
use WPSEO_Sitemaps_Router;
use Yoast\WP\SEO\Conditionals\Settings_Conditional;
use Yoast\WP\SEO\Config\Schema_Types;
use Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Dismiss_Notifications;
use Yoast\WP\SEO\Helpers\Current_Page_Helper;
use Yoast\WP\SEO\Helpers\Language_Helper;
use Yoast\WP\SEO\Helpers\Options_Helper;
use Yoast\WP\SEO\Helpers\Post_Type_Helper;
use Yoast\WP\SEO\Helpers\Product_Helper;
use Yoast\WP\SEO\Helpers\Schema\Article_Helper;
use Yoast\WP\SEO\Helpers\Taxonomy_Helper;
use Yoast\WP\SEO\Helpers\User_Helper;
use Yoast\WP\SEO\Helpers\Woocommerce_Helper;
use Yoast\WP\SEO\Promotions\Application\Promotion_Manager;
* Class Settings_Integration.
class Settings_Integration implements Integration_Interface {
public const PAGE = 'wpseo_page_settings';
* Holds the included WordPress options.
public const WP_OPTIONS = [ 'blogdescription' ];
* Holds the allowed option groups.
public const ALLOWED_OPTION_GROUPS = [ 'wpseo', 'wpseo_titles', 'wpseo_social' ];
* Holds the disallowed settings, per option group.
public const DISALLOWED_SETTINGS = [
'configuration_finished_steps',
'least_readability_ignore_list',
'least_seo_score_ignore_list',
'most_linked_ignore_list',
'least_linked_ignore_list',
'indexables_page_reading_list',
'show_new_content_type_notification',
* Holds the disabled on multisite settings, per option group.
public const DISABLED_ON_MULTISITE_SETTINGS = [
'deny_google_extended_crawling',
* Holds the WPSEO_Admin_Asset_Manager.
* @var WPSEO_Admin_Asset_Manager
protected $asset_manager;
* Holds the WPSEO_Replace_Vars.
* @var WPSEO_Replace_Vars
* Holds the Schema_Types.
* Holds the Current_Page_Helper.
* @var Current_Page_Helper
protected $current_page_helper;
* Holds the Post_Type_Helper.
protected $post_type_helper;
* Holds the Language_Helper.
protected $language_helper;
* Holds the Taxonomy_Helper.
protected $taxonomy_helper;
* Holds the Product_Helper.
protected $product_helper;
* Holds the Woocommerce_Helper.
* @var Woocommerce_Helper
protected $woocommerce_helper;
* Holds the Article_Helper.
protected $article_helper;
* Holds the Options_Helper instance.
* Holds the Content_Type_Visibility_Dismiss_Notifications instance.
* @var Content_Type_Visibility_Dismiss_Notifications
protected $content_type_visibility;
* Constructs Settings_Integration.
* @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager.
* @param WPSEO_Replace_Vars $replace_vars The WPSEO_Replace_Vars.
* @param Schema_Types $schema_types The Schema_Types.
* @param Current_Page_Helper $current_page_helper The Current_Page_Helper.
* @param Post_Type_Helper $post_type_helper The Post_Type_Helper.
* @param Language_Helper $language_helper The Language_Helper.
* @param Taxonomy_Helper $taxonomy_helper The Taxonomy_Helper.
* @param Product_Helper $product_helper The Product_Helper.
* @param Woocommerce_Helper $woocommerce_helper The Woocommerce_Helper.
* @param Article_Helper $article_helper The Article_Helper.
* @param User_Helper $user_helper The User_Helper.
* @param Options_Helper $options The options helper.
* @param Content_Type_Visibility_Dismiss_Notifications $content_type_visibility The Content_Type_Visibility_Dismiss_Notifications instance.
public function __construct(
WPSEO_Admin_Asset_Manager $asset_manager,
WPSEO_Replace_Vars $replace_vars,
Schema_Types $schema_types,
Current_Page_Helper $current_page_helper,
Post_Type_Helper $post_type_helper,
Language_Helper $language_helper,
Taxonomy_Helper $taxonomy_helper,
Product_Helper $product_helper,
Woocommerce_Helper $woocommerce_helper,
Article_Helper $article_helper,
User_Helper $user_helper,
Content_Type_Visibility_Dismiss_Notifications $content_type_visibility
$this->asset_manager = $asset_manager;
$this->replace_vars = $replace_vars;
$this->schema_types = $schema_types;
$this->current_page_helper = $current_page_helper;
$this->taxonomy_helper = $taxonomy_helper;
$this->post_type_helper = $post_type_helper;
$this->language_helper = $language_helper;
$this->product_helper = $product_helper;
$this->woocommerce_helper = $woocommerce_helper;
$this->article_helper = $article_helper;
$this->user_helper = $user_helper;
$this->options = $options;
$this->content_type_visibility = $content_type_visibility;
* Returns the conditionals based on which this loadable should be active.
public static function get_conditionals() {
return [ Settings_Conditional::class ];
* Initializes the integration.
* This is the place to register hooks and filters.
public function register_hooks() {
\add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] );
\add_filter( 'admin_menu', [ $this, 'add_settings_saved_page' ] );
// Are we saving the settings?
if ( $this->current_page_helper->get_current_admin_page() === 'options.php' ) {
// phpcs:disable WordPress.PHP.NoSilencedErrors.Discouraged -- This deprecation will be addressed later.
$post_action = \filter_input( \INPUT_POST, 'action', @\FILTER_SANITIZE_STRING );
$option_page = \filter_input( \INPUT_POST, 'option_page', @\FILTER_SANITIZE_STRING );
if ( $post_action === 'update' && $option_page === self::PAGE ) {
\add_action( 'admin_init', [ $this, 'register_setting' ] );
\add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
// Are we on the settings page?
if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) {
\add_action( 'admin_init', [ $this, 'register_setting' ] );
\add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] );
\add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX );
* Registers the different options under the setting.
public function register_setting() {
foreach ( WPSEO_Options::$options as $name => $instance ) {
if ( \in_array( $name, self::ALLOWED_OPTION_GROUPS, true ) ) {
\register_setting( self::PAGE, $name );
// Only register WP options when the user is allowed to manage them.
if ( \current_user_can( 'manage_options' ) ) {
foreach ( self::WP_OPTIONS as $name ) {
\register_setting( self::PAGE, $name );
* @param array $pages The pages.
* @return array The pages.
public function add_page( $pages ) {
\__( 'Settings', 'wordpress-seo' ),
[ $this, 'display_page' ],
* Because the options route NEEDS to redirect to something.
* @param array $pages The pages.
* @return array The pages.
public function add_settings_saved_page( $pages ) {
// Add success indication to HTML response.
$success = empty( \get_settings_errors() ) ? 'true' : 'false';
echo \esc_html( "{{ yoast-success: $success }}" );
public function display_page() {
echo '<div id="yoast-seo-settings"></div>';
public function enqueue_assets() {
// Remove the emoji script as it is incompatible with both React and any contenteditable fields.
\remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
$this->asset_manager->enqueue_script( 'new-settings' );
$this->asset_manager->enqueue_style( 'new-settings' );
if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2023-promotion' ) ) {
$this->asset_manager->enqueue_style( 'black-friday-banner' );
$this->asset_manager->localize_script( 'new-settings', 'wpseoScriptData', $this->get_script_data() );
* Removes all current WP notices.
public function remove_notices() {
\remove_all_actions( 'admin_notices' );
\remove_all_actions( 'user_admin_notices' );
\remove_all_actions( 'network_admin_notices' );
\remove_all_actions( 'all_admin_notices' );
* Creates the script data.
* @return array The script data.
protected function get_script_data() {
$default_setting_values = $this->get_default_setting_values();
$settings = $this->get_settings( $default_setting_values );
$post_types = $this->post_type_helper->get_indexable_post_type_objects();
$taxonomies = $this->taxonomy_helper->get_indexable_taxonomy_objects();
// Check if attachments are included in indexation.
if ( ! \array_key_exists( 'attachment', $post_types ) ) {
// Always include attachments in the settings, to let the user enable them again.
$attachment_object = \get_post_type_object( 'attachment' );
if ( ! empty( $attachment_object ) ) {
$post_types['attachment'] = $attachment_object;
// Check if post formats are included in indexation.
if ( ! \array_key_exists( 'post_format', $taxonomies ) ) {
// Always include post_format in the settings, to let the user enable them again.
$post_format_object = \get_taxonomy( 'post_format' );
if ( ! empty( $post_format_object ) ) {
$taxonomies['post_format'] = $post_format_object;
$transformed_post_types = $this->transform_post_types( $post_types );
$transformed_taxonomies = $this->transform_taxonomies( $taxonomies, \array_keys( $transformed_post_types ) );
// Check if there is a new content type to show notification only once in the settings.
$show_new_content_type_notification = $this->content_type_visibility->maybe_add_settings_notification();
'settings' => $this->transform_settings( $settings ),
'defaultSettingValues' => $default_setting_values,
'disabledSettings' => $this->get_disabled_settings( $settings ),
'endpoint' => \admin_url( 'options.php' ),
'nonce' => \wp_create_nonce( self::PAGE . '-options' ),
'separators' => WPSEO_Option_Titles::get_instance()->get_separator_options_for_display(),
'replacementVariables' => $this->get_replacement_variables(),
'schema' => $this->get_schema( $transformed_post_types ),
'preferences' => $this->get_preferences( $settings ),
'linkParams' => WPSEO_Shortlinker::get_query_params(),
'postTypes' => $transformed_post_types,
'taxonomies' => $transformed_taxonomies,
'fallbacks' => $this->get_fallbacks(),
'showNewContentTypeNotification' => $show_new_content_type_notification,
* Retrieves the preferences.
* @param array $settings The settings.
* @return array The preferences.
protected function get_preferences( $settings ) {
$shop_page_id = $this->woocommerce_helper->get_shop_page_id();
$homepage_is_latest_posts = \get_option( 'show_on_front' ) === 'posts';
$page_on_front = \get_option( 'page_on_front' );
$page_for_posts = \get_option( 'page_for_posts' );
$wpseo_plugin_availability_checker = new WPSEO_Plugin_Availability();
$woocommerce_seo_file = 'wpseo-woocommerce/wpseo-woocommerce.php';
$woocommerce_seo_active = $wpseo_plugin_availability_checker->is_active( $woocommerce_seo_file );
if ( empty( $page_on_front ) ) {
$page_on_front = $page_for_posts;
$business_settings_url = \get_admin_url( null, 'admin.php?page=wpseo_local' );
if ( \defined( 'WPSEO_LOCAL_FILE' ) ) {
$local_options = \get_option( 'wpseo_local' );
$multiple_locations = $local_options['use_multiple_locations'];
$same_organization = $local_options['multiple_locations_same_organization'];
if ( $multiple_locations === 'on' && $same_organization !== 'on' ) {
$business_settings_url = \get_admin_url( null, 'edit.php?post_type=wpseo_locations' );
'isPremium' => $this->product_helper->is_premium(),
'isNetworkAdmin' => \is_network_admin(),
'isMainSite' => \is_main_site(),
'isMultisite' => \is_multisite(),
'isWooCommerceActive' => $this->woocommerce_helper->is_active(),
'isLocalSeoActive' => \defined( 'WPSEO_LOCAL_FILE' ),
'isNewsSeoActive' => \defined( 'WPSEO_NEWS_FILE' ),
'isWooCommerceSEOActive' => $woocommerce_seo_active,
'promotions' => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(),
'siteUrl' => \get_bloginfo( 'url' ),
'siteTitle' => \get_bloginfo( 'name' ),
'sitemapUrl' => WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ),
'hasWooCommerceShopPage' => $shop_page_id !== -1,
'editWooCommerceShopPageUrl' => \get_edit_post_link( $shop_page_id, 'js' ),
'wooCommerceShopPageSettingUrl' => \get_admin_url( null, 'admin.php?page=wc-settings&tab=products' ),
'localSeoPageSettingUrl' => $business_settings_url,
'homepageIsLatestPosts' => $homepage_is_latest_posts,
'homepagePageEditUrl' => \get_edit_post_link( $page_on_front, 'js' ),
'homepagePostsEditUrl' => \get_edit_post_link( $page_for_posts, 'js' ),
'createUserUrl' => \admin_url( 'user-new.php' ),
'createPageUrl' => \admin_url( 'post-new.php?post_type=page' ),
'editUserUrl' => \admin_url( 'user-edit.php' ),
'editTaxonomyUrl' => \admin_url( 'edit-tags.php' ),
'generalSettingsUrl' => \admin_url( 'options-general.php' ),