<?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Abilities\User_Interface; use Yoast\WP\SEO\Abilities\Application\Score_Retriever; use Yoast\WP\SEO\Conditionals\Abilities_API_Conditional; use Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository; use Yoast\WP\SEO\Editors\Framework\Inclusive_Language_Analysis; use Yoast\WP\SEO\Editors\Framework\Keyphrase_Analysis; use Yoast\WP\SEO\Editors\Framework\Readability_Analysis; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Integration that registers Yoast SEO abilities with the WordPress Abilities API. */ class Abilities_Integration implements Integration_Interface { /** * The score retriever. * * @var Score_Retriever */ private $score_retriever; /** * The capability helper. * * @var Capability_Helper */ private $capability_helper; /** * The enabled analysis features repository. * * @var Enabled_Analysis_Features_Repository */ private $enabled_analysis_features_repository; /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The conditionals. */ public static function get_conditionals() { return [ Abilities_API_Conditional::class ]; } /** * Constructor. * * @param Score_Retriever $score_retriever The score retriever. * @param Capability_Helper $capability_helper The capability helper. * @param Enabled_Analysis_Features_Repository $enabled_analysis_features_repository The enabled analysis features repository. */ public function __construct( Score_Retriever $score_retriever, Capability_Helper $capability_helper, Enabled_Analysis_Features_Repository $enabled_analysis_features_repository ) { $this->score_retriever = $score_retriever; $this->capability_helper = $capability_helper; $this->enabled_analysis_features_repository = $enabled_analysis_features_repository; } /** * Registers hooks with WordPress. * * @return void */ public function register_hooks() { \add_action( 'wp_abilities_api_init', [ $this, 'register_abilities' ] ); } /** * Registers the Yoast SEO abilities. * * @return void */ public function register_abilities() { $enabled_features = $this->enabled_analysis_features_repository->get_features_by_keys( [ Keyphrase_Analysis::NAME, Readability_Analysis::NAME, Inclusive_Language_Analysis::NAME, ], )->to_array(); if ( $enabled_features[ Keyphrase_Analysis::NAME ] === true ) { $this->register_seo_scores_ability(); } if ( $enabled_features[ Readability_Analysis::NAME ] === true ) { $this->register_readability_scores_ability(); } if ( $enabled_features[ Inclusive_Language_Analysis::NAME ] === true ) { $this->register_inclusive_language_scores_ability(); } } /** * Checks whether the current user can read scores. * * @return bool Whether the current user can read scores. */ public function can_read_scores(): bool { return $this->capability_helper->current_user_can( 'wpseo_manage_options' ); } /** * Registers the SEO scores ability. * * @return void */ private function register_seo_scores_ability(): void { $output_schema = $this->get_score_output_schema(); $output_schema['properties']['focus_keyphrase'] = [ 'type' => [ 'string', 'null' ], 'description' => \__( 'The focus keyphrase for the post, or null if not set.', 'wordpress-seo' ), ]; \wp_register_ability( Ability_Categories_Integration::CATEGORY_SLUG . '/get-seo-scores', $this->get_shared_ability_args( [ 'label' => \__( 'Get SEO Scores', 'wordpress-seo' ), 'description' => \__( 'Get the SEO scores for the most recently modified posts.', 'wordpress-seo' ), 'output_schema' => $this->wrap_in_array_schema( $output_schema ), 'execute_callback' => [ $this->score_retriever, 'get_seo_scores' ], ], ), ); } /** * Registers the readability scores ability. * * @return void */ private function register_readability_scores_ability(): void { \wp_register_ability( Ability_Categories_Integration::CATEGORY_SLUG . '/get-readability-scores', $this->get_shared_ability_args( [ 'label' => \__( 'Get Readability Scores', 'wordpress-seo' ), 'description' => \__( 'Get the readability scores for the most recently modified posts.', 'wordpress-seo' ), 'output_schema' => $this->wrap_in_array_schema( $this->get_score_output_schema() ), 'execute_callback' => [ $this->score_retriever, 'get_readability_scores' ], ], ), ); } /** * Registers the inclusive language scores ability. * * @return void */ private function register_inclusive_language_scores_ability(): void { \wp_register_ability( Ability_Categories_Integration::CATEGORY_SLUG . '/get-inclusive-language-scores', $this->get_shared_ability_args( [ 'label' => \__( 'Get Inclusive Language Scores', 'wordpress-seo' ), 'description' => \__( 'Get the inclusive language scores for the most recently modified posts.', 'wordpress-seo' ), 'output_schema' => $this->wrap_in_array_schema( $this->get_score_output_schema() ), 'execute_callback' => [ $this->score_retriever, 'get_inclusive_language_scores' ], ], ), ); } // phpcs:disable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint -- Too complicated of a param declaration for this case. /** * Returns the shared ability arguments merged with ability-specific arguments. * * @param array<string, mixed> $ability_specific_args The ability-specific arguments. * * @return array<string, mixed> The merged ability arguments. */ private function get_shared_ability_args( array $ability_specific_args ): array { // phpcs:enable SlevomatCodingStandard.TypeHints.DisallowMixedTypeHint.DisallowedMixedTypeHint return \array_merge( [ 'category' => Ability_Categories_Integration::CATEGORY_SLUG, 'input_schema' => [ 'type' => 'object', 'properties' => [ 'number_of_posts' => [ 'type' => 'integer', 'description' => \__( 'The number of recently modified posts to retrieve scores for. Defaults to 10.', 'wordpress-seo' ), 'minimum' => 1, 'maximum' => 100, 'default' => 10, ], ], ], 'permission_callback' => [ $this, 'can_read_scores' ], 'meta' => [ 'show_in_rest' => true, 'annotations' => [ 'readonly' => true, 'destructive' => false, 'idempotent' => true, ], 'mcp' => [ 'public' => true, ], ], ], $ability_specific_args, ); } /** * Wraps an item schema in an array schema. * * @param array<string, array<string, string>> $item_schema The item schema. * * @return array<string, array<string, string>> The array schema. */ private function wrap_in_array_schema( array $item_schema ): array { return [ 'type' => 'array', 'items' => $item_schema, ]; } /** * Returns the score output schema, including the title property. * * @return array<string, array<string, string>> The score output schema. */ private function get_score_output_schema(): array { return [ 'type' => 'object', 'properties' => [ 'title' => [ 'type' => 'string', 'description' => \__( 'The post title.', 'wordpress-seo' ), ], 'score' => [ 'type' => 'string', 'enum' => [ 'na', 'bad', 'ok', 'good' ], 'description' => \__( 'The score slug.', 'wordpress-seo' ), ], 'label' => [ 'type' => 'string', 'description' => \__( 'A human-readable label for the score.', 'wordpress-seo' ), ], ], ]; } }