: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
/* *********** METHODS influencing add_option(), update_option() and saving from admin pages. *********** */
* Register (whitelist) the option for the configuration pages.
* The validation callback is already registered separately on the sanitize_option hook,
* so no need to double register.
public function register_setting() {
if ( ! WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ) ) {
if ( $this->multisite_only === true ) {
$network_settings_api = Yoast_Network_Settings_API::get();
if ( $network_settings_api->meets_requirements() ) {
$network_settings_api->register_setting( $this->group_name, $this->option_name );
register_setting( $this->group_name, $this->option_name );
* @param mixed $option_value The unvalidated new value for the option.
* @return array Validated new value for the option.
public function validate( $option_value ) {
$clean = $this->get_defaults();
/* Return the defaults if the new value is empty. */
if ( ! is_array( $option_value ) || $option_value === [] ) {
$option_value = array_map( [ 'WPSEO_Utils', 'trim_recursive' ], $option_value );
$old = $this->get_original_option();
if ( ! is_array( $old ) ) {
$old = array_merge( $clean, $old );
$clean = $this->validate_option( $option_value, $clean, $old );
// Prevent updates to variables that are disabled via the override option.
$clean = $this->prevent_disabled_options_update( $clean, $old );
/* Retain the values for variable array keys even when the post type/taxonomy is not yet registered. */
if ( isset( $this->variable_array_key_patterns ) ) {
$clean = $this->retain_variable_keys( $option_value, $clean );
$this->remove_default_filters();
* Checks whether a specific option key is disabled.
* This is determined by whether an override option is available with a key that equals the given key prefixed
* @param string $key Option key.
* @return bool True if option key is disabled, false otherwise.
public function is_disabled( $key ) {
$override_option = $this->get_override_option();
if ( empty( $override_option ) ) {
return isset( $override_option[ self::ALLOW_KEY_PREFIX . $key ] ) && ! $override_option[ self::ALLOW_KEY_PREFIX . $key ];
* All concrete classes must contain a validate_option() method which validates all
* values within the option.
* @param array $dirty New value for the option.
* @param array $clean Clean value for the option, normally the defaults.
* @param array $old Old value of the option.
abstract protected function validate_option( $dirty, $clean, $old );
/* *********** METHODS for ADDING/UPDATING/UPGRADING the option. *********** */
* Retrieve the real old value (unmerged with defaults).
* @return array|bool The original option value (which can be false if the option doesn't exist).
protected function get_original_option() {
$this->remove_default_filters();
$this->remove_option_filters();
// Get (unvalidated) array, NOT merged with defaults.
if ( $this->multisite_only !== true ) {
$option_value = get_option( $this->option_name );
$option_value = get_site_option( $this->option_name );
$this->add_option_filters();
$this->add_default_filters();
* Add the option if it doesn't exist for some strange reason.
* @uses WPSEO_Option::get_original_option()
public function maybe_add_option() {
if ( $this->get_original_option() === false ) {
if ( $this->multisite_only !== true ) {
update_option( $this->option_name, $this->get_defaults() );
$this->update_site_option( $this->get_defaults() );
* {@internal This special method is only needed for multisite options, but very needed indeed there.
* The order in which certain functions and hooks are run is different between
* get_option() and get_site_option() which means in practice that the removing
* of the default filters would be done too late and the re-adding of the default
* filters might not be done at all.
* Aka: use the WPSEO_Options::update_site_option() method (which calls this method)
* for safely adding/updating multisite options.}}
* @param mixed $value The new value for the option.
* @return bool Whether the update was successful.
public function update_site_option( $value ) {
if ( $this->multisite_only === true && is_multisite() ) {
$this->remove_default_filters();
$result = update_site_option( $this->option_name, $value );
$this->add_default_filters();
* Retrieve the real old value (unmerged with defaults), clean and re-save the option.
* @uses WPSEO_Option::get_original_option()
* @uses WPSEO_Option::import()
* @param string|null $current_version Optional. Version from which to upgrade, if not set,
* version-specific upgrades will be disregarded.
public function clean( $current_version = null ) {
$option_value = $this->get_original_option();
$this->import( $option_value, $current_version );
* Clean and re-save the option.
* @uses clean_option() method from concrete class if it exists.
* @todo [JRF/whomever] Figure out a way to show settings error during/after the upgrade - maybe
* something along the lines of:
* -> add them to a property in this class
* -> if that property isset at the end of the routine and add_settings_error function does not exist,
* save as transient (or update the transient if one already exists)
* -> next time an admin is in the WP back-end, show the errors and delete the transient or only delete it
* once the admin has dismissed the message (add ajax function)
* Important: all validation routines which add_settings_errors would need to be changed for this to work
* @param array $option_value Option value to be imported.
* @param string|null $current_version Optional. Version from which to upgrade, if not set,
* version-specific upgrades will be disregarded.
* @param array|null $all_old_option_values Optional. Only used when importing old options to
* have access to the real old values, in contrast to
public function import( $option_value, $current_version = null, $all_old_option_values = null ) {
if ( $option_value === false ) {
$option_value = $this->get_defaults();
elseif ( is_array( $option_value ) && method_exists( $this, 'clean_option' ) ) {
$option_value = $this->clean_option( $option_value, $current_version, $all_old_option_values );
* Save the cleaned value - validation will take care of cleaning out array keys which
* should no longer be there.
if ( $this->multisite_only !== true ) {
update_option( $this->option_name, $option_value );
$this->update_site_option( $this->option_name, $option_value );
* Returns the variable array key patterns for an options class.
public function get_patterns() {
return (array) $this->variable_array_key_patterns;
* Retrieves the option name.
* @return string The set option name.
public function get_option_name() {
return $this->option_name;
* Concrete classes *may* contain a clean_option method which will clean out old/renamed
* values within the option.
* abstract public function clean_option( $option_value, $current_version = null, $all_old_option_values = null );
/* *********** HELPER METHODS for internal use. *********** */
* Helper method - Combines a fixed array of default values with an options array
* while filtering out any keys which are not in the defaults array.
* @todo [JRF] - shouldn't this be a straight array merge ? at the end of the day, the validation
* removes any invalid keys on save.
* @param array|null $options Optional. Current options. If not set, the option defaults
* for the $option_key will be returned.
* @return array Combined and filtered options array.
protected function array_filter_merge( $options = null ) {
$defaults = $this->get_defaults();
if ( ! isset( $options ) || $options === false || $options === [] ) {
$options = (array) $options;
if ( $defaults !== array() ) {
foreach ( $defaults as $key => $default_value ) {
// @todo should this walk through array subkeys ?
$filtered[ $key ] = ( isset( $options[ $key ] ) ? $options[ $key ] : $default_value );
$filtered = array_merge( $defaults, $options );
* Sets updated values for variables that are disabled via the override option back to their previous values.
* @param array $updated Updated option value.
* @param array $old Old option value.
* @return array Updated option value, with all disabled variables set to their old values.
protected function prevent_disabled_options_update( $updated, $old ) {
$override_option = $this->get_override_option();
if ( empty( $override_option ) ) {
* This loop could as well call `is_disabled( $key )` for each iteration,
* however this would be worse performance-wise.
foreach ( $old as $key => $value ) {
if ( isset( $override_option[ self::ALLOW_KEY_PREFIX . $key ] ) && ! $override_option[ self::ALLOW_KEY_PREFIX . $key ] ) {
$updated[ $key ] = $old[ $key ];
* Retrieves the value of the override option, if available.
* An override option contains values that may determine access to certain sub-variables
* Only regular options in multisite can have override options, which in that case
* would be network options.
* @return array Override option value, or empty array if unavailable.
protected function get_override_option() {
if ( empty( $this->override_option_name ) || $this->multisite_only === true || ! is_multisite() ) {
return get_site_option( $this->override_option_name, [] );
* Make sure that any set option values relating to post_types and/or taxonomies are retained,
* even when that post_type or taxonomy may not yet have been registered.
* {@internal The wpseo_titles concrete class overrules this method. Make sure that any
* changes applied here, also get ported to that version.}}
* @param array $dirty Original option as retrieved from the database.
* @param array $clean Filtered option where any options which shouldn't be in our option
* have already been removed and any options which weren't set
* have been set to their defaults.
protected function retain_variable_keys( $dirty, $clean ) {
if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== [] ) && ( is_array( $dirty ) && $dirty !== [] ) ) {
foreach ( $dirty as $key => $value ) {
// Do nothing if already in filtered options.
if ( isset( $clean[ $key ] ) ) {
foreach ( $this->variable_array_key_patterns as $pattern ) {
if ( strpos( $key, $pattern ) === 0 ) {
* Check whether a given array key conforms to one of the variable array key patterns for this option.
* @used-by validate_option() methods for options with variable array keys.
* @param string $key Array key to check.
* @return string Pattern if it conforms, original array key if it doesn't or if the option
* does not have variable array keys.
protected function get_switch_key( $key ) {
if ( ! isset( $this->variable_array_key_patterns ) || ( ! is_array( $this->variable_array_key_patterns ) || $this->variable_array_key_patterns === [] ) ) {
foreach ( $this->variable_array_key_patterns as $pattern ) {
if ( strpos( $key, $pattern ) === 0 ) {