>|null */ private $fields; /** * The validators registered along with the fields, keyed by option name. * * @var array|null */ private $validators; /** * Constructor. * * @param Local_SEO_Active_Conditional $local_seo_active_conditional The Local SEO active conditional. * @param Site_Representation_Field_Validators $field_validators The validators of the site representation fields of Yoast SEO. */ public function __construct( Local_SEO_Active_Conditional $local_seo_active_conditional, Site_Representation_Field_Validators $field_validators ) { $this->local_seo_active_conditional = $local_seo_active_conditional; $this->field_validators = $field_validators; } /** * Returns the writable site representation fields, mapped to their JSON schema. * * @return array> The JSON schema of each field, keyed by option name. */ public function get_fields(): array { $this->resolve_fields(); return $this->fields; } /** * Returns the validators registered along with the fields. * * @return array The validator of each field that has one, keyed by option name. */ public function get_validators(): array { $this->resolve_fields(); return $this->validators; } /** * Resolves the fields and their validators, once, so the schemas and the updater always agree on them. * * @return void */ private function resolve_fields(): void { if ( $this->fields !== null ) { return; } $default_fields = $this->get_default_fields(); /** * Filter: 'wpseo_site_representation_ability_fields' - Allows adding site representation settings that * the site representation abilities can write. * * Each field is keyed by its option name and maps to the JSON schema of its value. The option has to be * a Yoast SEO option, as the value is saved through the options helper, so it is sanitized like it is * when saved from the settings page. * * Validation beyond the schema goes in an optional 'validate_callback' key next to the schema. It receives * the value and the option name, and returns a warning to skip saving the field or null to save it. The * warning is relayed to the user, so it should explain why the value was not saved. A field whose * 'validate_callback' is not callable is dropped, so it is never saved unvalidated. The fields of Yoast SEO * come with their own 'validate_callback', which must be kept. * * Organization social profiles are validated by the validator they are registered with through the * 'wpseo_organization_social_profile_fields' filter instead. * * @internal * * @param array> $fields The JSON schema of each field, keyed by option name. */ $fields = \apply_filters( 'wpseo_site_representation_ability_fields', $default_fields ); if ( ! \is_array( $fields ) ) { $fields = $default_fields; } $this->fields = []; $this->validators = []; foreach ( $fields as $field_name => $schema ) { // A field without a schema can neither be described to agents nor validated, so it is dropped. if ( ! \is_array( $schema ) ) { continue; } if ( \array_key_exists( 'validate_callback', $schema ) ) { if ( ! \is_callable( $schema['validate_callback'] ) ) { continue; } $this->validators[ $field_name ] = $schema['validate_callback']; // The callback is not part of the JSON schema that is published to agents. unset( $schema['validate_callback'] ); } $this->fields[ $field_name ] = $schema; } } /** * Returns the site representation fields of Yoast SEO. * * @return array> The JSON schema of each field, keyed by option name. */ private function get_default_fields(): array { return [ 'company_or_person' => [ 'type' => 'string', 'enum' => [ 'company', 'person' ], 'description' => $this->get_company_or_person_description(), 'validate_callback' => [ $this->field_validators, 'validate_company_or_person' ], ], 'company_name' => [ 'type' => 'string', 'description' => \__( 'The name of the organization the site represents. When empty, the site name is used. Only used when company_or_person is "company".', 'wordpress-seo' ), ], 'company_alternate_name' => [ 'type' => 'string', 'description' => \__( 'An alternate name of the organization the site represents, like an acronym or a shorter version of its name. Only used when company_or_person is "company".', 'wordpress-seo' ), ], 'company_logo_id' => [ 'type' => 'integer', 'minimum' => 0, 'description' => \__( 'The attachment ID of the organization logo, an image from the media library. 0 when no logo is set, in which case the site logo is used. Only used when company_or_person is "company".', 'wordpress-seo' ), 'validate_callback' => [ $this->field_validators, 'validate_logo' ], ], 'company_or_person_user_id' => [ 'type' => 'integer', 'minimum' => 0, 'description' => \__( 'The ID of the user the site represents, whose profile information is used in search results. The person\'s name is taken from the display name of this user. Unless you can list users, only users with published posts can be set. 0 when no user is set. Only used when company_or_person is "person".', 'wordpress-seo' ), 'validate_callback' => [ $this->field_validators, 'validate_user_id' ], ], 'person_logo_id' => [ 'type' => 'integer', 'minimum' => 0, 'description' => \__( 'The attachment ID of the personal logo or avatar, an image from the media library. 0 when no logo is set, in which case the site logo is used. Only used when company_or_person is "person".', 'wordpress-seo' ), 'validate_callback' => [ $this->field_validators, 'validate_logo' ], ], 'facebook_site' => [ 'type' => 'string', 'description' => \__( 'The URL of the Facebook page of the organization. Only used when company_or_person is "company". For a person, the Facebook URL from the user\'s profile is used instead.', 'wordpress-seo' ), ], 'twitter_site' => [ 'type' => 'string', 'description' => \__( 'The X username of the organization, without the @. Only added to the schema when company_or_person is "company". It is also used in the X card meta tags: as the site\'s X account, unless the site represents a person whose profile has an X username, and as the X account of posts whose author has no X username.', 'wordpress-seo' ), ], 'other_social_urls' => [ 'type' => 'array', 'items' => [ 'type' => 'string', ], 'description' => \__( 'The URLs of the other social profiles of the organization, like Instagram, LinkedIn or YouTube. Only used when company_or_person is "company". For a person, the social profiles from the user\'s profile are used instead.', 'wordpress-seo' ), ], ]; } // phpcs:enable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint /** * Returns the description of the company_or_person field, which tells agents upfront when the site * cannot be set to represent a person. * * @return string The description. */ private function get_company_or_person_description(): string { $description = \__( 'Whether the site represents an organization ("company") or a person ("person").', 'wordpress-seo' ); if ( ! $this->local_seo_active_conditional->is_met() ) { return $description; } return $description . ' ' . \sprintf( /* translators: %s expands to Yoast Local SEO. */ \__( '%s is active on this site and requires the site to represent an organization, so it always does.', 'wordpress-seo' ), 'Yoast Local SEO', ); } }