x ) { $enriched_defaults[ 'title-tax-' . $tax->name ] = $archives . ' %%page%% %%sep%% %%sitename%%'; // Text field. $enriched_defaults[ 'metadesc-tax-' . $tax->name ] = ''; // Text area. $enriched_defaults[ 'display-metabox-tax-' . $tax->name ] = true; $enriched_defaults[ 'noindex-tax-' . $tax->name ] = ( $tax->name === 'post_format' ); $enriched_defaults[ 'social-title-tax-' . $tax->name ] = $archives; // Text field. $enriched_defaults[ 'social-description-tax-' . $tax->name ] = ''; // Text area. $enriched_defaults[ 'social-image-url-tax-' . $tax->name ] = ''; // Hidden input field. $enriched_defaults[ 'social-image-id-tax-' . $tax->name ] = 0; // Hidden input field. $enriched_defaults[ 'taxonomy-' . $tax->name . '-ptparent' ] = 0; // Select box;. } } $this->enriched_defaults = $enriched_defaults; $this->defaults += $enriched_defaults; } /** * Invalidates enrich_defaults() cache. * * Called from actions: * - (un)registered_post_type * - (un)registered_taxonomy * * @return void */ public function invalidate_enrich_defaults_cache() { $this->enriched_defaults = null; } /** * Validate 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. * * @return array Validated clean value for the option to be saved to the database. */ protected function validate_option( $dirty, $clean, $old ) { $allowed_post_types = $this->get_allowed_post_types(); foreach ( $clean as $key => $value ) { $switch_key = $this->get_switch_key( $key ); switch ( $switch_key ) { // Only ever set programmatically, so no reason for intense validation. case 'company_logo_meta': case 'person_logo_meta': if ( isset( $dirty[ $key ] ) ) { $clean[ $key ] = $dirty[ $key ]; } break; /* Breadcrumbs text fields. */ case 'breadcrumbs-404crumb': case 'breadcrumbs-archiveprefix': case 'breadcrumbs-home': case 'breadcrumbs-prefix': case 'breadcrumbs-searchprefix': case 'breadcrumbs-sep': if ( isset( $dirty[ $key ] ) ) { $clean[ $key ] = wp_kses_post( $dirty[ $key ] ); } break; /* * Text fields. */ /* * Covers: * 'title-home-wpseo', 'title-author-wpseo', 'title-archive-wpseo', // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This isn't commented out code. * 'title-search-wpseo', 'title-404-wpseo' * 'title-' . $pt->name * 'title-ptarchive-' . $pt->name * 'title-tax-' . $tax->name * 'social-title-' . $pt->name * 'social-title-ptarchive-' . $pt->name * 'social-title-tax-' . $tax->name * 'social-title-author-wpseo', 'social-title-archive-wpseo' * 'open_graph_frontpage_title' */ case 'website_name': case 'alternate_website_name': case 'title-': case 'social-title-': case 'open_graph_frontpage_title': if ( isset( $dirty[ $key ] ) ) { $clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] ); } break; case 'company_or_person': if ( isset( $dirty[ $key ] ) ) { if ( in_array( $dirty[ $key ], [ 'company', 'person' ], true ) ) { $clean[ $key ] = $dirty[ $key ]; } else { $defaults = $this->get_defaults(); $clean[ $key ] = $defaults['company_or_person']; } } break; /* * Covers: * 'company_logo', 'person_logo' // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This isn't commented out code. */ case 'company_logo': case 'person_logo': case 'open_graph_frontpage_image': // When a logo changes, we need to ditch the caches we have for it. unset( $clean[ $switch_key . '_id' ] ); unset( $clean[ $switch_key . '_meta' ] ); $this->validate_url( $key, $dirty, $old, $clean ); break; /* * Covers: * 'social-image-url-' . $pt->name * 'social-image-url-ptarchive-' . $pt->name * 'social-image-url-tax-' . $tax->name * 'social-image-url-author-wpseo', 'social-image-url-archive-wpseo' */ case 'social-image-url-': $this->validate_url( $key, $dirty, $old, $clean ); break; /* * Covers: * 'metadesc-home-wpseo', 'metadesc-author-wpseo', 'metadesc-archive-wpseo' * 'metadesc-' . $pt->name * 'metadesc-ptarchive-' . $pt->name * 'metadesc-tax-' . $tax->name * and also: * 'bctitle-ptarchive-' . $pt->name * 'social-description-' . $pt->name * 'social-description-ptarchive-' . $pt->name * 'social-description-tax-' . $tax->name * 'social-description-author-wpseo', 'social-description-archive-wpseo' * 'open_graph_frontpage_desc' */ case 'metadesc-': case 'bctitle-ptarchive-': case 'company_name': case 'company_alternate_name': case 'person_name': case 'social-description-': case 'open_graph_frontpage_desc': if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) { $clean[ $key ] = WPSEO_Utils::sanitize_text_field( $dirty[ $key ] ); } break; /* * Covers: 'rssbefore', 'rssafter' // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This isn't commented out code. */ case 'rssbefore': case 'rssafter': if ( isset( $dirty[ $key ] ) ) { $clean[ $key ] = wp_kses_post( $dirty[ $key ] ); } break; /* 'post_types-' . $pt->name . '-maintax' fields. */ case 'post_types-': $post_type = str_replace( [ 'post_types-', '-maintax' ], '', $key ); $taxonomies = get_object_taxonomies( $post_type, 'names' ); if ( isset( $dirty[ $key ] ) ) { if ( $taxonomies !== [] && in_array( $dirty[ $key ], $taxonomies, true ) ) { $clean[ $key ] = $dirty[ $key ]; } elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) { $clean[ $key ] = 0; } elseif ( sanitize_title_with_dashes( $dirty[ $key ] ) === $dirty[ $key ] ) { // Allow taxonomies which may not be registered yet. $clean[ $key ] = $dirty[ $key ]; } else { if ( isset( $old[ $key ] ) ) { $clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] ); } /* * @todo [JRF => whomever] Maybe change the untranslated $pt name in the * error message to the nicely translated label ? */ add_settings_error( $this->group_name, // Slug title of the setting. $key, // Suffix-id for the error message box. /* translators: %s expands to a post type. */ sprintf( __( 'Please select a valid taxonomy for post type "%s"', 'wordpress-seo' ), $post_type ), // The error message. 'error' // Message type. ); } } elseif ( isset( $old[ $key ] ) ) { $clean[ $key ] = sanitize_title_with_dashes( $old[ $key ] ); } unset( $taxonomies, $post_type ); break; /* 'taxonomy-' . $tax->name . '-ptparent' fields. */ case 'taxonomy-': if ( isset( $dirty[ $key ] ) ) { if ( $allowed_post_types !== [] && in_array( $dirty[ $key ], $allowed_post_types, true ) ) { $clean[ $key ] = $dirty[ $key ]; } elseif ( (string) $dirty[ $key ] === '0' || (string) $dirty[ $key ] === '' ) { $clean[ $key ] = 0; } elseif ( sanitize_key( $dirty[ $key ] ) === $dirty[ $key ] ) { // Allow taxonomies which may not be registered yet. $clean[ $key ] = $dirty[ $key ]; } else { if ( isset( $old[ $key ] ) ) { $clean[ $key ] = sanitize_key( $old[ $key ] ); } /* * @todo [JRF =? whomever] Maybe change the untranslated $tax name in the * error message to the nicely translated label ? */ $tax = str_replace( [ 'taxonomy-', '-ptparent' ], '', $key ); add_settings_error( $this->group_name, // Slug title of the setting. '_' . $tax, // Suffix-ID for the error message box. /* translators: %s expands to a taxonomy slug. */ sprintf( __( 'Please select a valid post type for taxonomy "%s"', 'wordpress-seo' ), $tax ), // The error message. 'error' // Message type. ); unset( $tax ); } } elseif ( isset( $old[ $key ] ) ) { $clean[ $key ] = sanitize_key( $old[ $key ] ); } break; /* * Covers: * 'company_or_person_user_id' * 'company_logo_id', 'person_logo_id', 'open_graph_frontpage_image_id' * 'social-image-id-' . $pt->name * 'social-image-id-ptarchive-' . $pt->name * 'social-image-id-tax-' . $tax->name * 'social-image-id-author-wpseo', 'social-image-id-archive-wpseo' */ case 'company_or_person_user_id': case 'company_logo_id': case 'person_logo_id': case 'social-image-id-': case 'open_graph_frontpage_image_id': case 'publishing_principles_id': case 'ownership_funding_info_id': case 'actionable_feedback_policy_id': case 'corrections_policy_id': case 'ethics_policy_id': case 'diversity_policy_id': case 'diversity_staffing_report_id': if ( isset( $dirty[ $key ] ) ) { $int = WPSEO_Utils::validate_int( $dirty[ $key ] ); if ( $int !== false && $int >= 0 ) { $clean[ $key ] = $int; } } elseif ( isset( $old[ $key ] ) ) { $int = WPSEO_Utils::validate_int( $old[ $key ] ); if ( $int !== false && $int >= 0 ) { $clean[ $key ] = $int; } } break; /* Separator field - Radio. */ case 'separator': if ( isset( $dirty[ $key ] ) && $dirty[ $key ] !== '' ) { // Get separator fields. $separator_fields = $this->get_separator_options(); // Check if the given separator exists. if ( isset( $separator_fields[ $dirty[ $key ] ] ) ) { $clean[ $key ] = $dirty[ $key ]; } } break; case 'schema-page-type-': if ( isset( $dirty[ $key ] ) && is_string( $dirty[ $key ] ) ) { if ( array_key_exists( $dirty[ $key ], Schema_Types::PAGE_TYPES ) ) { $clean[ $key ] = $dirty[ $key ]; } else { $defaults = $this->get_defaults(); $post_type = str_replace( $switch_key, '', $key ); $clean[ $key ] = $defaults[ $switch_key . $post_type ]; } } break; case 'schema-article-type-': if ( isset( $dirty[ $key ] ) && is_string( $dirty[ $key ] ) ) { /** * Filter: 'wpseo_schema_article_types' - Allow developers to filter the available article types. * * Make sure when you filter this to also filter `wpseo_schema_article_types_labels`. * * @api array $schema_article_types The available schema article types. */ if ( array_key_exists( $dirty[ $key ], apply_filters( 'wpseo_schema_article_types', Schema_Types::ARTICLE_TYPES ) ) ) { $clean[ $key ] = $dirty[ $key ]; } else { $defaults = $this->get_defaults(); $post_type = str_replace( $switch_key, '', $key ); $clean[ $key ] = $defaults[ $switch_key . $post_type ]; } } break; /* * Boolean fields. */ /* * Covers: * 'noindex-author-wpseo', 'noindex-author-noposts-wpseo', 'noindex-archive-wpseo' * 'noindex-' . $pt->name * 'noindex-ptarchive-' . $pt->name * 'noindex-tax-' . $tax->name * 'forcerewritetitle': * 'noodp': * 'noydir': * 'disable-author': * 'disable-date': * 'disable-post_format'; * 'noindex-' * 'display-metabox-pt-' * 'display-metabox-pt-'. $pt->name * 'display-metabox-tax-' * 'display-metabox-tax-' . $tax->name * 'breadcrumbs-display-blog-page' * 'breadcrumbs-boldlast' * 'breadcrumbs-enable' * 'stripcategorybase' */ default: $clean[ $key ] = ( isset( $dirty[ $key ] ) ? WPSEO_Utils::validate_bool( $dirty[ $key ] ) : false ); break; } } return $clean; } /** * Retrieve a list of the allowed post types as breadcrumb parent for a taxonomy. * Helper method for validation. * * {@internal Don't make static as new types may still be registered.}} * * @return array */ protected function get_allowed_post_types() { $allowed_post_types = []; /* * WPSEO_Post_Type::get_accessible_post_types() should *not* be used here. */ $post_types = get_post_types( [ 'public' => true ], 'objects' ); if ( get_option( 'show_on_front' ) === 'page' && get_option( 'page_for_posts' ) > 0 ) { $allowed_post_types[] = 'post'; } if ( is_array( $post_types ) && $post_types !== [] ) { foreach ( $post_types as $type ) { if ( WPSEO_Post_Type::has_archive( $type ) ) { $allowed_post_types[] = $type->name; } } } return $allowed_post_types; } /** * Clean a given option value. * * @param array $option_value Old (not merged with defaults or filtered) option value to * clean according to the rules for this option. * @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 the saved ones. * * @return array Cleaned option. */ protected function clean_option( $option_value, $current_version = null, $all_old_option_values = null ) { static $original = null; // Double-run this function to ensure renaming of the taxonomy options will work. if ( ! isset( $original ) && has_action( 'wpseo_double_clean_titles', [ $this, 'clean' ] ) === false ) { add_action( 'wpseo_double_clean_titles', [ $this, 'clean' ] ); $original = $option_value; } /* * Move options from very old option to this one. * * {@internal Don't rename to the 'current' names straight away as that would prevent * the rename/unset combi below from working.}} * * @todo [JRF] Maybe figure out a smarter way to deal with this. */ $old_option = null; if ( isset( $all_old_option_values ) ) { // Ok, we have an import. if ( isset( $all_old_option_values['wpseo_indexation'] ) && is_array( $all_old_option_values['wpseo_indexation'] ) && $all_old_option_values['wpseo_indexation'] !== [] ) { $old_option = $all_old_option_values['wpseo_indexation']; } } else { $old_option = get_option( 'wpseo_indexation' ); } if ( is_array( $old_option ) && $old_option !== [] ) { $move = [ 'noindexauthor' => 'noindex-author', 'disableauthor' => 'disable-author', 'noindexdate' => 'noindex-archive', 'noindexcat' => 'noindex-category', 'noindextag' => 'noindex-post_tag', 'noindexpostformat' => 'noindex-post_format', ]; foreach ( $move as $old => $new ) { if ( isset( $old_option[ $old ] ) && ! isset( $option_value[ $new ] ) ) { $option_value[ $new ] = $old_option[ $old ]; } } unset( $move, $old, $new ); } unset( $old_option ); // Fix wrongness created by buggy version 1.2.2. if ( isset( $option_value['title-home'] ) && $option_value['title-home'] === '%%sitename%% - %%sitedesc%% - 12345' ) { $option_value['title-home-wpseo'] = '%%sitename%% - %%sitedesc%%'; } /* * Renaming these options to avoid ever overwritting these if a (bloody stupid) user / * programmer would use any of the following as a custom post type or custom taxonomy: * 'home', 'author', 'archive', 'search', '404', 'subpages'. * * Similarly, renaming the tax options to avoid a custom post type and a taxonomy * with the same name occupying the same option. */ $rename = [ 'title-home' => 'title-home-wpseo', 'title-author' => 'title-author-wpseo', 'title-archive' => 'title-archive-wpseo', 'title-search' => 'title-search-wpseo', 'title-404' => 'title-404-wpseo', 'metadesc-home' => 'metadesc-home-wpseo', 'metadesc-author' => 'metadesc-author-wpseo', 'metadesc-archive' => 'metadesc-archive-wpseo', 'noindex-author' => 'noindex-author-wpseo', 'noindex-archive' => 'noindex-archive-wpseo', ]; foreach ( $rename as $old => $new ) { if ( isset( $option_value[ $old ] ) && ! isset( $option_value[ $new ] ) ) { $option_value[ $new ] = $option_value[ $old ]; unset( $option_value[ $old ] ); } } unset( $rename, $old, $new ); /* * {@internal This clean-up action can only be done effectively once the taxonomies * and post_types have been registered, i.e. at the end of the init action.}} */ if ( isset( $original ) && current_filter() === 'wpseo_double_clean_titles' || did_action( 'wpseo_double_clean_titles' ) > 0 ) { $rename = [ 'title-' => 'title-tax-', 'metadesc-' => 'metadesc-tax-', 'noindex-' => 'noindex-tax-', 'tax-hideeditbox-' => 'hideeditbox-tax-', ]; $taxonomy_names = get_taxonomies( [ 'public' => true ], 'names' ); $post_type_names = get_post_types( [ 'public' => true ], 'names' ); $defaults = $this->get_defaults(); if ( $taxonomy_names !== [] ) { foreach ( $taxonomy_names as $tax ) { foreach ( $rename as $old_prefix => $new_prefix ) { if ( ( isset( $original[ $old_prefix . $tax ] ) && ! isset( $original[ $new_prefix . $tax ] ) ) && ( ! isset( $option_value[ $new_prefix . $tax ] ) || ( isset( $option_value[ $new_prefix . $tax ] ) && $option_value[ $new_prefix . $tax ] === $defaults[ $new_prefix . $tax ] ) ) ) { $option_value[ $new_prefix . $tax ] = $original[ $old_prefix . $tax ]; /* * Check if there is a cpt with the same name as the tax, * if so, we should make sure that the old setting hasn't been removed. */ if ( ! isset( $post_type_names[ $tax ] ) && isset( $option_value[ $old_prefix . $tax ] ) ) { unset( $option_value[ $old_prefix . $tax ] ); } else { if ( isset( $post_type_names[ $tax ] ) && ! isset( $option_value[ $old_prefix . $tax ] ) ) { $option_value[ $old_prefix . $tax ] = $original[ $old_prefix . $tax ]; } } if ( $old_prefix === 'tax-hideeditbox-' ) { unset( $option_value[ $old_prefix . $tax ] ); } } } } } unset( $rename, $taxonomy_names, $post_type_names, $defaults, $tax, $old_prefix, $new_prefix ); } /* * Make sure the values of the variable option key options are cleaned as they * may be retained and would not be cleaned/validated then. */ if ( is_array( $option_value ) && $option_value !== [] ) { foreach ( $option_value as $key => $value ) { $switch_key = $this->get_switch_key( $key ); // Similar to validation routine - any changes made there should be made here too. switch ( $switch_key ) { /* Text fields. */ case 'title-': case 'metadesc-': case 'bctitle-ptarchive-': $option_value[ $key ] = WPSEO_Utils::sanitize_text_field( $value ); break; case 'separator': if ( ! array_key_exists( $value, $this->get_separator_options() ) ) { $option_value[ $key ] = false; } break; /* * Boolean fields. */ /* * Covers: * 'noindex-' * 'hideeditbox-' */ default: $option_value[ $key ] = WPSEO_Utils::validate_bool( $value ); break; } } unset( $key, $value, $switch_key ); } return $option_value; } /** * 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 Overrule the abstract class version of this to make sure one extra renamed * variable key does not get removed. IMPORTANT: keep this method in line with * the parent on which it is based!}} * * @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. * * @return array */ protected function retain_variable_keys( $dirty, $clean ) { if ( ( is_array( $this->variable_array_key_patterns ) && $this->variable_array_key_patterns !== [] ) && ( is_array( $dirty ) && $dirty !== [] ) ) { // Add the extra pattern. $patterns = $this->variable_array_key_patterns; $patterns[] = 'tax-hideeditbox-'; /** * Allow altering the array with variable array key patterns. * * @api array $patterns Array with the variable array key patterns. */ $patterns = apply_filters( 'wpseo_option_titles_variable_array_key_patterns', $patterns ); foreach ( $dirty as $key => $value ) { // Do nothing if already in filtered option array. if ( isset( $clean[ $key ] ) ) { continue; } foreach ( $patterns as $pattern ) { if ( strpos( $key, $pattern ) === 0 ) { $clean[ $key ] = $value; break; } } } } return $clean; } /** * Retrieves a list of separator options. * * @return array An array of the separator options. */ protected static function get_separator_option_list() { $separators = [ 'sc-dash' => [ 'option' => '-', 'label' => __( 'Dash', 'wordpress-seo' ), ], 'sc-ndash' => [ 'option' => '–', 'label' => __( 'En dash', 'wordpress-seo' ), ], 'sc-mdash' => [ 'option' => '—', 'label' => __( 'Em dash', 'wordpress-seo' ), ], 'sc-colon' => [ 'option' => ':', 'label' => __( 'Colon', 'wordpress-seo' ), ], 'sc-middot' => [ 'option' => '·', 'label' => __( 'Middle dot', 'wordpress-seo' ), ], 'sc-bull' => [ 'option' => '•', 'label' => __( 'Bullet', 'wordpress-seo' ), ], 'sc-star' => [ 'option' => '*', 'label' => __( 'Asterisk', 'wordpress-seo' ), ], 'sc-smstar' => [ 'option' => '⋆', 'label' => __( 'Low asterisk', 'wordpress-seo' ), ], 'sc-pipe' => [ 'option' => '|', 'label' => __( 'Vertical bar', 'wordpress-seo' ), ], 'sc-tilde' => [ 'option' => '~', 'label' => __( 'Small tilde', 'wordpress-seo' ), ], 'sc-laquo' => [ 'option' => '«', 'label' => __( 'Left angle quotation mark', 'wordpress-seo' ), ], 'sc-raquo' => [ 'option' => '»', 'label' => __( 'Right angle quotation mark', 'wordpress-seo' ), ], 'sc-lt' => [ 'option' => '>', 'label' => __( 'Less than sign', 'wordpress-seo' ), ], 'sc-gt' => [ 'option' => '<', 'label' => __( 'Greater than sign', 'wordpress-seo' ), ], ]; /** * Allows altering the separator options array. * * @api array $separators Array with the separator options. */ $separator_list = apply_filters( 'wpseo_separator_option_list', $separators ); if ( ! is_array( $separator_list ) ) { return $separators; } return $separator_list; } }