File manager - Edit - /home/colomboelectrici/public_html/wp-includes/css/dist/src.tar
Back
loader.php 0000644 00000015166 15025657560 0006547 0 ustar 00 <?php namespace Yoast\WP\SEO; use Throwable; use WP_CLI; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Class that manages loading integrations if and only if all their conditionals are met. */ class Loader { /** * The registered integrations. * * @var string[] */ protected $integrations = []; /** * The registered integrations. * * @var string[] */ protected $initializers = []; /** * The registered routes. * * @var string[] */ protected $routes = []; /** * The registered commands. * * @var string[] */ protected $commands = []; /** * The registered migrations. * * @var string[] */ protected $migrations = []; /** * The dependency injection container. * * @var ContainerInterface */ protected $container; /** * Loader constructor. * * @param ContainerInterface $container The dependency injection container. */ public function __construct( ContainerInterface $container ) { $this->container = $container; } /** * Registers an integration. * * @param string $integration_class The class name of the integration to be loaded. * * @return void */ public function register_integration( $integration_class ) { $this->integrations[] = $integration_class; } /** * Registers an initializer. * * @param string $initializer_class The class name of the initializer to be loaded. * * @return void */ public function register_initializer( $initializer_class ) { $this->initializers[] = $initializer_class; } /** * Registers a route. * * @param string $route_class The class name of the route to be loaded. * * @return void */ public function register_route( $route_class ) { $this->routes[] = $route_class; } /** * Registers a command. * * @param string $command_class The class name of the command to be loaded. * * @return void */ public function register_command( $command_class ) { $this->commands[] = $command_class; } /** * Registers a migration. * * @param string $plugin The plugin the migration belongs to. * @param string $version The version of the migration. * @param string $migration_class The class name of the migration to be loaded. * * @return void */ public function register_migration( $plugin, $version, $migration_class ) { if ( ! \array_key_exists( $plugin, $this->migrations ) ) { $this->migrations[ $plugin ] = []; } $this->migrations[ $plugin ][ $version ] = $migration_class; } /** * Loads all registered classes if their conditionals are met. * * @return void */ public function load() { $this->load_initializers(); if ( ! \did_action( 'init' ) ) { \add_action( 'init', [ $this, 'load_integrations' ] ); } else { $this->load_integrations(); } \add_action( 'rest_api_init', [ $this, 'load_routes' ] ); if ( \defined( 'WP_CLI' ) && \WP_CLI ) { $this->load_commands(); } } /** * Returns all registered migrations. * * @param string $plugin The plugin to get the migrations for. * * @return string[]|false The registered migrations. False if no migrations were registered. */ public function get_migrations( $plugin ) { if ( ! \array_key_exists( $plugin, $this->migrations ) ) { return false; } return $this->migrations[ $plugin ]; } /** * Loads all registered commands. * * @return void */ protected function load_commands() { foreach ( $this->commands as $class ) { $command = $this->get_class( $class ); if ( $command === null ) { continue; } WP_CLI::add_command( $class::get_namespace(), $command ); } } /** * Loads all registered initializers if their conditionals are met. * * @return void */ protected function load_initializers() { foreach ( $this->initializers as $class ) { if ( ! $this->conditionals_are_met( $class ) ) { continue; } $initializer = $this->get_class( $class ); if ( $initializer === null ) { continue; } $initializer->initialize(); } } /** * Loads all registered integrations if their conditionals are met. * * @return void */ public function load_integrations() { foreach ( $this->integrations as $class ) { if ( ! $this->conditionals_are_met( $class ) ) { continue; } $integration = $this->get_class( $class ); if ( $integration === null ) { continue; } $integration->register_hooks(); } } /** * Loads all registered routes if their conditionals are met. * * @return void */ public function load_routes() { foreach ( $this->routes as $class ) { if ( ! $this->conditionals_are_met( $class ) ) { continue; } $route = $this->get_class( $class ); if ( $route === null ) { continue; } $route->register_routes(); } } /** * Checks if all conditionals of a given loadable are met. * * @param string $loadable_class The class name of the loadable. * * @return bool Whether all conditionals of the loadable are met. */ protected function conditionals_are_met( $loadable_class ) { // In production environments do not fatal if the class does not exist but log and fail gracefully. if ( \YOAST_ENVIRONMENT === 'production' && ! \class_exists( $loadable_class ) ) { if ( \defined( 'WP_DEBUG' ) && \WP_DEBUG ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log \error_log( \sprintf( /* translators: %1$s expands to Yoast SEO, %2$s expands to the name of the class that could not be found. */ \__( '%1$s attempted to load the class %2$s but it could not be found.', 'wordpress-seo' ), 'Yoast SEO', $loadable_class ) ); } return false; } $conditionals = $loadable_class::get_conditionals(); foreach ( $conditionals as $class ) { $conditional = $this->get_class( $class ); if ( $conditional === null || ! $conditional->is_met() ) { return false; } } return true; } /** * Gets a class from the container. * * @param string $class_name The class name. * * @return object|null The class or, in production environments, null if it does not exist. * * @throws Throwable If the class does not exist in development environments. */ protected function get_class( $class_name ) { try { return $this->container->get( $class_name ); } catch ( Throwable $e ) { // In production environments do not fatal if the class could not be constructed but log and fail gracefully. if ( \YOAST_ENVIRONMENT === 'production' ) { if ( \defined( 'WP_DEBUG' ) && \WP_DEBUG ) { // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log \error_log( $e->getMessage() ); } return null; } throw $e; } } } memoizers/meta-tags-context-memoizer.php 0000644 00000012063 15025657560 0014475 0 ustar 00 <?php namespace Yoast\WP\SEO\Memoizers; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Helpers\Blocks_Helper; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * The meta tags context memoizer. */ class Meta_Tags_Context_Memoizer { /** * The blocks helper. * * @var Blocks_Helper */ protected $blocks; /** * The current page helper. * * @var Current_Page_Helper */ protected $current_page; /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The meta tags context. * * @var Meta_Tags_Context */ protected $context_prototype; /** * The presentation memoizer. * * @var Presentation_Memoizer */ protected $presentation_memoizer; /** * The meta tags context. * * @var Meta_Tags_Context[] */ protected $cache = []; /** * Meta_Tags_Context_Memoizer constructor. * * @param Blocks_Helper $blocks The blocks helper. * @param Current_Page_Helper $current_page The current page helper. * @param Indexable_Repository $repository Indexable repository. * @param Meta_Tags_Context $context_prototype The meta tags context prototype. * @param Presentation_Memoizer $presentation_memoizer Memoizer for the presentation. */ public function __construct( Blocks_Helper $blocks, Current_Page_Helper $current_page, Indexable_Repository $repository, Meta_Tags_Context $context_prototype, Presentation_Memoizer $presentation_memoizer ) { $this->blocks = $blocks; $this->current_page = $current_page; $this->repository = $repository; $this->context_prototype = $context_prototype; $this->presentation_memoizer = $presentation_memoizer; } /** * Gets the meta tags context for the current page. * This function is memoized so every call will return the same result. * * @return Meta_Tags_Context The meta tags context. */ public function for_current_page() { if ( ! isset( $this->cache['current_page'] ) ) { // First reset the query to ensure we actually have the current page. global $wp_query, $post; $old_wp_query = $wp_query; $old_post = $post; // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- Reason: The recommended function, wp_reset_postdata, doesn't reset wp_query. \wp_reset_query(); $indexable = $this->repository->for_current_page(); $page_type = $this->current_page->get_page_type(); if ( $page_type === 'Fallback' ) { // Do not cache the context if it's a fallback page. // The likely cause for this is that this function was called before the query was loaded. $context = $this->get( $indexable, $page_type ); // Restore the previous query. // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the query. $GLOBALS['wp_query'] = $old_wp_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the post. $GLOBALS['post'] = $old_post; return $context; } $this->cache['current_page'] = $this->get( $indexable, $page_type ); // Restore the previous query. // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the query. $GLOBALS['wp_query'] = $old_wp_query; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the post. $GLOBALS['post'] = $old_post; } return $this->cache['current_page']; } /** * Gets the meta tags context given an indexable. * This function is memoized by the indexable so every call with the same indexable will yield the same result. * * @param Indexable $indexable The indexable. * @param string $page_type The page type. * * @return Meta_Tags_Context The meta tags context. */ public function get( Indexable $indexable, $page_type ) { if ( ! isset( $this->cache[ $indexable->id ] ) ) { $blocks = []; $post = null; if ( $indexable->object_type === 'post' ) { $post = \get_post( $indexable->object_id ); $blocks = $this->blocks->get_all_blocks_from_content( $post->post_content ); } $context = $this->context_prototype->of( [ 'indexable' => $indexable, 'blocks' => $blocks, 'post' => $post, 'page_type' => $page_type, ] ); $context->presentation = $this->presentation_memoizer->get( $indexable, $context, $page_type ); $this->cache[ $indexable->id ] = $context; } return $this->cache[ $indexable->id ]; } /** * Clears the memoization of either a specific indexable or all indexables. * * @param Indexable|int|string|null $indexable Optional. The indexable or indexable id to clear the memoization of. * * @return void */ public function clear( $indexable = null ) { if ( $indexable instanceof Indexable ) { unset( $this->cache[ $indexable->id ] ); return; } if ( $indexable !== null ) { unset( $this->cache[ $indexable ] ); return; } $this->cache = []; } } memoizers/presentation-memoizer.php 0000644 00000004542 15025657560 0013647 0 ustar 00 <?php namespace Yoast\WP\SEO\Memoizers; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * The presentation memoizer. */ class Presentation_Memoizer { /** * The service container. * * @var ContainerInterface */ protected $container; /** * Cache with indexable presentations. * * @var Indexable_Presentation[] */ protected $cache = []; /** * Presentation_Memoizer constructor. * * @param ContainerInterface $service_container The service container. */ public function __construct( ContainerInterface $service_container ) { $this->container = $service_container; } /** * Gets the presentation of an indexable for a specific page type. * This function is memoized by the indexable so every call with the same indexable will yield the same result. * * @param Indexable $indexable The indexable to get a presentation of. * @param Meta_Tags_Context $context The current meta tags context. * @param string $page_type The page type. * * @return Indexable_Presentation The indexable presentation. */ public function get( Indexable $indexable, Meta_Tags_Context $context, $page_type ) { if ( ! isset( $this->cache[ $indexable->id ] ) ) { $presentation = $this->container->get( "Yoast\WP\SEO\Presentations\Indexable_{$page_type}_Presentation", ContainerInterface::NULL_ON_INVALID_REFERENCE ); if ( ! $presentation ) { $presentation = $this->container->get( Indexable_Presentation::class ); } $context->presentation = $presentation->of( [ 'model' => $indexable, 'context' => $context, ] ); $this->cache[ $indexable->id ] = $context->presentation; } return $this->cache[ $indexable->id ]; } /** * Clears the memoization of either a specific indexable or all indexables. * * @param Indexable|int|null $indexable Optional. The indexable or indexable id to clear the memoization of. * * @return void */ public function clear( $indexable = null ) { if ( $indexable instanceof Indexable ) { unset( $this->cache[ $indexable->id ] ); return; } if ( \is_int( $indexable ) ) { unset( $this->cache[ $indexable ] ); return; } if ( $indexable === null ) { $this->cache = []; } } } surfaces/schema-helpers-surface.php 0000644 00000005546 15025657560 0013443 0 ustar 00 <?php namespace Yoast\WP\SEO\Surfaces; use Yoast\WP\SEO\Exceptions\Forbidden_Property_Mutation_Exception; use Yoast\WP\SEO\Helpers\Schema; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Class Schema_Helpers_Surface. * * Surface for the indexables. * * @property Schema\Article_Helper $article * @property Schema\HTML_Helper $html * @property Schema\ID_Helper $id * @property Schema\Image_Helper $image * @property Schema\Language_Helper $language */ class Schema_Helpers_Surface { /** * The DI container. * * @var ContainerInterface */ private $container; /** * Helpers that should be fully capitalized. * * @var array */ private $capitalized_helpers = [ 'html', 'id' ]; /** * Loader constructor. * * @param ContainerInterface $container The dependency injection container. */ public function __construct( ContainerInterface $container ) { $this->container = $container; } /** * Magic getter for getting helper classes. * * @param string $helper The helper to get. * * @return mixed The helper class. */ public function __get( $helper ) { return $this->container->get( $this->get_helper_class( $helper ) ); } /** * Magic isset for ensuring helper exists. * * @param string $helper The helper to get. * * @return bool Whether the helper exists. */ public function __isset( $helper ) { return $this->container->has( $this->get_helper_class( $helper ) ); } /** * Prevents setting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * @param mixed $value The property value. * * @return void * * @throws Forbidden_Property_Mutation_Exception Set is never meant to be called. */ public function __set( $name, $value ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- __set must have a name and value - PHPCS #3715. throw Forbidden_Property_Mutation_Exception::cannot_set_because_property_is_immutable( $name ); } /** * Prevents unsetting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * * @return void * * @throws Forbidden_Property_Mutation_Exception Unset is never meant to be called. */ public function __unset( $name ) { throw Forbidden_Property_Mutation_Exception::cannot_unset_because_property_is_immutable( $name ); } /** * Get the class name from a helper slug * * @param string $helper The name of the helper. * * @return string */ protected function get_helper_class( $helper ) { if ( \in_array( $helper, $this->capitalized_helpers, true ) ) { $helper = \strtoupper( $helper ); } $helper = \implode( '_', \array_map( 'ucfirst', \explode( '_', $helper ) ) ); return "Yoast\WP\SEO\Helpers\Schema\\{$helper}_Helper"; } } surfaces/classes-surface.php 0000644 00000001456 15025657560 0012174 0 ustar 00 <?php namespace Yoast\WP\SEO\Surfaces; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Class Classes_Surface. * * Surface for the indexables. */ class Classes_Surface { /** * The dependency injection container. * * @var ContainerInterface */ public $container; /** * Loader constructor. * * @param ContainerInterface $container The dependency injection container. */ public function __construct( ContainerInterface $container ) { $this->container = $container; } /** * Returns the instance of a class. Handy for unhooking things. * * @param string $class_name The class to get the instance of. * * @return mixed The instance of the class. */ public function get( $class_name ) { return $this->container->get( $class_name ); } } surfaces/meta-surface.php 0000644 00000024373 15025657560 0011470 0 ustar 00 <?php namespace Yoast\WP\SEO\Surfaces; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Surfaces\Values\Meta; use Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Meta_Surface class. * * Surface for the indexables. */ class Meta_Surface { /** * The container. * * @var ContainerInterface */ private $container; /** * The memoizer for the meta tags context. * * @var Meta_Tags_Context_Memoizer */ private $context_memoizer; /** * The indexable repository. * * @var Indexable_Repository */ private $repository; /** * Holds the WP rewrite wrapper instance. * * @var WP_Rewrite_Wrapper */ private $wp_rewrite_wrapper; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * Meta_Surface constructor. * * @param ContainerInterface $container The DI container. * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer. * @param Indexable_Repository $indexable_repository The indexable repository. * @param WP_Rewrite_Wrapper $wp_rewrite_wrapper The WP rewrite wrapper. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( ContainerInterface $container, Meta_Tags_Context_Memoizer $context_memoizer, Indexable_Repository $indexable_repository, WP_Rewrite_Wrapper $wp_rewrite_wrapper, Indexable_Helper $indexable_helper ) { $this->container = $container; $this->context_memoizer = $context_memoizer; $this->repository = $indexable_repository; $this->wp_rewrite_wrapper = $wp_rewrite_wrapper; $this->indexable_helper = $indexable_helper; } /** * Returns the meta tags context for the current page. * * @return Meta The meta values. */ public function for_current_page() { return $this->build_meta( $this->context_memoizer->for_current_page() ); } /** * Returns the meta tags context for the home page. * * @return Meta|false The meta values. False if none could be found. */ public function for_home_page() { $front_page_id = (int) \get_option( 'page_on_front' ); if ( \get_option( 'show_on_front' ) === 'page' && $front_page_id !== 0 ) { $indexable = $this->repository->find_by_id_and_type( $front_page_id, 'post' ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Static_Home_Page' ) ); } $indexable = $this->repository->find_for_home_page(); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Home_Page' ) ); } /** * Returns the meta tags context for the posts page. * * @return Meta|false The meta values. False if none could be found. */ public function for_posts_page() { $posts_page_id = (int) \get_option( 'page_for_posts' ); if ( $posts_page_id !== 0 ) { $indexable = $this->repository->find_by_id_and_type( $posts_page_id, 'post' ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Static_Posts_Page' ) ); } $indexable = $this->repository->find_for_home_page(); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Home_Page' ) ); } /** * Returns the meta tags context for a post type archive. * * @param string|null $post_type Optional. The post type to get the archive meta for. Defaults to the current post type. * * @return Meta|false The meta values. False if none could be found. */ public function for_post_type_archive( $post_type = null ) { if ( $post_type === null ) { $post_type = \get_post_type(); } $indexable = $this->repository->find_for_post_type_archive( $post_type ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Post_Type_Archive' ) ); } /** * Returns the meta tags context for the search result page. * * @return Meta|false The meta values. False if none could be found. */ public function for_search_result() { $indexable = $this->repository->find_for_system_page( 'search-result' ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Search_Result_Page' ) ); } /** * Returns the meta tags context for the search result page. * * @return Meta|false The meta values. False if none could be found. */ public function for_404() { $indexable = $this->repository->find_for_system_page( '404' ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Error_Page' ) ); } /** * Returns the meta tags context for a post. * * @param int $id The ID of the post. * * @return Meta|false The meta values. False if none could be found. */ public function for_post( $id ) { $indexable = $this->repository->find_by_id_and_type( $id, 'post' ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Post_Type' ) ); } /** * Returns the meta tags context for a number of posts. * * @param int[] $ids The IDs of the posts. * * @return Meta[]|false The meta values. False if none could be found. */ public function for_posts( $ids ) { $indexables = $this->repository->find_by_multiple_ids_and_type( $ids, 'post' ); if ( empty( $indexables ) ) { return false; } // Remove all false values. $indexables = \array_filter( $indexables ); return \array_map( function ( $indexable ) { return $this->build_meta( $this->context_memoizer->get( $indexable, 'Post_Type' ) ); }, $indexables ); } /** * Returns the meta tags context for a term. * * @param int $id The ID of the term. * * @return Meta|false The meta values. False if none could be found. */ public function for_term( $id ) { $indexable = $this->repository->find_by_id_and_type( $id, 'term' ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Term_Archive' ) ); } /** * Returns the meta tags context for an author. * * @param int $id The ID of the author. * * @return Meta|false The meta values. False if none could be found. */ public function for_author( $id ) { $indexable = $this->repository->find_by_id_and_type( $id, 'user' ); if ( ! $indexable ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, 'Author_Archive' ) ); } /** * Returns the meta for an indexable. * * @param Indexable $indexable The indexable. * @param string|null $page_type Optional. The page type if already known. * * @return Meta|false The meta values. False if none could be found. */ public function for_indexable( $indexable, $page_type = null ) { if ( ! \is_a( $indexable, Indexable::class ) ) { return false; } if ( $page_type === null ) { $page_type = $this->indexable_helper->get_page_type_for_indexable( $indexable ); } return $this->build_meta( $this->context_memoizer->get( $indexable, $page_type ) ); } /** * Returns the meta for an indexable. * * @param Indexable[] $indexables The indexables. * @param string|null $page_type Optional. The page type if already known. * * @return Meta|false The meta values. False if none could be found. */ public function for_indexables( $indexables, $page_type = null ) { $closure = function ( $indexable ) use ( $page_type ) { $this_page_type = $page_type; if ( $this_page_type === null ) { $this_page_type = $this->indexable_helper->get_page_type_for_indexable( $indexable ); } return $this->build_meta( $this->context_memoizer->get( $indexable, $this_page_type ) ); }; return \array_map( $closure, $indexables ); } /** * Returns the meta tags context for a url. * * @param string $url The url of the page. Required to be relative to the site url. * * @return Meta|false The meta values. False if none could be found. */ public function for_url( $url ) { $url_parts = \wp_parse_url( $url ); $site_parts = \wp_parse_url( \site_url() ); if ( ( ! \is_array( $url_parts ) || ! \is_array( $site_parts ) ) || ! isset( $url_parts['host'], $url_parts['path'], $site_parts['host'], $site_parts['scheme'] ) ) { return false; } if ( $url_parts['host'] !== $site_parts['host'] ) { return false; } // Ensure the scheme is consistent with values in the DB. $url = $site_parts['scheme'] . '://' . $url_parts['host'] . $url_parts['path']; if ( $this->is_date_archive_url( $url ) ) { $indexable = $this->repository->find_for_date_archive(); } else { $indexable = $this->repository->find_by_permalink( $url ); } // If we still don't have an indexable abort, the WP globals could be anything so we can't use the unknown indexable. if ( ! $indexable ) { return false; } $page_type = $this->indexable_helper->get_page_type_for_indexable( $indexable ); if ( $page_type === false ) { return false; } return $this->build_meta( $this->context_memoizer->get( $indexable, $page_type ) ); } /** * Checks if a given URL is a date archive URL. * * @param string $url The url. * * @return bool */ protected function is_date_archive_url( $url ) { $path = \wp_parse_url( $url, \PHP_URL_PATH ); if ( $path === null ) { return false; } $path = \ltrim( $path, '/' ); $wp_rewrite = $this->wp_rewrite_wrapper->get(); $date_rewrite = $wp_rewrite->generate_rewrite_rules( $wp_rewrite->get_date_permastruct(), \EP_DATE ); $date_rewrite = \apply_filters( 'date_rewrite_rules', $date_rewrite ); foreach ( (array) $date_rewrite as $match => $query ) { if ( \preg_match( "#^$match#", $path ) ) { return true; } } return false; } /** * Creates a new meta value object * * @param Meta_Tags_Context $context The meta tags context. * * @return Meta The meta value */ protected function build_meta( Meta_Tags_Context $context ) { return new Meta( $context, $this->container ); } } surfaces/helpers-surface.php 0000644 00000013100 15025657560 0012166 0 ustar 00 <?php namespace Yoast\WP\SEO\Surfaces; use Yoast\WP\SEO\Exceptions\Forbidden_Property_Mutation_Exception; use Yoast\WP\SEO\Helpers; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Class Helpers_Surface. * * Surface for the indexables. * * @property Helpers\Asset_Helper $asset * @property Helpers\Author_Archive_Helper $author_archive * @property Helpers\Blocks_Helper $blocks * @property Helpers\Capability_Helper $capability * @property Helpers\Current_Page_Helper $current_page * @property Helpers\Date_Helper $date * @property Helpers\Environment_Helper $environment * @property Helpers\First_Time_Configuration_Notice_Helper $first_time_configuration_notice * @property Helpers\Home_Url_Helper $home_url * @property Helpers\Image_Helper $image * @property Helpers\Indexable_Helper $indexable * @property Helpers\Indexing_Helper $indexing * @property Helpers\Input_Helper $input * @property Helpers\Language_Helper $language * @property Helpers\Meta_Helper $meta * @property Helpers\Notification_Helper $notification * @property Helpers\Options_Helper $options * @property Helpers\Pagination_Helper $pagination * @property Helpers\Permalink_Helper $permalink * @property Helpers\Post_Helper $post * @property Helpers\Post_Type_Helper $post_type * @property Helpers\Primary_Term_Helper $primary_term * @property Helpers\Product_Helper $product * @property Helpers\Redirect_Helper $redirect * @property Helpers\Require_File_Helper $require_file * @property Helpers\Robots_Helper $robots * @property Helpers\Short_Link_Helper $short_link * @property Helpers\Site_Helper $site * @property Helpers\String_Helper $string * @property Helpers\Social_Profiles_Helper $social_profiles * @property Helpers\Taxonomy_Helper $taxonomy * @property Helpers\Url_Helper $url * @property Helpers\User_Helper $user * @property Helpers\Woocommerce_Helper $woocommerce * @property Helpers\Wordpress_Helper $wordpress */ class Helpers_Surface { /** * The DI container. * * @var ContainerInterface */ private $container; /** * The open_graph helper namespace * * @var Open_Graph_Helpers_Surface */ public $open_graph; /** * The schema helper namespace * * @var Schema_Helpers_Surface */ public $schema; /** * The twitter helper namespace * * @var Twitter_Helpers_Surface */ public $twitter; /** * Loader constructor. * * @param ContainerInterface $container The dependency injection container. * @param Open_Graph_Helpers_Surface $open_graph The OpenGraph helpers surface. * @param Schema_Helpers_Surface $schema The Schema helpers surface. * @param Twitter_Helpers_Surface $twitter The Twitter helpers surface. */ public function __construct( ContainerInterface $container, Open_Graph_Helpers_Surface $open_graph, Schema_Helpers_Surface $schema, Twitter_Helpers_Surface $twitter ) { $this->container = $container; $this->open_graph = $open_graph; $this->schema = $schema; $this->twitter = $twitter; } /** * Magic getter for getting helper classes. * * @param string $helper The helper to get. * * @return mixed The helper class. */ public function __get( $helper ) { return $this->container->get( $this->get_helper_class( $helper ) ); } /** * Magic isset for ensuring helper exists. * * @param string $helper The helper to get. * * @return bool Whether the helper exists. */ public function __isset( $helper ) { return $this->container->has( $this->get_helper_class( $helper ) ); } /** * Prevents setting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * @param mixed $value The property value. * * @return void * * @throws Forbidden_Property_Mutation_Exception Set is never meant to be called. */ public function __set( $name, $value ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- __set must have a name and value - PHPCS #3715. throw Forbidden_Property_Mutation_Exception::cannot_set_because_property_is_immutable( $name ); } /** * Prevents unsetting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * * @return void * * @throws Forbidden_Property_Mutation_Exception Unset is never meant to be called. */ public function __unset( $name ) { throw Forbidden_Property_Mutation_Exception::cannot_unset_because_property_is_immutable( $name ); } /** * Get the class name from a helper slug * * @param string $helper The name of the helper. * * @return string */ protected function get_helper_class( $helper ) { $helper = \implode( '_', \array_map( 'ucfirst', \explode( '_', $helper ) ) ); return "Yoast\WP\SEO\Helpers\\{$helper}_Helper"; } } surfaces/open-graph-helpers-surface.php 0000644 00000004757 15025657560 0014246 0 ustar 00 <?php namespace Yoast\WP\SEO\Surfaces; use Yoast\WP\SEO\Exceptions\Forbidden_Property_Mutation_Exception; use Yoast\WP\SEO\Helpers\Open_Graph; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Class Open_Graph_Helpers_Surface. * * Surface for the indexables. * * @property Open_Graph\Image_Helper $image */ class Open_Graph_Helpers_Surface { /** * The DI container. * * @var ContainerInterface */ private $container; /** * Loader constructor. * * @param ContainerInterface $container The dependency injection container. */ public function __construct( ContainerInterface $container ) { $this->container = $container; } /** * Magic getter for getting helper classes. * * @param string $helper The helper to get. * * @return mixed The helper class. */ public function __get( $helper ) { return $this->container->get( $this->get_helper_class( $helper ) ); } /** * Magic isset for ensuring helper exists. * * @param string $helper The helper to get. * * @return bool Whether the helper exists. */ public function __isset( $helper ) { return $this->container->has( $this->get_helper_class( $helper ) ); } /** * Prevents setting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * @param mixed $value The property value. * * @return void * * @throws Forbidden_Property_Mutation_Exception Set is never meant to be called. */ public function __set( $name, $value ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- __set must have a name and value - PHPCS #3715. throw Forbidden_Property_Mutation_Exception::cannot_set_because_property_is_immutable( $name ); } /** * Prevents unsetting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * * @return void * * @throws Forbidden_Property_Mutation_Exception Unset is never meant to be called. */ public function __unset( $name ) { throw Forbidden_Property_Mutation_Exception::cannot_unset_because_property_is_immutable( $name ); } /** * Get the class name from a helper slug * * @param string $helper The name of the helper. * * @return string */ protected function get_helper_class( $helper ) { $helper = \implode( '_', \array_map( 'ucfirst', \explode( '_', $helper ) ) ); return "Yoast\WP\SEO\Helpers\Open_Graph\\{$helper}_Helper"; } } surfaces/twitter-helpers-surface.php 0000644 00000004740 15025657560 0013700 0 ustar 00 <?php namespace Yoast\WP\SEO\Surfaces; use Yoast\WP\SEO\Exceptions\Forbidden_Property_Mutation_Exception; use Yoast\WP\SEO\Helpers\Twitter; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Class Twitter_Helpers_Surface. * * Surface for the indexables. * * @property Twitter\Image_Helper $image */ class Twitter_Helpers_Surface { /** * The DI container. * * @var ContainerInterface */ private $container; /** * Loader constructor. * * @param ContainerInterface $container The dependency injection container. */ public function __construct( ContainerInterface $container ) { $this->container = $container; } /** * Magic getter for getting helper classes. * * @param string $helper The helper to get. * * @return mixed The helper class. */ public function __get( $helper ) { return $this->container->get( $this->get_helper_class( $helper ) ); } /** * Magic isset for ensuring helper exists. * * @param string $helper The helper to get. * * @return bool Whether the helper exists. */ public function __isset( $helper ) { return $this->container->has( $this->get_helper_class( $helper ) ); } /** * Prevents setting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * @param mixed $value The property value. * * @return void * * @throws Forbidden_Property_Mutation_Exception Set is never meant to be called. */ public function __set( $name, $value ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- __set must have a name and value - PHPCS #3715. throw Forbidden_Property_Mutation_Exception::cannot_set_because_property_is_immutable( $name ); } /** * Prevents unsetting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * * @return void * * @throws Forbidden_Property_Mutation_Exception Unset is never meant to be called. */ public function __unset( $name ) { throw Forbidden_Property_Mutation_Exception::cannot_unset_because_property_is_immutable( $name ); } /** * Get the class name from a helper slug * * @param string $helper The name of the helper. * * @return string */ protected function get_helper_class( $helper ) { $helper = \implode( '_', \array_map( 'ucfirst', \explode( '_', $helper ) ) ); return "Yoast\WP\SEO\Helpers\Twitter\\{$helper}_Helper"; } } surfaces/values/meta.php 0000644 00000027632 15025657560 0011342 0 ustar 00 <?php namespace Yoast\WP\SEO\Surfaces\Values; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Exceptions\Forbidden_Property_Mutation_Exception; use Yoast\WP\SEO\Integrations\Front_End_Integration; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter; use Yoast\WP\SEO\Presenters\Rel_Next_Presenter; use Yoast\WP\SEO\Presenters\Rel_Prev_Presenter; use Yoast\WP\SEO\Surfaces\Helpers_Surface; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Meta value object. * * @property array $breadcrumbs The breadcrumbs array for the current page. * @property string $canonical The canonical URL for the current page. * @property string $company_name The company name from the Knowledge graph settings. * @property int $company_logo_id The attachment ID for the company logo. * @property string $description The meta description for the current page, if set. * @property int $estimated_reading_time_minutes The estimated reading time in minutes for posts. * @property Indexable $indexable The indexable object. * @property string $main_schema_id Schema ID that points to the main Schema thing on the page, usually the webpage or article Schema piece. * @property string $meta_description The meta description for the current page, if set. * @property string $open_graph_article_author The article:author value. * @property string $open_graph_article_modified_time The article:modified_time value. * @property string $open_graph_article_published_time The article:published_time value. * @property string $open_graph_article_publisher The article:publisher value. * @property string $open_graph_description The og:description. * @property bool $open_graph_enabled Whether OpenGraph is enabled on this site. * @property string $open_graph_fb_app_id The Facebook App ID. * @property array $open_graph_images The array of images we have for this page. * @property string $open_graph_locale The og:locale for the current page. * @property string $open_graph_publisher The OpenGraph publisher reference. * @property string $open_graph_site_name The og:site_name. * @property string $open_graph_title The og:title. * @property string $open_graph_type The og:type. * @property string $open_graph_url The og:url. * @property string $page_type The Schema page type. * @property array $robots An array of the robots values set for the current page. * @property string $rel_next The next page in the series, if any. * @property string $rel_prev The previous page in the series, if any. * @property array $schema The entire Schema array for the current page. * @property string $schema_page_type The Schema page type. * @property string $site_name The site name from the Yoast SEO settings. * @property string $site_represents Whether the site represents a 'person' or a 'company'. * @property array|false $site_represents_reference The schema reference ID for what this site represents. * @property string $site_url The site's main URL. * @property int $site_user_id If the site represents a 'person', this is the ID of the accompanying user profile. * @property string $title The SEO title for the current page. * @property string $twitter_card The Twitter card type for the current page. * @property string $twitter_creator The Twitter card author for the current page. * @property string $twitter_description The Twitter card description for the current page. * @property string $twitter_image The Twitter card image for the current page. * @property string $twitter_site The Twitter card site reference for the current page. * @property string $twitter_title The Twitter card title for the current page. * @property string $wordpress_site_name The site name from the WordPress settings. */ class Meta { /** * The container. * * @var ContainerInterface */ protected $container; /** * The meta tags context. * * @var Meta_Tags_Context */ protected $context; /** * The front end integration. * * @var Front_End_Integration */ protected $front_end; /** * The helpers surface. * * @var Helpers_Surface */ protected $helpers; /** * The replace vars helper * * @var WPSEO_Replace_Vars */ protected $replace_vars; /** * Collection of properties dynamically set via the magic __get() method. * * @var array<string, mixed> Key is the property name. */ private $properties_bin = []; /** * Create a meta value object. * * @param Meta_Tags_Context $context The indexable presentation. * @param ContainerInterface $container The DI container. */ public function __construct( Meta_Tags_Context $context, ContainerInterface $container ) { $this->container = $container; $this->context = $context; $this->helpers = $this->container->get( Helpers_Surface::class ); $this->replace_vars = $this->container->get( WPSEO_Replace_Vars::class ); $this->front_end = $this->container->get( Front_End_Integration::class ); } /** * Returns the output as would be presented in the head. * * @return object The HTML and JSON presentation of the head metadata. */ public function get_head() { $presenters = $this->get_presenters(); /** This filter is documented in src/integrations/front-end-integration.php */ $presentation = \apply_filters( 'wpseo_frontend_presentation', $this->context->presentation, $this->context ); $html_output = ''; $json_head_fields = []; foreach ( $presenters as $presenter ) { $presenter->presentation = $presentation; $presenter->replace_vars = $this->replace_vars; $presenter->helpers = $this->helpers; $html_output .= $this->create_html_presentation( $presenter ); $json_field = $this->create_json_field( $presenter ); // Only use the output of presenters that could successfully present their data. if ( $json_field !== null && ! empty( $json_field->key ) ) { $json_head_fields[ $json_field->key ] = $json_field->value; } } $html_output = \trim( $html_output ); return (object) [ 'html' => $html_output, 'json' => $json_head_fields, ]; } /** * Magic getter for presenting values through the appropriate presenter, if it exists. * * @param string $name The property to get. * * @return mixed The value, as presented by the appropriate presenter. */ public function __get( $name ) { if ( \array_key_exists( $name, $this->properties_bin ) ) { return $this->properties_bin[ $name ]; } /** This filter is documented in src/integrations/front-end-integration.php */ $presentation = \apply_filters( 'wpseo_frontend_presentation', $this->context->presentation, $this->context ); if ( ! isset( $presentation->{$name} ) ) { if ( isset( $this->context->{$name} ) ) { $this->properties_bin[ $name ] = $this->context->{$name}; return $this->properties_bin[ $name ]; } return null; } $presenter_namespace = 'Yoast\WP\SEO\Presenters\\'; $parts = \explode( '_', $name ); if ( $parts[0] === 'twitter' ) { $presenter_namespace .= 'Twitter\\'; $parts = \array_slice( $parts, 1 ); } elseif ( $parts[0] === 'open' && $parts[1] === 'graph' ) { $presenter_namespace .= 'Open_Graph\\'; $parts = \array_slice( $parts, 2 ); } $presenter_class = $presenter_namespace . \implode( '_', \array_map( 'ucfirst', $parts ) ) . '_Presenter'; if ( \class_exists( $presenter_class ) ) { /** * The indexable presenter. * * @var Abstract_Indexable_Presenter $presenter */ $presenter = new $presenter_class(); $presenter->presentation = $presentation; $presenter->helpers = $this->helpers; $presenter->replace_vars = $this->replace_vars; $value = $presenter->get(); } else { $value = $presentation->{$name}; } $this->properties_bin[ $name ] = $value; return $this->properties_bin[ $name ]; } /** * Magic isset for ensuring properties on the presentation are recognised. * * @param string $name The property to get. * * @return bool Whether or not the requested property exists. */ public function __isset( $name ) { if ( \array_key_exists( $name, $this->properties_bin ) ) { return true; } return isset( $this->context->presentation->{$name} ); } /** * Prevents setting dynamic properties and overwriting the value of declared properties * from an inaccessible context. * * @param string $name The property name. * @param mixed $value The property value. * * @return void * * @throws Forbidden_Property_Mutation_Exception Set is never meant to be called. */ public function __set( $name, $value ) { // @phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- __set must have a name and value - PHPCS #3715. throw Forbidden_Property_Mutation_Exception::cannot_set_because_property_is_immutable( $name ); } /** * Prevents unsetting dynamic properties and unsetting declared properties * from an inaccessible context. * * @param string $name The property name. * * @return void * * @throws Forbidden_Property_Mutation_Exception Unset is never meant to be called. */ public function __unset( $name ) { throw Forbidden_Property_Mutation_Exception::cannot_unset_because_property_is_immutable( $name ); } /** * Strips all nested dependencies from the debug info. * * @return array */ public function __debugInfo() { return [ 'context' => $this->context ]; } /** * Returns all presenters. * * @return Abstract_Indexable_Presenter[] */ protected function get_presenters() { $presenters = $this->front_end->get_presenters( $this->context->page_type, $this->context ); if ( $this->context->page_type === 'Date_Archive' ) { /** * Define a filter that removes objects of type Rel_Next_Presenter or Rel_Prev_Presenter from a list. * * @param object $presenter The presenter to verify. * * @return bool True if the presenter is not a Rel_Next or Rel_Prev presenter. */ $callback = static function ( $presenter ) { return ! \is_a( $presenter, Rel_Next_Presenter::class ) && ! \is_a( $presenter, Rel_Prev_Presenter::class ); }; $presenters = \array_filter( $presenters, $callback ); } return $presenters; } /** * Uses the presenter to create a line of HTML. * * @param Abstract_Indexable_Presenter $presenter The presenter. * * @return string */ protected function create_html_presentation( $presenter ) { $presenter_output = $presenter->present(); if ( ! empty( $presenter_output ) ) { return $presenter_output . \PHP_EOL; } return ''; } /** * Converts a presenter's key and value to JSON. * * @param Abstract_Indexable_Presenter $presenter The presenter whose key and value are to be converted to JSON. * * @return object|null */ protected function create_json_field( $presenter ) { if ( $presenter->get_key() === 'NO KEY PROVIDED' ) { return null; } $value = $presenter->get(); if ( empty( $value ) ) { return null; } return (object) [ 'key' => $presenter->escape_key(), 'value' => $value, ]; } } repositories/indexable-repository.php 0000644 00000036357 15025657560 0014205 0 ustar 00 <?php namespace Yoast\WP\SEO\Repositories; use Psr\Log\LoggerInterface; use wpdb; use Yoast\WP\Lib\Model; use Yoast\WP\Lib\ORM; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Loggers\Logger; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager; /** * Class Indexable_Repository. */ class Indexable_Repository { /** * The indexable builder. * * @var Indexable_Builder */ private $builder; /** * Represents the hierarchy repository. * * @var Indexable_Hierarchy_Repository */ protected $hierarchy_repository; /** * The current page helper. * * @var Current_Page_Helper */ protected $current_page; /** * The logger object. * * @var LoggerInterface */ protected $logger; /** * The WordPress database. * * @var wpdb */ protected $wpdb; /** * Represents the indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Checks if Indexables are up to date. * * @var Indexable_Version_Manager */ protected $version_manager; /** * Returns the instance of this class constructed through the ORM Wrapper. * * @param Indexable_Builder $builder The indexable builder. * @param Current_Page_Helper $current_page The current post helper. * @param Logger $logger The logger. * @param Indexable_Hierarchy_Repository $hierarchy_repository The hierarchy repository. * @param wpdb $wpdb The WordPress database instance. * @param Indexable_Version_Manager $version_manager The indexable version manager. */ public function __construct( Indexable_Builder $builder, Current_Page_Helper $current_page, Logger $logger, Indexable_Hierarchy_Repository $hierarchy_repository, wpdb $wpdb, Indexable_Version_Manager $version_manager ) { $this->builder = $builder; $this->current_page = $current_page; $this->logger = $logger; $this->hierarchy_repository = $hierarchy_repository; $this->wpdb = $wpdb; $this->version_manager = $version_manager; } /** * Starts a query for this repository. * * @return ORM */ public function query() { return Model::of_type( 'Indexable' ); } /** * Attempts to find the indexable for the current WordPress page. Returns false if no indexable could be found. * This may be the result of the indexable not existing or of being unable to determine what type of page the * current page is. * * @return bool|Indexable The indexable. If no indexable is found returns an empty indexable. Returns false if there is a database error. */ public function for_current_page() { $indexable = false; switch ( true ) { case $this->current_page->is_simple_page(): $indexable = $this->find_by_id_and_type( $this->current_page->get_simple_page_id(), 'post' ); break; case $this->current_page->is_home_static_page(): $indexable = $this->find_by_id_and_type( $this->current_page->get_front_page_id(), 'post' ); break; case $this->current_page->is_home_posts_page(): $indexable = $this->find_for_home_page(); break; case $this->current_page->is_term_archive(): $indexable = $this->find_by_id_and_type( $this->current_page->get_term_id(), 'term' ); break; case $this->current_page->is_date_archive(): $indexable = $this->find_for_date_archive(); break; case $this->current_page->is_search_result(): $indexable = $this->find_for_system_page( 'search-result' ); break; case $this->current_page->is_post_type_archive(): $indexable = $this->find_for_post_type_archive( $this->current_page->get_queried_post_type() ); break; case $this->current_page->is_author_archive(): $indexable = $this->find_by_id_and_type( $this->current_page->get_author_id(), 'user' ); break; case $this->current_page->is_404(): $indexable = $this->find_for_system_page( '404' ); break; } if ( $indexable === false ) { return $this->query()->create( [ 'object_type' => 'unknown', 'post_status' => 'unindexed', 'version' => 1, ] ); } return $indexable; } /** * Retrieves an indexable by its permalink. * * @param string $permalink The indexable permalink. * * @return bool|Indexable The indexable, false if none could be found. */ public function find_by_permalink( $permalink ) { $permalink_hash = \strlen( $permalink ) . ':' . \md5( $permalink ); // Find by both permalink_hash and permalink, permalink_hash is indexed so will be used first by the DB to optimize the query. return $this->query() ->where( 'permalink_hash', $permalink_hash ) ->where( 'permalink', $permalink ) ->find_one(); } /** * Retrieves all the indexable instances of a certain object type. * * @param string $object_type The object type. * * @return Indexable[] The array with all the indexable instances of a certain object type. */ public function find_all_with_type( $object_type ) { /** * The array with all the indexable instances of a certain object type. * * @var Indexable[] $indexables */ $indexables = $this ->query() ->where( 'object_type', $object_type ) ->find_many(); return \array_map( [ $this, 'upgrade_indexable' ], $indexables ); } /** * Retrieves all the indexable instances of a certain object subtype. * * @param string $object_type The object type. * @param string $object_sub_type The object subtype. * * @return Indexable[] The array with all the indexable instances of a certain object subtype. */ public function find_all_with_type_and_sub_type( $object_type, $object_sub_type ) { /** * The array with all the indexable instances of a certain object type and subtype. * * @var Indexable[] $indexables */ $indexables = $this ->query() ->where( 'object_type', $object_type ) ->where( 'object_sub_type', $object_sub_type ) ->find_many(); return \array_map( [ $this, 'upgrade_indexable' ], $indexables ); } /** * Retrieves the homepage indexable. * * @param bool $auto_create Optional. Create the indexable if it does not exist. * * @return bool|Indexable Instance of indexable. */ public function find_for_home_page( $auto_create = true ) { $indexable = \wp_cache_get( 'home-page', 'yoast-seo-indexables' ); if ( ! $indexable ) { /** * Indexable instance. * * @var Indexable $indexable */ $indexable = $this->query()->where( 'object_type', 'home-page' )->find_one(); if ( $auto_create && ! $indexable ) { $indexable = $this->builder->build_for_home_page(); } $indexable = $this->upgrade_indexable( $indexable ); \wp_cache_set( 'home-page', $indexable, 'yoast-seo-indexables', ( 5 * \MINUTE_IN_SECONDS ) ); } return $indexable; } /** * Retrieves the date archive indexable. * * @param bool $auto_create Optional. Create the indexable if it does not exist. * * @return bool|Indexable Instance of indexable. */ public function find_for_date_archive( $auto_create = true ) { /** * Indexable instance. * * @var Indexable $indexable */ $indexable = $this->query()->where( 'object_type', 'date-archive' )->find_one(); if ( $auto_create && ! $indexable ) { $indexable = $this->builder->build_for_date_archive(); } return $this->upgrade_indexable( $indexable ); } /** * Retrieves an indexable for a post type archive. * * @param string $post_type The post type. * @param bool $auto_create Optional. Create the indexable if it does not exist. * * @return bool|Indexable The indexable, false if none could be found. */ public function find_for_post_type_archive( $post_type, $auto_create = true ) { /** * Indexable instance. * * @var Indexable $indexable */ $indexable = $this->query() ->where( 'object_type', 'post-type-archive' ) ->where( 'object_sub_type', $post_type ) ->find_one(); if ( $auto_create && ! $indexable ) { $indexable = $this->builder->build_for_post_type_archive( $post_type ); } return $this->upgrade_indexable( $indexable ); } /** * Retrieves the indexable for a system page. * * @param string $object_sub_type The type of system page. * @param bool $auto_create Optional. Create the indexable if it does not exist. * * @return bool|Indexable Instance of indexable. */ public function find_for_system_page( $object_sub_type, $auto_create = true ) { /** * Indexable instance. * * @var Indexable $indexable */ $indexable = $this->query() ->where( 'object_type', 'system-page' ) ->where( 'object_sub_type', $object_sub_type ) ->find_one(); if ( $auto_create && ! $indexable ) { $indexable = $this->builder->build_for_system_page( $object_sub_type ); } return $this->upgrade_indexable( $indexable ); } /** * Retrieves an indexable by its ID and type. * * @param int $object_id The indexable object ID. * @param string $object_type The indexable object type. * @param bool $auto_create Optional. Create the indexable if it does not exist. * * @return bool|Indexable Instance of indexable. */ public function find_by_id_and_type( $object_id, $object_type, $auto_create = true ) { $indexable = $this->query() ->where( 'object_id', $object_id ) ->where( 'object_type', $object_type ) ->find_one(); if ( $auto_create && ! $indexable ) { $indexable = $this->builder->build_for_id_and_type( $object_id, $object_type ); } else { $indexable = $this->upgrade_indexable( $indexable ); } return $indexable; } /** * Retrieves multiple indexables at once by their id's and type. * * @param int[] $object_ids The array of indexable object id's. * @param string $object_type The indexable object type. * @param bool $auto_create Optional. Create the indexable if it does not exist. * * @return Indexable[] An array of indexables. */ public function find_by_multiple_ids_and_type( $object_ids, $object_type, $auto_create = true ) { if ( empty( $object_ids ) ) { return []; } /** * Represents an array of indexable objects. * * @var Indexable[] $indexables */ $indexables = $this->query() ->where_in( 'object_id', $object_ids ) ->where( 'object_type', $object_type ) ->find_many(); if ( $auto_create ) { $indexables_available = []; foreach ( $indexables as $indexable ) { $indexables_available[] = $indexable->object_id; } $indexables_to_create = \array_diff( $object_ids, $indexables_available ); foreach ( $indexables_to_create as $indexable_to_create ) { $indexables[] = $this->builder->build_for_id_and_type( $indexable_to_create, $object_type ); } } return \array_map( [ $this, 'upgrade_indexable' ], $indexables ); } /** * Finds the indexables by id's. * * @param array $indexable_ids The indexable id's. * * @return Indexable[] The found indexables. */ public function find_by_ids( array $indexable_ids ) { if ( empty( $indexable_ids ) ) { return []; } $indexables = $this ->query() ->where_in( 'id', $indexable_ids ) ->find_many(); return \array_map( [ $this, 'upgrade_indexable' ], $indexables ); } /** * Returns all ancestors of a given indexable. * * @param Indexable $indexable The indexable to find the ancestors of. * * @return Indexable[] All ancestors of the given indexable. */ public function get_ancestors( Indexable $indexable ) { // If we've already set ancestors on the indexable no need to get them again. if ( \is_array( $indexable->ancestors ) && ! empty( $indexable->ancestors ) ) { return \array_map( [ $this, 'upgrade_indexable' ], $indexable->ancestors ); } $indexable_ids = $this->hierarchy_repository->find_ancestors( $indexable ); // If we've set ancestors on the indexable because we had to build them to find them. if ( \is_array( $indexable->ancestors ) && ! empty( $indexable->ancestors ) ) { return \array_map( [ $this, 'upgrade_indexable' ], $indexable->ancestors ); } if ( empty( $indexable_ids ) ) { return []; } if ( $indexable_ids[0] === 0 && \count( $indexable_ids ) === 1 ) { return []; } $indexables = $this->query() ->where_in( 'id', $indexable_ids ) ->order_by_expr( 'FIELD(id,' . \implode( ',', $indexable_ids ) . ')' ) ->find_many(); return \array_map( [ $this, 'upgrade_indexable' ], $indexables ); } /** * Returns all subpages with a given post_parent. * * @param int $post_parent The post parent. * @param array $exclude_ids The id's to exclude. * * @return Indexable[] array of indexables. */ public function get_subpages_by_post_parent( $post_parent, $exclude_ids = [] ) { $query = $this->query() ->where( 'post_parent', $post_parent ) ->where( 'object_type', 'post' ) ->where( 'post_status', 'publish' ); if ( ! empty( $exclude_ids ) ) { $query->where_not_in( 'object_id', $exclude_ids ); } return $query->find_many(); } /** * Updates the incoming link count for an indexable without first fetching it. * * @param int $indexable_id The indexable id. * @param int $count The incoming link count. * * @return bool Whether or not the update was succeful. */ public function update_incoming_link_count( $indexable_id, $count ) { return (bool) $this->query() ->set( 'incoming_link_count', $count ) ->where( 'id', $indexable_id ) ->update_many(); } /** * Ensures that the given indexable has a permalink. * * Will be deprecated in 17.3 - Use upgrade_indexable instead. * * @codeCoverageIgnore * * @param Indexable $indexable The indexable. * * @return bool|Indexable The indexable. */ public function ensure_permalink( $indexable ) { // @phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- self::class is safe. // @phpcs:ignore Squiz.PHP.CommentedOutCode.Found // _deprecated_function( __METHOD__, 'Yoast SEO 17.3', self::class . '::upgrade_indexable' ); return $this->upgrade_indexable( $indexable ); } /** * Checks if an Indexable is outdated, and rebuilds it when necessary. * * @param Indexable $indexable The indexable. * * @return Indexable The indexable. */ public function upgrade_indexable( $indexable ) { if ( $this->version_manager->indexable_needs_upgrade( $indexable ) ) { $indexable = $this->builder->build( $indexable ); } return $indexable; } /** * Resets the permalinks of the passed object type and subtype. * * @param string|null $type The type of the indexable. Can be null. * @param string|null $subtype The subtype. Can be null. * * @return int|bool The number of permalinks changed if the query was succesful. False otherwise. */ public function reset_permalink( $type = null, $subtype = null ) { $query = $this->query()->set( [ 'permalink' => null, 'permalink_hash' => null, 'version' => 0, ] ); if ( $type !== null ) { $query->where( 'object_type', $type ); } if ( $type !== null && $subtype !== null ) { $query->where( 'object_sub_type', $subtype ); } return $query->update_many(); } /** * Gets the total number of stored indexables. * * @return int The total number of stored indexables. */ public function get_total_number_of_indexables() { return $this->query()->count(); } } repositories/indexable-cleanup-repository.php 0000644 00000066314 15025657560 0015626 0 ustar 00 <?php namespace Yoast\WP\SEO\Repositories; use mysqli_result; use Yoast\WP\Lib\Model; use Yoast\WP\Lib\ORM; use Yoast\WP\SEO\Helpers\Author_Archive_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; /** * Repository containing all cleanup queries. */ class Indexable_Cleanup_Repository { /** * A helper for taxonomies. * * @var Taxonomy_Helper */ private $taxonomy; /** * A helper for post types. * * @var Post_Type_Helper */ private $post_type; /** * A helper for author archives. * * @var Author_Archive_Helper */ private $author_archive; /** * The constructor. * * @param Taxonomy_Helper $taxonomy A helper for taxonomies. * @param Post_Type_Helper $post_type A helper for post types. * @param Author_Archive_Helper $author_archive A helper for author archives. */ public function __construct( Taxonomy_Helper $taxonomy, Post_Type_Helper $post_type, Author_Archive_Helper $author_archive ) { $this->taxonomy = $taxonomy; $this->post_type = $post_type; $this->author_archive = $author_archive; } /** * Starts a query for this repository. * * @return ORM */ public function query() { return Model::of_type( 'Indexable' ); } /** * Deletes rows from the indexable table depending on the object_type and object_sub_type. * * @param string $object_type The object type to query. * @param string $object_sub_type The object subtype to query. * @param int $limit The limit we'll apply to the delete query. * * @return int|bool The number of rows that was deleted or false if the query failed. */ public function clean_indexables_with_object_type_and_object_sub_type( string $object_type, string $object_sub_type, int $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $sql = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = %s AND object_sub_type = %s ORDER BY id LIMIT %d", $object_type, $object_sub_type, $limit ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->query( $sql ); } /** * Counts amount of indexables by object type and object sub type. * * @param string $object_type The object type to check. * @param string $object_sub_type The object sub type to check. * * @return float|int */ public function count_indexables_with_object_type_and_object_sub_type( string $object_type, string $object_sub_type ) { return $this ->query() ->where( 'object_type', $object_type ) ->where( 'object_sub_type', $object_sub_type ) ->count(); } /** * Deletes rows from the indexable table depending on the post_status. * * @param string $post_status The post status to query. * @param int $limit The limit we'll apply to the delete query. * * @return int|bool The number of rows that was deleted or false if the query failed. */ public function clean_indexables_with_post_status( $post_status, $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $sql = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'post' AND post_status = %s ORDER BY id LIMIT %d", $post_status, $limit ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->query( $sql ); } /** * Counts indexables with a certain post status. * * @param string $post_status The post status to count. * * @return float|int */ public function count_indexables_with_post_status( string $post_status ) { return $this ->query() ->where( 'object_type', 'post' ) ->where( 'post_status', $post_status ) ->count(); } /** * Cleans up any indexables that belong to post types that are not/no longer publicly viewable. * * @param int $limit The limit we'll apply to the queries. * * @return bool|int The number of deleted rows, false if the query fails. */ public function clean_indexables_for_non_publicly_viewable_post( $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $included_post_types = $this->post_type->get_indexable_post_types(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix. if ( empty( $included_post_types ) ) { $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'post' AND object_sub_type IS NOT NULL LIMIT %d", $limit ); } else { // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Reason: we're passing an array instead. $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'post' AND object_sub_type IS NOT NULL AND object_sub_type NOT IN ( " . \implode( ', ', \array_fill( 0, \count( $included_post_types ), '%s' ) ) . ' ) LIMIT %d', \array_merge( $included_post_types, [ $limit ] ) ); } // phpcs:enable // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. return $wpdb->query( $delete_query ); // phpcs:enable } /** * Counts all indexables for non public post types. * * @return float|int */ public function count_indexables_for_non_publicly_viewable_post() { $included_post_types = $this->post_type->get_indexable_post_types(); if ( empty( $included_post_types ) ) { return $this ->query() ->where( 'object_type', 'post' ) ->where_not_equal( 'object_sub_type', 'null' ) ->count(); } else { return $this ->query() ->where( 'object_type', 'post' ) ->where_not_equal( 'object_sub_type', 'null' ) ->where_not_in( 'object_sub_type', $included_post_types ) ->count(); } } /** * Cleans up any indexables that belong to taxonomies that are not/no longer publicly viewable. * * @param int $limit The limit we'll apply to the queries. * * @return bool|int The number of deleted rows, false if the query fails. */ public function clean_indexables_for_non_publicly_viewable_taxonomies( $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $included_taxonomies = $this->taxonomy->get_indexable_taxonomies(); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix. if ( empty( $included_taxonomies ) ) { $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'term' AND object_sub_type IS NOT NULL LIMIT %d", $limit ); } else { // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Reason: we're passing an array instead. $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'term' AND object_sub_type IS NOT NULL AND object_sub_type NOT IN ( " . \implode( ', ', \array_fill( 0, \count( $included_taxonomies ), '%s' ) ) . ' ) LIMIT %d', \array_merge( $included_taxonomies, [ $limit ] ) ); } // phpcs:enable // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. return $wpdb->query( $delete_query ); // phpcs:enable } /** * Cleans up any indexables that belong to post type archive page that are not/no longer publicly viewable. * * @param int $limit The limit we'll apply to the queries. * * @return bool|int The number of deleted rows, false if the query fails. */ public function clean_indexables_for_non_publicly_viewable_post_type_archive_pages( $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $included_post_types = $this->post_type->get_indexable_post_archives(); $post_archives = []; foreach ( $included_post_types as $post_type ) { $post_archives[] = $post_type->name; } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix. if ( empty( $post_archives ) ) { $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'post-type-archive' AND object_sub_type IS NOT NULL LIMIT %d", $limit ); } else { // phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Reason: we're passing an array instead. $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'post-type-archive' AND object_sub_type IS NOT NULL AND object_sub_type NOT IN ( " . \implode( ', ', \array_fill( 0, \count( $post_archives ), '%s' ) ) . ' ) LIMIT %d', \array_merge( $post_archives, [ $limit ] ) ); } // phpcs:enable // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. return $wpdb->query( $delete_query ); // phpcs:enable } /** * Counts indexables for non publicly viewable taxonomies. * * @return float|int */ public function count_indexables_for_non_publicly_viewable_taxonomies() { $included_taxonomies = $this->taxonomy->get_indexable_taxonomies(); if ( empty( $included_taxonomies ) ) { return $this ->query() ->where( 'object_type', 'term' ) ->where_not_equal( 'object_sub_type', 'null' ) ->count(); } else { return $this ->query() ->where( 'object_type', 'term' ) ->where_not_equal( 'object_sub_type', 'null' ) ->where_not_in( 'object_sub_type', $included_taxonomies ) ->count(); } } /** * Counts indexables for non publicly viewable taxonomies. * * @return float|int */ public function count_indexables_for_non_publicly_post_type_archive_pages() { $included_post_types = $this->post_type->get_indexable_post_archives(); $post_archives = []; foreach ( $included_post_types as $post_type ) { $post_archives[] = $post_type->name; } if ( empty( $post_archives ) ) { return $this ->query() ->where( 'object_type', 'post-type-archive' ) ->where_not_equal( 'object_sub_type', 'null' ) ->count(); } return $this ->query() ->where( 'object_type', 'post-type-archive' ) ->where_not_equal( 'object_sub_type', 'null' ) ->where_not_in( 'object_sub_type', $post_archives ) ->count(); } /** * Cleans up any user indexables when the author archives have been disabled. * * @param int $limit The limit we'll apply to the queries. * * @return bool|int The number of deleted rows, false if the query fails. */ public function clean_indexables_for_authors_archive_disabled( $limit ) { global $wpdb; if ( ! $this->author_archive->are_disabled() ) { return 0; } $indexable_table = Model::get_table_name( 'Indexable' ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix. $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'user' LIMIT %d", $limit ); // phpcs:enable // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. return $wpdb->query( $delete_query ); // phpcs:enable } /** * Counts the amount of author archive indexables if they are not disabled. * * @return float|int */ public function count_indexables_for_authors_archive_disabled() { if ( ! $this->author_archive->are_disabled() ) { return 0; } return $this ->query() ->where( 'object_type', 'user' ) ->count(); } /** * Cleans up any indexables that belong to users that have their author archives disabled. * * @param int $limit The limit we'll apply to the queries. * * @return bool|int The number of deleted rows, false if the query fails. */ public function clean_indexables_for_authors_without_archive( $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $author_archive_post_types = $this->author_archive->get_author_archive_post_types(); $viewable_post_stati = \array_filter( \get_post_stati(), 'is_post_status_viewable' ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix. // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Reason: we're passing an array instead. $delete_query = $wpdb->prepare( "DELETE FROM $indexable_table WHERE object_type = 'user' AND object_id NOT IN ( SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_type IN ( " . \implode( ', ', \array_fill( 0, \count( $author_archive_post_types ), '%s' ) ) . ' ) AND post_status IN ( ' . \implode( ', ', \array_fill( 0, \count( $viewable_post_stati ), '%s' ) ) . ' ) ) LIMIT %d', \array_merge( $author_archive_post_types, $viewable_post_stati, [ $limit ] ) ); // phpcs:enable // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. return $wpdb->query( $delete_query ); // phpcs:enable } /** * Counts total amount of indexables for authors without archives. * * @return bool|int|mysqli_result|resource|null */ public function count_indexables_for_authors_without_archive() { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $author_archive_post_types = $this->author_archive->get_author_archive_post_types(); $viewable_post_stati = \array_filter( \get_post_stati(), 'is_post_status_viewable' ); // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: Too hard to fix. // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Reason: we're passing an array instead. $count_query = $wpdb->prepare( "SELECT count(*) FROM $indexable_table WHERE object_type = 'user' AND object_id NOT IN ( SELECT DISTINCT post_author FROM $wpdb->posts WHERE post_type IN ( " . \implode( ', ', \array_fill( 0, \count( $author_archive_post_types ), '%s' ) ) . ' ) AND post_status IN ( ' . \implode( ', ', \array_fill( 0, \count( $viewable_post_stati ), '%s' ) ) . ' ) )', \array_merge( $author_archive_post_types, $viewable_post_stati ) ); // phpcs:enable // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. return $wpdb->get_col( $count_query )[0]; // phpcs:enable } /** * Deletes rows from the indexable table where the source is no longer there. * * @param string $source_table The source table which we need to check the indexables against. * @param string $source_identifier The identifier which the indexables are matched to. * @param string $object_type The indexable object type. * @param int $limit The limit we'll apply to the delete query. * * @return int|bool The number of rows that was deleted or false if the query failed. */ public function clean_indexables_for_object_type_and_source_table( $source_table, $source_identifier, $object_type, $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $source_table = $wpdb->prefix . $source_table; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $query = $wpdb->prepare( " SELECT indexable_table.object_id FROM {$indexable_table} indexable_table LEFT JOIN {$source_table} AS source_table ON indexable_table.object_id = source_table.{$source_identifier} WHERE source_table.{$source_identifier} IS NULL AND indexable_table.object_id IS NOT NULL AND indexable_table.object_type = '{$object_type}' LIMIT %d", $limit ); // phpcs:enable // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. $orphans = $wpdb->get_col( $query ); if ( empty( $orphans ) ) { return 0; } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->query( "DELETE FROM $indexable_table WHERE object_type = '{$object_type}' AND object_id IN( " . \implode( ',', $orphans ) . ' )' ); } /** * Deletes rows from the indexable table where the source is no longer there. * * @param int $limit The limit we'll apply to the delete query. * * @return int|bool The number of rows that was deleted or false if the query failed. */ public function clean_indexables_for_orphaned_users( $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $source_table = $wpdb->users; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $query = $wpdb->prepare( " SELECT indexable_table.object_id FROM {$indexable_table} indexable_table LEFT JOIN {$source_table} AS source_table ON indexable_table.object_id = source_table.ID WHERE source_table.ID IS NULL AND indexable_table.object_id IS NOT NULL AND indexable_table.object_type = 'user' LIMIT %d", $limit ); // phpcs:enable // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. $orphans = $wpdb->get_col( $query ); if ( empty( $orphans ) ) { return 0; } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->query( "DELETE FROM $indexable_table WHERE object_type = 'user' AND object_id IN( " . \implode( ',', $orphans ) . ' )' ); } /** * Counts indexables for given source table + source identifier + object type. * * @param string $source_table The source table. * @param string $source_identifier The source identifier. * @param string $object_type The object type. * * @return mixed */ public function count_indexables_for_object_type_and_source_table( string $source_table, string $source_identifier, string $object_type ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $source_table = $wpdb->prefix . $source_table; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->get_col( " SELECT count(*) FROM {$indexable_table} indexable_table LEFT JOIN {$source_table} AS source_table ON indexable_table.object_id = source_table.{$source_identifier} WHERE source_table.{$source_identifier} IS NULL AND indexable_table.object_id IS NOT NULL AND indexable_table.object_type = '{$object_type}'" )[0]; // phpcs:enable } /** * Counts indexables for orphaned users. * * @return mixed */ public function count_indexables_for_orphaned_users() { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $source_table = $wpdb->users; //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->get_col( " SELECT count(*) FROM {$indexable_table} indexable_table LEFT JOIN {$source_table} AS source_table ON indexable_table.object_id = source_table.ID WHERE source_table.ID IS NULL AND indexable_table.object_id IS NOT NULL AND indexable_table.object_type = 'user'" )[0]; // phpcs:enable } /** * Cleans orphaned rows from a yoast table. * * @param string $table The table to clean up. * @param string $column The table column the cleanup will rely on. * @param int $limit The limit we'll apply to the queries. * * @return int|bool The number of deleted rows, false if the query fails. */ public function cleanup_orphaned_from_table( $table, $column, $limit ) { global $wpdb; $table = Model::get_table_name( $table ); $indexable_table = Model::get_table_name( 'Indexable' ); // Warning: If this query is changed, make sure to update the query in cleanup_orphaned_from_table in Premium as well. // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $query = $wpdb->prepare( " SELECT table_to_clean.{$column} FROM {$table} table_to_clean LEFT JOIN {$indexable_table} AS indexable_table ON table_to_clean.{$column} = indexable_table.id WHERE indexable_table.id IS NULL AND table_to_clean.{$column} IS NOT NULL LIMIT %d", $limit ); // phpcs:enable // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. $orphans = $wpdb->get_col( $query ); if ( empty( $orphans ) ) { return 0; } // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->query( "DELETE FROM $table WHERE {$column} IN( " . \implode( ',', $orphans ) . ' )' ); } /** * Counts orphaned rows from a yoast table. * * @param string $table The table to clean up. * @param string $column The table column the cleanup will rely on. * * @return int|bool The number of deleted rows, false if the query fails. */ public function count_orphaned_from_table( string $table, string $column ) { global $wpdb; $table = Model::get_table_name( $table ); $indexable_table = Model::get_table_name( 'Indexable' ); // Warning: If this query is changed, make sure to update the query in cleanup_orphaned_from_table in Premium as well. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->get_col( " SELECT count(*) FROM {$table} table_to_clean LEFT JOIN {$indexable_table} AS indexable_table ON table_to_clean.{$column} = indexable_table.id WHERE indexable_table.id IS NULL AND table_to_clean.{$column} IS NOT NULL" )[0]; // phpcs:enable } /** * Updates the author_id of indexables which author_id is not in the wp_users table with the id of the reassingned * user. * * @param int $limit The limit we'll apply to the queries. * * @return int|bool The number of updated rows, false if query to get data fails. */ public function update_indexables_author_to_reassigned( $limit ) { // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. $reassigned_authors_objs = $this->get_reassigned_authors( $limit ); if ( $reassigned_authors_objs === false ) { return false; } return $this->update_indexable_authors( $reassigned_authors_objs, $limit ); } /** * Fetches pairs of old_id -> new_id indexed by old_id. * By using the old_id (i.e. the id of the user that has been deleted) as key of the associative array, we can * easily compose an array of unique pairs of old_id -> new_id. * * @param int $limit The limit we'll apply to the queries. * * @return int|bool The associative array with shape [ old_id => [ old_id, new_author ] ] or false if query to get * data fails. */ private function get_reassigned_authors( $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); $posts_table = $wpdb->posts; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $query = $wpdb->prepare( " SELECT {$indexable_table}.author_id, {$posts_table}.post_author FROM {$indexable_table} JOIN {$posts_table} on {$indexable_table}.object_id = {$posts_table}.id WHERE object_type='post' AND {$indexable_table}.author_id <> {$posts_table}.post_author GROUP BY {$indexable_table}.author_id, {$posts_table}.post_author ORDER BY {$indexable_table}.author_id LIMIT %d", $limit ); // phpcs:enable // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. return $wpdb->get_results( $query, \OBJECT_K ); } /** * Updates the indexable's author_id referring to a deleted author with the id of the reassigned user. * * @param array $reassigned_authors_objs The array of objects with shape [ old_id => [ old_id, new_id ] ]. * @param int $limit The limit we'll apply to the queries. * * @return int|bool The associative array with shape [ old_id => [ old_id, new_author ] ] or false if query to get * data fails. */ private function update_indexable_authors( $reassigned_authors_objs, $limit ) { global $wpdb; $indexable_table = Model::get_table_name( 'Indexable' ); // This is a workaround for the fact that the array_column function does not work on objects in PHP 5.6. $reassigned_authors_array = \array_map( static function ( $obj ) { return (array) $obj; }, $reassigned_authors_objs ); $reassigned_authors = \array_combine( \array_column( $reassigned_authors_array, 'author_id' ), \array_column( $reassigned_authors_array, 'post_author' ) ); foreach ( $reassigned_authors as $old_author_id => $new_author_id ) { // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $query = $wpdb->prepare( " UPDATE {$indexable_table} SET {$indexable_table}.author_id = {$new_author_id} WHERE {$indexable_table}.author_id = {$old_author_id} AND object_type='post' LIMIT %d", $limit ); // phpcs:enable // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: Already prepared. $wpdb->query( $query ); } return \count( $reassigned_authors ); } } repositories/primary-term-repository.php 0000644 00000002236 15025657560 0014667 0 ustar 00 <?php namespace Yoast\WP\SEO\Repositories; use Yoast\WP\Lib\Model; use Yoast\WP\Lib\ORM; use Yoast\WP\SEO\Models\Primary_Term; /** * Class Primary_Term_Repository. */ class Primary_Term_Repository { /** * Starts a query for this repository. * * @return ORM */ public function query() { return Model::of_type( 'Primary_Term' ); } /** * Retrieves a primary term by a post ID and taxonomy. * * @param int $post_id The post the indexable is based upon. * @param string $taxonomy The taxonomy the indexable belongs to. * @param bool $auto_create Optional. Creates an indexable if it does not exist yet. * * @return Primary_Term|null Instance of a primary term. */ public function find_by_post_id_and_taxonomy( $post_id, $taxonomy, $auto_create = true ) { /** * Instance of the primary term. * * @var Primary_Term $primary_term_indexable */ $primary_term_indexable = $this->query() ->where( 'post_id', $post_id ) ->where( 'taxonomy', $taxonomy ) ->find_one(); if ( $auto_create && ! $primary_term_indexable ) { $primary_term_indexable = $this->query()->create(); } return $primary_term_indexable; } } repositories/indexable-hierarchy-repository.php 0000644 00000007622 15025657560 0016152 0 ustar 00 <?php namespace Yoast\WP\SEO\Repositories; use Yoast\WP\Lib\Model; use Yoast\WP\Lib\ORM; use Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Models\Indexable; /** * Class Indexable_Hierarchy_Repository. */ class Indexable_Hierarchy_Repository { /** * Represents the indexable hierarchy builder. * * @var Indexable_Hierarchy_Builder */ protected $builder; /** * Represents the indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Sets the hierarchy builder. * * @required * * @param Indexable_Hierarchy_Builder $builder The indexable hierarchy builder. * * @return void */ public function set_builder( Indexable_Hierarchy_Builder $builder ) { $this->builder = $builder; } /** * Sets the indexable helper. * * @required * * @param Indexable_Helper $indexable_helper The indexable helper. * * @return void */ public function set_helper( Indexable_Helper $indexable_helper ) { $this->indexable_helper = $indexable_helper; } /** * Removes all ancestors for an indexable. * * @param int $indexable_id The indexable id. * * @return bool Whether or not the indexables were successfully deleted. */ public function clear_ancestors( $indexable_id ) { return $this->query()->where( 'indexable_id', $indexable_id )->delete_many(); } /** * Adds an ancestor to an indexable. * * @param int $indexable_id The indexable id. * @param int $ancestor_id The ancestor id. * @param int $depth The depth. * * @return bool Whether or not the ancestor was added successfully. */ public function add_ancestor( $indexable_id, $ancestor_id, $depth ) { if ( ! $this->indexable_helper->should_index_indexables() ) { return false; } $hierarchy = $this->query()->create( [ 'indexable_id' => $indexable_id, 'ancestor_id' => $ancestor_id, 'depth' => $depth, 'blog_id' => \get_current_blog_id(), ] ); return $hierarchy->save(); } /** * Retrieves the ancestors. Create them when empty. * * @param Indexable $indexable The indexable to get the ancestors for. * * @return int[] The indexable id's of the ancestors in order of grandparent to child. */ public function find_ancestors( Indexable $indexable ) { $ancestors = $this->query() ->select( 'ancestor_id' ) ->where( 'indexable_id', $indexable->id ) ->order_by_desc( 'depth' ) ->find_array(); if ( ! empty( $ancestors ) ) { if ( \count( $ancestors ) === 1 && $ancestors[0]['ancestor_id'] === '0' ) { return []; } return \wp_list_pluck( $ancestors, 'ancestor_id' ); } $indexable = $this->builder->build( $indexable ); return \wp_list_pluck( $indexable->ancestors, 'id' ); } /** * Finds the children for a given indexable. * * @param Indexable $indexable The indexable to find the children for. * * @return array Array with indexable id's for the children. */ public function find_children( Indexable $indexable ) { $children = $this->query() ->select( 'indexable_id' ) ->where( 'ancestor_id', $indexable->id ) ->find_array(); if ( empty( $children ) ) { return []; } return \wp_list_pluck( $children, 'indexable_id' ); } /** * Starts a query for this repository. * * @return ORM */ public function query() { return Model::of_type( 'Indexable_Hierarchy' ); } /** * Finds all the children by given ancestor id's. * * @param array $object_ids List of id's to get the children for. * * @return array List of indexable id's for the children. */ public function find_children_by_ancestor_ids( array $object_ids ) { if ( empty( $object_ids ) ) { return []; } $children = $this->query() ->select( 'indexable_id' ) ->where_in( 'ancestor_id', $object_ids ) ->find_array(); if ( empty( $children ) ) { return []; } return \wp_list_pluck( $children, 'indexable_id' ); } } repositories/seo-links-repository.php 0000644 00000012642 15025657560 0014145 0 ustar 00 <?php namespace Yoast\WP\SEO\Repositories; use Yoast\WP\Lib\Model; use Yoast\WP\Lib\ORM; use Yoast\WP\SEO\Models\SEO_Links; /** * Class SEO_Links_Repository. */ class SEO_Links_Repository { /** * Starts a query for this repository. * * @return ORM */ public function query() { return Model::of_type( 'SEO_Links' ); } /** * Finds all SEO Links by post ID. * * @param int $post_id The post ID. * * @return SEO_Links[] The SEO Links. */ public function find_all_by_post_id( $post_id ) { return $this->query() ->where( 'post_id', $post_id ) ->find_many(); } /** * Finds all SEO Links by indexable ID. * * @param int $indexable_id The indexable ID. * * @return SEO_Links[] The SEO Links. */ public function find_all_by_indexable_id( $indexable_id ) { return $this->query() ->where( 'indexable_id', $indexable_id ) ->find_many(); } /** * Retrieves an SEO Link by url. * * @param string $url The SEO Link's url. * * @return SEO_Links|false The SEO Link, or false if none found. */ public function find_one_by_url( $url ) { return $this->query() ->select( 'target_post_id' ) ->where( 'url', $url ) ->find_one(); } /** * Retrieves all SEO Links by target post ID. * * @param string $target_post_id The SEO Link's target post ID. * * @return SEO_Links[] The SEO Links. */ public function find_all_by_target_post_id( $target_post_id ) { return $this->query() ->where( 'target_post_id', $target_post_id ) ->find_many(); } /** * Updates the ID of the target indexable of a link. * * @param int $link_id The ID of the link to be updated. * @param int $target_indexable_id The ID of the target indexable. * * @return bool Whether or not the update was succeful. */ public function update_target_indexable_id( $link_id, $target_indexable_id ) { return (bool) $this->query() ->set( 'target_indexable_id', $target_indexable_id ) ->where( 'id', $link_id ) ->update_many(); } /** * Clears all SEO Links by post ID. * * @param int $post_id The post ID. * * @return bool Whether or not the delete was succesfull. */ public function delete_all_by_post_id( $post_id ) { return $this->query() ->where( 'post_id', $post_id ) ->delete_many(); } /** * Clears all SEO Links by post ID where the indexable id is null. * * @param int $post_id The post ID. * * @return bool Whether or not the delete was succesfull. */ public function delete_all_by_post_id_where_indexable_id_null( $post_id ) { return $this->query() ->where( 'post_id', $post_id ) ->where_null( 'indexable_id' ) ->delete_many(); } /** * Clears all SEO Links by indexable ID. * * @param int $indexable_id The indexable ID. * * @return bool Whether or not the delete was succesfull. */ public function delete_all_by_indexable_id( $indexable_id ) { return $this->query() ->where( 'indexable_id', $indexable_id ) ->delete_many(); } /** * Returns incoming link counts for a number of posts. * * @param array $post_ids The post IDs. * * @return array An array of associative arrays, each containing a post id and incoming property. */ public function get_incoming_link_counts_for_post_ids( $post_ids ) { return $this->query() ->select_expr( 'COUNT( id )', 'incoming' ) ->select( 'target_post_id', 'post_id' ) ->where_in( 'target_post_id', $post_ids ) ->group_by( 'target_post_id' ) ->find_array(); } /** * Returns incoming link counts for a number of indexables. * * @param array $indexable_ids The indexable IDs. * * @return array An array of associative arrays, each containing a indexable id and incoming property. */ public function get_incoming_link_counts_for_indexable_ids( $indexable_ids ) { if ( empty( $indexable_ids ) ) { return []; } // This query only returns ID's with an incoming count > 0. We need to restore any ID's with 0 incoming links later. $indexable_counts = $this->query() ->select_expr( 'COUNT( id )', 'incoming' ) ->select( 'target_indexable_id' ) ->where_in( 'target_indexable_id', $indexable_ids ) ->group_by( 'target_indexable_id' ) ->find_array(); // If the above query fails, do not update anything. if ( ! \is_array( $indexable_counts ) ) { return []; } // Get all ID's returned from the query and set them as keys for easy access. $returned_ids = \array_flip( \array_column( $indexable_counts, 'target_indexable_id' ) ); // Loop over the original ID's and search them in the returned ID's. If they don't exist, add them with an incoming count of 0. foreach ( $indexable_ids as $id ) { // Cast the ID to string, as the arrays only contain stringified versions of the ID. $id = \strval( $id ); if ( isset( $returned_ids[ $id ] ) === false ) { $indexable_counts[] = [ 'incoming' => '0', 'target_indexable_id' => $id, ]; } } return $indexable_counts; } /** * Deletes all seo links for the given ids. * * @param int[] $ids The seo link ids. * * @return bool Whether or not the delete was succesfull. */ public function delete_many_by_id( $ids ) { return $this->query() ->where_in( 'id', $ids ) ->delete_many(); } /** * Insert multiple seo links. * * @param SEO_Links[] $links The seo links to be inserted. * * @return bool Whether or not the insert was succesfull. */ public function insert_many( $links ) { return $this->query() ->insert_many( $links ); } } context/meta-tags-context.php 0000644 00000045701 15025657560 0012327 0 ustar 00 <?php namespace Yoast\WP\SEO\Context; use WP_Block_Parser_Block; use WP_Post; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Config\Schema_IDs; use Yoast\WP\SEO\Config\Schema_Types; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Permalink_Helper; use Yoast\WP\SEO\Helpers\Schema\ID_Helper; use Yoast\WP\SEO\Helpers\Site_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Helpers\User_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Presentations\Abstract_Presentation; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Class Meta_Tags_Context. * * Class that contains all relevant data for rendering the meta tags. * * @property string $canonical * @property string $permalink * @property string $title * @property string $description * @property string $id * @property string $site_name * @property string $alternate_site_name * @property string $wordpress_site_name * @property string $site_url * @property string $company_name * @property string $company_alternate_name * @property int $company_logo_id * @property array $company_logo_meta * @property int $person_logo_id * @property array $person_logo_meta * @property int $site_user_id * @property string $site_represents * @property array|false $site_represents_reference * @property string|string[] $schema_page_type * @property string|string[] $schema_article_type Represents the type of article. * @property string $main_schema_id * @property string|array $main_entity_of_page * @property bool $open_graph_enabled * @property string $open_graph_publisher * @property string $twitter_card * @property string $page_type * @property bool $has_article * @property bool $has_image * @property int $main_image_id * @property string $main_image_url */ class Meta_Tags_Context extends Abstract_Presentation { /** * The indexable. * * @var Indexable */ public $indexable; /** * The WP Block Parser Block. * * @var WP_Block_Parser_Block[] */ public $blocks; /** * The WP Post. * * @var WP_Post */ public $post; /** * The indexable presentation. * * @var Indexable_Presentation */ public $presentation; /** * Determines whether we have an Article piece. Set to true by the Article piece itself. * * @var bool */ public $has_article = false; /** * The options helper. * * @var Options_Helper */ private $options; /** * The URL helper. * * @var Url_Helper */ private $url; /** * The image helper. * * @var Image_Helper */ private $image; /** * The ID helper. * * @var ID_Helper */ private $id_helper; /** * The WPSEO Replace Vars object. * * @var WPSEO_Replace_Vars */ private $replace_vars; /** * The site helper. * * @var Site_Helper */ private $site; /** * The user helper. * * @var User_Helper */ private $user; /** * The permalink helper. * * @var Permalink_Helper */ private $permalink_helper; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * The indexable repository. * * @var Indexable_Repository */ private $indexable_repository; /** * Meta_Tags_Context constructor. * * @param Options_Helper $options The options helper. * @param Url_Helper $url The url helper. * @param Image_Helper $image The image helper. * @param ID_Helper $id_helper The schema id helper. * @param WPSEO_Replace_Vars $replace_vars The replace vars helper. * @param Site_Helper $site The site helper. * @param User_Helper $user The user helper. * @param Permalink_Helper $permalink_helper The permalink helper. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Indexable_Repository $indexable_repository The indexable repository. */ public function __construct( Options_Helper $options, Url_Helper $url, Image_Helper $image, ID_Helper $id_helper, WPSEO_Replace_Vars $replace_vars, Site_Helper $site, User_Helper $user, Permalink_Helper $permalink_helper, Indexable_Helper $indexable_helper, Indexable_Repository $indexable_repository ) { $this->options = $options; $this->url = $url; $this->image = $image; $this->id_helper = $id_helper; $this->replace_vars = $replace_vars; $this->site = $site; $this->user = $user; $this->permalink_helper = $permalink_helper; $this->indexable_helper = $indexable_helper; $this->indexable_repository = $indexable_repository; } /** * Generates the title. * * @return string the title */ public function generate_title() { return $this->replace_vars->replace( $this->presentation->title, $this->presentation->source ); } /** * Generates the description. * * @return string the description */ public function generate_description() { return $this->replace_vars->replace( $this->presentation->meta_description, $this->presentation->source ); } /** * Generates the canonical. * * @return string the canonical */ public function generate_canonical() { return $this->presentation->canonical; } /** * Generates the permalink. * * @return string */ public function generate_permalink() { if ( ! \is_search() ) { return $this->presentation->permalink; } return \add_query_arg( 's', \rawurlencode( \get_search_query() ), \trailingslashit( $this->site_url ) ); } /** * Generates the id. * * @return string the id */ public function generate_id() { return $this->indexable->object_id; } /** * Generates the site name. * * @return string The site name. */ public function generate_site_name() { $site_name = $this->options->get( 'website_name', '' ); if ( $site_name !== '' ) { return $site_name; } return \get_bloginfo( 'name' ); } /** * Generates the alternate site name. * * @return string The alternate site name. */ public function generate_alternate_site_name() { return (string) $this->options->get( 'alternate_website_name', '' ); } /** * Generates the site name from the WordPress options. * * @return string The site name from the WordPress options. */ public function generate_wordpress_site_name() { return $this->site->get_site_name(); } /** * Generates the site url. * * @return string The site url. */ public function generate_site_url() { $home_page_indexable = $this->indexable_repository->find_for_home_page(); if ( $this->indexable_helper->dynamic_permalinks_enabled() ) { return \trailingslashit( $this->permalink_helper->get_permalink_for_indexable( $home_page_indexable ) ); } return \trailingslashit( $home_page_indexable->permalink ); } /** * Generates the company name. * * @return string The company name. */ public function generate_company_name() { /** * Filter: 'wpseo_schema_company_name' - Allows filtering company name * * @param string $company_name. */ $company_name = \apply_filters( 'wpseo_schema_company_name', $this->options->get( 'company_name' ) ); if ( empty( $company_name ) ) { $company_name = $this->site_name; } return $company_name; } /** * Generates the alternate company name. * * @return string */ public function generate_company_alternate_name() { return (string) $this->options->get( 'company_alternate_name' ); } /** * Generates the person logo id. * * @return int|bool The company logo id. */ public function generate_person_logo_id() { $person_logo_id = $this->image->get_attachment_id_from_settings( 'person_logo' ); if ( empty( $person_logo_id ) ) { $person_logo_id = $this->fallback_to_site_logo(); } /** * Filter: 'wpseo_schema_person_logo_id' - Allows filtering person logo id. * * @param int $person_logo_id. */ return \apply_filters( 'wpseo_schema_person_logo_id', $person_logo_id ); } /** * Retrieve the person logo meta. * * @return array<string|array<int>>|bool */ public function generate_person_logo_meta() { $person_logo_meta = $this->image->get_attachment_meta_from_settings( 'person_logo' ); if ( empty( $person_logo_meta ) ) { $person_logo_id = $this->fallback_to_site_logo(); $person_logo_meta = $this->image->get_best_attachment_variation( $person_logo_id ); } /** * Filter: 'wpseo_schema_person_logo_meta' - Allows filtering person logo meta. * * @param string $person_logo_meta. */ return \apply_filters( 'wpseo_schema_person_logo_meta', $person_logo_meta ); } /** * Generates the company logo id. * * @return int|bool The company logo id. */ public function generate_company_logo_id() { $company_logo_id = $this->image->get_attachment_id_from_settings( 'company_logo' ); if ( empty( $company_logo_id ) ) { $company_logo_id = $this->fallback_to_site_logo(); } /** * Filter: 'wpseo_schema_company_logo_id' - Allows filtering company logo id. * * @param int $company_logo_id. */ return \apply_filters( 'wpseo_schema_company_logo_id', $company_logo_id ); } /** * Retrieve the company logo meta. * * @return array<string|array<int>>|bool */ public function generate_company_logo_meta() { $company_logo_meta = $this->image->get_attachment_meta_from_settings( 'company_logo' ); /** * Filter: 'wpseo_schema_company_logo_meta' - Allows filtering company logo meta. * * @param string $company_logo_meta. */ return \apply_filters( 'wpseo_schema_company_logo_meta', $company_logo_meta ); } /** * Generates the site user id. * * @return int The site user id. */ public function generate_site_user_id() { return (int) $this->options->get( 'company_or_person_user_id', false ); } /** * Determines what our site represents, and grabs their values. * * @return string|false Person or company. False if invalid value. */ public function generate_site_represents() { switch ( $this->options->get( 'company_or_person', false ) ) { case 'company': // Do not use a non-named company. if ( empty( $this->company_name ) ) { return false; } /* * Do not use a company without a logo. * The logic check is on `< 1` instead of `false` due to how `get_attachment_id_from_settings` works. */ if ( $this->company_logo_id < 1 ) { return false; } return 'company'; case 'person': // Do not use a non-existing user. if ( $this->site_user_id !== false && \get_user_by( 'id', $this->site_user_id ) === false ) { return false; } return 'person'; } return false; } /** * Returns the site represents reference. * * @return array<string>|bool The site represents reference. False if none. */ public function generate_site_represents_reference() { if ( $this->site_represents === 'person' ) { return [ '@id' => $this->id_helper->get_user_schema_id( $this->site_user_id, $this ) ]; } if ( $this->site_represents === 'company' ) { return [ '@id' => $this->site_url . Schema_IDs::ORGANIZATION_HASH ]; } return false; } /** * Returns whether or not open graph is enabled. * * @return bool Whether or not open graph is enabled. */ public function generate_open_graph_enabled() { return $this->options->get( 'opengraph' ) === true; } /** * Returns the open graph publisher. * * @return string The open graph publisher. */ public function generate_open_graph_publisher() { if ( $this->site_represents === 'company' ) { return $this->options->get( 'facebook_site', '' ); } if ( $this->site_represents === 'person' ) { return $this->user->get_the_author_meta( 'facebook', $this->site_user_id ); } return $this->options->get( 'facebook_site', '' ); } /** * Returns the twitter card type. * * @return string The twitter card type. */ public function generate_twitter_card() { return 'summary_large_image'; } /** * Returns the schema page type. * * @return string|array<string> The schema page type. */ public function generate_schema_page_type() { switch ( $this->indexable->object_type ) { case 'system-page': switch ( $this->indexable->object_sub_type ) { case 'search-result': $type = [ 'CollectionPage', 'SearchResultsPage' ]; break; default: $type = 'WebPage'; } break; case 'user': $type = 'ProfilePage'; break; case 'home-page': case 'date-archive': case 'term': case 'post-type-archive': $type = 'CollectionPage'; break; default: $additional_type = $this->indexable->schema_page_type; if ( $additional_type === null ) { $additional_type = $this->options->get( 'schema-page-type-' . $this->indexable->object_sub_type ); } $type = [ 'WebPage', $additional_type ]; // Is this indexable set as a page for posts, e.g. in the WordPress reading settings as a static homepage? if ( (int) \get_option( 'page_for_posts' ) === $this->indexable->object_id ) { $type[] = 'CollectionPage'; } // Ensure we get only unique values, and remove any null values and the index. $type = \array_filter( \array_values( \array_unique( $type ) ) ); } /** * Filter: 'wpseo_schema_webpage_type' - Allow changing the WebPage type. * * @param string|array $type The WebPage type. */ return \apply_filters( 'wpseo_schema_webpage_type', $type ); } /** * Returns the schema article type. * * @return string|array<string> The schema article type. */ public function generate_schema_article_type() { $additional_type = $this->indexable->schema_article_type; if ( $additional_type === null ) { $additional_type = $this->options->get( 'schema-article-type-' . $this->indexable->object_sub_type ); } /** This filter is documented in inc/options/class-wpseo-option-titles.php */ $allowed_article_types = \apply_filters( 'wpseo_schema_article_types', Schema_Types::ARTICLE_TYPES ); if ( ! \array_key_exists( $additional_type, $allowed_article_types ) ) { $additional_type = $this->options->get_title_default( 'schema-article-type-' . $this->indexable->object_sub_type ); } // If the additional type is a subtype of Article, we're fine, and we can bail here. if ( \stripos( $additional_type, 'Article' ) !== false ) { /** * Filter: 'wpseo_schema_article_type' - Allow changing the Article type. * * @param string|string[] $type The Article type. * @param Indexable $indexable The indexable. */ return \apply_filters( 'wpseo_schema_article_type', $additional_type, $this->indexable ); } $type = 'Article'; /* * If `None` is set (either on the indexable or as a default), set type to 'None'. * This simplifies is_needed checks downstream. */ if ( $additional_type === 'None' ) { $type = $additional_type; } if ( $additional_type !== $type ) { $type = [ $type, $additional_type ]; } // Filter documented on line 499 above. return \apply_filters( 'wpseo_schema_article_type', $type, $this->indexable ); } /** * Returns the main schema id. * * The main schema id. * * @return string */ public function generate_main_schema_id() { return $this->permalink; } /** * Retrieves the main image URL. This is the featured image by default. * * @return string|null The main image URL. */ public function generate_main_image_url() { if ( $this->main_image_id !== null ) { return $this->image->get_attachment_image_url( $this->main_image_id, 'full' ); } if ( \wp_is_serving_rest_request() ) { return $this->get_main_image_url_for_rest_request(); } if ( ! \is_singular() ) { return null; } $url = $this->image->get_post_content_image( $this->id ); if ( $url === '' ) { return null; } return $url; } /** * Generates the main image ID. * * @return int|null The main image ID. */ public function generate_main_image_id() { if ( \wp_is_serving_rest_request() ) { return $this->get_main_image_id_for_rest_request(); } switch ( true ) { case \is_singular(): return $this->get_singular_post_image( $this->id ); case \is_author(): case \is_tax(): case \is_tag(): case \is_category(): case \is_search(): case \is_date(): case \is_post_type_archive(): if ( ! empty( $GLOBALS['wp_query']->posts ) ) { if ( $GLOBALS['wp_query']->get( 'fields', 'all' ) === 'ids' ) { return $this->get_singular_post_image( $GLOBALS['wp_query']->posts[0] ); } return $this->get_singular_post_image( $GLOBALS['wp_query']->posts[0]->ID ); } return null; default: return null; } } /** * Determines whether the current indexable has an image. * * @return bool Whether the current indexable has an image. */ public function generate_has_image() { return $this->main_image_url !== null; } /** * Strips all nested dependencies from the debug info. * * @return array<Indexable|Indexable_Presentation> */ public function __debugInfo() { return [ 'indexable' => $this->indexable, 'presentation' => $this->presentation, ]; } /** * Retrieve the site logo ID from WordPress settings. * * @return int|false */ public function fallback_to_site_logo() { $logo_id = \get_option( 'site_logo' ); if ( ! $logo_id ) { $logo_id = \get_theme_mod( 'custom_logo', false ); } return $logo_id; } /** * Get the ID for a post's featured image. * * @param int $id Post ID. * * @return int|null */ private function get_singular_post_image( $id ) { if ( \has_post_thumbnail( $id ) ) { $thumbnail_id = \get_post_thumbnail_id( $id ); // Prevent returning something else than an int or null. if ( \is_int( $thumbnail_id ) && $thumbnail_id > 0 ) { return $thumbnail_id; } } if ( \is_singular( 'attachment' ) ) { return \get_query_var( 'attachment_id' ); } return null; } /** * Gets the main image ID for REST requests. * * @return int|null The main image ID. */ private function get_main_image_id_for_rest_request() { switch ( $this->page_type ) { case 'Post_Type': if ( $this->post instanceof WP_Post ) { return $this->get_singular_post_image( $this->post->ID ); } return null; default: return null; } } /** * Gets the main image URL for REST requests. * * @return string|null The main image URL. */ private function get_main_image_url_for_rest_request() { switch ( $this->page_type ) { case 'Post_Type': if ( $this->post instanceof WP_Post ) { $url = $this->image->get_post_content_image( $this->post->ID ); if ( $url === '' ) { return null; } return $url; } return null; default: return null; } } } \class_alias( Meta_Tags_Context::class, 'WPSEO_Schema_Context' ); main.php 0000644 00000003513 15025657560 0006216 0 ustar 00 <?php namespace Yoast\WP\SEO; use Yoast\WP\Lib\Abstract_Main; use Yoast\WP\SEO\Dependency_Injection\Container_Compiler; use Yoast\WP\SEO\Generated\Cached_Container; use Yoast\WP\SEO\Surfaces\Classes_Surface; use Yoast\WP\SEO\Surfaces\Helpers_Surface; use Yoast\WP\SEO\Surfaces\Meta_Surface; if ( ! \defined( 'WPSEO_VERSION' ) ) { \header( 'Status: 403 Forbidden' ); \header( 'HTTP/1.1 403 Forbidden' ); exit(); } /** * Class Main. * * @property Classes_Surface $classes The classes surface. * @property Meta_Surface $meta The meta surface. * @property Helpers_Surface $helpers The helpers surface. */ class Main extends Abstract_Main { /** * The API namespace constant. * * @var string */ public const API_V1_NAMESPACE = 'yoast/v1'; /** * The WP CLI namespace constant. * * @var string */ public const WP_CLI_NAMESPACE = 'yoast'; /** * {@inheritDoc} */ protected function get_container() { if ( $this->is_development() && \class_exists( '\Yoast\WP\SEO\Dependency_Injection\Container_Compiler' ) ) { // Exception here is unhandled as it will only occur in development. Container_Compiler::compile( $this->is_development(), __DIR__ . '/generated/container.php', __DIR__ . '/../config/dependency-injection/services.php', __DIR__ . '/../vendor/composer/autoload_classmap.php', 'Yoast\WP\SEO\Generated' ); } if ( \file_exists( __DIR__ . '/generated/container.php' ) ) { require_once __DIR__ . '/generated/container.php'; return new Cached_Container(); } return null; } /** * {@inheritDoc} */ protected function get_name() { return 'yoast-seo'; } /** * {@inheritDoc} */ protected function get_surfaces() { return [ 'classes' => Classes_Surface::class, 'meta' => Meta_Surface::class, 'helpers' => Helpers_Surface::class, ]; } } deprecated/frontend/frontend.php 0000644 00000017040 15025657560 0013030 0 ustar 00 <?php /** * Backwards compatibility class for WPSEO_Frontend. * * @package Yoast\YoastSEO\Backwards_Compatibility */ use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Presenters\Canonical_Presenter; use Yoast\WP\SEO\Presenters\Meta_Description_Presenter; use Yoast\WP\SEO\Presenters\Rel_Next_Presenter; use Yoast\WP\SEO\Presenters\Rel_Prev_Presenter; use Yoast\WP\SEO\Presenters\Robots_Presenter; use Yoast\WP\SEO\Surfaces\Helpers_Surface; /** * Class WPSEO_Frontend * * @codeCoverageIgnore Because of deprecation. */ class WPSEO_Frontend { /** * Instance of this class. * * @var WPSEO_Frontend */ public static $instance; /** * The memoizer for the meta tags context. * * @var Meta_Tags_Context_Memoizer */ private $context_memoizer; /** * The WPSEO Replace Vars object. * * @var WPSEO_Replace_Vars */ private $replace_vars; /** * The helpers surface. * * @var Helpers_Surface */ private $helpers; /** * WPSEO_Frontend constructor. */ public function __construct() { $this->context_memoizer = YoastSEO()->classes->get( Meta_Tags_Context_Memoizer::class ); $this->replace_vars = YoastSEO()->classes->get( WPSEO_Replace_Vars::class ); $this->helpers = YoastSEO()->classes->get( Helpers_Surface::class ); } /** * Catches call to methods that don't exist and might deprecated. * * @param string $method The called method. * @param array $arguments The given arguments. * * @return mixed */ public function __call( $method, $arguments ) { _deprecated_function( $method, 'Yoast SEO 14.0' ); $title_methods = [ 'title', 'fix_woo_title', 'get_content_title', 'get_seo_title', 'get_taxonomy_title', 'get_author_title', 'get_title_from_options', 'get_default_title', 'force_wp_title', ]; if ( in_array( $method, $title_methods, true ) ) { return $this->get_title(); } return null; } /** * Retrieves an instance of the class. * * @return static The instance. */ public static function get_instance() { if ( self::$instance === null ) { self::$instance = new self(); } return self::$instance; } /** * Outputs the canonical value. * * @param bool $echo Whether or not to output the canonical element. * @param bool $un_paged Whether or not to return the canonical with or without pagination added to the URL. * @param bool $no_override Whether or not to return a manually overridden canonical. * * @return string|void */ public function canonical( $echo = true, $un_paged = false, $no_override = false ) { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); $presentation = $this->get_current_page_presentation(); $presenter = new Canonical_Presenter(); /** This filter is documented in src/integrations/front-end-integration.php */ $presenter->presentation = $presentation; $presenter->helpers = $this->helpers; $presenter->replace_vars = $this->replace_vars; if ( ! $echo ) { return $presenter->get(); } echo $presenter->present(); } /** * Retrieves the meta robots value. * * @return string */ public function get_robots() { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); $presentation = $this->get_current_page_presentation(); return $presentation->robots; } /** * Outputs the meta robots value. * * @return void */ public function robots() { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); $presentation = $this->get_current_page_presentation(); $presenter = new Robots_Presenter(); $presenter->presentation = $presentation; $presenter->helpers = $this->helpers; $presenter->replace_vars = $this->replace_vars; echo $presenter->present(); } /** * Determine $robots values for a single post. * * @param array $robots Robots data array. * @param int $post_id The post ID for which to determine the $robots values, defaults to current post. * * @return array */ public function robots_for_single_post( $robots, $post_id = 0 ) { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); $presentation = $this->get_current_page_presentation(); return $presentation->robots; } /** * Used for static home and posts pages as well as singular titles. * * @param object|null $object If filled, object to get the title for. * * @return string The content title. */ private function get_title( $object = null ) { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); $presentation = $this->get_current_page_presentation(); $title = $presentation->title; return $this->replace_vars->replace( $title, $presentation->source ); } /** * This function adds paging details to the title. * * @param string $sep Separator used in the title. * @param string $seplocation Whether the separator should be left or right. * @param string $title The title to append the paging info to. * * @return string */ public function add_paging_to_title( $sep, $seplocation, $title ) { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); return $title; } /** * Add part to title, while ensuring that the $seplocation variable is respected. * * @param string $sep Separator used in the title. * @param string $seplocation Whether the separator should be left or right. * @param string $title The title to append the title_part to. * @param string $title_part The part to append to the title. * * @return string */ public function add_to_title( $sep, $seplocation, $title, $title_part ) { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); if ( $seplocation === 'right' ) { return $title . $sep . $title_part; } return $title_part . $sep . $title; } /** * Adds 'prev' and 'next' links to archives. * * @link http://googlewebmastercentral.blogspot.com/2011/09/pagination-with-relnext-and-relprev.html * * @return void */ public function adjacent_rel_links() { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); $presentation = $this->get_current_page_presentation(); $rel_prev_presenter = new Rel_Prev_Presenter(); $rel_prev_presenter->presentation = $presentation; $rel_prev_presenter->helpers = $this->helpers; $rel_prev_presenter->replace_vars = $this->replace_vars; echo $rel_prev_presenter->present(); $rel_next_presenter = new Rel_Next_Presenter(); $rel_next_presenter->presentation = $presentation; $rel_next_presenter->helpers = $this->helpers; $rel_next_presenter->replace_vars = $this->replace_vars; echo $rel_next_presenter->present(); } /** * Outputs the meta description element or returns the description text. * * @param bool $echo Echo or return output flag. * * @return string */ public function metadesc( $echo = true ) { _deprecated_function( __METHOD__, 'Yoast SEO 14.0' ); $presentation = $this->get_current_page_presentation(); $presenter = new Meta_Description_Presenter(); $presenter->presentation = $presentation; $presenter->helpers = $this->helpers; $presenter->replace_vars = $this->replace_vars; if ( ! $echo ) { return $presenter->get(); } $presenter->present(); } /** * Returns the current page presentation. * * @return Indexable_Presentation The current page presentation. */ private function get_current_page_presentation() { $context = $this->context_memoizer->for_current_page(); /** This filter is documented in src/integrations/front-end-integration.php */ return apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); } } deprecated/frontend/breadcrumbs.php 0000644 00000006307 15025657560 0013506 0 ustar 00 <?php /** * Backwards compatibility class for breadcrumbs. * * @package Yoast\YoastSEO\Backwards_Compatibility */ use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Presenters\Breadcrumbs_Presenter; use Yoast\WP\SEO\Surfaces\Helpers_Surface; /** * Class WPSEO_Breadcrumbs * * @codeCoverageIgnore Because of deprecation. */ class WPSEO_Breadcrumbs { /** * Instance of this class. * * @var WPSEO_Breadcrumbs */ public static $instance; /** * Last used 'before' string. * * @var string */ public static $before = ''; /** * Last used 'after' string. * * @var string */ public static $after = ''; /** * The memoizer for the meta tags context. * * @var Meta_Tags_Context_Memoizer */ protected $context_memoizer; /** * The helpers surface. * * @var Helpers_Surface */ protected $helpers; /** * The replace vars helper * * @var WPSEO_Replace_Vars */ protected $replace_vars; /** * WPSEO_Breadcrumbs constructor. */ public function __construct() { $this->context_memoizer = YoastSEO()->classes->get( Meta_Tags_Context_Memoizer::class ); $this->helpers = YoastSEO()->classes->get( Helpers_Surface::class ); $this->replace_vars = YoastSEO()->classes->get( WPSEO_Replace_Vars::class ); } /** * Get breadcrumb string using the singleton instance of this class. * * @param string $before Optional string to prepend. * @param string $after Optional string to append. * @param bool $display Echo or return flag. * * @return string Returns the breadcrumbs as a string. */ public static function breadcrumb( $before = '', $after = '', $display = true ) { // Remember the last used before/after for use in case the object goes __toString(). self::$before = $before; self::$after = $after; $output = $before . self::get_instance()->render() . $after; if ( $display === true ) { echo $output; return ''; } return $output; } /** * Magic method to use in case the class would be send to string. * * @return string The rendered breadcrumbs. */ public function __toString() { return self::$before . $this->render() . self::$after; } /** * Retrieves an instance of the class. * * @return static The instance. */ public static function get_instance() { if ( self::$instance === null ) { self::$instance = new self(); } return self::$instance; } /** * Returns the collected links for the breadcrumbs. * * @return array The collected links. */ public function get_links() { $context = $this->context_memoizer->for_current_page(); return $context->presentation->breadcrumbs; } /** * Renders the breadcrumbs. * * @return string The rendered breadcrumbs. */ private function render() { $presenter = new Breadcrumbs_Presenter(); $context = $this->context_memoizer->for_current_page(); /** This filter is documented in src/integrations/front-end-integration.php */ $presentation = apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); $presenter->presentation = $presentation; $presenter->replace_vars = $this->replace_vars; $presenter->helpers = $this->helpers; return $presenter->present(); } } deprecated/admin/class-customizer.php 0000644 00000002526 15025657560 0013774 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Admin\Customizer */ /** * Class with functionality to support WP SEO settings in WordPress Customizer. * * @codeCoverageIgnore * @deprecated 22.8 */ class WPSEO_Customizer { /** * Holds the customize manager. * * @deprecated 22.8 * * @var WP_Customize_Manager */ protected $wp_customize; /** * Construct Method. * * @codeCoverageIgnore * @deprecated 22.8 */ public function __construct() { _deprecated_function( __METHOD__, 'Yoast SEO 22.8' ); } /** * Function to support WordPress Customizer. * * @codeCoverageIgnore * @deprecated 22.8 * * @param WP_Customize_Manager $wp_customize Manager class instance. * * @return void */ public function wpseo_customize_register( $wp_customize ) { _deprecated_function( __METHOD__, 'Yoast SEO 22.8' ); } /** * Returns whether or not the breadcrumbs are active. * * @codeCoverageIgnore * @deprecated 22.8 * * @return void */ public function breadcrumbs_active_callback() { _deprecated_function( __METHOD__, 'Yoast SEO 22.8' ); } /** * Returns whether or not to show the breadcrumbs blog show option. * * @codeCoverageIgnore * @deprecated 22.8 * * @return void */ public function breadcrumbs_blog_show_active_cb() { _deprecated_function( __METHOD__, 'Yoast SEO 22.8' ); } } deprecated/src/integrations/admin/disable-concatenate-scripts-integration.php 0000644 00000002106 15025657560 0023647 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Disable_Concatenate_Scripts_Integration class. * * @deprecated 23.2 * @codeCoverageIgnore */ class Disable_Concatenate_Scripts_Integration implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * In this case: when on an admin page. * * @deprecated 23.2 * * @return array The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Registers an action to disable script concatenation. * * @deprecated 23.2 * * @return void */ public function register_hooks() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.2' ); } /** * Due to bugs in the 5.5 core release concatenate scripts is causing errors. * * Because of this we disable it. * * @return void */ public function disable_concatenate_scripts() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.2' ); } } deprecated/src/integrations/admin/old-premium-integration.php 0000644 00000005401 15025657560 0020530 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Old_Premium_Integration class * * @deprecated 20.10 * @codeCoverageIgnore */ class Old_Premium_Integration implements Integration_Interface { /** * The options' helper. * * @var Options_Helper */ private $options_helper; /** * The product helper. * * @var Product_Helper */ private $product_helper; /** * The capability helper. * * @var Capability_Helper */ private $capability_helper; /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * The Current_Page_Helper. * * @var Current_Page_Helper */ private $current_page_helper; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Old_Premium_Integration constructor. * * @deprecated 20.10 * @codeCoverageIgnore * * @param Options_Helper $options_helper The options helper. * @param Product_Helper $product_helper The product helper. * @param Capability_Helper $capability_helper The capability helper. * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. * @param Current_Page_Helper $current_page_helper The Current_Page_Helper. */ public function __construct( Options_Helper $options_helper, Product_Helper $product_helper, Capability_Helper $capability_helper, WPSEO_Admin_Asset_Manager $admin_asset_manager, Current_Page_Helper $current_page_helper ) { $this->options_helper = $options_helper; $this->product_helper = $product_helper; $this->capability_helper = $capability_helper; $this->admin_asset_manager = $admin_asset_manager; $this->current_page_helper = $current_page_helper; } /** * {@inheritDoc} * * @deprecated 20.10 * @codeCoverageIgnore */ public function register_hooks() { \_deprecated_function( __METHOD__, 'Yoast SEO 20.7' ); } /** * Shows a notice if Premium is older than 20.0-RC1 so Settings might be missing from the UI. * * @deprecated 20.10 * @codeCoverageIgnore * * @return void */ public function old_premium_notice() { \_deprecated_function( __METHOD__, 'Yoast SEO 20.7' ); } /** * Dismisses the old premium notice. * * @deprecated 20.10 * @codeCoverageIgnore * * @return bool */ public function dismiss_old_premium_notice() { \_deprecated_function( __METHOD__, 'Yoast SEO 20.7' ); return false; } } deprecated/src/integrations/admin/unsupported-php-version-notice.php 0000644 00000002620 15025657560 0022074 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Unsupported_PHP_Version_Notice. * * @package Yoast\WP\SEO\Integrations\Admin * * @deprecated 25.0 * @codeCoverageIgnore */ class Unsupported_PHP_Version_Notice implements Integration_Interface { /** * Returns the conditionals based on which this integration should be active. * * @deprecated 25.0 * @codeCoverageIgnore * * @return array<string> The array of conditionals. */ public static function get_conditionals() { \_deprecated_function( __METHOD__, 'Yoast SEO 25.0' ); return [ Yoast_Admin_And_Dashboard_Conditional::class, ]; } /** * Register hooks. * * @deprecated 25.0 * @codeCoverageIgnore * * @return void */ public function register_hooks() { \_deprecated_function( __METHOD__, 'Yoast SEO 25.0' ); } /** * Checks the current PHP version. * * @deprecated 25.0 * @codeCoverageIgnore * * @return void */ public function check_php_version() { \_deprecated_function( __METHOD__, 'Yoast SEO 25.0' ); } /** * Composes the body of the message to display. * * @deprecated 25.0 * @codeCoverageIgnore * * @return string The message to display. */ public function body() { \_deprecated_function( __METHOD__, 'Yoast SEO 25.0' ); return ''; } } deprecated/src/integrations/third-party/wordproof-integration-toggle.php 0000644 00000006636 15025657560 0022770 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Wordproof_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Feature_Toggle; /** * Class WordProofIntegrationToggle. * * @deprecated 21.6 * @codeCoverageIgnore * * @package Yoast\WP\SEO\Integrations\Third_Party */ class Wordproof_Integration_Toggle implements Integration_Interface { /** * The WordProof helper instance. * * @var Wordproof_Helper */ protected $wordproof; /** * The WordProof integration toggle constructor. * * @deprecated 21.6 * @codeCoverageIgnore * * @param Wordproof_Helper $wordproof The WordProof helper instance. */ public function __construct( Wordproof_Helper $wordproof ) { $this->wordproof = $wordproof; } /** * Returns the conditionals based in which this loadable should be active. * * @deprecated 21.6 * @codeCoverageIgnore * * @return array */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @deprecated 21.6 * @codeCoverageIgnore * * @return void */ public function register_hooks() { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } /** * Adds the WordProof integration toggle to the array. * * @deprecated 21.6 * @codeCoverageIgnore * * @param array $integration_toggles The integration toggles array. * * @return array The updated integration toggles array. */ public function add_integration_toggle( $integration_toggles ) { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); return $integration_toggles; } /** * Set the default WordProof integration option value depending if the integration is disabled or not. * * @param array $defaults Array containing default wpseo options. * * @return array */ public function default_values( $defaults ) { if ( $this->wordproof->integration_is_disabled() ) { $defaults['wordproof_integration_active'] = false; } return $defaults; } /** * Add an explainer when the integration toggle is disabled. * * @deprecated 20.3 * @codeCoverageIgnore * * @param Yoast_Feature_Toggle $integration The integration toggle class. * * @return void */ public function after_integration_toggle( $integration ) { \_deprecated_function( __METHOD__, 'Yoast SEO 20.3' ); if ( $integration->setting === 'wordproof_integration_active' ) { if ( $integration->disabled ) { $conditional = $this->wordproof->integration_is_disabled( true ); if ( $conditional === 'Non_Multisite_Conditional' ) { echo '<p>' . \sprintf( /* translators: %s expands to WordProof */ \esc_html__( 'Currently, the %s integration is not available for multisites.', 'wordpress-seo' ), 'WordProof' ) . '</p>'; } if ( $conditional === 'Wordproof_Plugin_Inactive_Conditional' ) { echo '<p>' . \esc_html__( 'The WordProof Timestamp plugin needs to be disabled before you can activate this integration.', 'wordpress-seo' ) . '</p>'; } } } } /** * Add an explainer when the network integration toggle is disabled. * * @param Yoast_Feature_Toggle $integration The integration toggle class. * * @return void */ public function after_network_integration_toggle( $integration ) { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } } deprecated/src/integrations/third-party/wincher.php 0000644 00000004422 15025657560 0016575 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Wincher_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Feature_Toggle; /** * Adds the Wincher integration. * * @deprecated 21.6 * @codeCoverageIgnore */ class Wincher implements Integration_Interface { /** * The Wincher helper instance. * * @var Wincher_Helper */ protected $wincher; /** * The Wincher integration toggle constructor. * * @deprecated 21.6 * @codeCoverageIgnore * * @param Wincher_Helper $wincher The Wincher helper instance. */ public function __construct( Wincher_Helper $wincher ) { $this->wincher = $wincher; } /** * Initializes the integration. * * @deprecated 21.6 * @codeCoverageIgnore * * @return void */ public function register_hooks() { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } /** * Returns the conditionals based in which this loadable should be active. * * @deprecated 21.6 * @codeCoverageIgnore * * @return array The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Adds the Wincher integration toggle to the $integration_toggles array. * * @deprecated 21.6 * @codeCoverageIgnore * * @param array $integration_toggles The integration toggles array. * * @return array The updated integration toggles array. */ public function add_integration_toggle( $integration_toggles ) { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); return $integration_toggles; } /** * Adds the disabled note when the integration toggle is disabled. * * @deprecated 21.6 * @codeCoverageIgnore * * @param Yoast_Feature_Toggle $integration The integration toggle class. * * @return void */ public function after_integration_toggle( $integration ) { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } /** * Adds the disabled note to the network integration toggle. * * @deprecated 21.6 * @codeCoverageIgnore * * @param Yoast_Feature_Toggle $integration The integration toggle class. * * @return void */ public function after_network_integration_toggle( $integration ) { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6' ); } } deprecated/src/integrations/third-party/wordproof.php 0000644 00000012152 15025657560 0017156 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Helpers\Wordproof_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class WordProof * * @deprecated 22.10 * @codeCoverageIgnore * * @package Yoast\WP\SEO\Integrations\Third_Party */ class Wordproof implements Integration_Interface { /** * The Yoast meta key used to save if a post shiould be timestamped. * * @var string */ protected $post_meta_key = '_yoast_wpseo_wordproof_timestamp'; /** * The WordProof helper instance. * * @var Wordproof_Helper */ protected $wordproof; /** * Asset manager instance. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * The WordProof integration constructor. * * @deprecated 22.10 * @codeCoverageIgnore * * @param Wordproof_Helper $wordproof The WordProof helper instance. * @param WPSEO_Admin_Asset_Manager|null $asset_manager The WPSEO admin asset manager instance. */ public function __construct( Wordproof_Helper $wordproof, ?WPSEO_Admin_Asset_Manager $asset_manager = null ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); if ( ! $asset_manager ) { $asset_manager = new WPSEO_Admin_Asset_Manager(); } $this->asset_manager = $asset_manager; $this->wordproof = $wordproof; } /** * Returns the conditionals based in which this loadable should be active. * * @deprecated 22.10 * @codeCoverageIgnore * * @return array */ public static function get_conditionals() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return []; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @deprecated 22.10 * @codeCoverageIgnore * * @return void */ public function register_hooks() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); } /** * Initializes the WordProof WordPress SDK. * * @deprecated 22.10 * @codeCoverageIgnore * * @return void */ public function sdk_setup() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); } /** * Removes the WordProof timestamp post meta if a legal page is changed. * * @deprecated 22.10 * @codeCoverageIgnore * * @param int $old_post_id The old post id. * @param int $new_post_id The new post id. * * @return void */ public function disable_timestamp_for_previous_legal_page( $old_post_id, $new_post_id ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); } /** * Return the Yoast post meta key for the SDK to determine if the post should be timestamped. * * @deprecated 22.10 * @codeCoverageIgnore * * @param array $meta_keys The array containing meta keys that should be used. * @return array */ public function add_post_meta_key( $meta_keys ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return [ $this->post_meta_key ]; } /** * Return an empty array to disable automatically timestamping selected post types. * * @deprecated 22.10 * @codeCoverageIgnore * * @param array $post_types The array containing post types that should be automatically timestamped. * @return array */ public function wordproof_timestamp_post_types( $post_types ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return []; } /** * This filters hides the certificate if the Yoast post meta key is not set to true. * * @deprecated 22.10 * @codeCoverageIgnore * * @param bool $value If the certificate should be shown. * @param WP_Post $post The post object of the post for which to determine the certificate should be shown. * @return bool|null */ public function show_certificate( $value, $post ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return null; } /** * Adds the WordProof integration toggle to the array. * * @deprecated 22.10 * @codeCoverageIgnore * * @param array $fields The currently registered meta fields. * * @return array A new array with meta fields. */ public function add_meta_field( $fields ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); $fields['advanced']['wordproof_timestamp'] = [ 'type' => 'hidden', 'title' => '', 'default_value' => '', 'description' => '0', ]; return $fields; } /** * Enqueue the uikit script. * * @deprecated 22.10 * @codeCoverageIgnore * * @return void */ public function enqueue_assets() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); } /** * Adds async to the wordproof-uikit script. * * @deprecated 22.10 * @codeCoverageIgnore * * @param string $tag The script tag for the enqueued script. * @param string $handle The script's registered handle. * @param string $src The script's source URL. * * @return string The script's tag. * * @phpcs:disable WordPress.WP.EnqueuedResources.NonEnqueuedScript */ public function add_async_to_script( $tag, $handle, $src ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); if ( $handle !== WPSEO_Admin_Asset_Manager::PREFIX . 'wordproof-uikit' ) { return $tag; } return "<script src={$src} async></script>"; } } deprecated/src/integrations/duplicate-post-integration.php 0000644 00000001730 15025657560 0020144 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Yoast\WP\SEO\Conditionals\No_Conditionals; /** * Class to manage the integration with Yoast Duplicate Post. * * @deprecated 23.4 * @codeCoverageIgnore */ class Duplicate_Post_Integration implements Integration_Interface { use No_Conditionals; /** * Initializes the integration. * * This is the place to register hooks and filters. * * @deprecated 23.4 * @codeCoverageIgnore * * @return void */ public function register_hooks() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.4' ); } /** * Filters out the Zapier meta when you copy a post with Yoast Duplicate Post. * * @deprecated 23.4 * @codeCoverageIgnore * * @param array $meta_excludelist The current excludelist of meta fields. * * @return array The updated excludelist. */ public function exclude_zapier_meta( $meta_excludelist ) { \_deprecated_function( __METHOD__, 'Yoast SEO 23.4' ); return $meta_excludelist; } } deprecated/src/helpers/request-helper.php 0000644 00000001057 15025657560 0014571 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for the request state. * * @deprecated 23.6 * @codeCoverageIgnore Because of deprecation. */ class Request_Helper { /** * Checks if the current request is a REST request. * * @deprecated 23.6 * @codeCoverageIgnore * * @return bool True when the current request is a REST request. */ public function is_rest_request() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.6', 'wp_is_serving_rest_request' ); return \defined( 'REST_REQUEST' ) && \REST_REQUEST === true; } } deprecated/src/helpers/wordproof-helper.php 0000644 00000005061 15025657560 0015121 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for WordProof integration. * * @deprecated 22.10 * @codeCoverageIgnore */ class Wordproof_Helper { /** * Holds the Current Page helper instance. * * @var Current_Page_Helper */ protected $current_page; /** * Holds the WooCommerce helper instance. * * @var Woocommerce_Helper */ protected $woocommerce; /** * Holds the Options Page helper instance. * * @var Options_Helper */ protected $options; /** * WordProof_Helper constructor. * * @deprecated 22.10 * @codeCoverageIgnore * * @param Current_Page_Helper $current_page The current page helper. * @param Woocommerce_Helper $woocommerce The woocommerce helper. * @param Options_Helper $options The options helper. */ public function __construct( Current_Page_Helper $current_page, Woocommerce_Helper $woocommerce, Options_Helper $options ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); $this->current_page = $current_page; $this->woocommerce = $woocommerce; $this->options = $options; } /** * Remove site options after disabling the integration. * * @deprecated 22.10 * @codeCoverageIgnore * * @return bool Returns if the options are deleted */ public function remove_site_options() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return \delete_site_option( 'wordproof_access_token' ) && \delete_site_option( 'wordproof_source_id' ); } /** * Returns if conditionals are met. If not, the integration should be disabled. * * @deprecated 22.10 * @codeCoverageIgnore * * @param bool $return_conditional If the conditional class name that was unmet should be returned. * * @return bool|string Returns if the integration should be disabled. */ public function integration_is_disabled( $return_conditional = false ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return true; } /** * Returns if the WordProof integration toggle is turned on. * * @deprecated 22.10 * @codeCoverageIgnore * * @return bool Returns if the integration toggle is set to true if conditionals are met. */ public function integration_is_active() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return false; } /** * Return if WordProof should be active for this post editor page. * * @deprecated 22.10 * @codeCoverageIgnore * * @return bool Returns if WordProof should be active for this page. */ public function is_active() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return false; } } deprecated/src/introductions/application/ai-generate-titles-and-descriptions-introduction-upsell.php0000644 00000005343 15025657560 0030343 0 ustar 00 <?php /** * Graceful deprecation of the Ai_Generate_Titles_And_Descriptions_Introduction_Upsell class. * * {@internal As this file is just (temporarily) put in place to warn extending * plugins about the class name changes, it is exempt from select CS standards.} * * @deprecated 23.2 * * @codeCoverageIgnore * * @phpcs:disable Generic.Files.OneObjectStructurePerFile.MultipleFound * @phpcs:disable WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedNamespaceFound * @phpcs:disable Yoast.Commenting.CodeCoverageIgnoreDeprecated * @phpcs:disable Yoast.Commenting.FileComment.Unnecessary * @phpcs:disable Yoast.Files.FileName.InvalidClassFileName */ namespace Yoast\WP\SEO\Introductions\Application; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; /** * Represents the introduction for the AI generate titles and introduction upsell. * * @deprecated 23.2 Use {@see \Yoast\WP\SEO\Introductions\Application\Ai_Fix_Assessments_Upsell} instead. */ class Ai_Generate_Titles_And_Descriptions_Introduction_Upsell extends Ai_Fix_Assessments_Upsell { use Current_Page_Trait; use User_Allowed_Trait; use Version_Trait; /** * Holds the product helper. * * @var Product_Helper */ private $product_helper; /** * Holds the options' helper. * * @var Options_Helper */ private $options_helper; /** * Constructs the introduction. * * @deprecated 23.2 * * @param Product_Helper $product_helper The product helper. * @param Options_Helper $options_helper The options' helper. */ public function __construct( Product_Helper $product_helper, Options_Helper $options_helper ) { $this->product_helper = $product_helper; $this->options_helper = $options_helper; } /** * Returns the ID. * * @deprecated 23.2 * @codeCoverageIgnore * * @return string */ public function get_id() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.2' ); return 'ai-generate-titles-and-descriptions-upsell'; } /** * Returns the unique name. * * @deprecated 21.6 * @codeCoverageIgnore * * @return string */ public function get_name() { \_deprecated_function( __METHOD__, 'Yoast SEO 21.6', 'Please use get_id() instead' ); return $this->get_id(); } /** * Returns the requested pagination priority. Lower means earlier. * * @deprecated 23.2 * @codeCoverageIgnore * * @return int */ public function get_priority() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.2' ); return 10; } /** * Returns whether this introduction should show. * * @deprecated 23.2 * @codeCoverageIgnore * * @return bool */ public function should_show() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.2' ); // Outdated feature introduction. return false; } } deprecated/src/config/wordproof-app-config.php 0000644 00000001301 15025657560 0015461 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Class WordProof_App_Config. * * @deprecated 22.10 * @codeCoverageIgnore * * @package Yoast\WP\SEO\Config */ class Wordproof_App_Config { /** * Returns the partner. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The partner. */ public function getPartner() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return 'yoast'; } /** * Returns if the WordProof Uikit should be loaded from a cdn. * * @deprecated 22.10 * @codeCoverageIgnore * * @return bool True or false. */ public function getLoadUikitFromCdn() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return false; } } deprecated/src/config/wordproof-translations.php 0000644 00000006462 15025657560 0016174 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Class WordProof_Translations * * @deprecated 22.10 * @codeCoverageIgnore * * @package Yoast\WP\SEO\Config */ class Wordproof_Translations { /** * Returns no balance notice translation. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getNoBalanceNotice() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); /* translators: %s expands to WordProof. */ return \sprintf( \__( 'You are out of timestamps. Please upgrade your account by opening the %s settings.', 'wordpress-seo' ), 'WordProof' ); } /** * Returns no balance notice translation. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getTimestampSuccessNotice() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); /* translators: %s expands to WordProof. */ return \sprintf( \__( '%s has successfully timestamped this page.', 'wordpress-seo' ), 'WordProof' ); } /** * Returns timestamp failed notice translation. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getTimestampFailedNotice() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); /* translators: %s expands to WordProof. */ return \sprintf( \__( '%1$s failed to timestamp this page. Please check if you\'re correctly authenticated with %1$s and try to save this page again.', 'wordpress-seo' ), 'WordProof' ); } /** * Returns webhook failed notice translation. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getWebhookFailedNotice() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); /* translators: %s expands to WordProof. */ return \sprintf( \__( 'The timestamp is not retrieved by your site. Please try again or contact %1$s support.', 'wordpress-seo' ), 'WordProof' ); } /** * Returns no authentication notice translation. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getNotAuthenticatedNotice() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); /* translators: %s expands to WordProof. */ return \sprintf( \__( 'The timestamp is not created because you need to authenticate with %s first.', 'wordpress-seo' ), 'WordProof' ); } /** * Returns authenticate button text. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getOpenAuthenticationButtonText() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return \__( 'Open authentication', 'wordpress-seo' ); } /** * Returns open settings button translation. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getOpenSettingsButtonText() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return \__( 'Open settings', 'wordpress-seo' ); } /** * Returns get contact WordProof Support button translation. * * @deprecated 22.10 * @codeCoverageIgnore * * @return string The translation. */ public function getContactWordProofSupportButtonText() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return \__( 'Contact WordProof support', 'wordpress-seo' ); } } deprecated/src/conditionals/third-party/wordproof-plugin-inactive-conditional.php 0000644 00000001205 15025657560 0024530 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is met when the WordProof Timestamp plugin is inactive. * * @deprecated 22.10 * @codeCoverageIgnore */ class Wordproof_Plugin_Inactive_Conditional implements Conditional { /** * Returns whether or not the WordProof Timestamp plugin is active. * * @deprecated 22.10 * @codeCoverageIgnore * * @return bool Whether or not the WordProof Timestamp plugin is active. */ public function is_met() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return ! \defined( 'WORDPROOF_VERSION' ); } } deprecated/src/conditionals/third-party/wordproof-integration-active-conditional.php 0000644 00000002127 15025657560 0025232 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; use Yoast\WP\SEO\Helpers\Wordproof_Helper; /** * Conditional that is met when the WordProof integration is toggled on. * * @deprecated 22.10 * @codeCoverageIgnore */ class Wordproof_Integration_Active_Conditional implements Conditional { /** * The WordProof helper. * * @var Wordproof_Helper */ private $wordproof; /** * WordProof integration active constructor. * * @deprecated 22.10 * @codeCoverageIgnore * * @param Wordproof_Helper $wordproof The options helper. */ public function __construct( Wordproof_Helper $wordproof ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); $this->wordproof = $wordproof; } /** * Returns whether or not the WordProof Timestamp plugin is active. * * @deprecated 22.10 * @codeCoverageIgnore * * @return bool Whether or not the WordProof Timestamp plugin is active. */ public function is_met() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return $this->wordproof->integration_is_active(); } } integrations/blocks/abstract-dynamic-block-v3.php 0000644 00000005074 15025657560 0016124 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Blocks; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Dynamic_Block class. */ abstract class Dynamic_Block_V3 implements Integration_Interface { /** * The name of the block. * * @var string */ protected $block_name; /** * The editor script for the block. * * @var string */ protected $script; /** * The base path for the block. * * @var string */ protected $base_path; /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> */ public static function get_conditionals() { return []; } /** * Initializes the integration. * * Integrations hooking on `init` need to have a priority of 11 or higher to * ensure that they run, as priority 10 is used by the loader to load the integrations. * * @return void */ public function register_hooks() { \add_action( 'init', [ $this, 'register_block' ], 11 ); } /** * Registers the block. * * @return void */ public function register_block() { \register_block_type( $this->base_path . $this->block_name . '/block.json', [ 'editor_script' => $this->script, 'render_callback' => [ $this, 'present' ], ] ); } /** * Presents the block output. This is abstract because in the loop we need to be able to build the data for the * presenter in the last moment. * * @param array<string, bool|string|int|array> $attributes The block attributes. * * @return string The block output. */ abstract public function present( $attributes ); /** * Checks whether the links in the block should have target="blank". * * This is needed because when the editor is loaded in an Iframe the link needs to open in a different browser window. * We don't want this behaviour in the front-end and the way to check this is to check if the block is rendered in a REST request with the `context` set as 'edit'. Thus being in the editor. * * @return bool returns if the block should be opened in another window. */ protected function should_link_target_blank(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['context'] ) && \is_string( $_GET['context'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing. if ( \wp_unslash( $_GET['context'] ) === 'edit' ) { return true; } } return false; } } integrations/blocks/abstract-dynamic-block.php 0000644 00000002365 15025657560 0015576 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Blocks; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Dynamic_Block class. */ abstract class Dynamic_Block implements Integration_Interface { /** * The name of the block. * * @var string */ protected $block_name; /** * The editor script for the block. * * @var string */ protected $script; /** * {@inheritDoc} */ public static function get_conditionals() { return []; } /** * {@inheritDoc} */ public function register_hooks() { \add_action( 'init', [ $this, 'register_block' ], 11 ); } /** * Registers the block. * * @return void */ public function register_block() { \register_block_type( 'yoast-seo/' . $this->block_name, [ 'editor_script' => $this->script, 'render_callback' => [ $this, 'present' ], 'attributes' => [ 'className' => [ 'default' => '', 'type' => 'string', ], ], ] ); } /** * Presents the block output. This is abstract because in the loop we need to be able to build the data for the * presenter in the last moment. * * @param array $attributes The block attributes. * * @return string The block output. */ abstract public function present( $attributes ); } integrations/blocks/structured-data-blocks.php 0000644 00000027116 15025657560 0015650 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Blocks; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class to load assets required for structured data blocks. */ class Structured_Data_Blocks implements Integration_Interface { use No_Conditionals; /** * An instance of the WPSEO_Admin_Asset_Manager class. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * An instance of the image helper class. * * @var Image_Helper */ protected $image_helper; /** * The image caches per post. * * @var array */ protected $caches = []; /** * The used cache keys per post. * * @var array */ protected $used_caches = []; /** * Whether or not we've registered our shutdown function. * * @var bool */ protected $registered_shutdown_function = false; /** * Structured_Data_Blocks constructor. * * @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager. * @param Image_Helper $image_helper The image helper. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Image_Helper $image_helper ) { $this->asset_manager = $asset_manager; $this->image_helper = $image_helper; } /** * Registers hooks for Structured Data Blocks with WordPress. * * @return void */ public function register_hooks() { $this->register_blocks(); } /** * Registers the blocks. * * @return void */ public function register_blocks() { /** * Filter: 'wpseo_enable_structured_data_blocks' - Allows disabling Yoast's schema blocks entirely. * * @param bool $enable If false, our structured data blocks won't show. */ if ( ! \apply_filters( 'wpseo_enable_structured_data_blocks', true ) ) { return; } \register_block_type( \WPSEO_PATH . 'blocks/structured-data-blocks/faq/block.json', [ 'render_callback' => [ $this, 'optimize_faq_images' ], ] ); \register_block_type( \WPSEO_PATH . 'blocks/structured-data-blocks/how-to/block.json', [ 'render_callback' => [ $this, 'optimize_how_to_images' ], ] ); } /** * Optimizes images in the FAQ blocks. * * @param array $attributes The attributes. * @param string $content The content. * * @return string The content with images optimized. */ public function optimize_faq_images( $attributes, $content ) { if ( ! isset( $attributes['questions'] ) ) { return $content; } return $this->optimize_images( $attributes['questions'], 'answer', $content ); } /** * Transforms the durations into a translated string containing the count, and either singular or plural unit. * For example (in en-US): If 'days' is 1, it returns "1 day". If 'days' is 2, it returns "2 days". * If a number value is 0, we don't output the string. * * @param number $days Number of days. * @param number $hours Number of hours. * @param number $minutes Number of minutes. * @return array Array of pluralized durations. */ private function transform_duration_to_string( $days, $hours, $minutes ) { $strings = []; if ( $days ) { $strings[] = \sprintf( /* translators: %d expands to the number of day/days. */ \_n( '%d day', '%d days', $days, 'wordpress-seo' ), $days ); } if ( $hours ) { $strings[] = \sprintf( /* translators: %d expands to the number of hour/hours. */ \_n( '%d hour', '%d hours', $hours, 'wordpress-seo' ), $hours ); } if ( $minutes ) { $strings[] = \sprintf( /* translators: %d expands to the number of minute/minutes. */ \_n( '%d minute', '%d minutes', $minutes, 'wordpress-seo' ), $minutes ); } return $strings; } /** * Formats the durations into a translated string. * * @param array $attributes The attributes. * @return string The formatted duration. */ private function build_duration_string( $attributes ) { $days = ( $attributes['days'] ?? 0 ); $hours = ( $attributes['hours'] ?? 0 ); $minutes = ( $attributes['minutes'] ?? 0 ); $elements = $this->transform_duration_to_string( $days, $hours, $minutes ); $elements_length = \count( $elements ); switch ( $elements_length ) { case 1: return $elements[0]; case 2: return \sprintf( /* translators: %s expands to a unit of time (e.g. 1 day). */ \__( '%1$s and %2$s', 'wordpress-seo' ), ...$elements ); case 3: return \sprintf( /* translators: %s expands to a unit of time (e.g. 1 day). */ \__( '%1$s, %2$s and %3$s', 'wordpress-seo' ), ...$elements ); default: return ''; } } /** * Presents the duration text of the How-To block in the site language. * * @param array $attributes The attributes. * @param string $content The content. * * @return string The content with the duration text in the site language. */ public function present_duration_text( $attributes, $content ) { $duration = $this->build_duration_string( $attributes ); // 'Time needed:' is the default duration text that will be shown if a user doesn't add one. $duration_text = \__( 'Time needed:', 'wordpress-seo' ); if ( isset( $attributes['durationText'] ) && $attributes['durationText'] !== '' ) { $duration_text = $attributes['durationText']; } return \preg_replace( '/(<p class="schema-how-to-total-time">)(<span class="schema-how-to-duration-time-text">.*<\/span>)(.[^\/p>]*)(<\/p>)/', '<p class="schema-how-to-total-time"><span class="schema-how-to-duration-time-text">' . $duration_text . ' </span>' . $duration . '</p>', $content, 1 ); } /** * Optimizes images in the How-To blocks. * * @param array $attributes The attributes. * @param string $content The content. * * @return string The content with images optimized. */ public function optimize_how_to_images( $attributes, $content ) { if ( ! isset( $attributes['steps'] ) ) { return $content; } $content = $this->present_duration_text( $attributes, $content ); return $this->optimize_images( $attributes['steps'], 'text', $content ); } /** * Optimizes images in structured data blocks. * * @param array $elements The list of elements from the block attributes. * @param string $key The key in the data to iterate over. * @param string $content The content. * * @return string The content with images optimized. */ private function optimize_images( $elements, $key, $content ) { global $post; if ( ! $post ) { return $content; } $this->add_images_from_attributes_to_used_cache( $post->ID, $elements, $key ); // Then replace all images with optimized versions in the content. $content = \preg_replace_callback( '/<img[^>]+>/', function ( $matches ) { \preg_match( '/src="([^"]+)"/', $matches[0], $src_matches ); if ( ! $src_matches || ! isset( $src_matches[1] ) ) { return $matches[0]; } $attachment_id = $this->attachment_src_to_id( $src_matches[1] ); if ( $attachment_id === 0 ) { return $matches[0]; } $image_size = 'full'; $image_style = [ 'style' => 'max-width: 100%; height: auto;' ]; \preg_match( '/style="[^"]*width:\s*(\d+)px[^"]*"/', $matches[0], $style_matches ); if ( $style_matches && isset( $style_matches[1] ) ) { $width = (int) $style_matches[1]; $meta_data = \wp_get_attachment_metadata( $attachment_id ); if ( isset( $meta_data['height'] ) && isset( $meta_data['width'] ) && $meta_data['height'] > 0 && $meta_data['width'] > 0 ) { $aspect_ratio = ( $meta_data['height'] / $meta_data['width'] ); $height = ( $width * $aspect_ratio ); $image_size = [ $width, $height ]; } $image_style = ''; } /** * Filter: 'wpseo_structured_data_blocks_image_size' - Allows adjusting the image size in structured data blocks. * * @since 18.2 * * @param string|int[] $image_size The image size. Accepts any registered image size name, or an array of width and height values in pixels (in that order). * @param int $attachment_id The id of the attachment. * @param string $attachment_src The attachment src. */ $image_size = \apply_filters( 'wpseo_structured_data_blocks_image_size', $image_size, $attachment_id, $src_matches[1] ); $image_html = \wp_get_attachment_image( $attachment_id, $image_size, false, $image_style ); if ( empty( $image_html ) ) { return $matches[0]; } return $image_html; }, $content ); if ( ! $this->registered_shutdown_function ) { \register_shutdown_function( [ $this, 'maybe_save_used_caches' ] ); $this->registered_shutdown_function = true; } return $content; } /** * If the caches of structured data block images have been changed, saves them. * * @return void */ public function maybe_save_used_caches() { foreach ( $this->used_caches as $post_id => $used_cache ) { if ( isset( $this->caches[ $post_id ] ) && $used_cache === $this->caches[ $post_id ] ) { continue; } \update_post_meta( $post_id, 'yoast-structured-data-blocks-images-cache', $used_cache ); } } /** * Converts an attachment src to an attachment ID. * * @param string $src The attachment src. * * @return int The attachment ID. 0 if none was found. */ private function attachment_src_to_id( $src ) { global $post; if ( isset( $this->used_caches[ $post->ID ][ $src ] ) ) { return $this->used_caches[ $post->ID ][ $src ]; } $cache = $this->get_cache_for_post( $post->ID ); if ( isset( $cache[ $src ] ) ) { $this->used_caches[ $post->ID ][ $src ] = $cache[ $src ]; return $cache[ $src ]; } $this->used_caches[ $post->ID ][ $src ] = $this->image_helper->get_attachment_by_url( $src ); return $this->used_caches[ $post->ID ][ $src ]; } /** * Returns the cache from postmeta for a given post. * * @param int $post_id The post ID. * * @return array The images cache. */ private function get_cache_for_post( $post_id ) { if ( isset( $this->caches[ $post_id ] ) ) { return $this->caches[ $post_id ]; } $cache = \get_post_meta( $post_id, 'yoast-structured-data-blocks-images-cache', true ); if ( ! $cache ) { $cache = []; } $this->caches[ $post_id ] = $cache; return $cache; } /** * Adds any images that have their ID in the block attributes to the cache. * * @param int $post_id The post ID. * @param array $elements The elements. * @param string $key The key in the elements we should loop over. * * @return void */ private function add_images_from_attributes_to_used_cache( $post_id, $elements, $key ) { // First grab all image IDs from the attributes. $images = []; foreach ( $elements as $element ) { if ( ! isset( $element[ $key ] ) ) { continue; } if ( isset( $element[ $key ] ) && \is_array( $element[ $key ] ) ) { foreach ( $element[ $key ] as $part ) { if ( ! \is_array( $part ) || ! isset( $part['type'] ) || $part['type'] !== 'img' ) { continue; } if ( ! isset( $part['key'] ) || ! isset( $part['props']['src'] ) ) { continue; } $images[ $part['props']['src'] ] = (int) $part['key']; } } } if ( isset( $this->used_caches[ $post_id ] ) ) { $this->used_caches[ $post_id ] = \array_merge( $this->used_caches[ $post_id ], $images ); } else { $this->used_caches[ $post_id ] = $images; } } /* DEPRECATED METHODS */ /** * Enqueue Gutenberg block assets for backend editor. * * @deprecated 22.7 * @codeCoverageIgnore * * @return void */ public function enqueue_block_editor_assets() { \_deprecated_function( __METHOD__, 'Yoast SEO 22.7' ); } } integrations/blocks/breadcrumbs-block.php 0000644 00000006656 15025657560 0014651 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Blocks; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Presenters\Breadcrumbs_Presenter; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Surfaces\Helpers_Surface; /** * Siblings block class */ class Breadcrumbs_Block extends Dynamic_Block_V3 { /** * The name of the block. * * @var string */ protected $block_name = 'breadcrumbs'; /** * The editor script for the block. * * @var string */ protected $script = 'yoast-seo-dynamic-blocks'; /** * The Meta_Tags_Context_Memoizer. * * @var Meta_Tags_Context_Memoizer */ private $context_memoizer; /** * The Replace vars helper. * * @var WPSEO_Replace_Vars */ private $replace_vars; /** * The helpers surface. * * @var Helpers_Surface */ private $helpers; /** * The indexable repository. * * @var Indexable_Repository */ private $indexable_repository; /** * Siblings_Block constructor. * * @param Meta_Tags_Context_Memoizer $context_memoizer The context. * @param WPSEO_Replace_Vars $replace_vars The replace variable helper. * @param Helpers_Surface $helpers The Helpers surface. * @param Indexable_Repository $indexable_repository The indexable repository. */ public function __construct( Meta_Tags_Context_Memoizer $context_memoizer, WPSEO_Replace_Vars $replace_vars, Helpers_Surface $helpers, Indexable_Repository $indexable_repository ) { $this->context_memoizer = $context_memoizer; $this->replace_vars = $replace_vars; $this->helpers = $helpers; $this->indexable_repository = $indexable_repository; $this->base_path = \WPSEO_PATH . 'blocks/dynamic-blocks/'; } /** * Presents the breadcrumbs output for the current page or the available post_id. * * @param array<string, bool|string|int|array> $attributes The block attributes. * * @return string The block output. */ public function present( $attributes ) { $presenter = new Breadcrumbs_Presenter(); // $this->context_memoizer->for_current_page only works on the frontend. To render the right breadcrumb in the // editor, we need the repository. if ( \wp_is_serving_rest_request() || \is_admin() ) { $post_id = \get_the_ID(); if ( $post_id ) { $indexable = $this->indexable_repository->find_by_id_and_type( $post_id, 'post' ); if ( ! $indexable ) { $post = \get_post( $post_id ); $indexable = $this->indexable_repository->query()->create( [ 'object_id' => $post_id, 'object_type' => 'post', 'object_sub_type' => $post->post_type, ] ); } $context = $this->context_memoizer->get( $indexable, 'Post_Type' ); } } if ( ! isset( $context ) ) { $context = $this->context_memoizer->for_current_page(); } /** This filter is documented in src/integrations/front-end-integration.php */ $presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); $presenter->presentation = $presentation; $presenter->replace_vars = $this->replace_vars; $presenter->helpers = $this->helpers; $class_name = 'yoast-breadcrumbs'; if ( ! empty( $attributes['className'] ) ) { $class_name .= ' ' . \esc_attr( $attributes['className'] ); } return '<div class="' . $class_name . '">' . $presenter->present() . '</div>'; } } integrations/blocks/block-editor-integration.php 0000644 00000002332 15025657560 0016152 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Blocks; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Block_Editor_Integration class to enqueue the block editor assets also for the iframe. */ class Block_Editor_Integration implements Integration_Interface { /** * Represents the admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * {@inheritDoc} * * @return array<Post_Conditional> */ public static function get_conditionals() { return [ Post_Conditional::class ]; } /** * Constructor. * * @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager ) { $this->asset_manager = $asset_manager; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'enqueue_block_assets', [ $this, 'enqueue' ] ); } /** * Enqueues the assets for the block editor. * * @return void */ public function enqueue() { $this->asset_manager->enqueue_style( 'block-editor' ); } } integrations/blocks/block-categories.php 0000644 00000002132 15025657560 0014466 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Blocks; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Internal_Linking_Category block class. */ class Internal_Linking_Category implements Integration_Interface { /** * {@inheritDoc} */ public static function get_conditionals() { return []; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'block_categories_all', [ $this, 'add_block_categories' ] ); } /** * Adds Yoast block categories. * * @param array $categories The categories. * @return array The filtered categories. */ public function add_block_categories( $categories ) { $categories[] = [ 'slug' => 'yoast-structured-data-blocks', 'title' => \sprintf( /* translators: %1$s expands to Yoast. */ \__( '%1$s Structured Data Blocks', 'wordpress-seo' ), 'Yoast' ), ]; $categories[] = [ 'slug' => 'yoast-internal-linking-blocks', 'title' => \sprintf( /* translators: %1$s expands to Yoast. */ \__( '%1$s Internal Linking Blocks', 'wordpress-seo' ), 'Yoast' ), ]; return $categories; } } integrations/xmlrpc.php 0000644 00000002344 15025657560 0011306 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Yoast\WP\SEO\Conditionals\XMLRPC_Conditional; /** * Noindexes the xmlrpc.php file and all ways to request it. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded -- Known false positive with acronyms. Fix expected in YoastCS 3.x. */ class XMLRPC implements Integration_Interface { /** * Returns the conditionals based on which this loadable should be active. * * In this case when the current request is an XML-RPC request. * * @return array The conditionals based on which this class should be loaded. */ public static function get_conditionals() { return [ XMLRPC_Conditional::class ]; } /** * Initializes the integration. * * @return void */ public function register_hooks() { \add_filter( 'xmlrpc_methods', [ $this, 'robots_header' ] ); } /** * Sets a noindex, follow x-robots-tag header on all XMLRPC requests. * * @codeCoverageIgnore Basically impossible to test from the command line. * * @param array $methods The methods. * * @return array The methods. */ public function robots_header( $methods ) { if ( \headers_sent() === false ) { \header( 'X-Robots-Tag: noindex, follow', true ); } return $methods; } } integrations/abstract-exclude-post-type.php 0000644 00000002216 15025657560 0015173 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; /** * Abstract class for excluding certain post types from being indexed. */ abstract class Abstract_Exclude_Post_Type implements Integration_Interface { /** * Initializes the integration. * * @return void */ public function register_hooks() { \add_filter( 'wpseo_indexable_excluded_post_types', [ $this, 'exclude_post_types' ] ); } /** * Exclude the post type from the indexable table. * * @param array $excluded_post_types The excluded post types. * * @return array The excluded post types, including the specific post type. */ public function exclude_post_types( $excluded_post_types ) { return \array_merge( $excluded_post_types, $this->get_post_type() ); } /** * This integration is only active when the child class's conditionals are met. * * @return string[] The conditionals. */ public static function get_conditionals() { return []; } /** * Returns the names of the post types to be excluded. * To be used in the wpseo_indexable_excluded_post_types filter. * * @return array The names of the post types. */ abstract public function get_post_type(); } integrations/admin/integrations-page.php 0000644 00000023470 15025657560 0014514 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Easy_Digital_Downloads; use SeriouslySimplePodcasting\Integrations\Yoast\Schema\PodcastEpisode; use TEC\Events\Integrations\Plugins\WordPress_SEO\Events_Schema; use WP_Recipe_Maker; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Jetpack_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Consent_Management_Endpoint; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Woocommerce_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Integrations_Page class */ class Integrations_Page implements Integration_Interface { /** * The Woocommerce helper. * * @var Woocommerce_Helper */ private $woocommerce_helper; /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The elementor conditional. * * @var Elementor_Activated_Conditional */ private $elementor_conditional; /** * The jetpack conditional. * * @var Jetpack_Conditional */ private $jetpack_conditional; /** * The site kit integration configuration data. * * @var Site_Kit */ private $site_kit_integration_data; /** * The site kit consent management endpoint. * * @var Site_Kit_Consent_Management_Endpoint */ private $site_kit_consent_management_endpoint; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Workouts_Integration constructor. * * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. * @param Options_Helper $options_helper The options helper. * @param Woocommerce_Helper $woocommerce_helper The WooCommerce helper. * @param Elementor_Activated_Conditional $elementor_conditional The elementor conditional. * @param Jetpack_Conditional $jetpack_conditional The Jetpack conditional. * @param Site_Kit $site_kit_integration_data The site kit integration * configuration data. * @param Site_Kit_Consent_Management_Endpoint $site_kit_consent_management_endpoint The site kit consent * management endpoint. */ public function __construct( WPSEO_Admin_Asset_Manager $admin_asset_manager, Options_Helper $options_helper, Woocommerce_Helper $woocommerce_helper, Elementor_Activated_Conditional $elementor_conditional, Jetpack_Conditional $jetpack_conditional, Site_Kit $site_kit_integration_data, Site_Kit_Consent_Management_Endpoint $site_kit_consent_management_endpoint ) { $this->admin_asset_manager = $admin_asset_manager; $this->options_helper = $options_helper; $this->woocommerce_helper = $woocommerce_helper; $this->elementor_conditional = $elementor_conditional; $this->jetpack_conditional = $jetpack_conditional; $this->site_kit_integration_data = $site_kit_integration_data; $this->site_kit_consent_management_endpoint = $site_kit_consent_management_endpoint; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'wpseo_submenu_pages', [ $this, 'add_submenu_page' ], 10 ); \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 ); } /** * Adds the integrations submenu page. * * @param array $submenu_pages The Yoast SEO submenu pages. * * @return array The filtered submenu pages. */ public function add_submenu_page( $submenu_pages ) { $integrations_page = [ 'wpseo_dashboard', '', \__( 'Integrations', 'wordpress-seo' ), 'wpseo_manage_options', 'wpseo_integrations', [ $this, 'render_target' ], ]; \array_splice( $submenu_pages, 1, 0, [ $integrations_page ] ); return $submenu_pages; } /** * Enqueue the integrations app. * * @return void */ public function enqueue_assets() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved. if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_integrations' ) { return; } $this->admin_asset_manager->enqueue_style( 'admin-css' ); $this->admin_asset_manager->enqueue_style( 'tailwind' ); $this->admin_asset_manager->enqueue_style( 'monorepo' ); $this->admin_asset_manager->enqueue_script( 'integrations-page' ); $woocommerce_seo_file = 'wpseo-woocommerce/wpseo-woocommerce.php'; $acf_seo_file = 'acf-content-analysis-for-yoast-seo/yoast-acf-analysis.php'; $acf_seo_file_github = 'yoast-acf-analysis/yoast-acf-analysis.php'; $algolia_file = 'wp-search-with-algolia/algolia.php'; $old_algolia_file = 'search-by-algolia-instant-relevant-results/algolia.php'; $addon_manager = new WPSEO_Addon_Manager(); $woocommerce_seo_installed = $addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ); $woocommerce_seo_active = \is_plugin_active( $woocommerce_seo_file ); $woocommerce_active = $this->woocommerce_helper->is_active(); $acf_seo_installed = \file_exists( \WP_PLUGIN_DIR . '/' . $acf_seo_file ); $acf_seo_github_installed = \file_exists( \WP_PLUGIN_DIR . '/' . $acf_seo_file_github ); $acf_seo_active = \is_plugin_active( $acf_seo_file ); $acf_seo_github_active = \is_plugin_active( $acf_seo_file_github ); $acf_active = \class_exists( 'acf' ); $algolia_active = \is_plugin_active( $algolia_file ); $edd_active = \class_exists( Easy_Digital_Downloads::class ); $old_algolia_active = \is_plugin_active( $old_algolia_file ); $tec_active = \class_exists( Events_Schema::class ); $ssp_active = \class_exists( PodcastEpisode::class ); $wp_recipe_maker_active = \class_exists( WP_Recipe_Maker::class ); $mastodon_active = $this->is_mastodon_active(); $woocommerce_seo_activate_url = \wp_nonce_url( \self_admin_url( 'plugins.php?action=activate&plugin=' . $woocommerce_seo_file ), 'activate-plugin_' . $woocommerce_seo_file ); if ( $acf_seo_installed ) { $acf_seo_activate_url = \wp_nonce_url( \self_admin_url( 'plugins.php?action=activate&plugin=' . $acf_seo_file ), 'activate-plugin_' . $acf_seo_file ); } else { $acf_seo_activate_url = \wp_nonce_url( \self_admin_url( 'plugins.php?action=activate&plugin=' . $acf_seo_file_github ), 'activate-plugin_' . $acf_seo_file_github ); } $acf_seo_install_url = \wp_nonce_url( \self_admin_url( 'update.php?action=install-plugin&plugin=acf-content-analysis-for-yoast-seo' ), 'install-plugin_acf-content-analysis-for-yoast-seo' ); $this->admin_asset_manager->localize_script( 'integrations-page', 'wpseoIntegrationsData', [ 'semrush_integration_active' => $this->options_helper->get( 'semrush_integration_active', true ), 'allow_semrush_integration' => $this->options_helper->get( 'allow_semrush_integration_active', true ), 'algolia_integration_active' => $this->options_helper->get( 'algolia_integration_active', false ), 'allow_algolia_integration' => $this->options_helper->get( 'allow_algolia_integration_active', true ), 'wincher_integration_active' => $this->options_helper->get( 'wincher_integration_active', true ), 'allow_wincher_integration' => null, 'elementor_integration_active' => $this->elementor_conditional->is_met(), 'jetpack_integration_active' => $this->jetpack_conditional->is_met(), 'woocommerce_seo_installed' => $woocommerce_seo_installed, 'woocommerce_seo_active' => $woocommerce_seo_active, 'woocommerce_active' => $woocommerce_active, 'woocommerce_seo_activate_url' => $woocommerce_seo_activate_url, 'acf_seo_installed' => $acf_seo_installed || $acf_seo_github_installed, 'acf_seo_active' => $acf_seo_active || $acf_seo_github_active, 'acf_active' => $acf_active, 'acf_seo_activate_url' => $acf_seo_activate_url, 'acf_seo_install_url' => $acf_seo_install_url, 'algolia_active' => $algolia_active || $old_algolia_active, 'edd_integration_active' => $edd_active, 'ssp_integration_active' => $ssp_active, 'tec_integration_active' => $tec_active, 'wp-recipe-maker_integration_active' => $wp_recipe_maker_active, 'mastodon_active' => $mastodon_active, 'is_multisite' => \is_multisite(), 'plugin_url' => \plugins_url( '', \WPSEO_FILE ), 'site_kit_configuration' => $this->site_kit_integration_data->to_array(), 'site_kit_consent_management_url' => $this->site_kit_consent_management_endpoint->get_url(), ] ); } /** * Renders the target for the React to mount to. * * @return void */ public function render_target() { ?> <div class="wrap yoast wpseo-admin-page page-wpseo"> <div class="wp-header-end" style="height: 0; width: 0;"></div> <div id="wpseo-integrations"></div> </div> <?php } /** * Checks whether the Mastodon profile field has been filled in. * * @return bool */ private function is_mastodon_active() { return \apply_filters( 'wpseo_mastodon_active', false ); } } integrations/admin/activation-cleanup-integration.php 0000644 00000003545 15025657560 0017204 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Cleanup_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * This integration registers a run of the cleanup routine whenever the plugin is activated. */ class Activation_Cleanup_Integration implements Integration_Interface { use No_Conditionals; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * Activation_Cleanup_Integration constructor. * * @param Options_Helper $options_helper The options helper. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Options_Helper $options_helper, Indexable_Helper $indexable_helper ) { $this->options_helper = $options_helper; $this->indexable_helper = $indexable_helper; } /** * Registers the action to register a cleanup routine run after the plugin is activated. * * @return void */ public function register_hooks() { \add_action( 'wpseo_activate', [ $this, 'register_cleanup_routine' ], 11 ); } /** * Registers a run of the cleanup routine if this has not happened yet. * * @return void */ public function register_cleanup_routine() { if ( ! $this->indexable_helper->should_index_indexables() ) { return; } $first_activated_on = $this->options_helper->get( 'first_activated_on', false ); if ( ! $first_activated_on || \time() > ( $first_activated_on + ( \MINUTE_IN_SECONDS * 5 ) ) ) { if ( ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) { \wp_schedule_single_event( ( \time() + \DAY_IN_SECONDS ), Cleanup_Integration::START_HOOK ); } } } } integrations/admin/fix-news-dependencies-integration.php 0000644 00000003065 15025657560 0017577 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WP_Screen; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\News_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Fix_News_Dependencies_Integration class. */ class Fix_News_Dependencies_Integration implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * In this case: when on an admin page. * * @return array The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class, News_Conditional::class ]; } /** * Registers an action to disable script concatenation. * * @return void */ public function register_hooks() { global $pagenow; // Load the editor script when on an edit post or new post page. $is_post_edit_page = $pagenow === 'post.php' || $pagenow === 'post-new.php'; if ( $is_post_edit_page ) { \add_action( 'admin_enqueue_scripts', [ $this, 'add_news_script_dependency' ], 11 ); } } /** * Fixes the news script dependency. * * @return void */ public function add_news_script_dependency() { $scripts = \wp_scripts(); if ( ! isset( $scripts->registered['wpseo-news-editor'] ) ) { return; } $is_block_editor = WP_Screen::get()->is_block_editor(); $post_edit_handle = 'post-edit'; if ( ! $is_block_editor ) { $post_edit_handle = 'post-edit-classic'; } $scripts->registered['wpseo-news-editor']->deps[] = WPSEO_Admin_Asset_Manager::PREFIX . $post_edit_handle; } } integrations/admin/installation-success-integration.php 0000644 00000010063 15025657560 0017556 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Installation_Success_Integration class */ class Installation_Success_Integration implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The product helper. * * @var Product_Helper */ protected $product_helper; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Installation_Success_Integration constructor. * * @param Options_Helper $options_helper The options helper. * @param Product_Helper $product_helper The product helper. */ public function __construct( Options_Helper $options_helper, Product_Helper $product_helper ) { $this->options_helper = $options_helper; $this->product_helper = $product_helper; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'admin_menu', [ $this, 'add_submenu_page' ], 9 ); \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); \add_action( 'admin_init', [ $this, 'maybe_redirect' ] ); } /** * Redirects to the installation success page if an installation has just occurred. * * @return void */ public function maybe_redirect() { if ( \defined( 'DOING_AJAX' ) && \DOING_AJAX ) { return; } if ( ! $this->options_helper->get( 'should_redirect_after_install_free', false ) ) { return; } $this->options_helper->set( 'should_redirect_after_install_free', false ); if ( ! empty( $this->options_helper->get( 'activation_redirect_timestamp_free', 0 ) ) ) { return; } $this->options_helper->set( 'activation_redirect_timestamp_free', \time() ); // phpcs:ignore WordPress.Security.NonceVerification -- This is not a form. if ( isset( $_REQUEST['activate-multi'] ) && $_REQUEST['activate-multi'] === 'true' ) { return; } if ( $this->product_helper->is_premium() ) { return; } if ( \is_network_admin() || \is_plugin_active_for_network( \WPSEO_BASENAME ) ) { return; } \wp_safe_redirect( \admin_url( 'admin.php?page=wpseo_installation_successful_free' ), 302, 'Yoast SEO' ); $this->terminate_execution(); } /** * Adds the installation success submenu page. * * @param array $submenu_pages The Yoast SEO submenu pages. * * @return array the filtered submenu pages. */ public function add_submenu_page( $submenu_pages ) { \add_submenu_page( '', \__( 'Installation Successful', 'wordpress-seo' ), '', 'manage_options', 'wpseo_installation_successful_free', [ $this, 'render_page' ] ); return $submenu_pages; } /** * Enqueue assets on the Installation success page. * * @return void */ public function enqueue_assets() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved. if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_installation_successful_free' ) { return; } $asset_manager = new WPSEO_Admin_Asset_Manager(); $asset_manager->enqueue_script( 'installation-success' ); $asset_manager->enqueue_style( 'tailwind' ); $asset_manager->enqueue_style( 'monorepo' ); $ftc_url = \esc_url( \admin_url( 'admin.php?page=wpseo_dashboard#/first-time-configuration' ) ); $asset_manager->localize_script( 'installation-success', 'wpseoInstallationSuccess', [ 'pluginUrl' => \esc_url( \plugins_url( '', \WPSEO_FILE ) ), 'firstTimeConfigurationUrl' => $ftc_url, 'dashboardUrl' => \esc_url( \admin_url( 'admin.php?page=wpseo_dashboard' ) ), ] ); } /** * Renders the installation success page. * * @return void */ public function render_page() { echo '<div id="wpseo-installation-successful-free" class="yoast"></div>'; } /** * Wrap the `exit` function to make unit testing easier. * * @return void */ public function terminate_execution() { exit; } } integrations/admin/background-indexing-integration.php 0000644 00000025403 15025657560 0017335 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action; use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface; use Yoast\WP\SEO\Conditionals\Get_Request_Conditional; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Conditionals\WP_CRON_Enabled_Conditional; use Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Indexing_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Background_Indexing_Integration. * * @package Yoast\WP\SEO\Integrations\Admin */ class Background_Indexing_Integration implements Integration_Interface { /** * Represents the indexing completed action. * * @var Indexable_Indexing_Complete_Action */ protected $complete_indexation_action; /** * Represents the indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * An object that checks if we are on the Yoast admin or on the dashboard page. * * @var Yoast_Admin_And_Dashboard_Conditional */ protected $yoast_admin_and_dashboard_conditional; /** * All available indexing actions. * * @var Indexation_Action_Interface[] */ protected $indexing_actions; /** * An object that checks if we are handling a GET request. * * @var Get_Request_Conditional */ private $get_request_conditional; /** * An object that checks if WP_CRON is enabled. * * @var WP_CRON_Enabled_Conditional */ private $wp_cron_enabled_conditional; /** * The indexable helper * * @var Indexable_Helper */ private $indexable_helper; /** * Shutdown_Indexing_Integration constructor. * * @param Indexable_Indexing_Complete_Action $complete_indexation_action The complete indexing action. * @param Indexing_Helper $indexing_helper The indexing helper. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Yoast_Admin_And_Dashboard_Conditional $yoast_admin_and_dashboard_conditional An object that checks if we are on the Yoast admin or on the dashboard page. * @param Get_Request_Conditional $get_request_conditional An object that checks if we are handling a GET request. * @param WP_CRON_Enabled_Conditional $wp_cron_enabled_conditional An object that checks if WP_CRON is enabled. * @param Indexation_Action_Interface ...$indexing_actions A list of all available indexing actions. */ public function __construct( Indexable_Indexing_Complete_Action $complete_indexation_action, Indexing_Helper $indexing_helper, Indexable_Helper $indexable_helper, Yoast_Admin_And_Dashboard_Conditional $yoast_admin_and_dashboard_conditional, Get_Request_Conditional $get_request_conditional, WP_CRON_Enabled_Conditional $wp_cron_enabled_conditional, Indexation_Action_Interface ...$indexing_actions ) { $this->indexing_actions = $indexing_actions; $this->complete_indexation_action = $complete_indexation_action; $this->indexing_helper = $indexing_helper; $this->indexable_helper = $indexable_helper; $this->yoast_admin_and_dashboard_conditional = $yoast_admin_and_dashboard_conditional; $this->get_request_conditional = $get_request_conditional; $this->wp_cron_enabled_conditional = $wp_cron_enabled_conditional; } /** * Returns the conditionals based on which this integration should be active. * * @return array The array of conditionals. */ public static function get_conditionals() { return [ Migrations_Conditional::class, ]; } /** * Register hooks. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'register_shutdown_indexing' ] ); \add_action( 'wpseo_indexable_index_batch', [ $this, 'index' ] ); // phpcs:ignore WordPress.WP.CronInterval -- The sniff doesn't understand values with parentheses. https://github.com/WordPress/WordPress-Coding-Standards/issues/2025 \add_filter( 'cron_schedules', [ $this, 'add_cron_schedule' ] ); \add_action( 'admin_init', [ $this, 'schedule_cron_indexing' ], 11 ); $this->add_limit_filters(); } /** * Adds the filters that change the indexing limits. * * @return void */ public function add_limit_filters() { \add_filter( 'wpseo_post_indexation_limit', [ $this, 'throttle_cron_indexing' ] ); \add_filter( 'wpseo_post_type_archive_indexation_limit', [ $this, 'throttle_cron_indexing' ] ); \add_filter( 'wpseo_term_indexation_limit', [ $this, 'throttle_cron_indexing' ] ); \add_filter( 'wpseo_prominent_words_indexation_limit', [ $this, 'throttle_cron_indexing' ] ); \add_filter( 'wpseo_link_indexing_limit', [ $this, 'throttle_cron_link_indexing' ] ); } /** * Enqueues the required scripts. * * @return void */ public function register_shutdown_indexing() { if ( $this->should_index_on_shutdown( $this->get_shutdown_limit() ) ) { $this->register_shutdown_function( 'index' ); } } /** * Run a single indexing pass of each indexing action. Intended for use as a shutdown function. * * @return void */ public function index() { if ( \wp_doing_cron() && ! $this->should_index_on_cron() ) { $this->unschedule_cron_indexing(); return; } foreach ( $this->indexing_actions as $indexation_action ) { $indexation_action->index(); } if ( $this->indexing_helper->get_limited_filtered_unindexed_count_background( 1 ) === 0 ) { // We set this as complete, even though prominent words might not be complete. But that's the way we always treated that. $this->complete_indexation_action->complete(); } } /** * Adds the 'Every fifteen minutes' cron schedule to WP-Cron. * * @param array $schedules The existing schedules. * * @return array The schedules containing the fifteen_minutes schedule. */ public function add_cron_schedule( $schedules ) { if ( ! \is_array( $schedules ) ) { return $schedules; } $schedules['fifteen_minutes'] = [ 'interval' => ( 15 * \MINUTE_IN_SECONDS ), 'display' => \esc_html__( 'Every fifteen minutes', 'wordpress-seo' ), ]; return $schedules; } /** * Schedule background indexing every 15 minutes if the index isn't already up to date. * * @return void */ public function schedule_cron_indexing() { /** * Filter: 'wpseo_unindexed_count_queries_ran' - Informs whether the expensive unindexed count queries have been ran already. * * @internal * * @param bool $have_queries_ran */ $have_queries_ran = \apply_filters( 'wpseo_unindexed_count_queries_ran', false ); if ( ( ! $this->yoast_admin_and_dashboard_conditional->is_met() || ! $this->get_request_conditional->is_met() ) && ! $have_queries_ran ) { return; } if ( ! \wp_next_scheduled( 'wpseo_indexable_index_batch' ) && $this->should_index_on_cron() ) { \wp_schedule_event( ( \time() + \HOUR_IN_SECONDS ), 'fifteen_minutes', 'wpseo_indexable_index_batch' ); } } /** * Limit cron indexing to 15 indexables per batch instead of 25. * * @param int $indexation_limit The current limit (filter input). * * @return int The new batch limit. */ public function throttle_cron_indexing( $indexation_limit ) { if ( \wp_doing_cron() ) { /** * Filter: 'wpseo_cron_indexing_limit_size' - Adds the possibility to limit the number of items that are indexed when in cron action. * * @param int $limit Maximum number of indexables to be indexed per indexing action. */ return \apply_filters( 'wpseo_cron_indexing_limit_size', 15 ); } return $indexation_limit; } /** * Limit cron indexing to 3 links per batch instead of 5. * * @param int $link_indexation_limit The current limit (filter input). * * @return int The new batch limit. */ public function throttle_cron_link_indexing( $link_indexation_limit ) { if ( \wp_doing_cron() ) { /** * Filter: 'wpseo_cron_link_indexing_limit_size' - Adds the possibility to limit the number of links that are indexed when in cron action. * * @param int $limit Maximum number of link indexables to be indexed per link indexing action. */ return \apply_filters( 'wpseo_cron_link_indexing_limit_size', 3 ); } return $link_indexation_limit; } /** * Determine whether cron indexation should be performed. * * @return bool Should cron indexation be performed. */ protected function should_index_on_cron() { if ( ! $this->indexable_helper->should_index_indexables() ) { return false; } // The filter supersedes everything when preventing cron indexation. if ( \apply_filters( 'Yoast\WP\SEO\enable_cron_indexing', true ) !== true ) { return false; } return $this->indexing_helper->get_limited_filtered_unindexed_count_background( 1 ) > 0; } /** * Determine whether background indexation should be performed. * * @param int $shutdown_limit The shutdown limit used to determine whether indexation should be run. * * @return bool Should background indexation be performed. */ protected function should_index_on_shutdown( $shutdown_limit ) { if ( ! $this->yoast_admin_and_dashboard_conditional->is_met() || ! $this->get_request_conditional->is_met() ) { return false; } if ( ! $this->indexable_helper->should_index_indexables() ) { return false; } if ( $this->wp_cron_enabled_conditional->is_met() ) { return false; } $total_unindexed = $this->indexing_helper->get_limited_filtered_unindexed_count_background( $shutdown_limit ); if ( $total_unindexed === 0 || $total_unindexed > $shutdown_limit ) { return false; } return true; } /** * Retrieves the shutdown limit. This limit is the amount of indexables that is generated in the background. * * @return int The shutdown limit. */ protected function get_shutdown_limit() { /** * Filter 'wpseo_shutdown_indexation_limit' - Allow filtering the number of objects that can be indexed during shutdown. * * @param int $limit The maximum number of objects indexed. */ return \apply_filters( 'wpseo_shutdown_indexation_limit', 25 ); } /** * Removes the cron indexing job from the scheduled event queue. * * @return void */ protected function unschedule_cron_indexing() { $scheduled = \wp_next_scheduled( 'wpseo_indexable_index_batch' ); if ( $scheduled ) { \wp_unschedule_event( $scheduled, 'wpseo_indexable_index_batch' ); } } /** * Registers a method to be executed on shutdown. * This wrapper mostly exists for making this class more unittestable. * * @param string $method_name The name of the method on the current instance to register. * * @return void */ protected function register_shutdown_function( $method_name ) { \register_shutdown_function( [ $this, $method_name ] ); } } integrations/admin/cron-integration.php 0000644 00000001752 15025657560 0014355 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Date_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Cron_Integration class. */ class Cron_Integration implements Integration_Interface { /** * The indexing notification integration. * * @var Date_Helper */ protected $date_helper; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Cron_Integration constructor * * @param Date_Helper $date_helper The date helper. */ public function __construct( Date_Helper $date_helper ) { $this->date_helper = $date_helper; } /** * {@inheritDoc} */ public function register_hooks() { if ( ! \wp_next_scheduled( Indexing_Notification_Integration::NOTIFICATION_ID ) ) { \wp_schedule_event( $this->date_helper->current_time(), 'daily', Indexing_Notification_Integration::NOTIFICATION_ID ); } } } integrations/admin/admin-columns-cache-integration.php 0000644 00000017142 15025657560 0017223 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WP_Post; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Admin_Columns_Cache_Integration class. */ class Admin_Columns_Cache_Integration implements Integration_Interface { /** * Cache of indexables. * * @var Indexable[] */ protected $indexable_cache = []; /** * The indexable repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * Returns the conditionals based on which this loadable should be active. * * In this case: only when on an admin page. * * @return array The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Admin_Columns_Cache_Integration constructor. * * @param Indexable_Repository $indexable_repository The indexable repository. */ public function __construct( Indexable_Repository $indexable_repository ) { $this->indexable_repository = $indexable_repository; } /** * Registers the appropriate actions and filters to fill the cache with * indexables on admin pages. * * This cache is used in showing the Yoast SEO columns on the posts overview * page (e.g. keyword score, incoming link count, etc.) * * @return void */ public function register_hooks() { // Hook into tablenav to calculate links and linked. \add_action( 'manage_posts_extra_tablenav', [ $this, 'maybe_fill_cache' ] ); } /** * Makes sure we calculate all values in one query by filling our cache beforehand. * * @param string $target Extra table navigation location which is triggered. * * @return void */ public function maybe_fill_cache( $target ) { if ( $target === 'top' ) { $this->fill_cache(); } } /** * Fills the cache of indexables for all known post IDs. * * @return void */ public function fill_cache() { global $wp_query; // No need to continue building a cache if the main query did not return anything to cache. if ( empty( $wp_query->posts ) ) { return; } $posts = $wp_query->posts; $post_ids = []; // Post lists return a list of objects. if ( isset( $posts[0] ) && \is_a( $posts[0], 'WP_Post' ) ) { $post_ids = \wp_list_pluck( $posts, 'ID' ); } elseif ( isset( $posts[0] ) && \is_object( $posts[0] ) ) { $post_ids = $this->get_current_page_page_ids( $posts ); } elseif ( ! empty( $posts ) ) { // Page list returns an array of post IDs. $post_ids = \array_keys( $posts ); } if ( empty( $post_ids ) ) { return; } if ( isset( $posts[0] ) && ! \is_a( $posts[0], WP_Post::class ) ) { // Prime the post caches as core would to avoid duplicate queries. // This needs to be done as this executes before core does. \_prime_post_caches( $post_ids ); } $indexables = $this->indexable_repository->find_by_multiple_ids_and_type( $post_ids, 'post', false ); foreach ( $indexables as $indexable ) { if ( $indexable instanceof Indexable ) { $this->indexable_cache[ $indexable->object_id ] = $indexable; } } } /** * Returns the indexable for a given post ID. * * @param int $post_id The post ID. * * @return Indexable|false The indexable. False if none could be found. */ public function get_indexable( $post_id ) { if ( ! \array_key_exists( $post_id, $this->indexable_cache ) ) { $this->indexable_cache[ $post_id ] = $this->indexable_repository->find_by_id_and_type( $post_id, 'post' ); } return $this->indexable_cache[ $post_id ]; } /** * Gets all the page IDs set to be shown on the current page. * This is copied over with some changes from WP_Posts_List_Table::_display_rows_hierarchical. * * @param array $pages The pages, each containing an ID and post_parent. * * @return array The IDs of all pages shown on the current page. */ private function get_current_page_page_ids( $pages ) { global $per_page; $pagenum = isset( $_REQUEST['paged'] ) ? \absint( $_REQUEST['paged'] ) : 0; $pagenum = \max( 1, $pagenum ); /* * Arrange pages into two parts: top level pages and children_pages * children_pages is two dimensional array, eg. * children_pages[10][] contains all sub-pages whose parent is 10. * It only takes O( N ) to arrange this and it takes O( 1 ) for subsequent lookup operations * If searching, ignore hierarchy and treat everything as top level */ if ( empty( $_REQUEST['s'] ) ) { $top_level_pages = []; $children_pages = []; $pages_map = []; foreach ( $pages as $page ) { // Catch and repair bad pages. if ( $page->post_parent === $page->ID ) { $page->post_parent = 0; } if ( $page->post_parent === 0 ) { $top_level_pages[] = $page; } else { $children_pages[ $page->post_parent ][] = $page; } $pages_map[ $page->ID ] = $page; } $pages = $top_level_pages; } $count = 0; $start = ( ( $pagenum - 1 ) * $per_page ); $end = ( $start + $per_page ); $to_display = []; foreach ( $pages as $page ) { if ( $count >= $end ) { break; } if ( $count >= $start ) { $to_display[] = $page->ID; } ++$count; $this->get_child_page_ids( $children_pages, $count, $page->ID, $start, $end, $to_display, $pages_map ); } // If it is the last pagenum and there are orphaned pages, display them with paging as well. if ( isset( $children_pages ) && $count < $end ) { foreach ( $children_pages as $orphans ) { foreach ( $orphans as $op ) { if ( $count >= $end ) { break; } if ( $count >= $start ) { $to_display[] = $op->ID; } ++$count; } } } return $to_display; } /** * Adds all child pages due to be shown on the current page to the $to_display array. * Copied over with some changes from WP_Posts_List_Table::_page_rows. * * @param array $children_pages The full map of child pages. * @param int $count The number of pages already processed. * @param int $parent_id The id of the parent that's currently being processed. * @param int $start The number at which the current overview starts. * @param int $end The number at which the current overview ends. * @param int $to_display The page IDs to be shown. * @param int $pages_map A map of page ID to an object with ID and post_parent. * * @return void */ private function get_child_page_ids( &$children_pages, &$count, $parent_id, $start, $end, &$to_display, &$pages_map ) { if ( ! isset( $children_pages[ $parent_id ] ) ) { return; } foreach ( $children_pages[ $parent_id ] as $page ) { if ( $count >= $end ) { break; } // If the page starts in a subtree, print the parents. if ( $count === $start && $page->post_parent > 0 ) { $my_parents = []; $my_parent = $page->post_parent; while ( $my_parent ) { // Get the ID from the list or the attribute if my_parent is an object. $parent_id = $my_parent; if ( \is_object( $my_parent ) ) { $parent_id = $my_parent->ID; } $my_parent = $pages_map[ $parent_id ]; $my_parents[] = $my_parent; if ( ! $my_parent->post_parent ) { break; } $my_parent = $my_parent->post_parent; } while ( $my_parent = \array_pop( $my_parents ) ) { $to_display[] = $my_parent->ID; } } if ( $count >= $start ) { $to_display[] = $page->ID; } ++$count; $this->get_child_page_ids( $children_pages, $count, $page->ID, $start, $end, $to_display, $pages_map ); } unset( $children_pages[ $parent_id ] ); // Required in order to keep track of orphans. } } integrations/admin/indexables-exclude-taxonomy-integration.php 0000644 00000002613 15025657560 0021032 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Indexables_Exclude_Taxonomy_Integration class */ class Indexables_Exclude_Taxonomy_Integration implements Integration_Interface { use No_Conditionals; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * Indexables_Exclude_Taxonomy_Integration constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'wpseo_indexable_excluded_taxonomies', [ $this, 'exclude_taxonomies_for_indexation' ] ); } /** * Exclude the taxonomy from the indexable table. * * @param array $excluded_taxonomies The excluded taxonomies. * * @return array The excluded taxonomies, including specific taxonomies. */ public function exclude_taxonomies_for_indexation( $excluded_taxonomies ) { $taxonomies_to_exclude = \array_merge( $excluded_taxonomies, [ 'wp_pattern_category' ] ); if ( $this->options_helper->get( 'disable-post_format', false ) ) { return \array_merge( $taxonomies_to_exclude, [ 'post_format' ] ); } return $taxonomies_to_exclude; } } integrations/admin/indexing-notification-integration.php 0000644 00000015710 15025657560 0017704 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional; use Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Environment_Helper; use Yoast\WP\SEO\Helpers\Indexing_Helper; use Yoast\WP\SEO\Helpers\Notification_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Indexing_Failed_Notification_Presenter; use Yoast\WP\SEO\Presenters\Admin\Indexing_Notification_Presenter; use Yoast_Notification; use Yoast_Notification_Center; /** * Class Indexing_Notification_Integration. * * @package Yoast\WP\SEO\Integrations\Admin */ class Indexing_Notification_Integration implements Integration_Interface { /** * The notification ID. */ public const NOTIFICATION_ID = 'wpseo-reindex'; /** * The Yoast notification center. * * @var Yoast_Notification_Center */ protected $notification_center; /** * The product helper. * * @var Product_Helper */ protected $product_helper; /** * The current page helper. * * @var Current_Page_Helper */ protected $page_helper; /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * The notification helper. * * @var Notification_Helper */ protected $notification_helper; /** * The indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * The Addon Manager. * * @var WPSEO_Addon_Manager */ protected $addon_manager; /** * The Environment Helper. * * @var Environment_Helper */ protected $environment_helper; /** * Indexing_Notification_Integration constructor. * * @param Yoast_Notification_Center $notification_center The notification center. * @param Product_Helper $product_helper The product helper. * @param Current_Page_Helper $page_helper The current page helper. * @param Short_Link_Helper $short_link_helper The short link helper. * @param Notification_Helper $notification_helper The notification helper. * @param Indexing_Helper $indexing_helper The indexing helper. * @param WPSEO_Addon_Manager $addon_manager The addon manager. * @param Environment_Helper $environment_helper The environment helper. */ public function __construct( Yoast_Notification_Center $notification_center, Product_Helper $product_helper, Current_Page_Helper $page_helper, Short_Link_Helper $short_link_helper, Notification_Helper $notification_helper, Indexing_Helper $indexing_helper, WPSEO_Addon_Manager $addon_manager, Environment_Helper $environment_helper ) { $this->notification_center = $notification_center; $this->product_helper = $product_helper; $this->page_helper = $page_helper; $this->short_link_helper = $short_link_helper; $this->notification_helper = $notification_helper; $this->indexing_helper = $indexing_helper; $this->addon_manager = $addon_manager; $this->environment_helper = $environment_helper; } /** * Initializes the integration. * * Adds hooks and jobs to cleanup or add the notification when necessary. * * @return void */ public function register_hooks() { if ( $this->page_helper->get_current_yoast_seo_page() === 'wpseo_dashboard' ) { \add_action( 'admin_init', [ $this, 'maybe_cleanup_notification' ] ); } if ( $this->indexing_helper->has_reason() ) { \add_action( 'admin_init', [ $this, 'maybe_create_notification' ] ); } \add_action( self::NOTIFICATION_ID, [ $this, 'maybe_create_notification' ] ); } /** * Returns the conditionals based on which this loadable should be active. * * @return array The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class, Not_Admin_Ajax_Conditional::class, User_Can_Manage_Wpseo_Options_Conditional::class, ]; } /** * Checks whether the notification should be shown and adds * it to the notification center if this is the case. * * @return void */ public function maybe_create_notification() { if ( ! $this->should_show_notification() ) { return; } if ( ! $this->notification_center->get_notification_by_id( self::NOTIFICATION_ID ) ) { $notification = $this->notification(); $this->notification_helper->restore_notification( $notification ); $this->notification_center->add_notification( $notification ); } } /** * Checks whether the notification should not be shown anymore and removes * it from the notification center if this is the case. * * @return void */ public function maybe_cleanup_notification() { $notification = $this->notification_center->get_notification_by_id( self::NOTIFICATION_ID ); if ( $notification === null ) { return; } if ( $this->should_show_notification() ) { return; } $this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID ); } /** * Checks whether the notification should be shown. * * @return bool If the notification should be shown. */ protected function should_show_notification() { if ( ! $this->environment_helper->is_production_mode() ) { return false; } // Don't show a notification if the indexing has already been started earlier. if ( $this->indexing_helper->get_started() > 0 ) { return false; } // We're about to perform expensive queries, let's inform. \add_filter( 'wpseo_unindexed_count_queries_ran', '__return_true' ); // Never show a notification when nothing should be indexed. return $this->indexing_helper->get_limited_filtered_unindexed_count( 1 ) > 0; } /** * Returns an instance of the notification. * * @return Yoast_Notification The notification to show. */ protected function notification() { $reason = $this->indexing_helper->get_reason(); $presenter = $this->get_presenter( $reason ); return new Yoast_Notification( $presenter, [ 'type' => Yoast_Notification::WARNING, 'id' => self::NOTIFICATION_ID, 'capabilities' => 'wpseo_manage_options', 'priority' => 0.8, ] ); } /** * Gets the presenter to use to show the notification. * * @param string $reason The reason for the notification. * * @return Indexing_Failed_Notification_Presenter|Indexing_Notification_Presenter */ protected function get_presenter( $reason ) { if ( $reason === Indexing_Reasons::REASON_INDEXING_FAILED ) { $presenter = new Indexing_Failed_Notification_Presenter( $this->product_helper, $this->short_link_helper, $this->addon_manager ); } else { $total_unindexed = $this->indexing_helper->get_filtered_unindexed_count(); $presenter = new Indexing_Notification_Presenter( $this->short_link_helper, $total_unindexed, $reason ); } return $presenter; } } integrations/admin/redirect-integration.php 0000644 00000003343 15025657560 0015213 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Redirect_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Redirect_Integration. */ class Redirect_Integration implements Integration_Interface { /** * The redirect helper. * * @var Redirect_Helper */ private $redirect; /** * Sets the helpers. * * @param Redirect_Helper $redirect The redirect helper. */ public function __construct( Redirect_Helper $redirect ) { $this->redirect = $redirect; } /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'wp_loaded', [ $this, 'old_settings_redirect' ] ); } /** * Redirect to new settings URLs. We're adding this, so that not-updated add-ons don't point to non-existent pages. * * @return void */ public function old_settings_redirect() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( ! isset( $_GET['page'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $current_page = \sanitize_text_field( \wp_unslash( $_GET['page'] ) ); switch ( $current_page ) { case 'wpseo_titles': $this->redirect->do_safe_redirect( \admin_url( 'admin.php?page=wpseo_page_settings#/site-representation' ), 301 ); return; default: return; } } } integrations/admin/crawl-settings-integration.php 0000644 00000025620 15025657560 0016362 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Admin_Asset_Manager; use WPSEO_Option; use WPSEO_Shortlinker; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter; use Yoast_Form; /** * Crawl_Settings_Integration class */ class Crawl_Settings_Integration implements Integration_Interface { /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * Holds the settings + labels for the head clean up piece. * * @var array */ private $basic_settings; /** * Holds the settings + labels for the feeds clean up. * * @var array */ private $feed_settings; /** * Holds the settings + labels for permalink cleanup settings. * * @var array */ private $permalink_cleanup_settings; /** * Holds the settings + labels for search cleanup settings. * * @var array */ private $search_cleanup_settings; /** * Holds the settings + labels for unused resources settings. * * @var array */ private $unused_resources_settings; /** * The shortlinker. * * @var WPSEO_Shortlinker */ private $shortlinker; /** * Returns the conditionals based in which this loadable should be active. * * In this case: when on an admin page. * * @return array<string> */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Crawl_Settings_Integration constructor. * * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. * @param WPSEO_Shortlinker $shortlinker The shortlinker. */ public function __construct( WPSEO_Admin_Asset_Manager $admin_asset_manager, WPSEO_Shortlinker $shortlinker ) { $this->admin_asset_manager = $admin_asset_manager; $this->shortlinker = $shortlinker; } /** * Registers an action to add a new tab to the General page. * * @return void */ public function register_hooks() { $this->register_setting_labels(); \add_action( 'wpseo_settings_tab_crawl_cleanup_network', [ $this, 'add_crawl_settings_tab_content_network' ] ); \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } /** * Enqueue the workouts app. * * @return void */ public function enqueue_assets() { if ( ! \is_network_admin() ) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Page is not processed or saved. if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_dashboard' ) { return; } $this->admin_asset_manager->enqueue_script( 'crawl-settings' ); } /** * Connects the settings to their labels. * * @return void */ private function register_setting_labels() { $this->feed_settings = [ 'remove_feed_global' => \__( 'Global feed', 'wordpress-seo' ), 'remove_feed_global_comments' => \__( 'Global comment feeds', 'wordpress-seo' ), 'remove_feed_post_comments' => \__( 'Post comments feeds', 'wordpress-seo' ), 'remove_feed_authors' => \__( 'Post authors feeds', 'wordpress-seo' ), 'remove_feed_post_types' => \__( 'Post type feeds', 'wordpress-seo' ), 'remove_feed_categories' => \__( 'Category feeds', 'wordpress-seo' ), 'remove_feed_tags' => \__( 'Tag feeds', 'wordpress-seo' ), 'remove_feed_custom_taxonomies' => \__( 'Custom taxonomy feeds', 'wordpress-seo' ), 'remove_feed_search' => \__( 'Search results feeds', 'wordpress-seo' ), 'remove_atom_rdf_feeds' => \__( 'Atom/RDF feeds', 'wordpress-seo' ), ]; $this->basic_settings = [ 'remove_shortlinks' => \__( 'Shortlinks', 'wordpress-seo' ), 'remove_rest_api_links' => \__( 'REST API links', 'wordpress-seo' ), 'remove_rsd_wlw_links' => \__( 'RSD / WLW links', 'wordpress-seo' ), 'remove_oembed_links' => \__( 'oEmbed links', 'wordpress-seo' ), 'remove_generator' => \__( 'Generator tag', 'wordpress-seo' ), 'remove_pingback_header' => \__( 'Pingback HTTP header', 'wordpress-seo' ), 'remove_powered_by_header' => \__( 'Powered by HTTP header', 'wordpress-seo' ), ]; $this->permalink_cleanup_settings = [ 'clean_campaign_tracking_urls' => \__( 'Campaign tracking URL parameters', 'wordpress-seo' ), 'clean_permalinks' => \__( 'Unregistered URL parameters', 'wordpress-seo' ), ]; $this->search_cleanup_settings = [ 'search_cleanup' => \__( 'Filter search terms', 'wordpress-seo' ), 'search_cleanup_emoji' => \__( 'Filter searches with emojis and other special characters', 'wordpress-seo' ), 'search_cleanup_patterns' => \__( 'Filter searches with common spam patterns', 'wordpress-seo' ), 'deny_search_crawling' => \__( 'Prevent search engines from crawling site search URLs', 'wordpress-seo' ), 'redirect_search_pretty_urls' => \__( 'Redirect pretty URLs for search pages to raw format', 'wordpress-seo' ), ]; $this->unused_resources_settings = [ 'remove_emoji_scripts' => \__( 'Emoji scripts', 'wordpress-seo' ), 'deny_wp_json_crawling' => \__( 'Prevent search engines from crawling /wp-json/', 'wordpress-seo' ), 'deny_adsbot_crawling' => \__( 'Prevent Google AdsBot from crawling', 'wordpress-seo' ), ]; } /** * Adds content to the Crawl Cleanup network tab. * * @param Yoast_Form $yform The yoast form object. * * @return void */ public function add_crawl_settings_tab_content_network( $yform ) { $this->add_crawl_settings( $yform ); } /** * Print the settings sections. * * @param Yoast_Form $yform The Yoast form class. * * @return void */ private function add_crawl_settings( $yform ) { $this->print_toggles( $this->basic_settings, $yform, \__( 'Basic crawl settings', 'wordpress-seo' ) ); $this->print_toggles( $this->feed_settings, $yform, \__( 'Feed crawl settings', 'wordpress-seo' ) ); $this->print_toggles( $this->unused_resources_settings, $yform, \__( 'Remove unused resources', 'wordpress-seo' ) ); $first_search_setting = \array_slice( $this->search_cleanup_settings, 0, 1 ); $rest_search_settings = \array_slice( $this->search_cleanup_settings, 1 ); $search_settings_toggles = [ 'off' => \__( 'Disabled', 'wordpress-seo' ), 'on' => \__( 'Enabled', 'wordpress-seo' ), ]; $this->print_toggles( $first_search_setting, $yform, \__( 'Search cleanup settings', 'wordpress-seo' ), $search_settings_toggles ); $this->print_toggles( $rest_search_settings, $yform, '', $search_settings_toggles ); $permalink_warning = \sprintf( /* Translators: %1$s expands to an opening anchor tag for a link leading to the Yoast SEO page of the Permalink Cleanup features, %2$s expands to a closing anchor tag. */ \esc_html__( 'These are expert features, so make sure you know what you\'re doing before removing the parameters. %1$sRead more about how your site can be affected%2$s.', 'wordpress-seo' ), '<a href="' . \esc_url( $this->shortlinker->build_shortlink( 'https://yoa.st/permalink-cleanup' ) ) . '" target="_blank" rel="noopener noreferrer">', '</a>' ); $this->print_toggles( $this->permalink_cleanup_settings, $yform, \__( 'Permalink cleanup settings', 'wordpress-seo' ), [], $permalink_warning ); // Add the original option as hidden, so as not to lose any values if it's disabled and the form is saved. $yform->hidden( 'clean_permalinks_extra_variables', 'clean_permalinks_extra_variables' ); } /** * Prints a list of toggles for an array of settings with labels. * * @param array $settings The settings being displayed. * @param Yoast_Form $yform The Yoast form class. * @param string $title Optional title for the settings being displayed. * @param array $toggles Optional naming of the toggle buttons. * @param string $warning Optional warning to be displayed above the toggles. * * @return void */ private function print_toggles( array $settings, Yoast_Form $yform, $title = '', $toggles = [], $warning = '' ) { if ( ! empty( $title ) ) { echo '<h3 class="yoast-crawl-settings">', \esc_html( $title ), '</h3>'; } if ( ! empty( $warning ) ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in Alert_Presenter. echo new Alert_Presenter( $warning, 'warning' ); } if ( empty( $toggles ) ) { $toggles = [ 'off' => \__( 'Keep', 'wordpress-seo' ), 'on' => \__( 'Remove', 'wordpress-seo' ), ]; } $setting_prefix = WPSEO_Option::ALLOW_KEY_PREFIX; $toggles = [ // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO. 'on' => \__( 'Allow Control', 'wordpress-seo' ), // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch -- Reason: text is originally from Yoast SEO. 'off' => \__( 'Disable', 'wordpress-seo' ), ]; foreach ( $settings as $setting => $label ) { $attr = []; $variable = $setting_prefix . $setting; if ( $this->should_feature_be_disabled_permalink( $setting ) ) { $attr = [ 'disabled' => true, ]; $variable = $setting_prefix . $setting . '_disabled'; // Also add the original option as hidden, so as not to lose any values if it's disabled and the form is saved. $yform->hidden( $setting_prefix . $setting, $setting_prefix . $setting ); } elseif ( $this->should_feature_be_disabled_multisite( $setting ) ) { $attr = [ 'disabled' => true, 'preserve_disabled_value' => false, ]; } $yform->toggle_switch( $variable, $toggles, $label, '', $attr ); if ( $this->should_feature_be_disabled_permalink( $setting ) ) { echo '<p class="yoast-crawl-settings-help">'; if ( \current_user_can( 'manage_options' ) ) { \printf( /* translators: 1: Link start tag to the Permalinks settings page, 2: Link closing tag. */ \esc_html__( 'This feature is disabled when your site is not using %1$spretty permalinks%2$s.', 'wordpress-seo' ), '<a href="' . \esc_url( \admin_url( 'options-permalink.php' ) ) . '">', '</a>' ); } else { echo \esc_html__( 'This feature is disabled when your site is not using pretty permalinks.', 'wordpress-seo' ); } echo '</p>'; } } } /** * Checks if the feature should be disabled due to non-pretty permalinks. * * @param string $setting The setting to be displayed. * * @return bool */ protected function should_feature_be_disabled_permalink( $setting ) { return ( \in_array( $setting, [ 'clean_permalinks', 'clean_campaign_tracking_urls' ], true ) && empty( \get_option( 'permalink_structure' ) ) ); } /** * Checks if the feature should be disabled due to the site being a multisite. * * @param string $setting The setting to be displayed. * * @return bool */ protected function should_feature_be_disabled_multisite( $setting ) { return ( \in_array( $setting, [ 'deny_search_crawling', 'deny_wp_json_crawling', 'deny_adsbot_crawling' ], true ) && \is_multisite() ); } } integrations/admin/helpscout-beacon.php 0000644 00000031425 15025657560 0014326 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use WPSEO_Tracking_Server_Data; use WPSEO_Utils; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Config\Migration_Status; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Academy_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Integrations\Settings_Integration; use Yoast\WP\SEO\Integrations\Support_Integration; /** * Class WPSEO_HelpScout */ class HelpScout_Beacon implements Integration_Interface { /** * The id for the beacon. * * @var string */ protected $beacon_id = '2496aba6-0292-489c-8f5d-1c0fba417c2f'; /** * The id for the beacon for users that have tracking on. * * @var string */ protected $beacon_id_tracking_users = '6b8e74c5-aa81-4295-b97b-c2a62a13ea7f'; /** * The products the beacon is loaded for. * * @var array */ protected $products = []; /** * Whether to ask the user's consent before loading in HelpScout. * * @var bool */ protected $ask_consent = true; /** * The options helper. * * @var Options_Helper */ protected $options; /** * The array of pages we need to show the beacon on with their respective beacon IDs. * * @var array */ protected $pages_ids; /** * The array of pages we need to show the beacon on. * * @var array */ protected $base_pages = [ 'wpseo_dashboard', Settings_Integration::PAGE, Academy_Integration::PAGE, Support_Integration::PAGE, 'wpseo_search_console', 'wpseo_tools', 'wpseo_licenses', 'wpseo_workouts', 'wpseo_integrations', ]; /** * The current admin page * * @var string|null */ protected $page; /** * The asset manager. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * The migration status object. * * @var Migration_Status */ protected $migration_status; /** * Headless_Rest_Endpoints_Enabled_Conditional constructor. * * @param Options_Helper $options The options helper. * @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager. * @param Migration_Status $migration_status The migrations status. */ public function __construct( Options_Helper $options, WPSEO_Admin_Asset_Manager $asset_manager, Migration_Status $migration_status ) { $this->options = $options; $this->asset_manager = $asset_manager; $this->ask_consent = ! $this->options->get( 'tracking' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $this->page = \sanitize_text_field( \wp_unslash( $_GET['page'] ) ); } else { $this->page = null; } $this->migration_status = $migration_status; foreach ( $this->base_pages as $page ) { if ( $this->ask_consent ) { // We want to be able to show surveys to people who have tracking on, so we give them a different beacon. $this->pages_ids[ $page ] = $this->beacon_id_tracking_users; } else { $this->pages_ids[ $page ] = $this->beacon_id; } } } /** * {@inheritDoc} */ public function register_hooks() { \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_help_scout_script' ] ); \add_action( 'admin_footer', [ $this, 'output_beacon_js' ] ); } /** * Enqueues the HelpScout script. * * @return void */ public function enqueue_help_scout_script() { // Make sure plugins can filter in their "stuff", before we check whether we're outputting a beacon. $this->filter_settings(); if ( ! $this->is_beacon_page() ) { return; } $this->asset_manager->enqueue_script( 'help-scout-beacon' ); } /** * Outputs a small piece of javascript for the beacon. * * @return void */ public function output_beacon_js() { if ( ! $this->is_beacon_page() ) { return; } \printf( '<script type="text/javascript">window.%1$s(\'%2$s\', %3$s)</script>', ( $this->ask_consent ) ? 'wpseoHelpScoutBeaconConsent' : 'wpseoHelpScoutBeacon', \esc_html( $this->pages_ids[ $this->page ] ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- escaping done in format_json_encode. WPSEO_Utils::format_json_encode( (array) $this->get_session_data() ) ); } /** * Checks if the current page is a page containing the beacon. * * @return bool */ private function is_beacon_page() { $return = false; if ( ! empty( $this->page ) && $GLOBALS['pagenow'] === 'admin.php' && isset( $this->pages_ids[ $this->page ] ) ) { $return = true; } /** * Filter: 'wpseo_helpscout_show_beacon' - Allows overriding whether we show the HelpScout beacon. * * @param bool $show_beacon Whether we show the beacon or not. */ return \apply_filters( 'wpseo_helpscout_show_beacon', $return ); } /** * Retrieves the identifying data. * * @return string The data to pass as identifying data. */ protected function get_session_data() { // Short-circuit if we can get the needed data from a transient. $transient_data = \get_transient( 'yoast_beacon_session_data' ); if ( \is_array( $transient_data ) ) { return WPSEO_Utils::format_json_encode( $transient_data ); } $current_user = \wp_get_current_user(); // Do not make these strings translatable! They are for our support agents, the user won't see them! $data = \array_merge( [ 'name' => \trim( $current_user->user_firstname . ' ' . $current_user->user_lastname ), 'email' => $current_user->user_email, 'Languages' => $this->get_language_settings(), ], $this->get_server_info(), [ 'WordPress Version' => $this->get_wordpress_version(), 'Active theme' => $this->get_theme_info(), 'Active plugins' => $this->get_active_plugins(), 'Must-use and dropins' => $this->get_mustuse_and_dropins(), 'Indexables status' => $this->get_indexables_status(), ] ); if ( ! empty( $this->products ) ) { $addon_manager = new WPSEO_Addon_Manager(); foreach ( $this->products as $product ) { $subscription = $addon_manager->get_subscription( $product ); if ( ! $subscription ) { continue; } $data[ $subscription->product->name ] = $this->get_product_info( $subscription ); } } // Store the data in a transient for 5 minutes to prevent overhead on every backend pageload. \set_transient( 'yoast_beacon_session_data', $data, ( 5 * \MINUTE_IN_SECONDS ) ); return WPSEO_Utils::format_json_encode( $data ); } /** * Returns basic info about the server software. * * @return array */ private function get_server_info() { $server_tracking_data = new WPSEO_Tracking_Server_Data(); $server_data = $server_tracking_data->get(); $server_data = $server_data['server']; $fields_to_use = [ 'Server IP' => 'ip', 'PHP Version' => 'PhpVersion', 'cURL Version' => 'CurlVersion', ]; $server_data['CurlVersion'] = $server_data['CurlVersion']['version'] . ' (SSL Support ' . $server_data['CurlVersion']['sslSupport'] . ')'; $server_info = []; foreach ( $fields_to_use as $label => $field_to_use ) { if ( isset( $server_data[ $field_to_use ] ) ) { $server_info[ $label ] = \esc_html( $server_data[ $field_to_use ] ); } } // Get the memory limits for the server and, if different, from WordPress as well. $memory_limit = \ini_get( 'memory_limit' ); $server_info['Memory limits'] = 'Server memory limit: ' . $memory_limit; if ( $memory_limit !== \WP_MEMORY_LIMIT ) { $server_info['Memory limits'] .= ', WP_MEMORY_LIMIT: ' . \WP_MEMORY_LIMIT; } if ( $memory_limit !== \WP_MAX_MEMORY_LIMIT ) { $server_info['Memory limits'] .= ', WP_MAX_MEMORY_LIMIT: ' . \WP_MAX_MEMORY_LIMIT; } return $server_info; } /** * Returns info about the Yoast SEO plugin version and license. * * @param object $plugin The plugin. * * @return string The product info. */ private function get_product_info( $plugin ) { if ( empty( $plugin ) ) { return ''; } $product_info = \sprintf( 'Expiration date %1$s', $plugin->expiry_date ); return $product_info; } /** * Returns the WordPress version + a suffix about the multisite status. * * @return string The WordPress version string. */ private function get_wordpress_version() { global $wp_version; $wordpress_version = $wp_version; if ( \is_multisite() ) { $wordpress_version .= ' (multisite: yes)'; } else { $wordpress_version .= ' (multisite: no)'; } return $wordpress_version; } /** * Returns information about the current theme. * * @return string The theme info as string. */ private function get_theme_info() { $theme = \wp_get_theme(); $theme_info = \sprintf( '%1$s (Version %2$s, %3$s)', \esc_html( $theme->display( 'Name' ) ), \esc_html( $theme->display( 'Version' ) ), \esc_attr( $theme->display( 'ThemeURI' ) ) ); if ( \is_child_theme() ) { $theme_info .= \sprintf( ', this is a child theme of: %1$s', \esc_html( $theme->display( 'Template' ) ) ); } return $theme_info; } /** * Returns a stringified list of all active plugins, separated by a pipe. * * @return string The active plugins. */ private function get_active_plugins() { $updates_available = \get_site_transient( 'update_plugins' ); $active_plugins = ''; foreach ( \wp_get_active_and_valid_plugins() as $plugin ) { $plugin_data = \get_plugin_data( $plugin ); $plugin_file = \str_replace( \trailingslashit( \WP_PLUGIN_DIR ), '', $plugin ); $plugin_update_available = ''; if ( isset( $updates_available->response[ $plugin_file ] ) ) { $plugin_update_available = ' [update available]'; } $active_plugins .= \sprintf( '%1$s (Version %2$s%3$s, %4$s) | ', \esc_html( $plugin_data['Name'] ), \esc_html( $plugin_data['Version'] ), $plugin_update_available, \esc_attr( $plugin_data['PluginURI'] ) ); } return $active_plugins; } /** * Returns a CSV list of all must-use and drop-in plugins. * * @return string The active plugins. */ private function get_mustuse_and_dropins() { $dropins = \get_dropins(); $mustuse_plugins = \get_mu_plugins(); if ( ! \is_array( $dropins ) ) { $dropins = []; } if ( ! \is_array( $mustuse_plugins ) ) { $mustuse_plugins = []; } return \sprintf( 'Must-Use plugins: %1$d, Drop-ins: %2$d', \count( $mustuse_plugins ), \count( $dropins ) ); } /** * Return the indexables status details. * * @return string The indexables status in a string. */ private function get_indexables_status() { $indexables_status = 'Indexing completed: '; $indexing_completed = $this->options->get( 'indexables_indexing_completed' ); $indexing_reason = $this->options->get( 'indexing_reason' ); $indexables_status .= ( $indexing_completed ) ? 'yes' : 'no'; $indexables_status .= ( $indexing_reason ) ? ', latest indexing reason: ' . \esc_html( $indexing_reason ) : ''; foreach ( [ 'free', 'premium' ] as $migration_name ) { $current_status = $this->migration_status->get_error( $migration_name ); if ( \is_array( $current_status ) && isset( $current_status['message'] ) ) { $indexables_status .= ', migration error: ' . \esc_html( $current_status['message'] ); } } return $indexables_status; } /** * Returns language settings for the website and the current user. * * @return string The locale settings of the site and user. */ private function get_language_settings() { $site_locale = \get_locale(); $user_locale = \get_user_locale(); $language_settings = \sprintf( 'Site locale: %1$s, user locale: %2$s', ( \is_string( $site_locale ) ) ? \esc_html( $site_locale ) : 'unknown', ( \is_string( $user_locale ) ) ? \esc_html( $user_locale ) : 'unknown' ); return $language_settings; } /** * Returns the conditionals based on which this integration should be active. * * @return array The array of conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Allows filtering of the HelpScout settings. Hooked to admin_head to prevent timing issues, not too early, not too late. * * @return void */ protected function filter_settings() { $filterable_helpscout_setting = [ 'products' => $this->products, 'pages_ids' => $this->pages_ids, ]; /** * Filter: 'wpseo_helpscout_beacon_settings' - Allows overriding the HelpScout beacon settings. * * @param string $beacon_settings The HelpScout beacon settings. */ $helpscout_settings = \apply_filters( 'wpseo_helpscout_beacon_settings', $filterable_helpscout_setting ); $this->products = $helpscout_settings['products']; $this->pages_ids = $helpscout_settings['pages_ids']; } } integrations/admin/first-time-configuration-notice-integration.php 0000644 00000011772 15025657560 0021626 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\First_Time_Configuration_Notice_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter; /** * First_Time_Configuration_Notice_Integration class */ class First_Time_Configuration_Notice_Integration implements Integration_Interface { /** * The options' helper. * * @var Options_Helper */ private $options_helper; /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * The first time configuration notice helper. * * @var First_Time_Configuration_Notice_Helper */ private $first_time_configuration_notice_helper; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * First_Time_Configuration_Notice_Integration constructor. * * @param Options_Helper $options_helper The options helper. * @param First_Time_Configuration_Notice_Helper $first_time_configuration_notice_helper The first time configuration notice helper. * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. */ public function __construct( Options_Helper $options_helper, First_Time_Configuration_Notice_Helper $first_time_configuration_notice_helper, WPSEO_Admin_Asset_Manager $admin_asset_manager ) { $this->options_helper = $options_helper; $this->admin_asset_manager = $admin_asset_manager; $this->first_time_configuration_notice_helper = $first_time_configuration_notice_helper; } /** * {@inheritDoc} */ public function register_hooks() { \add_action( 'wp_ajax_dismiss_first_time_configuration_notice', [ $this, 'dismiss_first_time_configuration_notice' ] ); \add_action( 'admin_notices', [ $this, 'first_time_configuration_notice' ] ); } /** * Dismisses the First-time configuration notice. * * @return bool */ public function dismiss_first_time_configuration_notice() { // Check for nonce. if ( ! \check_ajax_referer( 'wpseo-dismiss-first-time-configuration-notice', 'nonce', false ) ) { return false; } return $this->options_helper->set( 'dismiss_configuration_workout_notice', true ); } /** * Determines whether and where the "First-time SEO Configuration" admin notice should be displayed. * * @return bool Whether the "First-time SEO Configuration" admin notice should be displayed. */ public function should_display_first_time_configuration_notice() { return $this->first_time_configuration_notice_helper->should_display_first_time_configuration_notice(); } /** * Displays an admin notice when the first-time configuration has not been finished yet. * * @return void */ public function first_time_configuration_notice() { if ( ! $this->should_display_first_time_configuration_notice() ) { return; } $this->admin_asset_manager->enqueue_style( 'monorepo' ); $title = $this->first_time_configuration_notice_helper->get_first_time_configuration_title(); $link_url = \esc_url( \self_admin_url( 'admin.php?page=wpseo_dashboard#/first-time-configuration' ) ); if ( ! $this->first_time_configuration_notice_helper->should_show_alternate_message() ) { $content = \sprintf( /* translators: 1: Link start tag to the first-time configuration, 2: Yoast SEO, 3: Link closing tag. */ \__( 'Get started quickly with the %1$s%2$s First-time configuration%3$s and configure Yoast SEO with the optimal SEO settings for your site!', 'wordpress-seo' ), '<a href="' . $link_url . '">', 'Yoast SEO', '</a>' ); } else { $content = \sprintf( /* translators: 1: Link start tag to the first-time configuration, 2: Link closing tag. */ \__( 'We noticed that you haven\'t fully configured Yoast SEO yet. Optimize your SEO settings even further by using our improved %1$s First-time configuration%2$s.', 'wordpress-seo' ), '<a href="' . $link_url . '">', '</a>' ); } $notice = new Notice_Presenter( $title, $content, 'mirrored_fit_bubble_woman_1_optim.svg', null, true, 'yoast-first-time-configuration-notice' ); //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from present() is considered safe. echo $notice->present(); // Enable permanently dismissing the notice. echo '<script> jQuery( document ).ready( function() { jQuery( "body" ).on( "click", "#yoast-first-time-configuration-notice .notice-dismiss", function() { jQuery( "#yoast-first-time-configuration-notice" ).hide(); const data = { "action": "dismiss_first_time_configuration_notice", "nonce": "' . \esc_js( \wp_create_nonce( 'wpseo-dismiss-first-time-configuration-notice' ) ) . '" }; jQuery.post( ajaxurl, data, function( response ) {}); } ); } ); </script>'; } } integrations/admin/addon-installation/dialog-integration.php 0000644 00000006661 15025657560 0020443 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Discussed in Tech Council, a better solution is being worked on. namespace Yoast\WP\SEO\Integrations\Admin\Addon_Installation; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional; use Yoast\WP\SEO\Conditionals\Admin\Licenses_Page_Conditional; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Represents the Addon installation feature. */ class Dialog_Integration implements Integration_Interface { /** * The addon manager. * * @var WPSEO_Addon_Manager */ protected $addon_manager; /** * The addons. * * @var array */ protected $owned_addons; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class, Licenses_Page_Conditional::class, Addon_Installation_Conditional::class, ]; } /** * Addon_Installation constructor. * * @param WPSEO_Addon_Manager $addon_manager The addon manager. */ public function __construct( WPSEO_Addon_Manager $addon_manager ) { $this->addon_manager = $addon_manager; } /** * Registers all hooks to WordPress. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'start_addon_installation' ] ); } /** * Starts the addon installation flow. * * @return void */ public function start_addon_installation() { // Only show the dialog when we explicitly want to see it. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: This is not a form. if ( ! isset( $_GET['install'] ) || $_GET['install'] !== 'true' ) { return; } $this->bust_myyoast_addon_information_cache(); $this->owned_addons = $this->get_owned_addons(); if ( \count( $this->owned_addons ) > 0 ) { \add_action( 'admin_enqueue_scripts', [ $this, 'show_modal' ] ); } else { \add_action( 'admin_notices', [ $this, 'throw_no_owned_addons_warning' ] ); } } /** * Throws a no owned addons warning. * * @return void */ public function throw_no_owned_addons_warning() { echo '<div class="notice notice-warning"><p>' . \sprintf( /* translators: %1$s expands to Yoast SEO */ \esc_html__( 'No %1$s plugins have been installed. You don\'t seem to own any active subscriptions.', 'wordpress-seo' ), 'Yoast SEO' ) . '</p></div>'; } /** * Shows the modal. * * @return void */ public function show_modal() { \wp_localize_script( WPSEO_Admin_Asset_Manager::PREFIX . 'addon-installation', 'wpseoAddonInstallationL10n', [ 'addons' => $this->owned_addons, 'nonce' => \wp_create_nonce( 'wpseo_addon_installation' ), ] ); $asset_manager = new WPSEO_Admin_Asset_Manager(); $asset_manager->enqueue_script( 'addon-installation' ); } /** * Retrieves a list of owned addons for the site in MyYoast. * * @return array List of owned addons with slug as key and name as value. */ protected function get_owned_addons() { $owned_addons = []; foreach ( $this->addon_manager->get_myyoast_site_information()->subscriptions as $addon ) { $owned_addons[] = $addon->product->name; } return $owned_addons; } /** * Bust the site information transients to have fresh data. * * @return void */ protected function bust_myyoast_addon_information_cache() { $this->addon_manager->remove_site_information_transients(); } } integrations/admin/addon-installation/installation-integration.php 0000644 00000013675 15025657560 0021710 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Discussed in Tech Council, a better solution is being worked on. namespace Yoast\WP\SEO\Integrations\Admin\Addon_Installation; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Actions\Addon_Installation\Addon_Activate_Action; use Yoast\WP\SEO\Actions\Addon_Installation\Addon_Install_Action; use Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional; use Yoast\WP\SEO\Conditionals\Admin\Licenses_Page_Conditional; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Activation_Error_Exception; use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Already_Installed_Exception; use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Installation_Error_Exception; use Yoast\WP\SEO\Exceptions\Addon_Installation\User_Cannot_Activate_Plugins_Exception; use Yoast\WP\SEO\Exceptions\Addon_Installation\User_Cannot_Install_Plugins_Exception; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Represents the Addon installation feature. */ class Installation_Integration implements Integration_Interface { /** * The installation action. * * @var Addon_Install_Action */ protected $addon_install_action; /** * The activation action. * * @var Addon_Activate_Action */ protected $addon_activate_action; /** * The addon manager. * * @var WPSEO_Addon_Manager */ protected $addon_manager; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class, Licenses_Page_Conditional::class, Addon_Installation_Conditional::class, ]; } /** * Addon_Installation constructor. * * @param WPSEO_Addon_Manager $addon_manager The addon manager. * @param Addon_Activate_Action $addon_activate_action The addon activate action. * @param Addon_Install_Action $addon_install_action The addon install action. */ public function __construct( WPSEO_Addon_Manager $addon_manager, Addon_Activate_Action $addon_activate_action, Addon_Install_Action $addon_install_action ) { $this->addon_manager = $addon_manager; $this->addon_activate_action = $addon_activate_action; $this->addon_install_action = $addon_install_action; } /** * Registers all hooks to WordPress. * * @return void */ public function register_hooks() { \add_action( 'wpseo_install_and_activate_addons', [ $this, 'install_and_activate_addons' ] ); } /** * Installs and activates missing addons. * * @return void */ public function install_and_activate_addons() { if ( ! isset( $_GET['action'] ) || ! \is_string( $_GET['action'] ) ) { return; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are only strictly comparing action below. $action = \wp_unslash( $_GET['action'] ); if ( $action !== 'install' ) { return; } \check_admin_referer( 'wpseo_addon_installation', 'nonce' ); echo '<div class="wrap yoast wpseo_table_page">'; \printf( '<h1 id="wpseo-title" class="yoast-h1">%s</h1>', \esc_html__( 'Installing and activating addons', 'wordpress-seo' ) ); $licensed_addons = $this->addon_manager->get_myyoast_site_information()->subscriptions; foreach ( $licensed_addons as $addon ) { \printf( '<p><strong>%s</strong></p>', \esc_html( $addon->product->name ) ); list( $installed, $output ) = $this->install_addon( $addon->product->slug, $addon->product->download ); if ( $installed ) { $activation_output = $this->activate_addon( $addon->product->slug ); $output = \array_merge( $output, $activation_output ); } echo '<p>'; echo \implode( '<br />', \array_map( 'esc_html', $output ) ); echo '</p>'; } \printf( /* translators: %1$s expands to an anchor tag to the admin premium page, %2$s expands to Yoast SEO Premium, %3$s expands to a closing anchor tag */ \esc_html__( '%1$s Continue to %2$s%3$s', 'wordpress-seo' ), '<a href="' . \esc_url( \admin_url( 'admin.php?page=wpseo_licenses' ) ) . '">', 'Yoast SEO Premium', '</a>' ); echo '</div>'; exit; } /** * Activates an addon. * * @param string $addon_slug The addon to activate. * * @return array The output of the activation. */ public function activate_addon( $addon_slug ) { $output = []; try { $this->addon_activate_action->activate_addon( $addon_slug ); /* Translators: %s expands to the name of the addon. */ $output[] = \__( 'Addon activated.', 'wordpress-seo' ); } catch ( User_Cannot_Activate_Plugins_Exception $exception ) { $output[] = \__( 'You are not allowed to activate plugins.', 'wordpress-seo' ); } catch ( Addon_Activation_Error_Exception $exception ) { $output[] = \sprintf( /* Translators:%s expands to the error message. */ \__( 'Addon activation failed because of an error: %s.', 'wordpress-seo' ), $exception->getMessage() ); } return $output; } /** * Installs an addon. * * @param string $addon_slug The slug of the addon to install. * @param string $addon_download The download URL of the addon. * * @return array The installation success state and the output of the installation. */ public function install_addon( $addon_slug, $addon_download ) { $installed = false; $output = []; try { $installed = $this->addon_install_action->install_addon( $addon_slug, $addon_download ); } catch ( Addon_Already_Installed_Exception $exception ) { /* Translators: %s expands to the name of the addon. */ $output[] = \__( 'Addon installed.', 'wordpress-seo' ); $installed = true; } catch ( User_Cannot_Install_Plugins_Exception $exception ) { $output[] = \__( 'You are not allowed to install plugins.', 'wordpress-seo' ); } catch ( Addon_Installation_Error_Exception $exception ) { $output[] = \sprintf( /* Translators: %s expands to the error message. */ \__( 'Addon installation failed because of an error: %s.', 'wordpress-seo' ), $exception->getMessage() ); } return [ $installed, $output ]; } } integrations/admin/first-time-configuration-integration.php 0000644 00000036264 15025657560 0020352 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WP_User; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use WPSEO_Option_Tab; use WPSEO_Shortlinker; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\General\User_Interface\General_Page_Integration; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Social_Profiles_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Routes\Indexing_Route; /** * First_Time_Configuration_Integration class */ class First_Time_Configuration_Integration implements Integration_Interface { /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * The addon manager. * * @var WPSEO_Addon_Manager */ private $addon_manager; /** * The shortlinker. * * @var WPSEO_Shortlinker */ private $shortlinker; /** * The options' helper. * * @var Options_Helper */ private $options_helper; /** * The social profiles helper. * * @var Social_Profiles_Helper */ private $social_profiles_helper; /** * The product helper. * * @var Product_Helper */ private $product_helper; /** * The meta tags context helper. * * @var Meta_Tags_Context */ private $meta_tags_context; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * First_Time_Configuration_Integration constructor. * * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. * @param WPSEO_Addon_Manager $addon_manager The addon manager. * @param WPSEO_Shortlinker $shortlinker The shortlinker. * @param Options_Helper $options_helper The options helper. * @param Social_Profiles_Helper $social_profiles_helper The social profile helper. * @param Product_Helper $product_helper The product helper. * @param Meta_Tags_Context $meta_tags_context The meta tags context helper. */ public function __construct( WPSEO_Admin_Asset_Manager $admin_asset_manager, WPSEO_Addon_Manager $addon_manager, WPSEO_Shortlinker $shortlinker, Options_Helper $options_helper, Social_Profiles_Helper $social_profiles_helper, Product_Helper $product_helper, Meta_Tags_Context $meta_tags_context ) { $this->admin_asset_manager = $admin_asset_manager; $this->addon_manager = $addon_manager; $this->shortlinker = $shortlinker; $this->options_helper = $options_helper; $this->social_profiles_helper = $social_profiles_helper; $this->product_helper = $product_helper; $this->meta_tags_context = $meta_tags_context; } /** * {@inheritDoc} */ public function register_hooks() { \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); \add_action( 'wpseo_settings_tabs_dashboard', [ $this, 'add_first_time_configuration_tab' ] ); } /** * Adds a dedicated tab in the General sub-page. * * @param WPSEO_Options_Tabs $dashboard_tabs Object representing the tabs of the General sub-page. * * @return void */ public function add_first_time_configuration_tab( $dashboard_tabs ) { $dashboard_tabs->add_tab( new WPSEO_Option_Tab( 'first-time-configuration', \__( 'First-time configuration', 'wordpress-seo' ), [ 'save_button' => false ] ) ); } /** * Adds the data for the first-time configuration to the wpseoFirstTimeConfigurationData object. * * @return void */ public function enqueue_assets() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved. if ( ! isset( $_GET['page'] ) || ( $_GET['page'] !== 'wpseo_dashboard' && $_GET['page'] !== General_Page_Integration::PAGE ) || \is_network_admin() ) { return; } $this->admin_asset_manager->enqueue_script( 'indexation' ); $this->admin_asset_manager->enqueue_style( 'first-time-configuration' ); $this->admin_asset_manager->enqueue_style( 'admin-css' ); $this->admin_asset_manager->enqueue_style( 'monorepo' ); $data = [ 'disabled' => ! \YoastSEO()->helpers->indexable->should_index_indexables(), 'amount' => \YoastSEO()->helpers->indexing->get_filtered_unindexed_count(), 'firstTime' => ( \YoastSEO()->helpers->indexing->is_initial_indexing() === true ), 'errorMessage' => '', 'restApi' => [ 'root' => \esc_url_raw( \rest_url() ), 'indexing_endpoints' => $this->get_endpoints(), 'nonce' => \wp_create_nonce( 'wp_rest' ), ], ]; /** * Filter: 'wpseo_indexing_data' Filter to adapt the data used in the indexing process. * * @param array $data The indexing data to adapt. */ $data = \apply_filters( 'wpseo_indexing_data', $data ); $this->admin_asset_manager->localize_script( 'indexation', 'yoastIndexingData', $data ); $person_id = $this->get_person_id(); $social_profiles = $this->get_social_profiles(); // This filter is documented in admin/views/tabs/metas/paper-content/general/knowledge-graph.php. $knowledge_graph_message = \apply_filters( 'wpseo_knowledge_graph_setting_msg', '' ); $finished_steps = $this->get_finished_steps(); $options = $this->get_company_or_person_options(); $selected_option_label = ''; $filtered_options = \array_filter( $options, function ( $item ) { return $item['value'] === $this->is_company_or_person(); } ); $selected_option = \reset( $filtered_options ); if ( \is_array( $selected_option ) ) { $selected_option_label = $selected_option['label']; } $data_ftc = [ 'canEditUser' => $this->can_edit_profile( $person_id ), 'companyOrPerson' => $this->is_company_or_person(), 'companyOrPersonLabel' => $selected_option_label, 'companyName' => $this->get_company_name(), 'fallbackCompanyName' => $this->get_fallback_company_name( $this->get_company_name() ), 'websiteName' => $this->get_website_name(), 'fallbackWebsiteName' => $this->get_fallback_website_name( $this->get_website_name() ), 'companyLogo' => $this->get_company_logo(), 'companyLogoFallback' => $this->get_company_fallback_logo( $this->get_company_logo() ), 'companyLogoId' => $this->get_person_logo_id(), 'finishedSteps' => $finished_steps, 'personId' => (int) $person_id, 'personName' => $this->get_person_name(), 'personLogo' => $this->get_person_logo(), 'personLogoFallback' => $this->get_person_fallback_logo( $this->get_person_logo() ), 'personLogoId' => $this->get_person_logo_id(), 'siteTagline' => $this->get_site_tagline(), 'socialProfiles' => [ 'facebookUrl' => $social_profiles['facebook_site'], 'twitterUsername' => $social_profiles['twitter_site'], 'otherSocialUrls' => $social_profiles['other_social_urls'], ], 'isPremium' => $this->product_helper->is_premium(), 'tracking' => $this->has_tracking_enabled(), 'isTrackingAllowedMultisite' => $this->is_tracking_enabled_multisite(), 'isMainSite' => $this->is_main_site(), 'companyOrPersonOptions' => $options, 'shouldForceCompany' => $this->should_force_company(), 'knowledgeGraphMessage' => $knowledge_graph_message, 'shortlinks' => [ 'gdpr' => $this->shortlinker->build_shortlink( 'https://yoa.st/gdpr-config-workout' ), 'configIndexables' => $this->shortlinker->build_shortlink( 'https://yoa.st/config-indexables' ), 'configIndexablesBenefits' => $this->shortlinker->build_shortlink( 'https://yoa.st/config-indexables-benefits' ), ], ]; $this->admin_asset_manager->localize_script( 'general-page', 'wpseoFirstTimeConfigurationData', $data_ftc ); } /** * Retrieves a list of the endpoints to use. * * @return array The endpoints. */ protected function get_endpoints() { $endpoints = [ 'prepare' => Indexing_Route::FULL_PREPARE_ROUTE, 'terms' => Indexing_Route::FULL_TERMS_ROUTE, 'posts' => Indexing_Route::FULL_POSTS_ROUTE, 'archives' => Indexing_Route::FULL_POST_TYPE_ARCHIVES_ROUTE, 'general' => Indexing_Route::FULL_GENERAL_ROUTE, 'indexablesComplete' => Indexing_Route::FULL_INDEXABLES_COMPLETE_ROUTE, 'post_link' => Indexing_Route::FULL_POST_LINKS_INDEXING_ROUTE, 'term_link' => Indexing_Route::FULL_TERM_LINKS_INDEXING_ROUTE, ]; $endpoints = \apply_filters( 'wpseo_indexing_endpoints', $endpoints ); $endpoints['complete'] = Indexing_Route::FULL_COMPLETE_ROUTE; return $endpoints; } // ** Private functions ** // /** * Returns the finished steps array. * * @return array An array with the finished steps. */ private function get_finished_steps() { return $this->options_helper->get( 'configuration_finished_steps', [] ); } /** * Returns the entity represented by the site. * * @return string The entity represented by the site. */ private function is_company_or_person() { return $this->options_helper->get( 'company_or_person', '' ); } /** * Gets the company name from the option in the database. * * @return string The company name. */ private function get_company_name() { return $this->options_helper->get( 'company_name', '' ); } /** * Gets the fallback company name from the option in the database if there is no company name. * * @param string $company_name The given company name by the user, default empty string. * * @return string|false The company name. */ private function get_fallback_company_name( $company_name ) { if ( $company_name ) { return false; } return \get_bloginfo( 'name' ); } /** * Gets the website name from the option in the database. * * @return string The website name. */ private function get_website_name() { return $this->options_helper->get( 'website_name', '' ); } /** * Gets the fallback website name from the option in the database if there is no website name. * * @param string $website_name The given website name by the user, default empty string. * * @return string|false The website name. */ private function get_fallback_website_name( $website_name ) { if ( $website_name ) { return false; } return \get_bloginfo( 'name' ); } /** * Gets the company logo from the option in the database. * * @return string The company logo. */ private function get_company_logo() { return $this->options_helper->get( 'company_logo', '' ); } /** * Gets the company logo id from the option in the database. * * @return string The company logo id. */ private function get_company_logo_id() { return $this->options_helper->get( 'company_logo_id', '' ); } /** * Gets the company logo url from the option in the database. * * @param string $company_logo The given company logo by the user, default empty. * * @return string|false The company logo URL. */ private function get_company_fallback_logo( $company_logo ) { if ( $company_logo ) { return false; } $logo_id = $this->meta_tags_context->fallback_to_site_logo(); return \esc_url( \wp_get_attachment_url( $logo_id ) ); } /** * Gets the person id from the option in the database. * * @return int|null The person id, null if empty. */ private function get_person_id() { return $this->options_helper->get( 'company_or_person_user_id' ); } /** * Gets the person id from the option in the database. * * @return int|null The person id, null if empty. */ private function get_person_name() { $user = \get_userdata( $this->get_person_id() ); if ( $user instanceof WP_User ) { return $user->get( 'display_name' ); } return ''; } /** * Gets the person avatar from the option in the database. * * @return string The person logo. */ private function get_person_logo() { return $this->options_helper->get( 'person_logo', '' ); } /** * Gets the person logo url from the option in the database. * * @param string $person_logo The given person logo by the user, default empty. * * @return string|false The person logo URL. */ private function get_person_fallback_logo( $person_logo ) { if ( $person_logo ) { return false; } $logo_id = $this->meta_tags_context->fallback_to_site_logo(); return \esc_url( \wp_get_attachment_url( $logo_id ) ); } /** * Gets the person logo id from the option in the database. * * @return string The person logo id. */ private function get_person_logo_id() { return $this->options_helper->get( 'person_logo_id', '' ); } /** * Gets the site tagline. * * @return string The site tagline. */ private function get_site_tagline() { return \get_bloginfo( 'description' ); } /** * Gets the social profiles stored in the database. * * @return string[] The social profiles. */ private function get_social_profiles() { return $this->social_profiles_helper->get_organization_social_profiles(); } /** * Checks whether tracking is enabled. * * @return bool True if tracking is enabled, false otherwise, null if in Free and conf. workout step not finished. */ private function has_tracking_enabled() { $default = false; if ( $this->product_helper->is_premium() ) { $default = true; } return $this->options_helper->get( 'tracking', $default ); } /** * Checks whether tracking option is allowed at network level. * * @return bool True if option change is allowed, false otherwise. */ private function is_tracking_enabled_multisite() { $default = true; if ( ! \is_multisite() ) { return $default; } return $this->options_helper->get( 'allow_tracking', $default ); } /** * Checks whether we are in a main site. * * @return bool True if it's the main site or a single site, false if it's a subsite. */ private function is_main_site() { return \is_main_site(); } /** * Gets the options for the Company or Person select. * Returns only the company option if it is forced (by Local SEO), otherwise returns company and person option. * * @return array The options for the company-or-person select. */ private function get_company_or_person_options() { $options = [ [ 'label' => \__( 'Organization', 'wordpress-seo' ), 'value' => 'company', 'id' => 'company', ], [ 'label' => \__( 'Person', 'wordpress-seo' ), 'value' => 'person', 'id' => 'person', ], ]; if ( $this->should_force_company() ) { $options = [ [ 'label' => \__( 'Organization', 'wordpress-seo' ), 'value' => 'company', 'id' => 'company', ], ]; } return $options; } /** * Checks whether we should force "Organization". * * @return bool */ private function should_force_company() { return $this->addon_manager->is_installed( WPSEO_Addon_Manager::LOCAL_SLUG ); } /** * Checks if the current user has the capability to edit a specific user. * * @param int $person_id The id of the person to edit. * * @return bool */ private function can_edit_profile( $person_id ) { return \current_user_can( 'edit_user', $person_id ); } } integrations/admin/indexing-tool-integration.php 0000644 00000016444 15025657560 0016200 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Conditionals\No_Tool_Selected_Conditional; use Yoast\WP\SEO\Conditionals\Yoast_Tools_Page_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Indexing_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Indexing_Error_Presenter; use Yoast\WP\SEO\Presenters\Admin\Indexing_List_Item_Presenter; use Yoast\WP\SEO\Routes\Importing_Route; use Yoast\WP\SEO\Routes\Indexing_Route; use Yoast\WP\SEO\Services\Importing\Importable_Detector_Service; /** * Class Indexing_Tool_Integration. Bridge to the Javascript indexing tool on Yoast SEO Tools page. * * @package Yoast\WP\SEO\Integrations\Admin */ class Indexing_Tool_Integration implements Integration_Interface { /** * Represents the admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * Represents the indexables helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * Represents the indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * The addon manager. * * @var WPSEO_Addon_Manager */ protected $addon_manager; /** * The product helper. * * @var Product_Helper */ protected $product_helper; /** * The Importable Detector service. * * @var Importable_Detector_Service */ protected $importable_detector; /** * The Importing Route class. * * @var Importing_Route */ protected $importing_route; /** * Returns the conditionals based on which this integration should be active. * * @return array The array of conditionals. */ public static function get_conditionals() { return [ Migrations_Conditional::class, No_Tool_Selected_Conditional::class, Yoast_Tools_Page_Conditional::class, ]; } /** * Indexing_Integration constructor. * * @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Short_Link_Helper $short_link_helper The short link helper. * @param Indexing_Helper $indexing_helper The indexing helper. * @param WPSEO_Addon_Manager $addon_manager The addon manager. * @param Product_Helper $product_helper The product helper. * @param Importable_Detector_Service $importable_detector The importable detector. * @param Importing_Route $importing_route The importing route. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Indexable_Helper $indexable_helper, Short_Link_Helper $short_link_helper, Indexing_Helper $indexing_helper, WPSEO_Addon_Manager $addon_manager, Product_Helper $product_helper, Importable_Detector_Service $importable_detector, Importing_Route $importing_route ) { $this->asset_manager = $asset_manager; $this->indexable_helper = $indexable_helper; $this->short_link_helper = $short_link_helper; $this->indexing_helper = $indexing_helper; $this->addon_manager = $addon_manager; $this->product_helper = $product_helper; $this->importable_detector = $importable_detector; $this->importing_route = $importing_route; } /** * Register hooks. * * @return void */ public function register_hooks() { \add_action( 'wpseo_tools_overview_list_items_internal', [ $this, 'render_indexing_list_item' ], 10 ); \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_scripts' ], 10 ); } /** * Enqueues the required scripts. * * @return void */ public function enqueue_scripts() { $this->asset_manager->enqueue_script( 'indexation' ); $this->asset_manager->enqueue_style( 'admin-css' ); $this->asset_manager->enqueue_style( 'monorepo' ); $data = [ 'disabled' => ! $this->indexable_helper->should_index_indexables(), 'amount' => $this->indexing_helper->get_filtered_unindexed_count(), 'firstTime' => ( $this->indexing_helper->is_initial_indexing() === true ), 'errorMessage' => $this->render_indexing_error(), 'restApi' => [ 'root' => \esc_url_raw( \rest_url() ), 'indexing_endpoints' => $this->get_indexing_endpoints(), 'importing_endpoints' => $this->get_importing_endpoints(), 'nonce' => \wp_create_nonce( 'wp_rest' ), ], ]; /** * Filter: 'wpseo_indexing_data' Filter to adapt the data used in the indexing process. * * @param array $data The indexing data to adapt. */ $data = \apply_filters( 'wpseo_indexing_data', $data ); $this->asset_manager->localize_script( 'indexation', 'yoastIndexingData', $data ); } /** * The error to show if optimization failed. * * @return string The error to show if optimization failed. */ protected function render_indexing_error() { $presenter = new Indexing_Error_Presenter( $this->short_link_helper, $this->product_helper, $this->addon_manager ); return $presenter->present(); } /** * Determines if the site has a valid Premium subscription. * * @return bool If the site has a valid Premium subscription. */ protected function has_valid_premium_subscription() { return $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ); } /** * Renders the indexing list item. * * @return void */ public function render_indexing_list_item() { if ( \current_user_can( 'manage_options' ) ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- The output is correctly escaped in the presenter. echo new Indexing_List_Item_Presenter( $this->short_link_helper ); } } /** * Retrieves a list of the indexing endpoints to use. * * @return array The endpoints. */ protected function get_indexing_endpoints() { $endpoints = [ 'prepare' => Indexing_Route::FULL_PREPARE_ROUTE, 'terms' => Indexing_Route::FULL_TERMS_ROUTE, 'posts' => Indexing_Route::FULL_POSTS_ROUTE, 'archives' => Indexing_Route::FULL_POST_TYPE_ARCHIVES_ROUTE, 'general' => Indexing_Route::FULL_GENERAL_ROUTE, 'indexablesComplete' => Indexing_Route::FULL_INDEXABLES_COMPLETE_ROUTE, 'post_link' => Indexing_Route::FULL_POST_LINKS_INDEXING_ROUTE, 'term_link' => Indexing_Route::FULL_TERM_LINKS_INDEXING_ROUTE, ]; $endpoints = \apply_filters( 'wpseo_indexing_endpoints', $endpoints ); $endpoints['complete'] = Indexing_Route::FULL_COMPLETE_ROUTE; return $endpoints; } /** * Retrieves a list of the importing endpoints to use. * * @return array The endpoints. */ protected function get_importing_endpoints() { $available_actions = $this->importable_detector->detect_importers(); $importing_endpoints = []; foreach ( $available_actions as $plugin => $types ) { foreach ( $types as $type ) { $importing_endpoints[ $plugin ][] = $this->importing_route->get_endpoint( $plugin, $type ); } } return $importing_endpoints; } } integrations/admin/redirects-page-integration.php 0000644 00000002660 15025657560 0016311 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Premium_Inactive_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Redirects_Page_Integration class. */ class Redirects_Page_Integration implements Integration_Interface { /** * Sets up the hooks. * * @return void */ public function register_hooks() { \add_filter( 'wpseo_submenu_pages', [ $this, 'add_submenu_page' ], 9 ); } /** * Returns the conditionals based on which this loadable should be active. * * In this case: only when on an admin page and Premium is not active. * * @return array The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class, Premium_Inactive_Conditional::class, ]; } /** * Adds the redirects submenu page. * * @param array $submenu_pages The Yoast SEO submenu pages. * * @return array The filtered submenu pages. */ public function add_submenu_page( $submenu_pages ) { $submenu_pages[] = [ 'wpseo_dashboard', '', \__( 'Redirects', 'wordpress-seo' ) . ' <span class="yoast-badge yoast-premium-badge"></span>', 'edit_others_posts', 'wpseo_redirects', [ $this, 'display' ], ]; return $submenu_pages; } /** * Displays the redirects page. * * @return void */ public function display() { require \WPSEO_PATH . 'admin/pages/redirects.php'; } } integrations/admin/health-check-integration.php 0000644 00000005373 15025657560 0015737 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Services\Health_Check\Health_Check; /** * Integrates health checks with WordPress' Site Health. */ class Health_Check_Integration implements Integration_Interface { /** * Contains all the health check implementations. * * @var Health_Check[] */ private $health_checks = []; /** * Uses the dependency injection container to obtain all available implementations of the Health_Check interface. * * @param Health_Check ...$health_checks The available health checks implementations. */ public function __construct( Health_Check ...$health_checks ) { $this->health_checks = $health_checks; } /** * Hooks the health checks into WordPress' site status tests. * * @return void */ public function register_hooks() { \add_filter( 'site_status_tests', [ $this, 'add_health_checks' ] ); } /** * Returns the conditionals based on which this loadable should be active. * * In this case: only when on an admin page. * * @return array The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Checks if the input is a WordPress site status tests array, and adds Yoast's health checks if it is. * * @param string[] $tests Array containing WordPress site status tests. * @return string[] Array containing WordPress site status tests with Yoast's health checks. */ public function add_health_checks( $tests ) { if ( ! $this->is_valid_site_status_tests_array( $tests ) ) { return $tests; } return $this->add_health_checks_to_site_status_tests( $tests ); } /** * Checks if the input array is a WordPress site status tests array. * * @param mixed $tests Array to check. * @return bool Returns true if the input array is a WordPress site status tests array. */ private function is_valid_site_status_tests_array( $tests ) { if ( ! \is_array( $tests ) ) { return false; } if ( ! \array_key_exists( 'direct', $tests ) ) { return false; } if ( ! \is_array( $tests['direct'] ) ) { return false; } return true; } /** * Adds the health checks to WordPress' site status tests. * * @param string[] $tests Array containing WordPress site status tests. * @return string[] Array containing WordPress site status tests with Yoast's health checks. */ private function add_health_checks_to_site_status_tests( $tests ) { foreach ( $this->health_checks as $health_check ) { if ( $health_check->is_excluded() ) { continue; } $tests['direct'][ $health_check->get_test_identifier() ] = [ 'test' => [ $health_check, 'run_and_get_result' ], ]; } return $tests; } } integrations/admin/link-count-columns-integration.php 0000644 00000017033 15025657560 0017154 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WP_Query; use wpdb; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action; use Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Should_Index_Links_Conditional; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Link_Count_Columns_Integration class. */ class Link_Count_Columns_Integration implements Integration_Interface { /** * Partial column name. * * @var string */ public const COLUMN_LINKED = 'linked'; /** * Partial column name. * * @var string */ public const COLUMN_LINKS = 'links'; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * The database object. * * @var wpdb */ protected $wpdb; /** * The post link builder. * * @var Post_Link_Indexing_Action */ protected $post_link_indexing_action; /** * The admin columns cache. * * @var Admin_Columns_Cache_Integration */ protected $admin_columns_cache; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class, Posts_Overview_Or_Ajax_Conditional::class, Should_Index_Links_Conditional::class, ]; } /** * Link_Count_Columns_Integration constructor * * @param Post_Type_Helper $post_type_helper The post type helper. * @param wpdb $wpdb The wpdb object. * @param Post_Link_Indexing_Action $post_link_indexing_action The post link indexing action. * @param Admin_Columns_Cache_Integration $admin_columns_cache The admin columns cache. */ public function __construct( Post_Type_Helper $post_type_helper, wpdb $wpdb, Post_Link_Indexing_Action $post_link_indexing_action, Admin_Columns_Cache_Integration $admin_columns_cache ) { $this->post_type_helper = $post_type_helper; $this->wpdb = $wpdb; $this->post_link_indexing_action = $post_link_indexing_action; $this->admin_columns_cache = $admin_columns_cache; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'posts_clauses', [ $this, 'order_by_links' ], 1, 2 ); \add_filter( 'posts_clauses', [ $this, 'order_by_linked' ], 1, 2 ); \add_action( 'admin_init', [ $this, 'register_init_hooks' ] ); // Adds a filter to exclude the attachments from the link count. \add_filter( 'wpseo_link_count_post_types', [ 'WPSEO_Post_Type', 'filter_attachment_post_type' ] ); } /** * Register hooks that need to be registered after `init` due to all post types not yet being registered. * * @return void */ public function register_init_hooks() { $public_post_types = \apply_filters( 'wpseo_link_count_post_types', $this->post_type_helper->get_accessible_post_types() ); if ( ! \is_array( $public_post_types ) || empty( $public_post_types ) ) { return; } foreach ( $public_post_types as $post_type ) { \add_filter( 'manage_' . $post_type . '_posts_columns', [ $this, 'add_post_columns' ] ); \add_action( 'manage_' . $post_type . '_posts_custom_column', [ $this, 'column_content' ], 10, 2 ); \add_filter( 'manage_edit-' . $post_type . '_sortable_columns', [ $this, 'column_sort' ] ); } } /** * Adds the columns for the post overview. * * @param array $columns Array with columns. * * @return array The extended array with columns. */ public function add_post_columns( $columns ) { if ( ! \is_array( $columns ) ) { return $columns; } $columns[ 'wpseo-' . self::COLUMN_LINKS ] = \sprintf( '<span class="yoast-linked-to yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>', \esc_attr__( 'Number of outgoing internal links in this post.', 'wordpress-seo' ), /* translators: Hidden accessibility text. */ \esc_html__( 'Outgoing internal links', 'wordpress-seo' ) ); if ( $this->post_link_indexing_action->get_total_unindexed() === 0 ) { $columns[ 'wpseo-' . self::COLUMN_LINKED ] = \sprintf( '<span class="yoast-linked-from yoast-column-header-has-tooltip" data-tooltip-text="%1$s"><span class="screen-reader-text">%2$s</span></span>', \esc_attr__( 'Number of internal links linking to this post.', 'wordpress-seo' ), /* translators: Hidden accessibility text. */ \esc_html__( 'Received internal links', 'wordpress-seo' ) ); } return $columns; } /** * Modifies the query pieces to allow ordering column by links to post. * * @param array $pieces Array of Query pieces. * @param WP_Query $query The Query on which to apply. * * @return array */ public function order_by_linked( $pieces, $query ) { if ( $query->get( 'orderby' ) !== 'wpseo-' . self::COLUMN_LINKED ) { return $pieces; } return $this->build_sort_query_pieces( $pieces, $query, 'incoming_link_count' ); } /** * Modifies the query pieces to allow ordering column by links to post. * * @param array $pieces Array of Query pieces. * @param WP_Query $query The Query on which to apply. * * @return array */ public function order_by_links( $pieces, $query ) { if ( $query->get( 'orderby' ) !== 'wpseo-' . self::COLUMN_LINKS ) { return $pieces; } return $this->build_sort_query_pieces( $pieces, $query, 'link_count' ); } /** * Builds the pieces for a sorting query. * * @param array $pieces Array of Query pieces. * @param WP_Query $query The Query on which to apply. * @param string $field The field in the table to JOIN on. * * @return array Modified Query pieces. */ protected function build_sort_query_pieces( $pieces, $query, $field ) { // We only want our code to run in the main WP query. if ( ! $query->is_main_query() ) { return $pieces; } // Get the order query variable - ASC or DESC. $order = \strtoupper( $query->get( 'order' ) ); // Make sure the order setting qualifies. If not, set default as ASC. if ( ! \in_array( $order, [ 'ASC', 'DESC' ], true ) ) { $order = 'ASC'; } $table = Model::get_table_name( 'Indexable' ); $pieces['join'] .= " LEFT JOIN $table AS yoast_indexable ON yoast_indexable.object_id = {$this->wpdb->posts}.ID AND yoast_indexable.object_type = 'post' "; $pieces['orderby'] = "yoast_indexable.$field $order, FIELD( {$this->wpdb->posts}.post_status, 'publish' ) $order, {$pieces['orderby']}"; return $pieces; } /** * Displays the column content for the given column. * * @param string $column_name Column to display the content for. * @param int $post_id Post to display the column content for. * * @return void */ public function column_content( $column_name, $post_id ) { $indexable = $this->admin_columns_cache->get_indexable( $post_id ); // Nothing to output if we don't have the value. if ( $indexable === false ) { return; } switch ( $column_name ) { case 'wpseo-' . self::COLUMN_LINKS: echo (int) $indexable->link_count; return; case 'wpseo-' . self::COLUMN_LINKED: if ( \get_post_status( $post_id ) === 'publish' ) { echo (int) $indexable->incoming_link_count; } } } /** * Sets the sortable columns. * * @param array $columns Array with sortable columns. * * @return array The extended array with sortable columns. */ public function column_sort( array $columns ) { $columns[ 'wpseo-' . self::COLUMN_LINKS ] = 'wpseo-' . self::COLUMN_LINKS; $columns[ 'wpseo-' . self::COLUMN_LINKED ] = 'wpseo-' . self::COLUMN_LINKED; return $columns; } } integrations/admin/menu-badge-integration.php 0000644 00000001624 15025657560 0015416 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Menu_Badge_Integration class. */ class Menu_Badge_Integration implements Integration_Interface { /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * {@inheritDoc} */ public function register_hooks() { \add_action( 'admin_enqueue_scripts', [ $this, 'add_inline_styles' ] ); } /** * Renders the migration error. * * @return void */ public function add_inline_styles() { $custom_css = 'ul.wp-submenu span.yoast-premium-badge::after, #wpadminbar span.yoast-premium-badge::after { content:"' . \__( 'Premium', 'wordpress-seo' ) . '"}'; \wp_add_inline_style( WPSEO_Admin_Asset_Manager::PREFIX . 'admin-global', $custom_css ); } } integrations/admin/import-integration.php 0000644 00000021206 15025657560 0014722 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Import_Tool_Selected_Conditional; use Yoast\WP\SEO\Conditionals\Yoast_Tools_Page_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Alert_Presenter; use Yoast\WP\SEO\Routes\Importing_Route; use Yoast\WP\SEO\Services\Importing\Importable_Detector_Service; /** * Loads import script when on the Tool's page. */ class Import_Integration implements Integration_Interface { /** * Contains the asset manager. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * The Importable Detector service. * * @var Importable_Detector_Service */ protected $importable_detector; /** * The Importing Route class. * * @var Importing_Route */ protected $importing_route; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Import_Tool_Selected_Conditional::class, Yoast_Tools_Page_Conditional::class, ]; } /** * Import Integration constructor. * * @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager. * @param Importable_Detector_Service $importable_detector The importable detector. * @param Importing_Route $importing_route The importing route. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Importable_Detector_Service $importable_detector, Importing_Route $importing_route ) { $this->asset_manager = $asset_manager; $this->importable_detector = $importable_detector; $this->importing_route = $importing_route; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_import_script' ] ); } /** * Enqueues the Import script. * * @return void */ public function enqueue_import_script() { \wp_enqueue_style( 'dashicons' ); $this->asset_manager->enqueue_script( 'import' ); $data = [ 'restApi' => [ 'root' => \esc_url_raw( \rest_url() ), 'cleanup_endpoints' => $this->get_cleanup_endpoints(), 'importing_endpoints' => $this->get_importing_endpoints(), 'nonce' => \wp_create_nonce( 'wp_rest' ), ], 'assets' => [ 'loading_msg_import' => \esc_html__( 'The import can take a long time depending on your site\'s size.', 'wordpress-seo' ), 'loading_msg_cleanup' => \esc_html__( 'The cleanup can take a long time depending on your site\'s size.', 'wordpress-seo' ), 'note' => \esc_html__( 'Note: ', 'wordpress-seo' ), 'cleanup_after_import_msg' => \esc_html__( 'After you\'ve imported data from another SEO plugin, please make sure to clean up all the original data from that plugin. (step 5)', 'wordpress-seo' ), 'select_placeholder' => \esc_html__( 'Select SEO plugin', 'wordpress-seo' ), 'no_data_msg' => \esc_html__( 'No data found from other SEO plugins.', 'wordpress-seo' ), 'validation_failure' => $this->get_validation_failure_alert(), 'import_failure' => $this->get_import_failure_alert( true ), 'cleanup_failure' => $this->get_import_failure_alert( false ), 'spinner' => \admin_url( 'images/loading.gif' ), 'replacing_texts' => [ 'cleanup_button' => \esc_html__( 'Clean up', 'wordpress-seo' ), 'import_explanation' => \esc_html__( 'Please select an SEO plugin below to see what data can be imported.', 'wordpress-seo' ), 'cleanup_explanation' => \esc_html__( 'Once you\'re certain that your site is working properly with the imported data from another SEO plugin, you can clean up all the original data from that plugin.', 'wordpress-seo' ), /* translators: %s: expands to the name of the plugin that is selected to be imported */ 'select_header' => \esc_html__( 'The import from %s includes:', 'wordpress-seo' ), 'plugins' => [ 'aioseo' => [ [ 'data_name' => \esc_html__( 'Post metadata (SEO titles, descriptions, etc.)', 'wordpress-seo' ), 'data_note' => \esc_html__( 'Note: This metadata will only be imported if there is no existing Yoast SEO metadata yet.', 'wordpress-seo' ), ], [ 'data_name' => \esc_html__( 'Default settings', 'wordpress-seo' ), 'data_note' => \esc_html__( 'Note: These settings will overwrite the default settings of Yoast SEO.', 'wordpress-seo' ), ], ], 'other' => [ [ 'data_name' => \esc_html__( 'Post metadata (SEO titles, descriptions, etc.)', 'wordpress-seo' ), 'data_note' => \esc_html__( 'Note: This metadata will only be imported if there is no existing Yoast SEO metadata yet.', 'wordpress-seo' ), ], ], ], ], ], ]; /** * Filter: 'wpseo_importing_data' Filter to adapt the data used in the import process. * * @param array $data The import data to adapt. */ $data = \apply_filters( 'wpseo_importing_data', $data ); $this->asset_manager->localize_script( 'import', 'yoastImportData', $data ); } /** * Retrieves a list of the importing endpoints to use. * * @return array The endpoints. */ protected function get_importing_endpoints() { $available_actions = $this->importable_detector->detect_importers(); $importing_endpoints = []; $available_sorted_actions = $this->sort_actions( $available_actions ); foreach ( $available_sorted_actions as $plugin => $types ) { foreach ( $types as $type ) { $importing_endpoints[ $plugin ][] = $this->importing_route->get_endpoint( $plugin, $type ); } } return $importing_endpoints; } /** * Sorts the array of importing actions, by moving any validating actions to the start for every plugin. * * @param array $available_actions The array of actions that we want to sort. * * @return array The sorted array of actions. */ protected function sort_actions( $available_actions ) { $first_action = 'validate_data'; $available_sorted_actions = []; foreach ( $available_actions as $plugin => $plugin_available_actions ) { $validate_action_position = \array_search( $first_action, $plugin_available_actions, true ); if ( ! empty( $validate_action_position ) ) { unset( $plugin_available_actions[ $validate_action_position ] ); \array_unshift( $plugin_available_actions, $first_action ); } $available_sorted_actions[ $plugin ] = $plugin_available_actions; } return $available_sorted_actions; } /** * Retrieves a list of the importing endpoints to use. * * @return array The endpoints. */ protected function get_cleanup_endpoints() { $available_actions = $this->importable_detector->detect_cleanups(); $importing_endpoints = []; foreach ( $available_actions as $plugin => $types ) { foreach ( $types as $type ) { $importing_endpoints[ $plugin ][] = $this->importing_route->get_endpoint( $plugin, $type ); } } return $importing_endpoints; } /** * Gets the validation failure alert using the Alert_Presenter. * * @return string The validation failure alert. */ protected function get_validation_failure_alert() { $content = \esc_html__( 'The AIOSEO import was cancelled because some AIOSEO data is missing. Please try and take the following steps to fix this:', 'wordpress-seo' ); $content .= '<br/>'; $content .= '<ol><li>'; $content .= \esc_html__( 'If you have never saved any AIOSEO \'Search Appearance\' settings, please do that first and run the import again.', 'wordpress-seo' ); $content .= '</li>'; $content .= '<li>'; $content .= \esc_html__( 'If you already have saved AIOSEO \'Search Appearance\' settings and the issue persists, please contact our support team so we can take a closer look.', 'wordpress-seo' ); $content .= '</li></ol>'; $validation_failure_alert = new Alert_Presenter( $content, 'error' ); return $validation_failure_alert->present(); } /** * Gets the import failure alert using the Alert_Presenter. * * @param bool $is_import Wether it's an import or not. * * @return string The import failure alert. */ protected function get_import_failure_alert( $is_import ) { $content = \esc_html__( 'Cleanup failed with the following error:', 'wordpress-seo' ); if ( $is_import ) { $content = \esc_html__( 'Import failed with the following error:', 'wordpress-seo' ); } $content .= '<br/><br/>'; $content .= \esc_html( '%s' ); $import_failure_alert = new Alert_Presenter( $content, 'error' ); return $import_failure_alert->present(); } } integrations/admin/deactivated-premium-integration.php 0000644 00000010331 15025657560 0017336 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter; /** * Deactivated_Premium_Integration class */ class Deactivated_Premium_Integration implements Integration_Interface { /** * The options' helper. * * @var Options_Helper */ private $options_helper; /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class, Non_Multisite_Conditional::class ]; } /** * First_Time_Configuration_Notice_Integration constructor. * * @param Options_Helper $options_helper The options helper. * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. */ public function __construct( Options_Helper $options_helper, WPSEO_Admin_Asset_Manager $admin_asset_manager ) { $this->options_helper = $options_helper; $this->admin_asset_manager = $admin_asset_manager; } /** * {@inheritDoc} */ public function register_hooks() { \add_action( 'admin_notices', [ $this, 'premium_deactivated_notice' ] ); \add_action( 'wp_ajax_dismiss_premium_deactivated_notice', [ $this, 'dismiss_premium_deactivated_notice' ] ); } /** * Shows a notice if premium is installed but not activated. * * @return void */ public function premium_deactivated_notice() { global $pagenow; if ( $pagenow === 'update.php' ) { return; } if ( $this->options_helper->get( 'dismiss_premium_deactivated_notice', false ) === true ) { return; } $premium_file = 'wordpress-seo-premium/wp-seo-premium.php'; if ( ! \current_user_can( 'activate_plugin', $premium_file ) ) { return; } if ( $this->premium_is_installed_not_activated( $premium_file ) ) { $this->admin_asset_manager->enqueue_style( 'monorepo' ); $content = \sprintf( /* translators: 1: Yoast SEO Premium 2: Link start tag to activate premium, 3: Link closing tag. */ \__( 'You\'ve installed %1$s but it\'s not activated yet. %2$sActivate %1$s now!%3$s', 'wordpress-seo' ), 'Yoast SEO Premium', '<a href="' . \esc_url( \wp_nonce_url( \self_admin_url( 'plugins.php?action=activate&plugin=' . $premium_file ), 'activate-plugin_' . $premium_file ) ) . '">', '</a>' ); // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped above. echo new Notice_Presenter( /* translators: 1: Yoast SEO Premium */ \sprintf( \__( 'Activate %1$s!', 'wordpress-seo' ), 'Yoast SEO Premium' ), $content, 'support-team.svg', null, true, 'yoast-premium-deactivated-notice' ); // phpcs:enable // Enable permanently dismissing the notice. echo "<script> function dismiss_premium_deactivated_notice(){ var data = { 'action': 'dismiss_premium_deactivated_notice', }; jQuery( '#yoast-premium-deactivated-notice' ).hide(); jQuery.post( ajaxurl, data, function( response ) {}); } jQuery( document ).ready( function() { jQuery( 'body' ).on( 'click', '#yoast-premium-deactivated-notice .notice-dismiss', function() { dismiss_premium_deactivated_notice(); } ); } ); </script>"; } } /** * Dismisses the premium deactivated notice. * * @return bool */ public function dismiss_premium_deactivated_notice() { return $this->options_helper->set( 'dismiss_premium_deactivated_notice', true ); } /** * Returns whether or not premium is installed and not activated. * * @param string $premium_file The premium file. * * @return bool Whether or not premium is installed and not activated. */ protected function premium_is_installed_not_activated( $premium_file ) { return ( ! \defined( 'WPSEO_PREMIUM_FILE' ) && \file_exists( \WP_PLUGIN_DIR . '/' . $premium_file ) ); } } integrations/admin/workouts-integration.php 0000644 00000026062 15025657560 0015312 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Notice_Presenter; /** * WorkoutsIntegration class */ class Workouts_Integration implements Integration_Interface { /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * The addon manager. * * @var WPSEO_Addon_Manager */ private $addon_manager; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The product helper. * * @var Product_Helper */ private $product_helper; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Workouts_Integration constructor. * * @param WPSEO_Addon_Manager $addon_manager The addon manager. * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. * @param Options_Helper $options_helper The options helper. * @param Product_Helper $product_helper The product helper. */ public function __construct( WPSEO_Addon_Manager $addon_manager, WPSEO_Admin_Asset_Manager $admin_asset_manager, Options_Helper $options_helper, Product_Helper $product_helper ) { $this->addon_manager = $addon_manager; $this->admin_asset_manager = $admin_asset_manager; $this->options_helper = $options_helper; $this->product_helper = $product_helper; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'wpseo_submenu_pages', [ $this, 'add_submenu_page' ], 8 ); \add_filter( 'wpseo_submenu_pages', [ $this, 'remove_old_submenu_page' ], 10 ); \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ], 11 ); } /** * Adds the workouts submenu page. * * @param array $submenu_pages The Yoast SEO submenu pages. * * @return array The filtered submenu pages. */ public function add_submenu_page( $submenu_pages ) { $submenu_pages[] = [ 'wpseo_dashboard', '', \__( 'Workouts', 'wordpress-seo' ) . ' <span class="yoast-badge yoast-premium-badge"></span>', 'edit_others_posts', 'wpseo_workouts', [ $this, 'render_target' ], ]; return $submenu_pages; } /** * Removes the workouts submenu page from older Premium versions * * @param array $submenu_pages The Yoast SEO submenu pages. * * @return array The filtered submenu pages. */ public function remove_old_submenu_page( $submenu_pages ) { if ( ! $this->should_update_premium() ) { return $submenu_pages; } // Copy only the Workouts page item that comes first in the array. $result_submenu_pages = []; $workouts_page_encountered = false; foreach ( $submenu_pages as $item ) { if ( $item[4] !== 'wpseo_workouts' || ! $workouts_page_encountered ) { $result_submenu_pages[] = $item; } if ( $item[4] === 'wpseo_workouts' ) { $workouts_page_encountered = true; } } return $result_submenu_pages; } /** * Enqueue the workouts app. * * @return void */ public function enqueue_assets() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Date is not processed or saved. if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_workouts' ) { return; } if ( $this->should_update_premium() ) { \wp_dequeue_script( 'yoast-seo-premium-workouts' ); } $this->admin_asset_manager->enqueue_style( 'workouts' ); $workouts_option = $this->get_workouts_option(); $ftc_url = \esc_url( \admin_url( 'admin.php?page=wpseo_dashboard#/first-time-configuration' ) ); $this->admin_asset_manager->enqueue_script( 'workouts' ); $this->admin_asset_manager->localize_script( 'workouts', 'wpseoWorkoutsData', [ 'workouts' => $workouts_option, 'homeUrl' => \home_url(), 'pluginUrl' => \esc_url( \plugins_url( '', \WPSEO_FILE ) ), 'toolsPageUrl' => \esc_url( \admin_url( 'admin.php?page=wpseo_tools' ) ), 'usersPageUrl' => \esc_url( \admin_url( 'users.php' ) ), 'firstTimeConfigurationUrl' => $ftc_url, 'isPremium' => $this->product_helper->is_premium(), 'upsellText' => $this->get_upsell_text(), 'upsellLink' => $this->get_upsell_link(), ] ); } /** * Renders the target for the React to mount to. * * @return void */ public function render_target() { if ( $this->should_update_premium() ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output escaped in get_update_premium_notice. echo $this->get_update_premium_notice(); } echo '<div id="wpseo-workouts-container-free" class="yoast"></div>'; } /** * Gets the workouts option. * * @return mixed|null Returns workouts option if found, null if not. */ private function get_workouts_option() { $workouts_option = $this->options_helper->get( 'workouts_data' ); // This filter is documented in src/routes/workouts-route.php. return \apply_filters( 'Yoast\WP\SEO\workouts_options', $workouts_option ); } /** * Returns the notification to show when Premium needs to be updated. * * @return string The notification to update Premium. */ private function get_update_premium_notice() { $url = $this->get_upsell_link(); if ( $this->has_premium_subscription_expired() ) { /* translators: %s: expands to 'Yoast SEO Premium'. */ $title = \sprintf( \__( 'Renew your subscription of %s', 'wordpress-seo' ), 'Yoast SEO Premium' ); $copy = \sprintf( /* translators: %s: expands to 'Yoast SEO Premium'. */ \esc_html__( 'Accessing the latest workouts requires an updated version of %s (at least 17.7), but it looks like your subscription has expired. Please renew your subscription to update and gain access to all the latest features.', 'wordpress-seo' ), 'Yoast SEO Premium' ); $button = '<a class="yoast-button yoast-button-upsell yoast-button--small" href="' . \esc_url( $url ) . '" target="_blank">' . \esc_html__( 'Renew your subscription', 'wordpress-seo' ) /* translators: Hidden accessibility text. */ . '<span class="screen-reader-text">' . \__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>' . '<span aria-hidden="true" class="yoast-button-upsell__caret"></span>' . '</a>'; } elseif ( $this->has_premium_subscription_activated() ) { /* translators: %s: expands to 'Yoast SEO Premium'. */ $title = \sprintf( \__( 'Update to the latest version of %s', 'wordpress-seo' ), 'Yoast SEO Premium' ); $copy = \sprintf( /* translators: 1: expands to 'Yoast SEO Premium', 2: Link start tag to the page to update Premium, 3: Link closing tag. */ \esc_html__( 'It looks like you\'re running an outdated version of %1$s, please %2$supdate to the latest version (at least 17.7)%3$s to gain access to our updated workouts section.', 'wordpress-seo' ), 'Yoast SEO Premium', '<a href="' . \esc_url( $url ) . '">', '</a>' ); $button = null; } else { /* translators: %s: expands to 'Yoast SEO Premium'. */ $title = \sprintf( \__( 'Activate your subscription of %s', 'wordpress-seo' ), 'Yoast SEO Premium' ); $url_button = 'https://yoa.st/workouts-activate-notice-help'; $copy = \sprintf( /* translators: 1: expands to 'Yoast SEO Premium', 2: Link start tag to the page to update Premium, 3: Link closing tag. */ \esc_html__( 'It looks like you’re running an outdated and unactivated version of %1$s, please activate your subscription in %2$sMyYoast%3$s and update to the latest version (at least 17.7) to gain access to our updated workouts section.', 'wordpress-seo' ), 'Yoast SEO Premium', '<a href="' . \esc_url( $url ) . '">', '</a>' ); $button = '<a class="yoast-button yoast-button--primary yoast-button--small" href="' . \esc_url( $url_button ) . '" target="_blank">' . \esc_html__( 'Get help activating your subscription', 'wordpress-seo' ) /* translators: Hidden accessibility text. */ . '<span class="screen-reader-text">' . \__( '(Opens in a new browser tab)', 'wordpress-seo' ) . '</span>' . '</a>'; } $notice = new Notice_Presenter( $title, $copy, null, $button ); return $notice->present(); } /** * Check whether Premium should be updated. * * @return bool Returns true when Premium is enabled and the version is below 17.7. */ private function should_update_premium() { $premium_version = $this->product_helper->get_premium_version(); return $premium_version !== null && \version_compare( $premium_version, '17.7-RC1', '<' ); } /** * Check whether the Premium subscription has expired. * * @return bool Returns true when Premium subscription has expired. */ private function has_premium_subscription_expired() { $subscription = $this->addon_manager->get_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ); return ( isset( $subscription->expiry_date ) && ( \strtotime( $subscription->expiry_date ) - \time() ) < 0 ); } /** * Check whether the Premium subscription is activated. * * @return bool Returns true when Premium subscription is activated. */ private function has_premium_subscription_activated() { return $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ); } /** * Returns the upsell/update copy to show in the card buttons. * * @return string Returns a string with the upsell/update copy for the card buttons. */ private function get_upsell_text() { if ( ! $this->product_helper->is_premium() || ! $this->should_update_premium() ) { // Use the default defined in the component. return ''; } if ( $this->has_premium_subscription_expired() ) { return \sprintf( /* translators: %s: expands to 'Yoast SEO Premium'. */ \__( 'Renew %s', 'wordpress-seo' ), 'Yoast SEO Premium' ); } if ( $this->has_premium_subscription_activated() ) { return \sprintf( /* translators: %s: expands to 'Yoast SEO Premium'. */ \__( 'Update %s', 'wordpress-seo' ), 'Yoast SEO Premium' ); } return \sprintf( /* translators: %s: expands to 'Yoast SEO Premium'. */ \__( 'Activate %s', 'wordpress-seo' ), 'Yoast SEO Premium' ); } /** * Returns the upsell/update link to show in the card buttons. * * @return string Returns a string with the upsell/update link for the card buttons. */ private function get_upsell_link() { if ( ! $this->product_helper->is_premium() || ! $this->should_update_premium() ) { // Use the default defined in the component. return ''; } if ( $this->has_premium_subscription_expired() ) { return 'https://yoa.st/workout-renew-notice'; } if ( $this->has_premium_subscription_activated() ) { return \wp_nonce_url( \self_admin_url( 'update.php?action=upgrade-plugin&plugin=wordpress-seo-premium/wp-seo-premium.php' ), 'upgrade-plugin_wordpress-seo-premium/wp-seo-premium.php' ); } return 'https://yoa.st/workouts-activate-notice-myyoast'; } } integrations/admin/migration-error-integration.php 0000644 00000002543 15025657560 0016533 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Config\Migration_Status; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Migration_Error_Presenter; /** * Migration_Error_Integration class. */ class Migration_Error_Integration implements Integration_Interface { /** * The migration status object. * * @var Migration_Status */ protected $migration_status; /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Migration_Error_Integration constructor. * * @param Migration_Status $migration_status The migration status object. */ public function __construct( Migration_Status $migration_status ) { $this->migration_status = $migration_status; } /** * {@inheritDoc} */ public function register_hooks() { if ( $this->migration_status->get_error( 'free' ) === false ) { return; } \add_action( 'admin_notices', [ $this, 'render_migration_error' ] ); } /** * Renders the migration error. * * @return void */ public function render_migration_error() { // phpcs:ignore WordPress.Security.EscapeOutput -- The Migration_Error_Presenter already escapes it's output. echo new Migration_Error_Presenter( $this->migration_status->get_error( 'free' ) ); } } integrations/admin/old-configuration-integration.php 0000644 00000003225 15025657560 0017034 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Old_Configuration_Integration class */ class Old_Configuration_Integration implements Integration_Interface { /** * {@inheritDoc} */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'admin_menu', [ $this, 'add_submenu_page' ], 11 ); \add_action( 'admin_init', [ $this, 'redirect_to_new_configuration' ] ); } /** * Adds the old configuration submenu page. * * @param array $submenu_pages The Yoast SEO submenu pages. * * @return array the filtered submenu pages. */ public function add_submenu_page( $submenu_pages ) { \add_submenu_page( '', \__( 'Old Configuration Wizard', 'wordpress-seo' ), '', 'manage_options', 'wpseo_configurator', [ $this, 'render_page' ] ); return $submenu_pages; } /** * Renders the old configuration page. * * @return void */ public function render_page() { // This page is never to be displayed. } /** * Redirects from the old configuration page to the new configuration page. * * @return void */ public function redirect_to_new_configuration() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved. if ( ! isset( $_GET['page'] ) || $_GET['page'] !== 'wpseo_configurator' ) { return; } $redirect_url = 'admin.php?page=wpseo_dashboard#/first-time-configuration'; \wp_safe_redirect( \admin_url( $redirect_url ), 302, 'Yoast SEO' ); exit; } } integrations/admin/check-required-version.php 0000644 00000011603 15025657560 0015445 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Admin; use Plugin_Upgrader; use WP_Error; use WP_Upgrader; use Yoast\WP\SEO\Conditionals\Check_Required_Version_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * The Check_Required_Version class. * * This class checks if the required version of Yoast SEO is installed. * It also adds the `Requires Yoast SEO` header to the list of headers and updates the comparison table for the plugin overwrite. */ class Check_Required_Version implements Integration_Interface { /** * Initializes the integration. * * @return void */ public function register_hooks() { \add_filter( 'upgrader_source_selection', [ $this, 'check_required_version' ], 10, 3 ); \add_filter( 'install_plugin_overwrite_comparison', [ $this, 'update_comparison_table' ], 10, 3 ); } /** * Returns the conditionals based on which this loadable should be active. * * @return string[] The conditionals based on which this loadable should be active. */ public static function get_conditionals() { return [ Check_Required_Version_Conditional::class ]; } /** * Checks if the required version of Yoast SEO is installed. * * The code is partly inspired by Plugin_Upgrader::check_package() in wp-admin/includes/class-plugin-upgrader.php. * * @param string $source File source location. * @param string|null $remote_source Remote file source location. * @param WP_Upgrader|null $upgrader WP_Upgrader instance. * * @return string|WP_Error The source location or a WP_Error object if the required version is not installed. */ public function check_required_version( $source, $remote_source = null, $upgrader = null ) { global $wp_filesystem; // Bail out early if we are not installing/upgrading a plugin. if ( ! $upgrader instanceof Plugin_Upgrader ) { return $source; } $info = []; if ( \is_wp_error( $source ) ) { return $source; } $working_directory = \str_replace( $wp_filesystem->wp_content_dir(), \trailingslashit( \WP_CONTENT_DIR ), $source ); if ( ! \is_dir( $working_directory ) ) { // Confidence check, if the above fails, let's not prevent installation. return $source; } // Check that the folder contains at least 1 valid plugin. $files = \glob( $working_directory . '*.php' ); if ( $files ) { foreach ( $files as $file ) { $info = \get_plugin_data( $file, false, false ); if ( ! empty( $info['Name'] ) ) { break; } } } $requires_yoast_seo = ! empty( $info['Requires Yoast SEO'] ) ? $info['Requires Yoast SEO'] : false; if ( ! $this->check_requirement( $requires_yoast_seo ) ) { $error = \sprintf( /* translators: 1: Current Yoast SEO version, 2: Version required by the uploaded plugin. */ \__( 'The Yoast SEO version on your site is %1$s, however the uploaded plugin requires %2$s.', 'wordpress-seo' ), \WPSEO_VERSION, \esc_html( $requires_yoast_seo ) ); return new WP_Error( 'incompatible_yoast_seo_required_version', \__( 'The package could not be installed because it\'s not supported by the currently installed Yoast SEO version.', 'wordpress-seo' ), $error ); } return $source; } /** * Update the comparison table for the plugin installation when overwriting an existing plugin. * * @param string $table The output table with Name, Version, Author, RequiresWP, and RequiresPHP info. * @param array<string> $current_plugin_data Array with current plugin data. * @param array<string> $new_plugin_data Array with uploaded plugin data. * * @return string The updated comparison table. */ public function update_comparison_table( $table, $current_plugin_data, $new_plugin_data ) { $requires_yoast_seo_current = ! empty( $current_plugin_data['Requires Yoast SEO'] ) ? $current_plugin_data['Requires Yoast SEO'] : false; $requires_yoast_seo_new = ! empty( $new_plugin_data['Requires Yoast SEO'] ) ? $new_plugin_data['Requires Yoast SEO'] : false; if ( $requires_yoast_seo_current !== false || $requires_yoast_seo_new !== false ) { $new_row = \sprintf( '<tr><td class="name-label">%1$s</td><td>%2$s</td><td>%3$s</td></tr>', \__( 'Required Yoast SEO version', 'wordpress-seo' ), ( $requires_yoast_seo_current !== false ) ? \esc_html( $requires_yoast_seo_current ) : '-', ( $requires_yoast_seo_new !== false ) ? \esc_html( $requires_yoast_seo_new ) : '-' ); $table = \str_replace( '</tbody>', $new_row . '</tbody>', $table ); } return $table; } /** * Check whether the required Yoast SEO version is installed. * * @param string|bool $required_version The required version. * * @return bool Whether the required version is installed, or no version is required. */ private function check_requirement( $required_version ) { if ( $required_version === false ) { return true; } return \version_compare( \WPSEO_VERSION, $required_version . '-RC0', '>=' ); } } integrations/integration-interface.php 0000644 00000000652 15025657560 0014262 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Yoast\WP\SEO\Loadable_Interface; /** * An interface for registering integrations with WordPress. * * @codeCoverageIgnore It represents an interface. */ interface Integration_Interface extends Loadable_Interface { /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks(); } integrations/breadcrumbs-integration.php 0000644 00000004025 15025657560 0014611 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Presenters\Breadcrumbs_Presenter; use Yoast\WP\SEO\Surfaces\Helpers_Surface; /** * Adds customizations to the front end for breadcrumbs. */ class Breadcrumbs_Integration implements Integration_Interface { /** * The breadcrumbs presenter. * * @var Breadcrumbs_Presenter */ private $presenter; /** * The meta tags context memoizer. * * @var Meta_Tags_Context_Memoizer */ private $context_memoizer; /** * Breadcrumbs integration constructor. * * @param Helpers_Surface $helpers The helpers. * @param WPSEO_Replace_Vars $replace_vars The replace vars. * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer. */ public function __construct( Helpers_Surface $helpers, WPSEO_Replace_Vars $replace_vars, Meta_Tags_Context_Memoizer $context_memoizer ) { $this->context_memoizer = $context_memoizer; $this->presenter = new Breadcrumbs_Presenter(); $this->presenter->helpers = $helpers; $this->presenter->replace_vars = $replace_vars; } /** * Returns the conditionals based in which this loadable should be active. * * @return array The array of conditionals. */ public static function get_conditionals() { return []; } /** * Registers the `wpseo_breadcrumb` shortcode. * * @codeCoverageIgnore * * @return void */ public function register_hooks() { \add_shortcode( 'wpseo_breadcrumb', [ $this, 'render' ] ); } /** * Renders the breadcrumbs. * * @return string The rendered breadcrumbs. */ public function render() { $context = $this->context_memoizer->for_current_page(); /** This filter is documented in src/integrations/front-end-integration.php */ $presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); $this->presenter->presentation = $presentation; return $this->presenter->present(); } } integrations/estimated-reading-time.php 0000644 00000002127 15025657560 0014322 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Yoast\WP\SEO\Conditionals\Admin\Estimated_Reading_Time_Conditional; /** * Estimated reading time class. */ class Estimated_Reading_Time implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Estimated_Reading_Time_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'wpseo_metabox_entries_general', [ $this, 'add_estimated_reading_time_hidden_fields' ] ); } /** * Adds an estimated-reading-time hidden field. * * @param array $field_defs The $fields_defs. * * @return array */ public function add_estimated_reading_time_hidden_fields( $field_defs ) { if ( \is_array( $field_defs ) ) { $field_defs['estimated-reading-time-minutes'] = [ 'type' => 'hidden', 'title' => 'estimated-reading-time-minutes', ]; } return $field_defs; } } integrations/settings-integration.php 0000644 00000100637 15025657560 0014166 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use WP_Post; use WP_Post_Type; use WP_Taxonomy; use WP_User; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use WPSEO_Admin_Editor_Specific_Replace_Vars; use WPSEO_Admin_Recommended_Replace_Vars; use WPSEO_Option_Titles; use WPSEO_Options; use WPSEO_Replace_Vars; use WPSEO_Shortlinker; 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. * * @var string[] */ public const WP_OPTIONS = [ 'blogdescription' ]; /** * Holds the allowed option groups. * * @var array */ public const ALLOWED_OPTION_GROUPS = [ 'wpseo', 'wpseo_titles', 'wpseo_social' ]; /** * Holds the disallowed settings, per option group. * * @var array */ public const DISALLOWED_SETTINGS = [ 'wpseo' => [ 'myyoast-oauth', 'semrush_tokens', 'custom_taxonomy_slugs', 'import_cursors', 'workouts_data', 'configuration_finished_steps', 'importing_completed', 'wincher_tokens', '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', 'new_post_types', 'new_taxonomies', ], 'wpseo_titles' => [ 'company_logo_meta', 'person_logo_meta', ], ]; /** * Holds the disabled on multisite settings, per option group. * * @var array */ public const DISABLED_ON_MULTISITE_SETTINGS = [ 'wpseo' => [ 'deny_search_crawling', 'deny_wp_json_crawling', 'deny_adsbot_crawling', 'deny_ccbot_crawling', 'deny_google_extended_crawling', 'deny_gptbot_crawling', 'enable_llms_txt', ], ]; /** * Holds the WPSEO_Admin_Asset_Manager. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * Holds the WPSEO_Replace_Vars. * * @var WPSEO_Replace_Vars */ protected $replace_vars; /** * Holds the Schema_Types. * * @var Schema_Types */ protected $schema_types; /** * Holds the Current_Page_Helper. * * @var Current_Page_Helper */ protected $current_page_helper; /** * Holds the Post_Type_Helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * Holds the Language_Helper. * * @var Language_Helper */ protected $language_helper; /** * Holds the Taxonomy_Helper. * * @var Taxonomy_Helper */ protected $taxonomy_helper; /** * Holds the Product_Helper. * * @var Product_Helper */ protected $product_helper; /** * Holds the Woocommerce_Helper. * * @var Woocommerce_Helper */ protected $woocommerce_helper; /** * Holds the Article_Helper. * * @var Article_Helper */ protected $article_helper; /** * Holds the User_Helper. * * @var User_Helper */ protected $user_helper; /** * Holds the Options_Helper instance. * * @var Options_Helper */ protected $options; /** * 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, Options_Helper $options, 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. * * @return array */ public static function get_conditionals() { return [ Settings_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { // Add page. \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' ) { $post_action = ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information. $post_action = \wp_unslash( $_POST['action'] ); } $option_page = ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( isset( $_POST['option_page'] ) && \is_string( $_POST['option_page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information. $option_page = \wp_unslash( $_POST['option_page'] ); } 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 ); } return; } // 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. * * @return void */ 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 ); } } } /** * Adds the page. * * @param array $pages The pages. * * @return array The pages. */ public function add_page( $pages ) { \array_splice( $pages, 1, 0, [ [ 'wpseo_dashboard', '', \__( 'Settings', 'wordpress-seo' ), 'wpseo_manage_options', self::PAGE, [ $this, 'display_page' ], ], ] ); return $pages; } /** * Adds a dummy 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_submenu_page( '', '', '', 'wpseo_manage_options', self::PAGE . '_saved', static function () { // Add success indication to HTML response. $success = empty( \get_settings_errors() ) ? 'true' : 'false'; echo \esc_html( "{{ yoast-success: $success }}" ); } ); return $pages; } /** * Displays the page. * * @return void */ public function display_page() { echo '<div id="yoast-seo-settings"></div>'; } /** * Enqueues the assets. * * @return void */ 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' ); \wp_enqueue_media(); $this->asset_manager->enqueue_script( 'new-settings' ); $this->asset_manager->enqueue_style( 'new-settings' ); if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-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. * * @return void */ 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(); return [ '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, 'currentPromotions' => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(), ]; } /** * 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' ); $addon_manager = new WPSEO_Addon_Manager(); $woocommerce_seo_active = \is_plugin_active( $addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ); 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' ); } } return [ 'isPremium' => $this->product_helper->is_premium(), 'isRtl' => \is_rtl(), '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, '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' ), 'companyOrPersonMessage' => \apply_filters( 'wpseo_knowledge_graph_setting_msg', '' ), 'currentUserId' => \get_current_user_id(), 'canCreateUsers' => \current_user_can( 'create_users' ), 'canCreatePages' => \current_user_can( 'edit_pages' ), 'canEditUsers' => \current_user_can( 'edit_users' ), 'canManageOptions' => \current_user_can( 'manage_options' ), 'userLocale' => \str_replace( '_', '-', \get_user_locale() ), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'showForceRewriteTitlesSetting' => ! \current_theme_supports( 'title-tag' ) && ! ( \function_exists( 'wp_is_block_theme' ) && \wp_is_block_theme() ), 'upsellSettings' => $this->get_upsell_settings(), 'siteRepresentsPerson' => $this->get_site_represents_person( $settings ), 'siteBasicsPolicies' => $this->get_site_basics_policies( $settings ), 'llmsTxtUrl' => \home_url( 'llms.txt' ), ]; } /** * Retrieves the currently represented person. * * @param array $settings The settings. * * @return array The currently represented person's ID and name. */ protected function get_site_represents_person( $settings ) { $person = [ 'id' => false, 'name' => '', ]; if ( isset( $settings['wpseo_titles']['company_or_person_user_id'] ) ) { $person['id'] = $settings['wpseo_titles']['company_or_person_user_id']; $user = \get_userdata( $person['id'] ); if ( $user instanceof WP_User ) { $person['name'] = $user->get( 'display_name' ); } } return $person; } /** * Get site policy data. * * @param array $settings The settings. * * @return array The policy data. */ private function get_site_basics_policies( $settings ) { $policies = []; $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['publishing_principles_id'], 'publishing_principles_id' ); $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ownership_funding_info_id'], 'ownership_funding_info_id' ); $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['actionable_feedback_policy_id'], 'actionable_feedback_policy_id' ); $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['corrections_policy_id'], 'corrections_policy_id' ); $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['ethics_policy_id'], 'ethics_policy_id' ); $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_policy_id'], 'diversity_policy_id' ); $policies = $this->maybe_add_policy( $policies, $settings['wpseo_titles']['diversity_staffing_report_id'], 'diversity_staffing_report_id' ); return $policies; } /** * Adds policy data if it is present. * * @param array $policies The existing policy data. * @param int $policy The policy id to check. * @param string $key The option key name. * * @return array<int, string> The policy data. */ private function maybe_add_policy( $policies, $policy, $key ) { $policy_array = [ 'id' => 0, 'name' => \__( 'None', 'wordpress-seo' ), ]; if ( isset( $policy ) && \is_int( $policy ) ) { $policy_array['id'] = $policy; $post = \get_post( $policy ); if ( $post instanceof WP_Post ) { if ( $post->post_status !== 'publish' || $post->post_password !== '' ) { return $policies; } $policy_array['name'] = $post->post_title; } } $policies[ $key ] = $policy_array; return $policies; } /** * Returns settings for the Call to Buy (CTB) buttons. * * @return array<string> The array of CTB settings. */ public function get_upsell_settings() { return [ 'actionId' => 'load-nfd-ctb', 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', ]; } /** * Retrieves the default setting values. * * These default values are currently being used in the UI for dummy fields. * Dummy fields should not expose or reflect the actual data. * * @return array The default setting values. */ protected function get_default_setting_values() { $defaults = []; // Add Yoast settings. foreach ( WPSEO_Options::$options as $option_name => $instance ) { if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) { $option_instance = WPSEO_Options::get_option_instance( $option_name ); $defaults[ $option_name ] = ( $option_instance ) ? $option_instance->get_defaults() : []; } } // Add WP settings. foreach ( self::WP_OPTIONS as $option_name ) { $defaults[ $option_name ] = ''; } // Remove disallowed settings. foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) { foreach ( $disallowed_settings as $disallowed_setting ) { unset( $defaults[ $option_name ][ $disallowed_setting ] ); } } if ( \defined( 'WPSEO_LOCAL_FILE' ) ) { $defaults = $this->get_defaults_from_local_seo( $defaults ); } return $defaults; } /** * Retrieves the organization schema values from Local SEO for defaults in Site representation fields. * Specifically for the org-vat-id, org-tax-id, org-email and org-phone options. * * @param array<string|int|bool> $defaults The settings defaults. * * @return array<string|int|bool> The settings defaults with local seo overides. */ protected function get_defaults_from_local_seo( $defaults ) { $local_options = \get_option( 'wpseo_local' ); $multiple_locations = $local_options['use_multiple_locations']; $same_organization = $local_options['multiple_locations_same_organization']; $shared_info = $local_options['multiple_locations_shared_business_info']; if ( $multiple_locations !== 'on' || ( $multiple_locations === 'on' && $same_organization === 'on' && $shared_info === 'on' ) ) { $defaults['wpseo_titles']['org-vat-id'] = $local_options['location_vat_id']; $defaults['wpseo_titles']['org-tax-id'] = $local_options['location_tax_id']; $defaults['wpseo_titles']['org-email'] = $local_options['location_email']; $defaults['wpseo_titles']['org-phone'] = $local_options['location_phone']; } if ( \wpseo_has_primary_location() ) { $primary_location = $local_options['multiple_locations_primary_location']; $location_keys = [ 'org-phone' => [ 'is_overridden' => '_wpseo_is_overridden_business_phone', 'value' => '_wpseo_business_phone', ], 'org-email' => [ 'is_overridden' => '_wpseo_is_overridden_business_email', 'value' => '_wpseo_business_email', ], 'org-tax-id' => [ 'is_overridden' => '_wpseo_is_overridden_business_tax_id', 'value' => '_wpseo_business_tax_id', ], 'org-vat-id' => [ 'is_overridden' => '_wpseo_is_overridden_business_vat_id', 'value' => '_wpseo_business_vat_id', ], ]; foreach ( $location_keys as $key => $meta_keys ) { $is_overridden = ( $shared_info === 'on' ) ? \get_post_meta( $primary_location, $meta_keys['is_overridden'], true ) : false; if ( $is_overridden === 'on' || $shared_info !== 'on' ) { $post_meta_value = \get_post_meta( $primary_location, $meta_keys['value'], true ); $defaults['wpseo_titles'][ $key ] = ( $post_meta_value ) ? $post_meta_value : ''; } } } return $defaults; } /** * Retrieves the settings and their values. * * @param array $default_setting_values The default setting values. * * @return array The settings. */ protected function get_settings( $default_setting_values ) { $settings = []; // Add Yoast settings. foreach ( WPSEO_Options::$options as $option_name => $instance ) { if ( \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) { $settings[ $option_name ] = \array_merge( $default_setting_values[ $option_name ], WPSEO_Options::get_option( $option_name ) ); } } // Add WP settings. foreach ( self::WP_OPTIONS as $option_name ) { $settings[ $option_name ] = \get_option( $option_name ); } // Remove disallowed settings. foreach ( self::DISALLOWED_SETTINGS as $option_name => $disallowed_settings ) { foreach ( $disallowed_settings as $disallowed_setting ) { unset( $settings[ $option_name ][ $disallowed_setting ] ); } } return $settings; } /** * Transforms setting values. * * @param array $settings The settings. * * @return array The settings. */ protected function transform_settings( $settings ) { if ( isset( $settings['wpseo_titles']['breadcrumbs-sep'] ) ) { /** * The breadcrumbs separator default value is the HTML entity `»`. * Which does not get decoded in our JS, while it did in our Yoast form. Decode it here as an exception. */ $settings['wpseo_titles']['breadcrumbs-sep'] = \html_entity_decode( $settings['wpseo_titles']['breadcrumbs-sep'], ( \ENT_NOQUOTES | \ENT_HTML5 ), 'UTF-8' ); } /** * Decode some WP options. */ $settings['blogdescription'] = \html_entity_decode( $settings['blogdescription'], ( \ENT_NOQUOTES | \ENT_HTML5 ), 'UTF-8' ); return $settings; } /** * Retrieves the disabled settings. * * @param array $settings The settings. * * @return array The settings. */ protected function get_disabled_settings( $settings ) { $disabled_settings = []; $site_language = $this->language_helper->get_language(); foreach ( WPSEO_Options::$options as $option_name => $instance ) { if ( ! \in_array( $option_name, self::ALLOWED_OPTION_GROUPS, true ) ) { continue; } $disabled_settings[ $option_name ] = []; $option_instance = WPSEO_Options::get_option_instance( $option_name ); if ( $option_instance === false ) { continue; } foreach ( $settings[ $option_name ] as $setting_name => $setting_value ) { if ( $option_instance->is_disabled( $setting_name ) ) { $disabled_settings[ $option_name ][ $setting_name ] = 'network'; } } } // Remove disabled on multisite settings. if ( \is_multisite() ) { foreach ( self::DISABLED_ON_MULTISITE_SETTINGS as $option_name => $disabled_ms_settings ) { if ( \array_key_exists( $option_name, $disabled_settings ) ) { foreach ( $disabled_ms_settings as $disabled_ms_setting ) { $disabled_settings[ $option_name ][ $disabled_ms_setting ] = 'multisite'; } } } } if ( \array_key_exists( 'wpseo', $disabled_settings ) && ! $this->language_helper->has_inclusive_language_support( $site_language ) ) { $disabled_settings['wpseo']['inclusive_language_analysis_active'] = 'language'; } return $disabled_settings; } /** * Retrieves the replacement variables. * * @return array The replacement variables. */ protected function get_replacement_variables() { $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars(); $specific_replace_vars = new WPSEO_Admin_Editor_Specific_Replace_Vars(); $replacement_variables = $this->replace_vars->get_replacement_variables_with_labels(); return [ 'variables' => $replacement_variables, 'recommended' => $recommended_replace_vars->get_recommended_replacevars(), 'specific' => $specific_replace_vars->get(), 'shared' => $specific_replace_vars->get_generic( $replacement_variables ), ]; } /** * Retrieves the schema. * * @param array $post_types The post types. * * @return array The schema. */ protected function get_schema( array $post_types ) { $schema = []; foreach ( $this->schema_types->get_article_type_options() as $article_type ) { $schema['articleTypes'][ $article_type['value'] ] = [ 'label' => $article_type['name'], 'value' => $article_type['value'], ]; } foreach ( $this->schema_types->get_page_type_options() as $page_type ) { $schema['pageTypes'][ $page_type['value'] ] = [ 'label' => $page_type['name'], 'value' => $page_type['value'], ]; } $schema['articleTypeDefaults'] = []; $schema['pageTypeDefaults'] = []; foreach ( $post_types as $name => $post_type ) { $schema['articleTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-article-type-$name" ); $schema['pageTypeDefaults'][ $name ] = WPSEO_Options::get_default( 'wpseo_titles', "schema-page-type-$name" ); } return $schema; } /** * Transforms the post types, to represent them. * * @param WP_Post_Type[] $post_types The WP_Post_Type array to transform. * * @return array The post types. */ protected function transform_post_types( $post_types ) { $transformed = []; $new_post_types = $this->options->get( 'new_post_types', [] ); foreach ( $post_types as $post_type ) { $transformed[ $post_type->name ] = [ 'name' => $post_type->name, 'route' => $this->get_route( $post_type->name, $post_type->rewrite, $post_type->rest_base ), 'label' => $post_type->label, 'singularLabel' => $post_type->labels->singular_name, 'hasArchive' => $this->post_type_helper->has_archive( $post_type ), 'hasSchemaArticleType' => $this->article_helper->is_article_post_type( $post_type->name ), 'menuPosition' => $post_type->menu_position, 'isNew' => \in_array( $post_type->name, $new_post_types, true ), ]; } \uasort( $transformed, [ $this, 'compare_post_types' ] ); return $transformed; } /** * Compares two post types. * * @param array $a The first post type. * @param array $b The second post type. * * @return int The order. */ protected function compare_post_types( $a, $b ) { if ( $a['menuPosition'] === null && $b['menuPosition'] !== null ) { return 1; } if ( $a['menuPosition'] !== null && $b['menuPosition'] === null ) { return -1; } if ( $a['menuPosition'] === null && $b['menuPosition'] === null ) { // No position specified, order alphabetically by label. return \strnatcmp( $a['label'], $b['label'] ); } return ( ( $a['menuPosition'] < $b['menuPosition'] ) ? -1 : 1 ); } /** * Transforms the taxonomies, to represent them. * * @param WP_Taxonomy[] $taxonomies The WP_Taxonomy array to transform. * @param string[] $post_type_names The post type names. * * @return array The taxonomies. */ protected function transform_taxonomies( $taxonomies, $post_type_names ) { $transformed = []; $new_taxonomies = $this->options->get( 'new_taxonomies', [] ); foreach ( $taxonomies as $taxonomy ) { $transformed[ $taxonomy->name ] = [ 'name' => $taxonomy->name, 'route' => $this->get_route( $taxonomy->name, $taxonomy->rewrite, $taxonomy->rest_base ), 'label' => $taxonomy->label, 'showUi' => $taxonomy->show_ui, 'singularLabel' => $taxonomy->labels->singular_name, 'postTypes' => \array_filter( $taxonomy->object_type, static function ( $object_type ) use ( $post_type_names ) { return \in_array( $object_type, $post_type_names, true ); } ), 'isNew' => \in_array( $taxonomy->name, $new_taxonomies, true ), ]; } \uasort( $transformed, static function ( $a, $b ) { return \strnatcmp( $a['label'], $b['label'] ); } ); return $transformed; } /** * Gets the route from a name, rewrite and rest_base. * * @param string $name The name. * @param array $rewrite The rewrite data. * @param string $rest_base The rest base. * * @return string The route. */ protected function get_route( $name, $rewrite, $rest_base ) { $route = $name; if ( isset( $rewrite['slug'] ) ) { $route = $rewrite['slug']; } if ( ! empty( $rest_base ) ) { $route = $rest_base; } // Always strip leading slashes. while ( \substr( $route, 0, 1 ) === '/' ) { $route = \substr( $route, 1 ); } return $route; } /** * Retrieves the fallbacks. * * @return array The fallbacks. */ protected function get_fallbacks() { $site_logo_id = \get_option( 'site_logo' ); if ( ! $site_logo_id ) { $site_logo_id = \get_theme_mod( 'custom_logo' ); } if ( ! $site_logo_id ) { $site_logo_id = '0'; } return [ 'siteLogoId' => $site_logo_id, ]; } } integrations/front-end-integration.php 0000644 00000040765 15025657560 0014227 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use WP_HTML_Tag_Processor; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter; use Yoast\WP\SEO\Presenters\Debug\Marker_Close_Presenter; use Yoast\WP\SEO\Presenters\Debug\Marker_Open_Presenter; use Yoast\WP\SEO\Presenters\Title_Presenter; use Yoast\WP\SEO\Surfaces\Helpers_Surface; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; /** * Class Front_End_Integration. */ class Front_End_Integration implements Integration_Interface { /** * The memoizer for the meta tags context. * * @var Meta_Tags_Context_Memoizer */ private $context_memoizer; /** * The container. * * @var ContainerInterface */ protected $container; /** * Represents the options helper. * * @var Options_Helper */ protected $options; /** * The helpers surface. * * @var Helpers_Surface */ protected $helpers; /** * The replace vars helper. * * @var WPSEO_Replace_Vars */ protected $replace_vars; /** * The presenters we loop through on each page load. * * @var string[] */ protected $base_presenters = [ 'Title', 'Meta_Description', 'Robots', ]; /** * The presenters we loop through on each page load. * * @var string[] */ protected $indexing_directive_presenters = [ 'Canonical', 'Rel_Prev', 'Rel_Next', ]; /** * The Open Graph specific presenters. * * @var string[] */ protected $open_graph_presenters = [ 'Open_Graph\Locale', 'Open_Graph\Type', 'Open_Graph\Title', 'Open_Graph\Description', 'Open_Graph\Url', 'Open_Graph\Site_Name', 'Open_Graph\Article_Publisher', 'Open_Graph\Article_Author', 'Open_Graph\Article_Published_Time', 'Open_Graph\Article_Modified_Time', 'Open_Graph\Image', 'Meta_Author', ]; /** * The Open Graph specific presenters that should be output on error pages. * * @var array<string> */ protected $open_graph_error_presenters = [ 'Open_Graph\Locale', 'Open_Graph\Title', 'Open_Graph\Site_Name', ]; /** * The Twitter card specific presenters. * * @var array<string> */ protected $twitter_card_presenters = [ 'Twitter\Card', 'Twitter\Title', 'Twitter\Description', 'Twitter\Image', 'Twitter\Creator', 'Twitter\Site', ]; /** * The Slack specific presenters. * * @var array<string> */ protected $slack_presenters = [ 'Slack\Enhanced_Data', ]; /** * The Webmaster verification specific presenters. * * @var array<string> */ protected $webmaster_verification_presenters = [ 'Webmaster\Baidu', 'Webmaster\Bing', 'Webmaster\Google', 'Webmaster\Pinterest', 'Webmaster\Yandex', ]; /** * Presenters that are only needed on singular pages. * * @var array<string> */ protected $singular_presenters = [ 'Meta_Author', 'Open_Graph\Article_Author', 'Open_Graph\Article_Publisher', 'Open_Graph\Article_Published_Time', 'Open_Graph\Article_Modified_Time', 'Twitter\Creator', 'Slack\Enhanced_Data', ]; /** * The presenters we want to be last in our output. * * @var array<string> */ protected $closing_presenters = [ 'Schema', ]; /** * The next output. * * @var string */ protected $next; /** * The prev output. * * @var string */ protected $prev; /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The conditionals. */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Front_End_Integration constructor. * * @codeCoverageIgnore It sets dependencies. * * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer. * @param ContainerInterface $service_container The DI container. * @param Options_Helper $options The options helper. * @param Helpers_Surface $helpers The helpers surface. * @param WPSEO_Replace_Vars $replace_vars The replace vars helper. */ public function __construct( Meta_Tags_Context_Memoizer $context_memoizer, ContainerInterface $service_container, Options_Helper $options, Helpers_Surface $helpers, WPSEO_Replace_Vars $replace_vars ) { $this->container = $service_container; $this->context_memoizer = $context_memoizer; $this->options = $options; $this->helpers = $helpers; $this->replace_vars = $replace_vars; } /** * Registers the appropriate hooks to show the SEO metadata on the frontend. * * Removes some actions to remove metadata that WordPress shows on the frontend, * to avoid duplicate and/or mismatched metadata. * * @return void */ public function register_hooks() { \add_filter( 'render_block', [ $this, 'query_loop_next_prev' ], 1, 2 ); \add_action( 'wp_head', [ $this, 'call_wpseo_head' ], 1 ); // Filter the title for compatibility with other plugins and themes. \add_filter( 'wp_title', [ $this, 'filter_title' ], 15 ); // Filter the title for compatibility with block-based themes. \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); // Removes our robots presenter from the list when wp_robots is handling this. \add_filter( 'wpseo_frontend_presenter_classes', [ $this, 'filter_robots_presenter' ] ); \add_action( 'wpseo_head', [ $this, 'present_head' ], -9999 ); \remove_action( 'wp_head', 'rel_canonical' ); \remove_action( 'wp_head', 'index_rel_link' ); \remove_action( 'wp_head', 'start_post_rel_link' ); \remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' ); \remove_action( 'wp_head', 'noindex', 1 ); \remove_action( 'wp_head', '_wp_render_title_tag', 1 ); \remove_action( 'wp_head', '_block_template_render_title_tag', 1 ); \remove_action( 'wp_head', 'gutenberg_render_title_tag', 1 ); } /** * Filters the title, mainly used for compatibility reasons. * * @return string */ public function filter_title() { $context = $this->context_memoizer->for_current_page(); $title_presenter = new Title_Presenter(); /** This filter is documented in src/integrations/front-end-integration.php */ $title_presenter->presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); $title_presenter->replace_vars = $this->replace_vars; $title_presenter->helpers = $this->helpers; \remove_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); $title = \esc_html( $title_presenter->get() ); \add_filter( 'pre_get_document_title', [ $this, 'filter_title' ], 15 ); return $title; } /** * Filters the next and prev links in the query loop block. * * @param string $html The HTML output. * @param array<string|array|null> $block The block. * @return string The filtered HTML output. */ public function query_loop_next_prev( $html, $block ) { if ( $block['blockName'] === 'core/query' ) { // Check that the query does not inherit the main query. if ( isset( $block['attrs']['query']['inherit'] ) && ! $block['attrs']['query']['inherit'] ) { \add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 1, 3 ); } } if ( $block['blockName'] === 'core/query-pagination-next' ) { $this->next = $html; } if ( $block['blockName'] === 'core/query-pagination-previous' ) { $this->prev = $html; } return $html; } /** * Returns correct adjacent pages when Query loop block does not inherit query from template. * Prioritizes existing prev and next links. * Includes a safety check for full urls though it is not expected in the query pagination block. * * @param string $link The current link. * @param string $rel Link relationship, prev or next. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string The correct link. */ public function adjacent_rel_url( $link, $rel, $presentation = null ) { // Prioritize existing prev and next links. if ( $link ) { return $link; } // Safety check for rel value. if ( $rel !== 'next' && $rel !== 'prev' ) { return $link; } // Check $this->next or $this->prev for existing links. if ( $this->$rel === null ) { return $link; } $processor = new WP_HTML_Tag_Processor( $this->$rel ); if ( ! $processor->next_tag( [ 'tag_name' => 'a' ] ) ) { return $link; } $href = $processor->get_attribute( 'href' ); if ( ! $href ) { return $link; } // Safety check for full url, not expected. if ( \strpos( $href, 'http' ) === 0 ) { return $href; } // Check if $href is relative and append last part of the url to permalink. if ( \strpos( $href, '/' ) === 0 ) { $href_parts = \explode( '/', $href ); return $presentation->permalink . \end( $href_parts ); } return $link; } /** * Filters our robots presenter, but only when wp_robots is attached to the wp_head action. * * @param array<string> $presenters The presenters for current page. * * @return array<string> The filtered presenters. */ public function filter_robots_presenter( $presenters ) { if ( ! \function_exists( 'wp_robots' ) ) { return $presenters; } if ( ! \has_action( 'wp_head', 'wp_robots' ) ) { return $presenters; } if ( \wp_is_serving_rest_request() ) { return $presenters; } return \array_diff( $presenters, [ 'Yoast\\WP\\SEO\\Presenters\\Robots_Presenter' ] ); } /** * Presents the head in the front-end. Resets wp_query if it's not the main query. * * @codeCoverageIgnore It just calls a WordPress function. * * @return void */ public function call_wpseo_head() { global $wp_query; $old_wp_query = $wp_query; // phpcs:ignore WordPress.WP.DiscouragedFunctions.wp_reset_query_wp_reset_query -- Reason: The recommended function, wp_reset_postdata, doesn't reset wp_query. \wp_reset_query(); \do_action( 'wpseo_head' ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Reason: we have to restore the query. $GLOBALS['wp_query'] = $old_wp_query; } /** * Echoes all applicable presenters for a page. * * @return void */ public function present_head() { $context = $this->context_memoizer->for_current_page(); $presenters = $this->get_presenters( $context->page_type, $context ); /** * Filter 'wpseo_frontend_presentation' - Allow filtering the presentation used to output our meta values. * * @param Indexable_Presention $presentation The indexable presentation. * @param Meta_Tags_Context $context The meta tags context for the current page. */ $presentation = \apply_filters( 'wpseo_frontend_presentation', $context->presentation, $context ); echo \PHP_EOL; foreach ( $presenters as $presenter ) { $presenter->presentation = $presentation; $presenter->helpers = $this->helpers; $presenter->replace_vars = $this->replace_vars; $output = $presenter->present(); if ( ! empty( $output ) ) { // phpcs:ignore WordPress.Security.EscapeOutput -- Presenters are responsible for correctly escaping their output. echo "\t" . $output . \PHP_EOL; } } echo \PHP_EOL . \PHP_EOL; } /** * Returns all presenters for this page. * * @param string $page_type The page type. * @param Meta_Tags_Context|null $context The meta tags context for the current page. * * @return Abstract_Indexable_Presenter[] The presenters. */ public function get_presenters( $page_type, $context = null ) { if ( $context === null ) { $context = $this->context_memoizer->for_current_page(); } $needed_presenters = $this->get_needed_presenters( $page_type ); $callback = static function ( $presenter ) { if ( ! \class_exists( $presenter ) ) { return null; } return new $presenter(); }; $presenters = \array_filter( \array_map( $callback, $needed_presenters ) ); /** * Filter 'wpseo_frontend_presenters' - Allow filtering the presenter instances in or out of the request. * * @param Abstract_Indexable_Presenter[] $presenters List of presenter instances. * @param Meta_Tags_Context $context The meta tags context for the current page. */ $presenter_instances = \apply_filters( 'wpseo_frontend_presenters', $presenters, $context ); if ( ! \is_array( $presenter_instances ) ) { $presenter_instances = $presenters; } $is_presenter_callback = static function ( $presenter_instance ) { return $presenter_instance instanceof Abstract_Indexable_Presenter; }; $presenter_instances = \array_filter( $presenter_instances, $is_presenter_callback ); return \array_merge( [ new Marker_Open_Presenter() ], $presenter_instances, [ new Marker_Close_Presenter() ] ); } /** * Generate the array of presenters we need for the current request. * * @param string $page_type The page type we're retrieving presenters for. * * @return string[] The presenters. */ private function get_needed_presenters( $page_type ) { $presenters = $this->get_presenters_for_page_type( $page_type ); $presenters = $this->maybe_remove_title_presenter( $presenters ); $callback = static function ( $presenter ) { return "Yoast\WP\SEO\Presenters\\{$presenter}_Presenter"; }; $presenters = \array_map( $callback, $presenters ); /** * Filter 'wpseo_frontend_presenter_classes' - Allow filtering presenters in or out of the request. * * @param array $presenters List of presenters. * @param string $page_type The current page type. */ $presenters = \apply_filters( 'wpseo_frontend_presenter_classes', $presenters, $page_type ); return $presenters; } /** * Filters the presenters based on the page type. * * @param string $page_type The page type. * * @return string[] The presenters. */ private function get_presenters_for_page_type( $page_type ) { if ( $page_type === 'Error_Page' ) { $presenters = $this->base_presenters; if ( $this->options->get( 'opengraph' ) === true ) { $presenters = \array_merge( $presenters, $this->open_graph_error_presenters ); } return \array_merge( $presenters, $this->closing_presenters ); } $presenters = $this->get_all_presenters(); if ( \in_array( $page_type, [ 'Static_Home_Page', 'Home_Page' ], true ) ) { $presenters = \array_merge( $presenters, $this->webmaster_verification_presenters ); } // Filter out the presenters only needed for singular pages on non-singular pages. if ( ! \in_array( $page_type, [ 'Post_Type', 'Static_Home_Page' ], true ) ) { $presenters = \array_diff( $presenters, $this->singular_presenters ); } // Filter out `twitter:data` presenters for static home pages. if ( $page_type === 'Static_Home_Page' ) { $presenters = \array_diff( $presenters, $this->slack_presenters ); } return $presenters; } /** * Returns a list of all available presenters based on settings. * * @return string[] The presenters. */ private function get_all_presenters() { $presenters = \array_merge( $this->base_presenters, $this->indexing_directive_presenters ); if ( $this->options->get( 'opengraph' ) === true ) { $presenters = \array_merge( $presenters, $this->open_graph_presenters ); } if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) { $presenters = \array_merge( $presenters, $this->twitter_card_presenters ); } if ( $this->options->get( 'enable_enhanced_slack_sharing' ) === true && \apply_filters( 'wpseo_output_enhanced_slack_data', true ) !== false ) { $presenters = \array_merge( $presenters, $this->slack_presenters ); } return \array_merge( $presenters, $this->closing_presenters ); } /** * Whether the title presenter should be removed. * * @return bool True when the title presenter should be removed, false otherwise. */ public function should_title_presenter_be_removed() { return ! \get_theme_support( 'title-tag' ) && ! $this->options->get( 'forcerewritetitle', false ); } /** * Checks if the Title presenter needs to be removed. * * @param string[] $presenters The presenters. * * @return string[] The presenters. */ private function maybe_remove_title_presenter( $presenters ) { // Do not remove the title if we're on a REST request. if ( \wp_is_serving_rest_request() ) { return $presenters; } // Remove the title presenter if the theme is hardcoded to output a title tag so we don't have two title tags. if ( $this->should_title_presenter_be_removed() ) { $presenters = \array_diff( $presenters, [ 'Title' ] ); } return $presenters; } } integrations/watchers/primary-category-quick-edit-watcher.php 0000644 00000013062 15025657560 0020606 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use WP_Post; use WPSEO_Meta; use Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder; use Yoast\WP\SEO\Conditionals\Admin\Doing_Post_Quick_Edit_Save_Conditional; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\Primary_Term_Repository; /** * Class Primary_Category_Quick_Edit_Watcher */ class Primary_Category_Quick_Edit_Watcher implements Integration_Interface { /** * Holds the options helper. * * @var Options_Helper */ protected $options_helper; /** * Holds the primary term repository. * * @var Primary_Term_Repository */ protected $primary_term_repository; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * The indexable repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * The indexable hierarchy builder. * * @var Indexable_Hierarchy_Builder */ protected $indexable_hierarchy_builder; /** * Primary_Category_Quick_Edit_Watcher constructor. * * @param Options_Helper $options_helper The options helper. * @param Primary_Term_Repository $primary_term_repository The primary term repository. * @param Post_Type_Helper $post_type_helper The post type helper. * @param Indexable_Repository $indexable_repository The indexable repository. * @param Indexable_Hierarchy_Builder $indexable_hierarchy_builder The indexable hierarchy repository. */ public function __construct( Options_Helper $options_helper, Primary_Term_Repository $primary_term_repository, Post_Type_Helper $post_type_helper, Indexable_Repository $indexable_repository, Indexable_Hierarchy_Builder $indexable_hierarchy_builder ) { $this->options_helper = $options_helper; $this->primary_term_repository = $primary_term_repository; $this->post_type_helper = $post_type_helper; $this->indexable_repository = $indexable_repository; $this->indexable_hierarchy_builder = $indexable_hierarchy_builder; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'set_object_terms', [ $this, 'validate_primary_category' ], 10, 4 ); } /** * Returns the conditionals based on which this loadable should be active. * * @return array The conditionals. */ public static function get_conditionals() { return [ Migrations_Conditional::class, Doing_Post_Quick_Edit_Save_Conditional::class ]; } /** * Validates if the current primary category is still present. If not just remove the post meta for it. * * @param int $object_id Object ID. * @param array $terms Unused. An array of object terms. * @param array $tt_ids An array of term taxonomy IDs. * @param string $taxonomy Taxonomy slug. * * @return void */ public function validate_primary_category( $object_id, $terms, $tt_ids, $taxonomy ) { $post = \get_post( $object_id ); if ( $post === null ) { return; } $main_taxonomy = $this->options_helper->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return; } if ( $main_taxonomy !== $taxonomy ) { return; } $primary_category = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_category === false ) { return; } // The primary category isn't removed. if ( \in_array( (string) $primary_category, $tt_ids, true ) ) { return; } $this->remove_primary_term( $post->ID, $main_taxonomy ); // Rebuild the post hierarchy for this post now the primary term has been changed. $this->build_post_hierarchy( $post ); } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int|false The ID of the primary term, or `false` if the post ID is invalid. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } /** * Removes the primary category. * * @param int $post_id The post id to set primary taxonomy for. * @param string $main_taxonomy Name of the taxonomy that is set to be the primary one. * * @return void */ private function remove_primary_term( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { $primary_term->delete(); } // Remove it from the post meta. \delete_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy ); } /** * Builds the hierarchy for a post. * * @param WP_Post $post The post. * * @return void */ public function build_post_hierarchy( $post ) { if ( $this->post_type_helper->is_excluded( $post->post_type ) ) { return; } $indexable = $this->indexable_repository->find_by_id_and_type( $post->ID, 'post' ); if ( $indexable instanceof Indexable ) { $this->indexable_hierarchy_builder->build( $indexable ); } } } integrations/watchers/indexable-system-page-watcher.php 0000644 00000005223 15025657560 0017442 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Builders\Indexable_System_Page_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Search result watcher to save the meta data to an Indexable. * * Watches the search result options to save the meta information when updated. */ class Indexable_System_Page_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_System_Page_Watcher constructor. * * @param Indexable_Repository $repository The repository to use. * @param Indexable_Builder $builder The post builder to use. */ public function __construct( Indexable_Repository $repository, Indexable_Builder $builder ) { $this->repository = $repository; $this->builder = $builder; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 ); } /** * Checks if the home page indexable needs to be rebuild based on option values. * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * * @return void */ public function check_option( $old_value, $new_value ) { foreach ( Indexable_System_Page_Builder::OPTION_MAPPING as $type => $options ) { foreach ( $options as $option ) { // If both values aren't set they haven't changed. if ( ! isset( $old_value[ $option ] ) && ! isset( $new_value[ $option ] ) ) { return; } // If the value was set but now isn't, is set but wasn't or is not the same it has changed. if ( ! isset( $old_value[ $option ] ) || ! isset( $new_value[ $option ] ) || $old_value[ $option ] !== $new_value[ $option ] ) { $this->build_indexable( $type ); } } } } /** * Saves the search result. * * @param string $type The type of no index page. * * @return void */ public function build_indexable( $type ) { $indexable = $this->repository->find_for_system_page( $type, false ); $this->builder->build_for_system_page( $type, $indexable ); } } integrations/watchers/indexable-category-permalink-watcher.php 0000644 00000003364 15025657560 0021005 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use WPSEO_Utils; use Yoast\WP\SEO\Config\Indexing_Reasons; /** * Watches the stripcategorybase key in wpseo_titles, in order to clear the permalink of the category indexables. */ class Indexable_Category_Permalink_Watcher extends Indexable_Permalink_Watcher { /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 ); } /** * Checks if the stripcategorybase key in wpseo_titles has a change in value, and if so, * clears the permalink for category indexables. * * @param array $old_value The old value of the wpseo_titles option. * @param array $new_value The new value of the wpseo_titles option. * * @return void */ public function check_option( $old_value, $new_value ) { // If this is the first time saving the option, in which case its value would be false. if ( $old_value === false ) { $old_value = []; } // If either value is not an array, return. if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) { return; } // If both values aren't set, they haven't changed. if ( ! isset( $old_value['stripcategorybase'] ) && ! isset( $new_value['stripcategorybase'] ) ) { return; } // If a new value has been set for 'stripcategorybase', clear the category permalinks. if ( $old_value['stripcategorybase'] !== $new_value['stripcategorybase'] ) { $this->indexable_helper->reset_permalink_indexables( 'term', 'category', Indexing_Reasons::REASON_CATEGORY_BASE_PREFIX ); // Clear the rewrites, so the new permalink structure is used. WPSEO_Utils::clear_rewrites(); } } } integrations/watchers/indexable-term-watcher.php 0000644 00000007256 15025657560 0016163 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Builders\Indexable_Link_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Site_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Watches Terms/Taxonomies to fill the related Indexable. */ class Indexable_Term_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * The link builder. * * @var Indexable_Link_Builder */ protected $link_builder; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * Represents the site helper. * * @var Site_Helper */ protected $site; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Term_Watcher constructor. * * @param Indexable_Repository $repository The repository to use. * @param Indexable_Builder $builder The post builder to use. * @param Indexable_Link_Builder $link_builder The lint builder to use. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Site_Helper $site The site helper. */ public function __construct( Indexable_Repository $repository, Indexable_Builder $builder, Indexable_Link_Builder $link_builder, Indexable_Helper $indexable_helper, Site_Helper $site ) { $this->repository = $repository; $this->builder = $builder; $this->link_builder = $link_builder; $this->indexable_helper = $indexable_helper; $this->site = $site; } /** * Registers the hooks. * * @return void */ public function register_hooks() { \add_action( 'created_term', [ $this, 'build_indexable' ], \PHP_INT_MAX ); \add_action( 'edited_term', [ $this, 'build_indexable' ], \PHP_INT_MAX ); \add_action( 'delete_term', [ $this, 'delete_indexable' ], \PHP_INT_MAX ); } /** * Deletes a term from the index. * * @param int $term_id The Term ID to delete. * * @return void */ public function delete_indexable( $term_id ) { $indexable = $this->repository->find_by_id_and_type( $term_id, 'term', false ); if ( ! $indexable ) { return; } $indexable->delete(); \do_action( 'wpseo_indexable_deleted', $indexable ); } /** * Update the taxonomy meta data on save. * * @param int $term_id ID of the term to save data for. * * @return void */ public function build_indexable( $term_id ) { // Bail if this is a multisite installation and the site has been switched. if ( $this->site->is_multisite_and_switched() ) { return; } $term = \get_term( $term_id ); if ( $term === null || \is_wp_error( $term ) ) { return; } if ( ! \is_taxonomy_viewable( $term->taxonomy ) ) { return; } $indexable = $this->repository->find_by_id_and_type( $term_id, 'term', false ); // If we haven't found an existing indexable, create it. Otherwise update it. $indexable = $this->builder->build_for_id_and_type( $term_id, 'term', $indexable ); if ( ! $indexable ) { return; } // Update links. $this->link_builder->build( $indexable, $term->description ); $indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) ); $this->indexable_helper->save_indexable( $indexable ); } } integrations/watchers/indexable-permalink-watcher.php 0000644 00000017404 15025657560 0017172 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * WordPress Permalink structure watcher. * * Handles updates to the permalink_structure for the Indexables table. */ class Indexable_Permalink_Watcher implements Integration_Interface { /** * Represents the options helper. * * @var Options_Helper */ protected $options_helper; /** * The taxonomy helper. * * @var Taxonomy_Helper */ protected $taxonomy_helper; /** * The post type helper. * * @var Post_Type_Helper */ private $post_type; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Permalink_Watcher constructor. * * @param Post_Type_Helper $post_type The post type helper. * @param Options_Helper $options The options helper. * @param Indexable_Helper $indexable The indexable helper. * @param Taxonomy_Helper $taxonomy_helper The taxonomy helper. */ public function __construct( Post_Type_Helper $post_type, Options_Helper $options, Indexable_Helper $indexable, Taxonomy_Helper $taxonomy_helper ) { $this->post_type = $post_type; $this->options_helper = $options; $this->indexable_helper = $indexable; $this->taxonomy_helper = $taxonomy_helper; $this->schedule_cron(); } /** * Registers the hooks. * * @return void */ public function register_hooks() { \add_action( 'update_option_permalink_structure', [ $this, 'reset_permalinks' ] ); \add_action( 'update_option_category_base', [ $this, 'reset_permalinks_term' ], 10, 3 ); \add_action( 'update_option_tag_base', [ $this, 'reset_permalinks_term' ], 10, 3 ); \add_action( 'wpseo_permalink_structure_check', [ $this, 'force_reset_permalinks' ] ); \add_action( 'wpseo_deactivate', [ $this, 'unschedule_cron' ] ); } /** * Resets the permalinks for everything that is related to the permalink structure. * * @return void */ public function reset_permalinks() { $post_types = $this->get_post_types(); foreach ( $post_types as $post_type ) { $this->reset_permalinks_post_type( $post_type ); } $taxonomies = $this->get_taxonomies_for_post_types( $post_types ); foreach ( $taxonomies as $taxonomy ) { $this->indexable_helper->reset_permalink_indexables( 'term', $taxonomy ); } $this->indexable_helper->reset_permalink_indexables( 'user' ); $this->indexable_helper->reset_permalink_indexables( 'date-archive' ); $this->indexable_helper->reset_permalink_indexables( 'system-page' ); // Always update `permalink_structure` in the wpseo option. $this->options_helper->set( 'permalink_structure', \get_option( 'permalink_structure' ) ); } /** * Resets the permalink for the given post type. * * @param string $post_type The post type to reset. * * @return void */ public function reset_permalinks_post_type( $post_type ) { $this->indexable_helper->reset_permalink_indexables( 'post', $post_type ); $this->indexable_helper->reset_permalink_indexables( 'post-type-archive', $post_type ); } /** * Resets the term indexables when the base has been changed. * * @param string $old_value Unused. The old option value. * @param string $new_value Unused. The new option value. * @param string $type The option name. * * @return void */ public function reset_permalinks_term( $old_value, $new_value, $type ) { $subtype = $type; $reason = Indexing_Reasons::REASON_PERMALINK_SETTINGS; // When the subtype contains _base, just strip it. if ( \strstr( $subtype, '_base' ) ) { $subtype = \substr( $type, 0, -5 ); } if ( $subtype === 'tag' ) { $subtype = 'post_tag'; $reason = Indexing_Reasons::REASON_TAG_BASE_PREFIX; } if ( $subtype === 'category' ) { $reason = Indexing_Reasons::REASON_CATEGORY_BASE_PREFIX; } $this->indexable_helper->reset_permalink_indexables( 'term', $subtype, $reason ); } /** * Resets the permalink indexables automatically, if necessary. * * @return bool Whether the reset request ran. */ public function force_reset_permalinks() { if ( \get_option( 'tag_base' ) !== $this->options_helper->get( 'tag_base_url' ) ) { $this->reset_permalinks_term( null, null, 'tag_base' ); $this->options_helper->set( 'tag_base_url', \get_option( 'tag_base' ) ); } if ( \get_option( 'category_base' ) !== $this->options_helper->get( 'category_base_url' ) ) { $this->reset_permalinks_term( null, null, 'category_base' ); $this->options_helper->set( 'category_base_url', \get_option( 'category_base' ) ); } if ( $this->should_reset_permalinks() ) { $this->reset_permalinks(); return true; } $this->reset_altered_custom_taxonomies(); return true; } /** * Checks whether the permalinks should be reset after `permalink_structure` has changed. * * @return bool Whether the permalinks should be reset. */ public function should_reset_permalinks() { return \get_option( 'permalink_structure' ) !== $this->options_helper->get( 'permalink_structure' ); } /** * Resets custom taxonomies if their slugs have changed. * * @return void */ public function reset_altered_custom_taxonomies() { $taxonomies = $this->taxonomy_helper->get_custom_taxonomies(); $custom_taxonomy_bases = $this->options_helper->get( 'custom_taxonomy_slugs', [] ); $new_taxonomy_bases = []; foreach ( $taxonomies as $taxonomy ) { $taxonomy_slug = $this->taxonomy_helper->get_taxonomy_slug( $taxonomy ); $new_taxonomy_bases[ $taxonomy ] = $taxonomy_slug; if ( ! \array_key_exists( $taxonomy, $custom_taxonomy_bases ) ) { continue; } if ( $taxonomy_slug !== $custom_taxonomy_bases[ $taxonomy ] ) { $this->indexable_helper->reset_permalink_indexables( 'term', $taxonomy ); } } $this->options_helper->set( 'custom_taxonomy_slugs', $new_taxonomy_bases ); } /** * Retrieves a list with the public post types. * * @return array The post types. */ protected function get_post_types() { /** * Filter: Gives the possibility to filter out post types. * * @param array $post_types The post type names. * * @return array The post types. */ $post_types = \apply_filters( 'wpseo_post_types_reset_permalinks', $this->post_type->get_public_post_types() ); return $post_types; } /** * Retrieves the taxonomies that belongs to the public post types. * * @param array $post_types The post types to get taxonomies for. * * @return array The retrieved taxonomies. */ protected function get_taxonomies_for_post_types( $post_types ) { $taxonomies = []; foreach ( $post_types as $post_type ) { $taxonomies[] = \get_object_taxonomies( $post_type, 'names' ); } $taxonomies = \array_merge( [], ...$taxonomies ); $taxonomies = \array_unique( $taxonomies ); return $taxonomies; } /** * Schedules the WP-Cron job to check the permalink_structure status. * * @return void */ protected function schedule_cron() { if ( \wp_next_scheduled( 'wpseo_permalink_structure_check' ) ) { return; } \wp_schedule_event( \time(), 'daily', 'wpseo_permalink_structure_check' ); } /** * Unschedules the WP-Cron job to check the permalink_structure status. * * @return void */ public function unschedule_cron() { if ( ! \wp_next_scheduled( 'wpseo_permalink_structure_check' ) ) { return; } \wp_clear_scheduled_hook( 'wpseo_permalink_structure_check' ); } } integrations/watchers/indexable-homeurl-watcher.php 0000644 00000005410 15025657560 0016655 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use WP_CLI; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Home url option watcher. * * Handles updates to the home URL option for the Indexables table. */ class Indexable_HomeUrl_Watcher implements Integration_Interface { /** * Represents the options helper. * * @var Options_Helper */ protected $options_helper; /** * The post type helper. * * @var Post_Type_Helper */ private $post_type; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_HomeUrl_Watcher constructor. * * @param Post_Type_Helper $post_type The post type helper. * @param Options_Helper $options The options helper. * @param Indexable_Helper $indexable The indexable helper. */ public function __construct( Post_Type_Helper $post_type, Options_Helper $options, Indexable_Helper $indexable ) { $this->post_type = $post_type; $this->options_helper = $options; $this->indexable_helper = $indexable; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_home', [ $this, 'reset_permalinks' ] ); \add_action( 'wpseo_permalink_structure_check', [ $this, 'force_reset_permalinks' ] ); } /** * Resets the permalinks for everything that is related to the permalink structure. * * @return void */ public function reset_permalinks() { $this->indexable_helper->reset_permalink_indexables( null, null, Indexing_Reasons::REASON_HOME_URL_OPTION ); // Reset the home_url option. $this->options_helper->set( 'home_url', \get_home_url() ); } /** * Resets the permalink indexables automatically, if necessary. * * @return bool Whether the request ran. */ public function force_reset_permalinks() { if ( $this->should_reset_permalinks() ) { $this->reset_permalinks(); if ( \defined( 'WP_CLI' ) && \WP_CLI ) { WP_CLI::success( \__( 'All permalinks were successfully reset', 'wordpress-seo' ) ); } return true; } return false; } /** * Checks whether permalinks should be reset. * * @return bool Whether the permalinks should be reset. */ public function should_reset_permalinks() { return \get_home_url() !== $this->options_helper->get( 'home_url' ); } } integrations/watchers/woocommerce-beta-editor-watcher.php 0000644 00000010233 15025657560 0017764 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional; use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Helpers\Notification_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Woocommerce_Beta_Editor_Presenter; use Yoast_Notification; use Yoast_Notification_Center; /** * Shows a notification for users who have Woocommerce product beta editor enabled. */ class Woocommerce_Beta_Editor_Watcher implements Integration_Interface { /** * The notification ID. */ public const NOTIFICATION_ID = 'wpseo-woocommerce-beta-editor-warning'; /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * The Yoast notification center. * * @var Yoast_Notification_Center */ protected $notification_center; /** * The notification helper. * * @var Notification_Helper */ protected $notification_helper; /** * The WooCommerce conditional. * * @var WooCommerce_Conditional */ protected $woocommerce_conditional; /** * The Woocommerce beta editor presenter. * * @var Woocommerce_Beta_Editor_Presenter */ protected $presenter; /** * Woocommerce_Beta_Editor_Watcher constructor. * * @param Yoast_Notification_Center $notification_center The notification center. * @param Notification_Helper $notification_helper The notification helper. * @param Short_Link_Helper $short_link_helper The short link helper. * @param WooCommerce_Conditional $woocommerce_conditional The WooCommerce conditional. */ public function __construct( Yoast_Notification_Center $notification_center, Notification_Helper $notification_helper, Short_Link_Helper $short_link_helper, WooCommerce_Conditional $woocommerce_conditional ) { $this->notification_center = $notification_center; $this->notification_helper = $notification_helper; $this->short_link_helper = $short_link_helper; $this->woocommerce_conditional = $woocommerce_conditional; $this->presenter = new Woocommerce_Beta_Editor_Presenter( $this->short_link_helper ); } /** * Returns the conditionals based on which this loadable should be active. * * @return string[] The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class, Not_Admin_Ajax_Conditional::class ]; } /** * Initializes the integration. * * On admin_init, it is checked whether the notification about Woocommerce product beta editor enabled should be shown. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'manage_woocommerce_beta_editor_notification' ] ); } /** * Manage the Woocommerce product beta editor notification. * * Shows the notification if needed and deletes it if needed. * * @return void */ public function manage_woocommerce_beta_editor_notification() { if ( \get_option( 'woocommerce_feature_product_block_editor_enabled' ) === 'yes' && $this->woocommerce_conditional->is_met() ) { $this->maybe_add_woocommerce_beta_editor_notification(); } else { $this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID ); } } /** * Add the Woocommerce product beta editor enabled notification if it does not exist yet. * * @return void */ public function maybe_add_woocommerce_beta_editor_notification() { if ( ! $this->notification_center->get_notification_by_id( self::NOTIFICATION_ID ) ) { $notification = $this->notification(); $this->notification_helper->restore_notification( $notification ); $this->notification_center->add_notification( $notification ); } } /** * Returns an instance of the notification. * * @return Yoast_Notification The notification to show. */ protected function notification() { return new Yoast_Notification( $this->presenter->present(), [ 'type' => Yoast_Notification::ERROR, 'id' => self::NOTIFICATION_ID, 'capabilities' => 'wpseo_manage_options', 'priority' => 1, ] ); } } integrations/watchers/option-titles-watcher.php 0000644 00000006463 15025657560 0016074 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\WordPress\Wrapper; /** * Watcher for the titles option. * * Represents the option titles watcher. */ class Option_Titles_Watcher implements Integration_Interface { /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 ); } /** * Returns the conditionals based on which this loadable should be active. * * @return array<Migrations_Conditional> */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Checks if one of the relevant options has been changed. * * @param array<string|int|bool> $old_value The old value of the option. * @param array<string|int|bool> $new_value The new value of the option. * * @return bool Whether or not the ancestors are removed. */ public function check_option( $old_value, $new_value ) { // If this is the first time saving the option, thus when value is false. if ( $old_value === false ) { $old_value = []; } if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) { return false; } $relevant_keys = $this->get_relevant_keys(); if ( empty( $relevant_keys ) ) { return false; } $post_types = []; foreach ( $relevant_keys as $post_type => $relevant_option ) { // If both values aren't set they haven't changed. if ( ! isset( $old_value[ $relevant_option ] ) && ! isset( $new_value[ $relevant_option ] ) ) { continue; } if ( $old_value[ $relevant_option ] !== $new_value[ $relevant_option ] ) { $post_types[] = $post_type; } } return $this->delete_ancestors( $post_types ); } /** * Retrieves the relevant keys. * * @return array<string> Array with the relevant keys. */ protected function get_relevant_keys() { $post_types = \get_post_types( [ 'public' => true ], 'names' ); if ( ! \is_array( $post_types ) || $post_types === [] ) { return []; } $relevant_keys = []; foreach ( $post_types as $post_type ) { $relevant_keys[ $post_type ] = 'post_types-' . $post_type . '-maintax'; } return $relevant_keys; } /** * Removes the ancestors for given post types. * * @param array<string> $post_types The post types to remove hierarchy for. * * @return bool True when delete query was successful. */ protected function delete_ancestors( $post_types ) { if ( empty( $post_types ) ) { return false; } $wpdb = Wrapper::get_wpdb(); $hierarchy_table = Model::get_table_name( 'Indexable_Hierarchy' ); $indexable_table = Model::get_table_name( 'Indexable' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- Delete query. $result = $wpdb->query( $wpdb->prepare( " DELETE FROM %i WHERE indexable_id IN( SELECT id FROM %i WHERE object_type = 'post' AND object_sub_type IN( " . \implode( ', ', \array_fill( 0, \count( $post_types ), '%s' ) ) . ' ) )', $hierarchy_table, $indexable_table, ...$post_types ) ); return $result !== false; } } integrations/watchers/search-engines-discouraged-watcher.php 0000644 00000015752 15025657560 0020445 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Notification_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Search_Engines_Discouraged_Presenter; use Yoast_Notification; use Yoast_Notification_Center; /** * Shows a notification for users who have access for robots disabled. */ class Search_Engines_Discouraged_Watcher implements Integration_Interface { use No_Conditionals; /** * The notification ID. */ public const NOTIFICATION_ID = 'wpseo-search-engines-discouraged'; /** * The Yoast notification center. * * @var Yoast_Notification_Center */ protected $notification_center; /** * The notification helper. * * @var Notification_Helper */ protected $notification_helper; /** * The search engines discouraged presenter. * * @var Search_Engines_Discouraged_Presenter */ protected $presenter; /** * The current page helper. * * @var Current_Page_Helper */ protected $current_page_helper; /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The capability helper. * * @var Capability_Helper */ protected $capability_helper; /** * Search_Engines_Discouraged_Watcher constructor. * * @param Yoast_Notification_Center $notification_center The notification center. * @param Notification_Helper $notification_helper The notification helper. * @param Current_Page_Helper $current_page_helper The current page helper. * @param Options_Helper $options_helper The options helper. * @param Capability_Helper $capability_helper The capability helper. */ public function __construct( Yoast_Notification_Center $notification_center, Notification_Helper $notification_helper, Current_Page_Helper $current_page_helper, Options_Helper $options_helper, Capability_Helper $capability_helper ) { $this->notification_center = $notification_center; $this->notification_helper = $notification_helper; $this->current_page_helper = $current_page_helper; $this->options_helper = $options_helper; $this->capability_helper = $capability_helper; $this->presenter = new Search_Engines_Discouraged_Presenter(); } /** * Initializes the integration. * * On admin_init, it is checked whether the notification about search engines being discouraged should be shown. * On admin_notices, the notice about the search engines being discouraged will be shown when necessary. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'manage_search_engines_discouraged_notification' ] ); \add_action( 'update_option_blog_public', [ $this, 'restore_ignore_option' ] ); /* * The `admin_notices` hook fires on single site admin pages vs. * `network_admin_notices` which fires on multisite admin pages and * `user_admin_notices` which fires on multisite user admin pages. */ \add_action( 'admin_notices', [ $this, 'maybe_show_search_engines_discouraged_notice' ] ); } /** * Manage the search engines discouraged notification. * * Shows the notification if needed and deletes it if needed. * * @return void */ public function manage_search_engines_discouraged_notification() { if ( ! $this->should_show_search_engines_discouraged_notification() ) { $this->remove_search_engines_discouraged_notification_if_exists(); } else { $this->maybe_add_search_engines_discouraged_notification(); } } /** * Show the search engine discouraged notice when needed. * * @return void */ public function maybe_show_search_engines_discouraged_notice() { if ( ! $this->should_show_search_engines_discouraged_notice() ) { return; } $this->show_search_engines_discouraged_notice(); } /** * Whether the search engines discouraged notification should be shown. * * @return bool */ protected function should_show_search_engines_discouraged_notification() { return $this->search_engines_are_discouraged() && $this->options_helper->get( 'ignore_search_engines_discouraged_notice', false ) === false; } /** * Remove the search engines discouraged notification if it exists. * * @return void */ protected function remove_search_engines_discouraged_notification_if_exists() { $this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID ); } /** * Add the search engines discouraged notification if it does not exist yet. * * @return void */ protected function maybe_add_search_engines_discouraged_notification() { if ( ! $this->notification_center->get_notification_by_id( self::NOTIFICATION_ID ) ) { $notification = $this->notification(); $this->notification_helper->restore_notification( $notification ); $this->notification_center->add_notification( $notification ); } } /** * Checks whether search engines are discouraged from indexing the site. * * @return bool Whether search engines are discouraged from indexing the site. */ protected function search_engines_are_discouraged() { return (string) \get_option( 'blog_public' ) === '0'; } /** * Whether the search engines notice should be shown. * * @return bool */ protected function should_show_search_engines_discouraged_notice() { $pages_to_show_notice = [ 'index.php', 'plugins.php', 'update-core.php', ]; return ( $this->search_engines_are_discouraged() && $this->capability_helper->current_user_can( 'manage_options' ) && $this->options_helper->get( 'ignore_search_engines_discouraged_notice', false ) === false && ( $this->current_page_helper->is_yoast_seo_page() || \in_array( $this->current_page_helper->get_current_admin_page(), $pages_to_show_notice, true ) ) && $this->current_page_helper->get_current_yoast_seo_page() !== 'wpseo_dashboard' ); } /** * Show the search engines discouraged notice. * * @return void */ protected function show_search_engines_discouraged_notice() { \printf( '<div id="robotsmessage" class="notice notice-error">%1$s</div>', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output from present() is considered safe. $this->presenter->present() ); } /** * Returns an instance of the notification. * * @return Yoast_Notification The notification to show. */ protected function notification() { return new Yoast_Notification( $this->presenter->present(), [ 'type' => Yoast_Notification::ERROR, 'id' => self::NOTIFICATION_ID, 'capabilities' => 'wpseo_manage_options', 'priority' => 1, ] ); } /** * Should restore the ignore option for the search engines discouraged notice. * * @return void */ public function restore_ignore_option() { if ( ! $this->search_engines_are_discouraged() ) { $this->options_helper->set( 'ignore_search_engines_discouraged_notice', false ); } } } integrations/watchers/indexable-date-archive-watcher.php 0000644 00000005035 15025657560 0017541 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Date archive watcher to save the meta data to an indexable. * * Watches the date archive options to save the meta information when updated. */ class Indexable_Date_Archive_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Date_Archive_Watcher constructor. * * @param Indexable_Repository $repository The repository to use. * @param Indexable_Builder $builder The date archive builder to use. */ public function __construct( Indexable_Repository $repository, Indexable_Builder $builder ) { $this->repository = $repository; $this->builder = $builder; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 ); } /** * Checks if the date archive indexable needs to be rebuild based on option values. * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * * @return void */ public function check_option( $old_value, $new_value ) { $relevant_keys = [ 'title-archive-wpseo', 'breadcrumbs-archiveprefix', 'metadesc-archive-wpseo', 'noindex-archive-wpseo' ]; foreach ( $relevant_keys as $key ) { // If both values aren't set they haven't changed. if ( ! isset( $old_value[ $key ] ) && ! isset( $new_value[ $key ] ) ) { continue; } // If the value was set but now isn't, is set but wasn't or is not the same it has changed. if ( ! isset( $old_value[ $key ] ) || ! isset( $new_value[ $key ] ) || $old_value[ $key ] !== $new_value[ $key ] ) { $this->build_indexable(); return; } } } /** * Saves the date archive. * * @return void */ public function build_indexable() { $indexable = $this->repository->find_for_date_archive( false ); $this->builder->build_for_date_archive( $indexable ); } } integrations/watchers/indexable-home-page-watcher.php 0000644 00000007066 15025657560 0017055 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Home page watcher to save the meta data to an Indexable. * * Watches the home page options to save the meta information when updated. */ class Indexable_Home_Page_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Home_Page_Watcher constructor. * * @param Indexable_Repository $repository The repository to use. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Indexable_Builder $builder The post builder to use. */ public function __construct( Indexable_Repository $repository, Indexable_Helper $indexable_helper, Indexable_Builder $builder ) { $this->repository = $repository; $this->indexable_helper = $indexable_helper; $this->builder = $builder; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 15, 3 ); \add_action( 'update_option_wpseo_social', [ $this, 'check_option' ], 15, 3 ); \add_action( 'update_option_blog_public', [ $this, 'build_indexable' ] ); \add_action( 'update_option_blogdescription', [ $this, 'build_indexable' ] ); } /** * Checks if the home page indexable needs to be rebuild based on option values. * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * @param string $option The name of the option. * * @return void */ public function check_option( $old_value, $new_value, $option ) { $relevant_keys = [ 'wpseo_titles' => [ 'title-home-wpseo', 'breadcrumbs-home', 'metadesc-home-wpseo', 'open_graph_frontpage_title', 'open_graph_frontpage_desc', 'open_graph_frontpage_image', ], ]; if ( ! isset( $relevant_keys[ $option ] ) ) { return; } foreach ( $relevant_keys[ $option ] as $key ) { // If both values aren't set they haven't changed. if ( ! isset( $old_value[ $key ] ) && ! isset( $new_value[ $key ] ) ) { continue; } // If the value was set but now isn't, is set but wasn't or is not the same it has changed. if ( ! isset( $old_value[ $key ] ) || ! isset( $new_value[ $key ] ) || $old_value[ $key ] !== $new_value[ $key ] ) { $this->build_indexable(); return; } } } /** * Saves the home page. * * @return void */ public function build_indexable() { $indexable = $this->repository->find_for_home_page( false ); if ( $indexable === false && ! $this->indexable_helper->should_index_indexables() ) { return; } $indexable = $this->builder->build_for_home_page( $indexable ); if ( $indexable ) { $indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) ); $indexable->save(); } } } integrations/watchers/primary-term-watcher.php 0000644 00000010644 15025657560 0015706 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use WP_Term; use WPSEO_Meta; use WPSEO_Primary_Term; use Yoast\WP\SEO\Builders\Primary_Term_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Primary_Term_Helper; use Yoast\WP\SEO\Helpers\Site_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Primary_Term_Repository; /** * Primary Term watcher. * * Watches Posts to save the primary term when set. */ class Primary_Term_Watcher implements Integration_Interface { /** * The primary term repository. * * @var Primary_Term_Repository */ protected $repository; /** * Represents the site helper. * * @var Site_Helper */ protected $site; /** * The primary term helper. * * @var Primary_Term_Helper */ protected $primary_term; /** * The primary term builder. * * @var Primary_Term_Builder */ protected $primary_term_builder; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Primary_Term_Watcher constructor. * * @codeCoverageIgnore It sets dependencies. * * @param Primary_Term_Repository $repository The primary term repository. * @param Site_Helper $site The site helper. * @param Primary_Term_Helper $primary_term The primary term helper. * @param Primary_Term_Builder $primary_term_builder The primary term builder. */ public function __construct( Primary_Term_Repository $repository, Site_Helper $site, Primary_Term_Helper $primary_term, Primary_Term_Builder $primary_term_builder ) { $this->repository = $repository; $this->site = $site; $this->primary_term = $primary_term; $this->primary_term_builder = $primary_term_builder; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'save_post', [ $this, 'save_primary_terms' ], \PHP_INT_MAX ); \add_action( 'delete_post', [ $this, 'delete_primary_terms' ] ); } /** * Saves all selected primary terms. * * @param int $post_id Post ID to save primary terms for. * * @return void */ public function save_primary_terms( $post_id ) { // Bail if this is a multisite installation and the site has been switched. if ( $this->site->is_multisite_and_switched() ) { return; } $taxonomies = $this->primary_term->get_primary_term_taxonomies( $post_id ); foreach ( $taxonomies as $taxonomy ) { $this->save_primary_term( $post_id, $taxonomy ); } $this->primary_term_builder->build( $post_id ); } /** * Saves the primary term for a specific taxonomy. * * @param int $post_id Post ID to save primary term for. * @param WP_Term $taxonomy Taxonomy to save primary term for. * * @return void */ protected function save_primary_term( $post_id, $taxonomy ) { if ( isset( $_POST[ WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term' ] ) && \is_string( $_POST[ WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term' ] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are casting to an integer. $primary_term_id = (int) \wp_unslash( $_POST[ WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_term' ] ); if ( $primary_term_id <= 0 ) { $primary_term = ''; } else { $primary_term = (string) $primary_term_id; } // We accept an empty string here because we need to save that if no terms are selected. if ( \check_admin_referer( 'save-primary-term', WPSEO_Meta::$form_prefix . 'primary_' . $taxonomy->name . '_nonce' ) !== null ) { $primary_term_object = new WPSEO_Primary_Term( $taxonomy->name, $post_id ); $primary_term_object->set_primary_term( $primary_term ); } } } /** * Deletes primary terms for a post. * * @param int $post_id The post to delete the terms of. * * @return void */ public function delete_primary_terms( $post_id ) { foreach ( $this->primary_term->get_primary_term_taxonomies( $post_id ) as $taxonomy ) { $primary_term_indexable = $this->repository->find_by_post_id_and_taxonomy( $post_id, $taxonomy->name, false ); if ( ! $primary_term_indexable ) { continue; } $primary_term_indexable->delete(); } } } integrations/watchers/indexable-taxonomy-change-watcher.php 0000644 00000012070 15025657560 0020303 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Indexing_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; use Yoast\WP\SEO\Integrations\Cleanup_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Notification_Center; /** * Taxonomy watcher. * * Responds to changes in taxonomies public availability. */ class Indexable_Taxonomy_Change_Watcher implements Integration_Interface { /** * The indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Holds the Options_Helper instance. * * @var Options_Helper */ private $options; /** * Holds the Taxonomy_Helper instance. * * @var Taxonomy_Helper */ private $taxonomy_helper; /** * The notifications center. * * @var Yoast_Notification_Center */ private $notification_center; /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The conditionals. */ public static function get_conditionals() { return [ Not_Admin_Ajax_Conditional::class, Admin_Conditional::class, Migrations_Conditional::class ]; } /** * Indexable_Taxonomy_Change_Watcher constructor. * * @param Indexing_Helper $indexing_helper The indexing helper. * @param Options_Helper $options The options helper. * @param Taxonomy_Helper $taxonomy_helper The taxonomy helper. * @param Yoast_Notification_Center $notification_center The notification center. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Indexing_Helper $indexing_helper, Options_Helper $options, Taxonomy_Helper $taxonomy_helper, Yoast_Notification_Center $notification_center, Indexable_Helper $indexable_helper ) { $this->indexing_helper = $indexing_helper; $this->options = $options; $this->taxonomy_helper = $taxonomy_helper; $this->notification_center = $notification_center; $this->indexable_helper = $indexable_helper; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'check_taxonomy_public_availability' ] ); } /** * Checks if one or more taxonomies change visibility. * * @return void */ public function check_taxonomy_public_availability() { // We have to make sure this is just a plain http request, no ajax/REST. if ( \wp_is_json_request() ) { return; } $public_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies(); $last_known_public_taxonomies = $this->options->get( 'last_known_public_taxonomies', [] ); // Initializing the option on the first run. if ( empty( $last_known_public_taxonomies ) ) { $this->options->set( 'last_known_public_taxonomies', $public_taxonomies ); return; } // We look for new public taxonomies. $newly_made_public_taxonomies = \array_diff( $public_taxonomies, $last_known_public_taxonomies ); // We look fortaxonomies that from public have been made private. $newly_made_non_public_taxonomies = \array_diff( $last_known_public_taxonomies, $public_taxonomies ); // Nothing to be done if no changes has been made to taxonomies. if ( empty( $newly_made_public_taxonomies ) && ( empty( $newly_made_non_public_taxonomies ) ) ) { return; } // Update the list of last known public taxonomies in the database. $this->options->set( 'last_known_public_taxonomies', $public_taxonomies ); // There are new taxonomies that have been made public. if ( ! empty( $newly_made_public_taxonomies ) ) { // Force a notification requesting to start the SEO data optimization. \delete_transient( Indexable_Term_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( Indexable_Term_Indexation_Action::UNINDEXED_LIMITED_COUNT_TRANSIENT ); $this->indexing_helper->set_reason( Indexing_Reasons::REASON_TAXONOMY_MADE_PUBLIC ); \do_action( 'new_public_taxonomy_notifications', $newly_made_public_taxonomies ); } // There are taxonomies that have been made private. if ( ! empty( $newly_made_non_public_taxonomies ) && $this->indexable_helper->should_index_indexables() ) { // Schedule a cron job to remove all the terms whose taxonomy has been made private. $cleanup_not_yet_scheduled = ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ); if ( $cleanup_not_yet_scheduled ) { \wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK ); } \do_action( 'clean_new_public_taxonomy_notifications', $newly_made_non_public_taxonomies ); } } } integrations/watchers/auto-update-watcher.php 0000644 00000002772 15025657560 0015511 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Notification_Center; /** * Shows a notification for users who have WordPress auto updates enabled but not Yoast SEO auto updates. */ class Auto_Update_Watcher implements Integration_Interface { use No_Conditionals; /** * The notification ID. */ public const NOTIFICATION_ID = 'wpseo-auto-update'; /** * The Yoast notification center. * * @var Yoast_Notification_Center */ protected $notification_center; /** * Auto_Update constructor. * * @param Yoast_Notification_Center $notification_center The notification center. */ public function __construct( Yoast_Notification_Center $notification_center ) { $this->notification_center = $notification_center; } /** * Initializes the integration. * * On admin_init, it is checked whether the notification to auto-update Yoast SEO needs to be shown or removed. * This is also done when major WP core updates are being enabled or disabled, * and when automatic updates for Yoast SEO are being enabled or disabled. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'remove_notification' ] ); } /** * Removes the notification from the notification center, if it exists. * * @return void */ public function remove_notification() { $this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID ); } } integrations/watchers/indexable-ancestor-watcher.php 0000644 00000020337 15025657560 0017025 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Permalink_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Ancestor watcher to update the ancestor's children. * * Updates its children's permalink when the ancestor itself is updated. */ class Indexable_Ancestor_Watcher implements Integration_Interface { /** * Represents the indexable repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * Represents the indexable hierarchy builder. * * @var Indexable_Hierarchy_Builder */ protected $indexable_hierarchy_builder; /** * Represents the indexable hierarchy repository. * * @var Indexable_Hierarchy_Repository */ protected $indexable_hierarchy_repository; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * Represents the permalink helper. * * @var Permalink_Helper */ protected $permalink_helper; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * Sets the needed dependencies. * * @param Indexable_Repository $indexable_repository The indexable repository. * @param Indexable_Hierarchy_Builder $indexable_hierarchy_builder The indexable hierarchy builder. * @param Indexable_Hierarchy_Repository $indexable_hierarchy_repository The indexable hierarchy repository. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Permalink_Helper $permalink_helper The permalink helper. * @param Post_Type_Helper $post_type_helper The post type helper. */ public function __construct( Indexable_Repository $indexable_repository, Indexable_Hierarchy_Builder $indexable_hierarchy_builder, Indexable_Hierarchy_Repository $indexable_hierarchy_repository, Indexable_Helper $indexable_helper, Permalink_Helper $permalink_helper, Post_Type_Helper $post_type_helper ) { $this->indexable_repository = $indexable_repository; $this->indexable_hierarchy_builder = $indexable_hierarchy_builder; $this->indexable_hierarchy_repository = $indexable_hierarchy_repository; $this->indexable_helper = $indexable_helper; $this->permalink_helper = $permalink_helper; $this->post_type_helper = $post_type_helper; } /** * Registers the appropriate hooks. * * @return void */ public function register_hooks() { \add_action( 'wpseo_save_indexable', [ $this, 'reset_children' ], \PHP_INT_MAX, 2 ); } /** * Returns the conditionals based on which this loadable should be active. * * @return array<Migrations_Conditional> */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * If an indexable's permalink has changed, updates its children in the hierarchy table and resets the children's permalink. * * @param Indexable $indexable The indexable. * @param Indexable $indexable_before The old indexable. * * @return bool True if the children were reset. */ public function reset_children( $indexable, $indexable_before ) { if ( ! \in_array( $indexable->object_type, [ 'post', 'term' ], true ) ) { return false; } // If the permalink was null it means it was reset instead of changed. if ( $indexable->permalink === $indexable_before->permalink || $indexable_before->permalink === null ) { return false; } $child_indexable_ids = $this->indexable_hierarchy_repository->find_children( $indexable ); $child_indexables = $this->indexable_repository->find_by_ids( $child_indexable_ids ); \array_walk( $child_indexables, [ $this, 'update_hierarchy_and_permalink' ] ); if ( $indexable->object_type === 'term' ) { $child_indexables_for_term = $this->get_children_for_term( $indexable->object_id, $child_indexables ); \array_walk( $child_indexables_for_term, [ $this, 'update_hierarchy_and_permalink' ] ); } return true; } /** * Finds all child indexables for the given term. * * @param int $term_id Term to fetch the indexable for. * @param array<Indexable> $child_indexables The already known child indexables. * * @return array<Indexable> The list of additional child indexables for a given term. */ public function get_children_for_term( $term_id, array $child_indexables ) { // Finds object_ids (posts) for the term. $post_object_ids = $this->get_object_ids_for_term( $term_id, $child_indexables ); // Removes the objects that are already present in the children. $existing_post_indexables = \array_filter( $child_indexables, static function ( $indexable ) { return $indexable->object_type === 'post'; } ); $existing_post_object_ids = \wp_list_pluck( $existing_post_indexables, 'object_id' ); $post_object_ids = \array_diff( $post_object_ids, $existing_post_object_ids ); // Finds the indexables for the fetched post_object_ids. $post_indexables = $this->indexable_repository->find_by_multiple_ids_and_type( $post_object_ids, 'post', false ); // Finds the indexables for the posts that are attached to the term. $post_indexable_ids = \wp_list_pluck( $post_indexables, 'id' ); $additional_indexable_ids = $this->indexable_hierarchy_repository->find_children_by_ancestor_ids( $post_indexable_ids ); // Makes sure we only have indexable id's that we haven't fetched before. $additional_indexable_ids = \array_diff( $additional_indexable_ids, $post_indexable_ids ); // Finds the additional indexables. $additional_indexables = $this->indexable_repository->find_by_ids( $additional_indexable_ids ); // Merges all fetched indexables. return \array_merge( $post_indexables, $additional_indexables ); } /** * Updates the indexable hierarchy and indexable permalink. * * @param Indexable $indexable The indexable to update the hierarchy and permalink for. * * @return void */ protected function update_hierarchy_and_permalink( $indexable ) { if ( \is_a( $indexable, Indexable::class ) ) { $this->indexable_hierarchy_builder->build( $indexable ); $indexable->permalink = $this->permalink_helper->get_permalink_for_indexable( $indexable ); $this->indexable_helper->save_indexable( $indexable ); } } /** * Retrieves the object id's for a term based on the term-post relationship. * * @param int $term_id The term to get the object id's for. * @param array<Indexable> $child_indexables The child indexables. * * @return array<int> List with object ids for the term. */ protected function get_object_ids_for_term( $term_id, $child_indexables ) { global $wpdb; $filter_terms = static function ( $child ) { return $child->object_type === 'term'; }; $child_terms = \array_filter( $child_indexables, $filter_terms ); $child_object_ids = \array_merge( [ $term_id ], \wp_list_pluck( $child_terms, 'object_id' ) ); // Get the term-taxonomy id's for the term and its children. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $term_taxonomy_ids = $wpdb->get_col( $wpdb->prepare( 'SELECT term_taxonomy_id FROM %i WHERE term_id IN( ' . \implode( ', ', \array_fill( 0, ( \count( $child_object_ids ) ), '%s' ) ) . ' )', $wpdb->term_taxonomy, ...$child_object_ids ) ); // In the case of faulty data having been saved the above query can return 0 results. if ( empty( $term_taxonomy_ids ) ) { return []; } // Get the (post) object id's that are attached to the term. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching return $wpdb->get_col( $wpdb->prepare( 'SELECT DISTINCT object_id FROM %i WHERE term_taxonomy_id IN( ' . \implode( ', ', \array_fill( 0, \count( $term_taxonomy_ids ), '%s' ) ) . ' )', $wpdb->term_relationships, ...$term_taxonomy_ids ) ); } } integrations/watchers/indexable-static-home-page-watcher.php 0000644 00000004315 15025657560 0020334 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Watcher that checks for changes in the page used as homepage. * * Watches the static homepage option and updates the permalinks accordingly. */ class Indexable_Static_Home_Page_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Indexable_Static_Home_Page_Watcher constructor. * * @codeCoverageIgnore * * @param Indexable_Repository $repository The repository to use. */ public function __construct( Indexable_Repository $repository ) { $this->repository = $repository; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_page_on_front', [ $this, 'update_static_homepage_permalink' ], 10, 2 ); } /** * Updates the new and previous homepage's permalink when the static home page is updated. * * @param string $old_value The previous homepage's ID. * @param int $value The new homepage's ID. * * @return void */ public function update_static_homepage_permalink( $old_value, $value ) { if ( \is_string( $old_value ) ) { $old_value = (int) $old_value; } if ( $old_value === $value ) { return; } $this->update_permalink_for_page( $old_value ); $this->update_permalink_for_page( $value ); } /** * Updates the permalink based on the selected homepage settings. * * @param int $page_id The page's id. * * @return void */ private function update_permalink_for_page( $page_id ) { if ( $page_id === 0 ) { return; } $indexable = $this->repository->find_by_id_and_type( $page_id, 'post', false ); if ( $indexable === false ) { return; } $indexable->permalink = \get_permalink( $page_id ); $indexable->save(); } } integrations/watchers/indexable-author-archive-watcher.php 0000644 00000004516 15025657560 0020131 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Integrations\Cleanup_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Watches the `wpseo_titles` option for changes to the author archive settings. */ class Indexable_Author_Archive_Watcher implements Integration_Interface { /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Indexable_Author_Archive_Watcher constructor. * * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Indexable_Helper $indexable_helper ) { $this->indexable_helper = $indexable_helper; } /** * Check if the author archives are disabled whenever the `wpseo_titles` option * changes. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'reschedule_indexable_cleanup_when_author_archives_are_disabled' ], 10, 2 ); } /** * This watcher should only be run when the migrations have been run. * (Otherwise there may not be an indexable table to clean). * * @return string[] The conditionals. */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Reschedule the indexable cleanup routine if the author archives are disabled. * to make sure that all authors are removed from the indexables table. * * When author archives are disabled, they can never be indexed. * * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification * * @param array $old_value The old `wpseo_titles` option value. * @param array $new_value The new `wpseo_titles` option value. * * @phpcs:enable * @return void */ public function reschedule_indexable_cleanup_when_author_archives_are_disabled( $old_value, $new_value ) { if ( $old_value['disable-author'] !== true && $new_value['disable-author'] === true && $this->indexable_helper->should_index_indexables() ) { $cleanup_not_yet_scheduled = ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ); if ( $cleanup_not_yet_scheduled ) { \wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK ); } } } } integrations/watchers/indexable-attachment-watcher.php 0000644 00000011421 15025657560 0017331 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Helpers\Attachment_Cleanup_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Indexing_Helper; use Yoast\WP\SEO\Integrations\Cleanup_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Notification_Center; /** * Watches the disable-attachment key in wpseo_titles, in order to clear the permalink of the category indexables. */ class Indexable_Attachment_Watcher implements Integration_Interface { /** * The indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * The attachment cleanup helper. * * @var Attachment_Cleanup_Helper */ protected $attachment_cleanup; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The notifications center. * * @var Yoast_Notification_Center */ private $notification_center; /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The conditionals. */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Attachment_Watcher constructor. * * @param Indexing_Helper $indexing_helper The indexing helper. * @param Attachment_Cleanup_Helper $attachment_cleanup The attachment cleanup helper. * @param Yoast_Notification_Center $notification_center The notification center. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Indexing_Helper $indexing_helper, Attachment_Cleanup_Helper $attachment_cleanup, Yoast_Notification_Center $notification_center, Indexable_Helper $indexable_helper ) { $this->indexing_helper = $indexing_helper; $this->attachment_cleanup = $attachment_cleanup; $this->notification_center = $notification_center; $this->indexable_helper = $indexable_helper; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 20, 2 ); } /** * Checks if the disable-attachment key in wpseo_titles has a change in value, and if so, * either it cleans up attachment indexables when it has been toggled to true, * or it starts displaying a notification for the user to start a new SEO optimization. * * @phpcs:disable SlevomatCodingStandard.TypeHints.ParameterTypeHint.MissingTraversableTypeHintSpecification * * @param array $old_value The old value of the wpseo_titles option. * @param array $new_value The new value of the wpseo_titles option. * * @phpcs:enable * @return void */ public function check_option( $old_value, $new_value ) { // If this is the first time saving the option, in which case its value would be false. if ( $old_value === false ) { $old_value = []; } // If either value is not an array, return. if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) { return; } // If both values aren't set, they haven't changed. if ( ! isset( $old_value['disable-attachment'] ) && ! isset( $new_value['disable-attachment'] ) ) { return; } // If a new value has been set for 'disable-attachment', there's two things we might need to do, depending on what's the new value. if ( $old_value['disable-attachment'] !== $new_value['disable-attachment'] ) { // Delete cache because we now might have new stuff to index or old unindexed stuff don't need indexing anymore. \delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_LIMITED_COUNT_TRANSIENT ); // Set this core option (introduced in WP 6.4) to ensure consistency. if ( \get_option( 'wp_attachment_pages_enabled' ) !== false ) { \update_option( 'wp_attachment_pages_enabled', (int) ! $new_value['disable-attachment'] ); } switch ( $new_value['disable-attachment'] ) { case false: $this->indexing_helper->set_reason( Indexing_Reasons::REASON_ATTACHMENTS_MADE_ENABLED ); return; case true: $this->attachment_cleanup->remove_attachment_indexables( false ); $this->attachment_cleanup->clean_attachment_links_from_target_indexable_ids( false ); if ( $this->indexable_helper->should_index_indexables() && ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ) ) { // This just schedules the cleanup routine cron again. \wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK ); } return; } } } } integrations/watchers/indexable-post-watcher.php 0000644 00000023622 15025657560 0016174 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Exception; use WP_Post; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Builders\Indexable_Link_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Author_Archive_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Integrations\Cleanup_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Loggers\Logger; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository; use Yoast\WP\SEO\Repositories\Indexable_Repository; use YoastSEO_Vendor\Psr\Log\LogLevel; /** * WordPress Post watcher. * * Fills the Indexable according to Post data. */ class Indexable_Post_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * The indexable hierarchy repository. * * @var Indexable_Hierarchy_Repository */ private $hierarchy_repository; /** * The link builder. * * @var Indexable_Link_Builder */ protected $link_builder; /** * The author archive helper. * * @var Author_Archive_Helper */ private $author_archive; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * Holds the Post_Helper instance. * * @var Post_Helper */ private $post; /** * Holds the logger. * * @var Logger */ protected $logger; /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The conditionals. */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Post_Watcher constructor. * * @param Indexable_Repository $repository The repository to use. * @param Indexable_Builder $builder The post builder to use. * @param Indexable_Hierarchy_Repository $hierarchy_repository The hierarchy repository to use. * @param Indexable_Link_Builder $link_builder The link builder. * @param Author_Archive_Helper $author_archive The author archive helper. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Post_Helper $post The post helper. * @param Logger $logger The logger. */ public function __construct( Indexable_Repository $repository, Indexable_Builder $builder, Indexable_Hierarchy_Repository $hierarchy_repository, Indexable_Link_Builder $link_builder, Author_Archive_Helper $author_archive, Indexable_Helper $indexable_helper, Post_Helper $post, Logger $logger ) { $this->repository = $repository; $this->builder = $builder; $this->hierarchy_repository = $hierarchy_repository; $this->link_builder = $link_builder; $this->author_archive = $author_archive; $this->indexable_helper = $indexable_helper; $this->post = $post; $this->logger = $logger; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'wp_insert_post', [ $this, 'build_indexable' ], \PHP_INT_MAX ); \add_action( 'delete_post', [ $this, 'delete_indexable' ] ); \add_action( 'edit_attachment', [ $this, 'build_indexable' ], \PHP_INT_MAX ); \add_action( 'add_attachment', [ $this, 'build_indexable' ], \PHP_INT_MAX ); \add_action( 'delete_attachment', [ $this, 'delete_indexable' ] ); } /** * Deletes the meta when a post is deleted. * * @param int $post_id Post ID. * * @return void */ public function delete_indexable( $post_id ) { $indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false ); // Only interested in post indexables. if ( ! $indexable || $indexable->object_type !== 'post' ) { return; } $this->update_relations( $this->post->get_post( $post_id ) ); $this->update_has_public_posts( $indexable ); $this->hierarchy_repository->clear_ancestors( $indexable->id ); $this->link_builder->delete( $indexable ); $indexable->delete(); \do_action( 'wpseo_indexable_deleted', $indexable ); } /** * Updates the relations when the post indexable is built. * * @param Indexable $indexable The indexable. * @param WP_Post $post The post. * * @return void */ public function updated_indexable( $indexable, $post ) { // Only interested in post indexables. if ( $indexable->object_type !== 'post' ) { return; } if ( \is_a( $post, Indexable::class ) ) { \_deprecated_argument( __FUNCTION__, '17.7', 'The $old_indexable argument has been deprecated.' ); $post = $this->post->get_post( $indexable->object_id ); } $this->update_relations( $post ); } /** * Saves post meta. * * @param int $post_id Post ID. * * @return void */ public function build_indexable( $post_id ) { // Bail if this is a multisite installation and the site has been switched. if ( $this->is_multisite_and_switched() ) { return; } try { $indexable = $this->repository->find_by_id_and_type( $post_id, 'post', false ); $indexable = $this->builder->build_for_id_and_type( $post_id, 'post', $indexable ); $post = $this->post->get_post( $post_id ); /* * Update whether an author has public posts. * For example this post could be set to Draft or Private, * which can influence if its author has any public posts at all. */ if ( $indexable ) { $this->update_has_public_posts( $indexable ); } // Build links for this post. if ( $post && $indexable && \in_array( $post->post_status, $this->post->get_public_post_statuses(), true ) ) { $this->link_builder->build( $indexable, $post->post_content ); // Save indexable to persist the updated link count. $this->indexable_helper->save_indexable( $indexable ); $this->updated_indexable( $indexable, $post ); } } catch ( Exception $exception ) { $this->logger->log( LogLevel::ERROR, $exception->getMessage() ); } } /** * Updates the has_public_posts when the post indexable is built. * * @param Indexable $indexable The indexable to check. * * @return void */ protected function update_has_public_posts( $indexable ) { // Update the author indexable's has public posts value. try { $author_indexable = $this->repository->find_by_id_and_type( $indexable->author_id, 'user' ); if ( $author_indexable ) { $author_indexable->has_public_posts = $this->author_archive->author_has_public_posts( $author_indexable->object_id ); $this->indexable_helper->save_indexable( $author_indexable ); if ( $this->indexable_helper->should_index_indexable( $author_indexable ) ) { $this->reschedule_cleanup_if_author_has_no_posts( $author_indexable ); } } } catch ( Exception $exception ) { $this->logger->log( LogLevel::ERROR, $exception->getMessage() ); } // Update possible attachment's has public posts value. $this->post->update_has_public_posts_on_attachments( $indexable->object_id, $indexable->is_public ); } /** * Reschedule indexable cleanup if the author does not have any public posts. * This should remove the author from the indexable table, since we do not * want to store authors without public facing posts in the table. * * @param Indexable $author_indexable The author indexable. * * @return void */ protected function reschedule_cleanup_if_author_has_no_posts( $author_indexable ) { if ( $author_indexable->has_public_posts === false ) { $cleanup_not_yet_scheduled = ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ); if ( $cleanup_not_yet_scheduled ) { \wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK ); } } } /** * Updates the relations on post save or post status change. * * @param WP_Post $post The post that has been updated. * * @return void */ protected function update_relations( $post ) { $related_indexables = $this->get_related_indexables( $post ); foreach ( $related_indexables as $indexable ) { // Ignore everything that is not an actual indexable. if ( \is_a( $indexable, Indexable::class ) ) { $indexable->object_last_modified = \max( $indexable->object_last_modified, $post->post_modified_gmt ); $this->indexable_helper->save_indexable( $indexable ); } } } /** * Retrieves the related indexables for given post. * * @param WP_Post $post The post to get the indexables for. * * @return Indexable[] The indexables. */ protected function get_related_indexables( $post ) { /** * The related indexables. * * @var Indexable[] $related_indexables */ $related_indexables = []; $related_indexables[] = $this->repository->find_by_id_and_type( $post->post_author, 'user', false ); $related_indexables[] = $this->repository->find_for_post_type_archive( $post->post_type, false ); $related_indexables[] = $this->repository->find_for_home_page( false ); $taxonomies = \get_post_taxonomies( $post->ID ); $taxonomies = \array_filter( $taxonomies, 'is_taxonomy_viewable' ); $term_ids = []; foreach ( $taxonomies as $taxonomy ) { $terms = \get_the_terms( $post->ID, $taxonomy ); if ( empty( $terms ) || \is_wp_error( $terms ) ) { continue; } $term_ids = \array_merge( $term_ids, \wp_list_pluck( $terms, 'term_id' ) ); } $related_indexables = \array_merge( $related_indexables, $this->repository->find_by_multiple_ids_and_type( $term_ids, 'term', false ) ); return \array_filter( $related_indexables ); } /** * Tests if the site is multisite and switched. * * @return bool True when the site is multisite and switched */ protected function is_multisite_and_switched() { return \is_multisite() && \ms_is_switched(); } } integrations/watchers/indexable-author-watcher.php 0000644 00000007010 15025657560 0016502 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Watches an Author to save the meta information to an Indexable when updated. */ class Indexable_Author_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Author_Watcher constructor. * * @param Indexable_Repository $repository The repository to use. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Indexable_Builder $builder The builder to use. */ public function __construct( Indexable_Repository $repository, Indexable_Helper $indexable_helper, Indexable_Builder $builder ) { $this->repository = $repository; $this->indexable_helper = $indexable_helper; $this->builder = $builder; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'user_register', [ $this, 'build_indexable' ], \PHP_INT_MAX ); \add_action( 'profile_update', [ $this, 'build_indexable' ], \PHP_INT_MAX ); \add_action( 'deleted_user', [ $this, 'handle_user_delete' ], 10, 2 ); } /** * Deletes user meta. * * @param int $user_id User ID to delete the metadata of. * * @return void */ public function delete_indexable( $user_id ) { $indexable = $this->repository->find_by_id_and_type( $user_id, 'user', false ); if ( ! $indexable ) { return; } $indexable->delete(); \do_action( 'wpseo_indexable_deleted', $indexable ); } /** * Saves user meta. * * @param int $user_id User ID. * * @return void */ public function build_indexable( $user_id ) { $indexable = $this->repository->find_by_id_and_type( $user_id, 'user', false ); $indexable = $this->builder->build_for_id_and_type( $user_id, 'user', $indexable ); if ( $indexable ) { $indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) ); $this->indexable_helper->save_indexable( $indexable ); } } /** * Handles the case in which an author is deleted. * * @param int $user_id User ID. * @param int|null $new_user_id The ID of the user the old author's posts are reassigned to. * * @return void */ public function handle_user_delete( $user_id, $new_user_id = null ) { if ( $new_user_id !== null ) { $this->maybe_reassign_user_indexables( $user_id, $new_user_id ); } $this->delete_indexable( $user_id ); } /** * Reassigns the indexables of a user to another user. * * @param int $user_id The user ID. * @param int $new_user_id The user ID to reassign the indexables to. * * @return void */ public function maybe_reassign_user_indexables( $user_id, $new_user_id ) { $this->repository->query() ->set( 'author_id', $new_user_id ) ->where( 'author_id', $user_id ) ->update_many(); } } integrations/watchers/addon-update-watcher.php 0000644 00000016215 15025657560 0015623 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Enables Yoast add-on auto updates when Yoast SEO is enabled and the other way around. * * Also removes the auto-update toggles from the Yoast SEO add-ons. */ class Addon_Update_Watcher implements Integration_Interface { /** * ID string used by WordPress to identify the free plugin. * * @var string */ public const WPSEO_FREE_PLUGIN_ID = 'wordpress-seo/wp-seo.php'; /** * A list of Yoast add-on identifiers. * * @var string[] */ public const ADD_ON_PLUGIN_FILES = [ 'wordpress-seo-premium/wp-seo-premium.php', 'wpseo-video/video-seo.php', 'wpseo-local/local-seo.php', // When installing Local through a released zip, the path is different from the path on a dev environment. 'wpseo-woocommerce/wpseo-woocommerce.php', 'wpseo-news/wpseo-news.php', 'acf-content-analysis-for-yoast-seo/yoast-acf-analysis.php', // When installing ACF for Yoast through a released zip, the path is different from the path on a dev environment. ]; /** * Registers the hooks. * * @return void */ public function register_hooks() { \add_action( 'add_site_option_auto_update_plugins', [ $this, 'call_toggle_auto_updates_with_empty_array' ], 10, 2 ); \add_action( 'update_site_option_auto_update_plugins', [ $this, 'toggle_auto_updates_for_add_ons' ], 10, 3 ); \add_filter( 'plugin_auto_update_setting_html', [ $this, 'replace_auto_update_toggles_of_addons' ], 10, 2 ); \add_action( 'activated_plugin', [ $this, 'maybe_toggle_auto_updates_for_new_install' ] ); } /** * Returns the conditionals based on which this loadable should be active. * * @return string[] The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Replaces the auto-update toggle links for the Yoast add-ons * with a text explaining that toggling the Yoast SEO auto-update setting * automatically toggles the one for the setting for the add-ons as well. * * @param string $old_html The old HTML. * @param string $plugin The plugin. * * @return string The new HTML, with the auto-update toggle link replaced. */ public function replace_auto_update_toggles_of_addons( $old_html, $plugin ) { if ( ! \is_string( $old_html ) ) { return $old_html; } $not_a_yoast_addon = ! \in_array( $plugin, self::ADD_ON_PLUGIN_FILES, true ); if ( $not_a_yoast_addon ) { return $old_html; } $auto_updated_plugins = \get_site_option( 'auto_update_plugins' ); if ( $this->are_auto_updates_enabled( self::WPSEO_FREE_PLUGIN_ID, $auto_updated_plugins ) ) { return \sprintf( '<em>%s</em>', \sprintf( /* Translators: %1$s resolves to Yoast SEO. */ \esc_html__( 'Auto-updates are enabled based on this setting for %1$s.', 'wordpress-seo' ), 'Yoast SEO' ) ); } return \sprintf( '<em>%s</em>', \sprintf( /* Translators: %1$s resolves to Yoast SEO. */ \esc_html__( 'Auto-updates are disabled based on this setting for %1$s.', 'wordpress-seo' ), 'Yoast SEO' ) ); } /** * Handles the situation where the auto_update_plugins option did not previously exist. * * @param string $option The name of the option that is being created. * @param array|mixed $value The new (and first) value of the option that is being created. * * @return void */ public function call_toggle_auto_updates_with_empty_array( $option, $value ) { if ( $option !== 'auto_update_plugins' ) { return; } $this->toggle_auto_updates_for_add_ons( $option, $value, [] ); } /** * Enables premium auto updates when free are enabled and the other way around. * * @param string $option The name of the option that has been updated. * @param array $new_value The new value of the `auto_update_plugins` option. * @param array $old_value The old value of the `auto_update_plugins` option. * * @return void */ public function toggle_auto_updates_for_add_ons( $option, $new_value, $old_value ) { if ( $option !== 'auto_update_plugins' ) { // If future versions of WordPress change this filter's behavior, our behavior should stay consistent. return; } if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) { return; } $auto_updates_are_enabled = $this->are_auto_updates_enabled( self::WPSEO_FREE_PLUGIN_ID, $new_value ); $auto_updates_were_enabled = $this->are_auto_updates_enabled( self::WPSEO_FREE_PLUGIN_ID, $old_value ); if ( $auto_updates_are_enabled === $auto_updates_were_enabled ) { // Auto-updates for Yoast SEO have stayed the same, so have neither been enabled or disabled. return; } $auto_updates_have_been_enabled = $auto_updates_are_enabled && ! $auto_updates_were_enabled; if ( $auto_updates_have_been_enabled ) { $this->enable_auto_updates_for_addons( $new_value ); return; } else { $this->disable_auto_updates_for_addons( $new_value ); return; } if ( ! $auto_updates_are_enabled ) { return; } $auto_updates_have_been_removed = false; foreach ( self::ADD_ON_PLUGIN_FILES as $addon ) { if ( ! $this->are_auto_updates_enabled( $addon, $new_value ) ) { $auto_updates_have_been_removed = true; break; } } if ( $auto_updates_have_been_removed ) { $this->enable_auto_updates_for_addons( $new_value ); } } /** * Trigger a change in the auto update detection whenever a new Yoast addon is activated. * * @param string $plugin The plugin that is activated. * * @return void */ public function maybe_toggle_auto_updates_for_new_install( $plugin ) { $not_a_yoast_addon = ! \in_array( $plugin, self::ADD_ON_PLUGIN_FILES, true ); if ( $not_a_yoast_addon ) { return; } $enabled_auto_updates = \get_site_option( 'auto_update_plugins' ); $this->toggle_auto_updates_for_add_ons( 'auto_update_plugins', $enabled_auto_updates, [] ); } /** * Enables auto-updates for all addons. * * @param string[] $auto_updated_plugins The current list of auto-updated plugins. * * @return void */ protected function enable_auto_updates_for_addons( $auto_updated_plugins ) { $plugins = \array_unique( \array_merge( $auto_updated_plugins, self::ADD_ON_PLUGIN_FILES ) ); \update_site_option( 'auto_update_plugins', $plugins ); } /** * Disables auto-updates for all addons. * * @param string[] $auto_updated_plugins The current list of auto-updated plugins. * * @return void */ protected function disable_auto_updates_for_addons( $auto_updated_plugins ) { $plugins = \array_values( \array_diff( $auto_updated_plugins, self::ADD_ON_PLUGIN_FILES ) ); \update_site_option( 'auto_update_plugins', $plugins ); } /** * Checks whether auto updates for a plugin are enabled. * * @param string $plugin_id The plugin ID. * @param array $auto_updated_plugins The array of auto updated plugins. * * @return bool Whether auto updates for a plugin are enabled. */ protected function are_auto_updates_enabled( $plugin_id, $auto_updated_plugins ) { if ( $auto_updated_plugins === false || ! \is_array( $auto_updated_plugins ) ) { return false; } return \in_array( $plugin_id, $auto_updated_plugins, true ); } } integrations/watchers/indexable-post-meta-watcher.php 0000644 00000005632 15025657560 0017121 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use WPSEO_Meta; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * WordPress post meta watcher. */ class Indexable_Post_Meta_Watcher implements Integration_Interface { /** * The post watcher. * * @var Indexable_Post_Watcher */ protected $post_watcher; /** * An array of post IDs that need to be updated. * * @var array<int> */ protected $post_ids_to_update = []; /** * Returns the conditionals based on which this loadable should be active. * * @return string[] */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Postmeta_Watcher constructor. * * @param Indexable_Post_Watcher $post_watcher The post watcher. */ public function __construct( Indexable_Post_Watcher $post_watcher ) { $this->post_watcher = $post_watcher; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { // Register all posts whose meta have changed. \add_action( 'added_post_meta', [ $this, 'add_post_id' ], 10, 3 ); \add_action( 'updated_post_meta', [ $this, 'add_post_id' ], 10, 3 ); \add_action( 'deleted_post_meta', [ $this, 'add_post_id' ], 10, 3 ); // Remove posts that get saved as they are handled by the Indexable_Post_Watcher. \add_action( 'wp_insert_post', [ $this, 'remove_post_id' ] ); \add_action( 'delete_post', [ $this, 'remove_post_id' ] ); \add_action( 'edit_attachment', [ $this, 'remove_post_id' ] ); \add_action( 'add_attachment', [ $this, 'remove_post_id' ] ); \add_action( 'delete_attachment', [ $this, 'remove_post_id' ] ); // Update indexables of all registered posts. \register_shutdown_function( [ $this, 'update_indexables' ] ); } /** * Adds a post id to the array of posts to update. * * @param int|string $meta_id The meta ID. * @param int|string $post_id The post ID. * @param string $meta_key The meta key. * * @return void */ public function add_post_id( $meta_id, $post_id, $meta_key ) { // Only register changes to our own meta. if ( \is_string( $meta_key ) && \strpos( $meta_key, WPSEO_Meta::$meta_prefix ) !== 0 ) { return; } if ( ! \in_array( $post_id, $this->post_ids_to_update, true ) ) { $this->post_ids_to_update[] = (int) $post_id; } } /** * Removes a post id from the array of posts to update. * * @param int|string $post_id The post ID. * * @return void */ public function remove_post_id( $post_id ) { $this->post_ids_to_update = \array_diff( $this->post_ids_to_update, [ (int) $post_id ] ); } /** * Updates all indexables changed during the request. * * @return void */ public function update_indexables() { foreach ( $this->post_ids_to_update as $post_id ) { $this->post_watcher->build_indexable( $post_id ); } } } integrations/watchers/indexable-post-type-archive-watcher.php 0000644 00000007735 15025657560 0020601 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Post type archive watcher to save the meta data to an Indexable. * * Watches the home page options to save the meta information when updated. */ class Indexable_Post_Type_Archive_Watcher implements Integration_Interface { /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Indexable_Post_Type_Archive_Watcher constructor. * * @param Indexable_Repository $repository The repository to use. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Indexable_Builder $builder The post builder to use. */ public function __construct( Indexable_Repository $repository, Indexable_Helper $indexable_helper, Indexable_Builder $builder ) { $this->repository = $repository; $this->indexable_helper = $indexable_helper; $this->builder = $builder; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo_titles', [ $this, 'check_option' ], 10, 2 ); } /** * Checks if the home page indexable needs to be rebuild based on option values. * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * * @return bool Whether or not the option has been saved. */ public function check_option( $old_value, $new_value ) { $relevant_keys = [ 'title-ptarchive-', 'metadesc-ptarchive-', 'bctitle-ptarchive-', 'noindex-ptarchive-' ]; // If this is the first time saving the option, thus when value is false. if ( $old_value === false ) { $old_value = []; } if ( ! \is_array( $old_value ) || ! \is_array( $new_value ) ) { return false; } $keys = \array_unique( \array_merge( \array_keys( $old_value ), \array_keys( $new_value ) ) ); $post_types_rebuild = []; foreach ( $keys as $key ) { $post_type = false; // Check if it's a key relevant to post type archives. foreach ( $relevant_keys as $relevant_key ) { if ( \strpos( $key, $relevant_key ) === 0 ) { $post_type = \substr( $key, \strlen( $relevant_key ) ); break; } } // If it's not a relevant key or both values aren't set they haven't changed. if ( $post_type === false || ( ! isset( $old_value[ $key ] ) && ! isset( $new_value[ $key ] ) ) ) { continue; } // If the value was set but now isn't, is set but wasn't or is not the same it has changed. if ( ! \in_array( $post_type, $post_types_rebuild, true ) && ( ! isset( $old_value[ $key ] ) || ! isset( $new_value[ $key ] ) || $old_value[ $key ] !== $new_value[ $key ] ) ) { $this->build_indexable( $post_type ); $post_types_rebuild[] = $post_type; } } return true; } /** * Saves the post type archive. * * @param string $post_type The post type. * * @return void */ public function build_indexable( $post_type ) { $indexable = $this->repository->find_for_post_type_archive( $post_type, false ); $indexable = $this->builder->build_for_post_type_archive( $post_type, $indexable ); if ( $indexable ) { $indexable->object_last_modified = \max( $indexable->object_last_modified, \current_time( 'mysql' ) ); $this->indexable_helper->save_indexable( $indexable ); } } } integrations/watchers/option-wpseo-watcher.php 0000644 00000010141 15025657560 0015711 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Watcher for the wpseo option. * * Represents the option wpseo watcher. */ class Option_Wpseo_Watcher implements Integration_Interface { use No_Conditionals; /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo', [ $this, 'check_semrush_option_disabled' ], 10, 2 ); \add_action( 'update_option_wpseo', [ $this, 'check_wincher_option_disabled' ], 10, 2 ); \add_action( 'update_option_wpseo', [ $this, 'check_toggle_usage_tracking' ], 10, 2 ); } /** * Checks if the SEMrush integration is disabled; if so, deletes the tokens. * * We delete the tokens if the SEMrush integration is disabled, no matter if * the value has actually changed or not. * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * * @return bool Whether the SEMrush tokens have been deleted or not. */ public function check_semrush_option_disabled( $old_value, $new_value ) { return $this->check_token_option_disabled( 'semrush_integration_active', 'semrush_tokens', $new_value ); } /** * Checks if the Wincher integration is disabled; if so, deletes the tokens * and website id. * * We delete them if the Wincher integration is disabled, no matter if the * value has actually changed or not. * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * * @return bool Whether the Wincher tokens have been deleted or not. */ public function check_wincher_option_disabled( $old_value, $new_value ) { $disabled = $this->check_token_option_disabled( 'wincher_integration_active', 'wincher_tokens', $new_value ); if ( $disabled ) { \YoastSEO()->helpers->options->set( 'wincher_website_id', '' ); } return $disabled; } /** * Checks if the WordProof integration is disabled; if so, deletes the tokens * * We delete them if the WordProof integration is disabled, no matter if the * value has actually changed or not. * * @deprecated 22.10 * @codeCoverageIgnore * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * * @return bool Whether the WordProof tokens have been deleted or not. */ public function check_wordproof_option_disabled( $old_value, $new_value ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.10' ); return true; } /** * Checks if the usage tracking feature is toggled; if so, set an option to stop us from messing with it. * * @param array $old_value The old value of the option. * @param array $new_value The new value of the option. * * @return bool Whether the option is set. */ public function check_toggle_usage_tracking( $old_value, $new_value ) { $option_name = 'tracking'; if ( \array_key_exists( $option_name, $old_value ) && \array_key_exists( $option_name, $new_value ) && $old_value[ $option_name ] !== $new_value[ $option_name ] && $old_value['toggled_tracking'] === false ) { \YoastSEO()->helpers->options->set( 'toggled_tracking', true ); return true; } return false; } /** * Checks if the passed integration is disabled; if so, deletes the tokens. * * We delete the tokens if the integration is disabled, no matter if * the value has actually changed or not. * * @param string $integration_option The intergration option name. * @param string $target_option The target option to remove the tokens from. * @param array $new_value The new value of the option. * * @return bool Whether the tokens have been deleted or not. */ protected function check_token_option_disabled( $integration_option, $target_option, $new_value ) { if ( \array_key_exists( $integration_option, $new_value ) && $new_value[ $integration_option ] === false ) { \YoastSEO()->helpers->options->set( $target_option, [] ); return true; } return false; } } integrations/watchers/indexable-post-type-change-watcher.php 0000644 00000011776 15025657560 0020405 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Watchers; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Indexing_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Integrations\Cleanup_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Notification_Center; /** * Post type change watcher. */ class Indexable_Post_Type_Change_Watcher implements Integration_Interface { /** * The indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * Holds the Options_Helper instance. * * @var Options_Helper */ private $options; /** * Holds the Post_Type_Helper instance. * * @var Post_Type_Helper */ private $post_type_helper; /** * The notifications center. * * @var Yoast_Notification_Center */ private $notification_center; /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The conditionals. */ public static function get_conditionals() { return [ Not_Admin_Ajax_Conditional::class, Admin_Conditional::class, Migrations_Conditional::class ]; } /** * Indexable_Post_Type_Change_Watcher constructor. * * @param Options_Helper $options The options helper. * @param Indexing_Helper $indexing_helper The indexing helper. * @param Post_Type_Helper $post_type_helper The post_typehelper. * @param Yoast_Notification_Center $notification_center The notification center. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Options_Helper $options, Indexing_Helper $indexing_helper, Post_Type_Helper $post_type_helper, Yoast_Notification_Center $notification_center, Indexable_Helper $indexable_helper ) { $this->options = $options; $this->indexing_helper = $indexing_helper; $this->post_type_helper = $post_type_helper; $this->notification_center = $notification_center; $this->indexable_helper = $indexable_helper; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'check_post_types_public_availability' ] ); } /** * Checks if one or more post types change visibility. * * @return void */ public function check_post_types_public_availability() { // We have to make sure this is just a plain http request, no ajax/REST. if ( \wp_is_json_request() ) { return; } $public_post_types = $this->post_type_helper->get_indexable_post_types(); $last_known_public_post_types = $this->options->get( 'last_known_public_post_types', [] ); // Initializing the option on the first run. if ( empty( $last_known_public_post_types ) ) { $this->options->set( 'last_known_public_post_types', $public_post_types ); return; } // We look for new public post types. $newly_made_public_post_types = \array_diff( $public_post_types, $last_known_public_post_types ); // We look for post types that from public have been made private. $newly_made_non_public_post_types = \array_diff( $last_known_public_post_types, $public_post_types ); // Nothing to be done if no changes has been made to post types. if ( empty( $newly_made_public_post_types ) && ( empty( $newly_made_non_public_post_types ) ) ) { return; } // Update the list of last known public post types in the database. $this->options->set( 'last_known_public_post_types', $public_post_types ); // There are new post types that have been made public. if ( $newly_made_public_post_types ) { // Force a notification requesting to start the SEO data optimization. \delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_LIMITED_COUNT_TRANSIENT ); $this->indexing_helper->set_reason( Indexing_Reasons::REASON_POST_TYPE_MADE_PUBLIC ); \do_action( 'new_public_post_type_notifications', $newly_made_public_post_types ); } // There are post types that have been made private. if ( $newly_made_non_public_post_types && $this->indexable_helper->should_index_indexables() ) { // Schedule a cron job to remove all the posts whose post type has been made private. $cleanup_not_yet_scheduled = ! \wp_next_scheduled( Cleanup_Integration::START_HOOK ); if ( $cleanup_not_yet_scheduled ) { \wp_schedule_single_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), Cleanup_Integration::START_HOOK ); } \do_action( 'clean_new_public_post_type_notifications', $newly_made_non_public_post_types ); } } } integrations/alerts/black-friday-promotion-notification.php 0000644 00000000470 15025657560 0020331 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Alerts; /** * Black_Friday_Promotion_Notification class. */ class Black_Friday_Promotion_Notification extends Abstract_Dismissable_Alert { /** * Holds the alert identifier. * * @var string */ protected $alert_identifier = 'black-friday-2024-promotion'; } integrations/alerts/webinar-promo-notification.php 0000644 00000000445 15025657560 0016540 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Alerts; /** * Webinar_Promo_Notification class. */ class Webinar_Promo_Notification extends Abstract_Dismissable_Alert { /** * Holds the alert identifier. * * @var string */ protected $alert_identifier = 'webinar-promo-notification'; } integrations/alerts/black-friday-sidebar-checklist-notification.php 0000644 00000000504 15025657560 0021661 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Alerts; /** * Black_Friday_Promo_Notification class. */ class Black_Friday_Sidebar_Checklist_Notification extends Abstract_Dismissable_Alert { /** * Holds the alert identifier. * * @var string */ protected $alert_identifier = 'black-friday-2023-sidebar-checklist'; } integrations/alerts/black-friday-product-editor-checklist-notification.php 0000644 00000000653 15025657560 0023221 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Alerts; /** * Black_Friday_Product_Editor_Checklist_Notification class. * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Black_Friday_Product_Editor_Checklist_Notification extends Abstract_Dismissable_Alert { /** * Holds the alert identifier. * * @var string */ protected $alert_identifier = 'black-friday-2023-product-editor-checklist'; } integrations/alerts/trustpilot-review-notification.php 0000644 00000000461 15025657560 0017505 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Alerts; /** * Trustpilot_Review_Notification class. */ class Trustpilot_Review_Notification extends Abstract_Dismissable_Alert { /** * Holds the alert identifier. * * @var string */ protected $alert_identifier = 'trustpilot-review-notification'; } integrations/alerts/abstract-dismissable-alert.php 0000644 00000001645 15025657560 0016503 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Alerts; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Dismissable_Alert class. */ abstract class Abstract_Dismissable_Alert implements Integration_Interface { /** * Holds the alert identifier. * * @var string */ protected $alert_identifier; /** * {@inheritDoc} */ public static function get_conditionals() { return []; } /** * {@inheritDoc} */ public function register_hooks() { \add_filter( 'wpseo_allowed_dismissable_alerts', [ $this, 'register_dismissable_alert' ] ); } /** * Registers the dismissable alert. * * @param string[] $allowed_dismissable_alerts The allowed dismissable alerts. * * @return string[] The allowed dismissable alerts. */ public function register_dismissable_alert( $allowed_dismissable_alerts ) { $allowed_dismissable_alerts[] = $this->alert_identifier; return $allowed_dismissable_alerts; } } integrations/exclude-attachment-post-type.php 0000644 00000001452 15025657560 0015521 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Yoast\WP\SEO\Conditionals\Attachment_Redirections_Enabled_Conditional; /** * Excludes Attachment post types from the indexable table. * * Posts with these post types will not be saved to the indexable table. */ class Exclude_Attachment_Post_Type extends Abstract_Exclude_Post_Type { /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Attachment_Redirections_Enabled_Conditional::class ]; } /** * Returns the names of the post types to be excluded. * To be used in the wpseo_indexable_excluded_post_types filter. * * @return array The names of the post types. */ public function get_post_type() { return [ 'attachment' ]; } } integrations/support-integration.php 0000644 00000010750 15025657560 0014036 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Promotions\Application\Promotion_Manager; /** * Class Support_Integration. */ class Support_Integration implements Integration_Interface { public const PAGE = 'wpseo_page_support'; public const CAPABILITY = 'wpseo_manage_options'; /** * Holds the WPSEO_Admin_Asset_Manager. * * @var WPSEO_Admin_Asset_Manager */ private $asset_manager; /** * Holds the Current_Page_Helper. * * @var Current_Page_Helper */ private $current_page_helper; /** * Holds the Product_Helper. * * @var Product_Helper */ private $product_helper; /** * Holds the Short_Link_Helper. * * @var Short_Link_Helper */ private $shortlink_helper; /** * Constructs Support_Integration. * * @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager. * @param Current_Page_Helper $current_page_helper The Current_Page_Helper. * @param Product_Helper $product_helper The Product_Helper. * @param Short_Link_Helper $shortlink_helper The Short_Link_Helper. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Current_Page_Helper $current_page_helper, Product_Helper $product_helper, Short_Link_Helper $shortlink_helper ) { $this->asset_manager = $asset_manager; $this->current_page_helper = $current_page_helper; $this->product_helper = $product_helper; $this->shortlink_helper = $shortlink_helper; } /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Admin_Conditional::class, User_Can_Manage_Wpseo_Options_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { // Add page. \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ], \PHP_INT_MAX ); // Are we on the settings page? if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) { \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX ); } } /** * Adds the page. * * @param array $pages The pages. * * @return array The pages. */ public function add_page( $pages ) { $pages[] = [ 'wpseo_dashboard', '', \__( 'Support', 'wordpress-seo' ), self::CAPABILITY, self::PAGE, [ $this, 'display_page' ], ]; return $pages; } /** * Displays the page. * * @return void */ public function display_page() { echo '<div id="yoast-seo-support"></div>'; } /** * Enqueues the assets. * * @return void */ 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( 'support' ); $this->asset_manager->enqueue_style( 'support' ); if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) { $this->asset_manager->enqueue_style( 'black-friday-banner' ); } $this->asset_manager->localize_script( 'support', 'wpseoScriptData', $this->get_script_data() ); } /** * Removes all current WP notices. * * @return void */ 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. */ public function get_script_data() { return [ 'preferences' => [ 'isPremium' => $this->product_helper->is_premium(), 'isRtl' => \is_rtl(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'upsellSettings' => [ 'actionId' => 'load-nfd-ctb', 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', ], ], 'linkParams' => $this->shortlink_helper->get_query_params(), 'currentPromotions' => \YoastSEO()->classes->get( Promotion_Manager::class )->get_current_promotions(), ]; } } integrations/cleanup-integration.php 0000644 00000023335 15025657560 0013754 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Closure; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository; /** * Adds cleanup hooks. */ class Cleanup_Integration implements Integration_Interface { /** * Identifier used to determine the current task. */ public const CURRENT_TASK_OPTION = 'wpseo-cleanup-current-task'; /** * Identifier for the cron job. */ public const CRON_HOOK = 'wpseo_cleanup_cron'; /** * Identifier for starting the cleanup. */ public const START_HOOK = 'wpseo_start_cleanup_indexables'; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * The cleanup repository. * * @var Indexable_Cleanup_Repository */ private $cleanup_repository; /** * The constructor. * * @param Indexable_Cleanup_Repository $cleanup_repository The cleanup repository. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Indexable_Cleanup_Repository $cleanup_repository, Indexable_Helper $indexable_helper ) { $this->cleanup_repository = $cleanup_repository; $this->indexable_helper = $indexable_helper; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( self::START_HOOK, [ $this, 'run_cleanup' ] ); \add_action( self::CRON_HOOK, [ $this, 'run_cleanup_cron' ] ); \add_action( 'wpseo_deactivate', [ $this, 'reset_cleanup' ] ); } /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The array of conditionals. */ public static function get_conditionals() { return []; } /** * Starts the indexables cleanup. * * @return void */ public function run_cleanup() { $this->reset_cleanup(); if ( ! $this->indexable_helper->should_index_indexables() ) { \wp_unschedule_hook( self::START_HOOK ); return; } $cleanups = $this->get_cleanup_tasks(); $limit = $this->get_limit(); foreach ( $cleanups as $name => $action ) { $items_cleaned = $action( $limit ); if ( $items_cleaned === false ) { return; } if ( $items_cleaned < $limit ) { continue; } // There are more items to delete for the current cleanup job, start a cronjob at the specified job. $this->start_cron_job( $name ); return; } } /** * Returns an array of cleanup tasks. * * @return Closure[] The cleanup tasks. */ public function get_cleanup_tasks() { return \array_merge( [ 'clean_indexables_with_object_type_and_object_sub_type_shop_order' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_with_object_type_and_object_sub_type( 'post', 'shop_order', $limit ); }, 'clean_indexables_by_post_status_auto-draft' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_with_post_status( 'auto-draft', $limit ); }, 'clean_indexables_for_non_publicly_viewable_post' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post( $limit ); }, 'clean_indexables_for_non_publicly_viewable_taxonomies' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_taxonomies( $limit ); }, 'clean_indexables_for_non_publicly_viewable_post_type_archive_pages' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_non_publicly_viewable_post_type_archive_pages( $limit ); }, 'clean_indexables_for_authors_archive_disabled' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_authors_archive_disabled( $limit ); }, 'clean_indexables_for_authors_without_archive' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_authors_without_archive( $limit ); }, 'update_indexables_author_to_reassigned' => function ( $limit ) { return $this->cleanup_repository->update_indexables_author_to_reassigned( $limit ); }, 'clean_orphaned_user_indexables_without_wp_user' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_orphaned_users( $limit ); }, 'clean_orphaned_user_indexables_without_wp_post' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'posts', 'ID', 'post', $limit ); }, 'clean_orphaned_user_indexables_without_wp_term' => function ( $limit ) { return $this->cleanup_repository->clean_indexables_for_object_type_and_source_table( 'terms', 'term_id', 'term', $limit ); }, ], $this->get_additional_indexable_cleanups(), [ /* These should always be the last ones to be called. */ 'clean_orphaned_content_indexable_hierarchy' => function ( $limit ) { return $this->cleanup_repository->cleanup_orphaned_from_table( 'Indexable_Hierarchy', 'indexable_id', $limit ); }, 'clean_orphaned_content_seo_links_indexable_id' => function ( $limit ) { return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'indexable_id', $limit ); }, 'clean_orphaned_content_seo_links_target_indexable_id' => function ( $limit ) { return $this->cleanup_repository->cleanup_orphaned_from_table( 'SEO_Links', 'target_indexable_id', $limit ); }, ], $this->get_additional_misc_cleanups() ); } /** * Gets additional tasks from the 'wpseo_cleanup_tasks' filter. * * @return Closure[] Associative array of indexable cleanup functions. */ private function get_additional_indexable_cleanups() { /** * Filter: Adds the possibility to add additional indexable cleanup functions. * * @param array $additional_tasks Associative array with unique keys. Value should be a cleanup function that receives a limit. */ $additional_tasks = \apply_filters( 'wpseo_cleanup_tasks', [] ); return $this->validate_additional_tasks( $additional_tasks ); } /** * Gets additional tasks from the 'wpseo_misc_cleanup_tasks' filter. * * @return Closure[] Associative array of indexable cleanup functions. */ private function get_additional_misc_cleanups() { /** * Filter: Adds the possibility to add additional non-indexable cleanup functions. * * @param array $additional_tasks Associative array with unique keys. Value should be a cleanup function that receives a limit. */ $additional_tasks = \apply_filters( 'wpseo_misc_cleanup_tasks', [] ); return $this->validate_additional_tasks( $additional_tasks ); } /** * Validates the additional tasks. * * @param Closure[] $additional_tasks The additional tasks to validate. * * @return Closure[] The validated additional tasks. */ private function validate_additional_tasks( $additional_tasks ) { if ( ! \is_array( $additional_tasks ) ) { return []; } foreach ( $additional_tasks as $key => $value ) { if ( \is_int( $key ) ) { return []; } if ( ( ! \is_object( $value ) ) || ! ( $value instanceof Closure ) ) { return []; } } return $additional_tasks; } /** * Gets the deletion limit for cleanups. * * @return int The limit for the amount of entities to be cleaned. */ private function get_limit() { /** * Filter: Adds the possibility to limit the number of items that are deleted from the database on cleanup. * * @param int $limit Maximum number of indexables to be cleaned up per query. */ $limit = \apply_filters( 'wpseo_cron_query_limit_size', 1000 ); if ( ! \is_int( $limit ) ) { $limit = 1000; } return \abs( $limit ); } /** * Resets and stops the cleanup integration. * * @return void */ public function reset_cleanup() { \delete_option( self::CURRENT_TASK_OPTION ); \wp_unschedule_hook( self::CRON_HOOK ); } /** * Starts the cleanup cron job. * * @param string $task_name The task name of the next cleanup task to run. * @param int $schedule_time The time in seconds to wait before running the first cron job. Default is 1 hour. * * @return void */ public function start_cron_job( $task_name, $schedule_time = 3600 ) { \update_option( self::CURRENT_TASK_OPTION, $task_name ); \wp_schedule_event( ( \time() + $schedule_time ), 'hourly', self::CRON_HOOK ); } /** * The callback that is called for the cleanup cron job. * * @return void */ public function run_cleanup_cron() { if ( ! $this->indexable_helper->should_index_indexables() ) { $this->reset_cleanup(); return; } $current_task_name = \get_option( self::CURRENT_TASK_OPTION ); if ( $current_task_name === false ) { $this->reset_cleanup(); return; } $limit = $this->get_limit(); $tasks = $this->get_cleanup_tasks(); // The task may have been added by a filter that has been removed, in that case just start over. if ( ! isset( $tasks[ $current_task_name ] ) ) { $current_task_name = \key( $tasks ); } $current_task = \current( $tasks ); while ( $current_task !== false ) { // Skip the tasks that have already been done. if ( \key( $tasks ) !== $current_task_name ) { $current_task = \next( $tasks ); continue; } // Call the cleanup callback function that accompanies the current task. $items_cleaned = $current_task( $limit ); if ( $items_cleaned === false ) { $this->reset_cleanup(); return; } if ( $items_cleaned === 0 ) { // Check if we are finished with all tasks. if ( \next( $tasks ) === false ) { $this->reset_cleanup(); return; } // Continue with the next task next time the cron job is run. \update_option( self::CURRENT_TASK_OPTION, \key( $tasks ) ); return; } // There were items deleted for the current task, continue with the same task next cron call. return; } } } integrations/third-party/wincher-publish.php 0000644 00000011157 15025657560 0015355 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use WP_Post; use Yoast\WP\SEO\Actions\Wincher\Wincher_Account_Action; use Yoast\WP\SEO\Actions\Wincher\Wincher_Keyphrases_Action; use Yoast\WP\SEO\Conditionals\Wincher_Automatically_Track_Conditional; use Yoast\WP\SEO\Conditionals\Wincher_Conditional; use Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional; use Yoast\WP\SEO\Conditionals\Wincher_Token_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Handles automatically tracking published posts with Wincher. */ class Wincher_Publish implements Integration_Interface { /** * The Wincher enabled conditional. * * @var Wincher_Enabled_Conditional */ protected $wincher_enabled; /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The Wincher keyphrases action handler. * * @var Wincher_Keyphrases_Action */ protected $keyphrases_action; /** * The Wincher account action handler. * * @var Wincher_Account_Action */ protected $account_action; /** * Wincher publish constructor. * * @param Wincher_Enabled_Conditional $wincher_enabled The WPML WPSEO conditional. * @param Options_Helper $options_helper The options helper. * @param Wincher_Keyphrases_Action $keyphrases_action The keyphrases action class. * @param Wincher_Account_Action $account_action The account action class. */ public function __construct( Wincher_Enabled_Conditional $wincher_enabled, Options_Helper $options_helper, Wincher_Keyphrases_Action $keyphrases_action, Wincher_Account_Action $account_action ) { $this->wincher_enabled = $wincher_enabled; $this->options_helper = $options_helper; $this->keyphrases_action = $keyphrases_action; $this->account_action = $account_action; } /** * Initializes the integration. * * @return void */ public function register_hooks() { /** * Called in the REST API when submitting the post copy in the Block Editor. * Runs the republishing of the copy onto the original. */ \add_action( 'rest_after_insert_post', [ $this, 'track_after_rest_api_request' ] ); /** * Called by `wp_insert_post()` when submitting the post copy, which runs in two cases: * - In the Classic Editor, where there's only one request that updates everything. * - In the Block Editor, only when there are custom meta boxes. */ \add_action( 'wp_insert_post', [ $this, 'track_after_post_request' ], \PHP_INT_MAX, 2 ); } /** * Returns the conditionals based in which this loadable should be active. * * This integration should only be active when the feature is enabled, a token is available and automatically tracking is enabled. * * @return array The conditionals. */ public static function get_conditionals() { return [ Wincher_Conditional::class, Wincher_Enabled_Conditional::class, Wincher_Automatically_Track_Conditional::class, Wincher_Token_Conditional::class, ]; } /** * Determines whether the current request is a REST request. * * @deprecated 23.6 * @codeCoverageIgnore * * @return bool Whether the request is a REST request. */ public function is_rest_request() { \_deprecated_function( __METHOD__, 'Yoast SEO 23.6', 'wp_is_serving_rest_request' ); return \defined( 'REST_REQUEST' ) && \REST_REQUEST; } /** * Sends the keyphrases associated with the post to Wincher for automatic tracking. * * @param WP_Post $post The post to extract the keyphrases from. * * @return void */ public function track_request( $post ) { if ( ! $post instanceof WP_Post ) { return; } // Filter out empty entries. $keyphrases = \array_filter( $this->keyphrases_action->collect_keyphrases_from_post( $post ) ); if ( ! empty( $keyphrases ) ) { $this->keyphrases_action->track_keyphrases( $keyphrases, $this->account_action->check_limit() ); } } /** * Republishes the original post with the passed post, when using the Block Editor. * * @param WP_Post $post The copy's post object. * * @return void */ public function track_after_rest_api_request( $post ) { $this->track_request( $post ); } /** * Republishes the original post with the passed post, when using the Classic Editor. * * Runs also in the Block Editor to save the custom meta data only when there * are custom meta boxes. * * @param int $post_id The copy's post ID. * @param WP_Post $post The copy's post object. * * @return void */ public function track_after_post_request( $post_id, $post ) { if ( \wp_is_serving_rest_request() ) { return; } $this->track_request( $post ); } } integrations/third-party/web-stories.php 0000644 00000007731 15025657560 0014520 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Web_Stories_Conditional; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Integrations\Front_End_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Title_Presenter; /** * Web Stories integration. */ class Web_Stories implements Integration_Interface { /** * The front end integration. * * @var Front_End_Integration */ protected $front_end; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Web_Stories_Conditional::class ]; } /** * Constructs the Web Stories integration * * @param Front_End_Integration $front_end The front end integration. */ public function __construct( Front_End_Integration $front_end ) { $this->front_end = $front_end; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { // Disable default title and meta description output in the Web Stories plugin, // and force-add title & meta description presenter, regardless of theme support. \add_filter( 'web_stories_enable_document_title', '__return_false' ); \add_filter( 'web_stories_enable_metadata', '__return_false' ); \add_filter( 'wpseo_frontend_presenters', [ $this, 'filter_frontend_presenters' ], 10, 2 ); \add_action( 'web_stories_enable_schemaorg_metadata', '__return_false' ); \add_action( 'web_stories_enable_open_graph_metadata', '__return_false' ); \add_action( 'web_stories_enable_twitter_metadata', '__return_false' ); \add_action( 'web_stories_story_head', [ $this, 'web_stories_story_head' ], 1 ); \add_filter( 'wpseo_schema_article_type', [ $this, 'filter_schema_article_type' ], 10, 2 ); \add_filter( 'wpseo_metadesc', [ $this, 'filter_meta_description' ], 10, 2 ); } /** * Filter 'wpseo_frontend_presenters' - Allow filtering the presenter instances in or out of the request. * * @param array $presenters The presenters. * @param Meta_Tags_Context $context The meta tags context for the current page. * @return array Filtered presenters. */ public function filter_frontend_presenters( $presenters, $context ) { if ( $context->indexable->object_sub_type !== 'web-story' ) { return $presenters; } $has_title_presenter = false; foreach ( $presenters as $presenter ) { if ( $presenter instanceof Title_Presenter ) { $has_title_presenter = true; } } if ( ! $has_title_presenter ) { $presenters[] = new Title_Presenter(); } return $presenters; } /** * Hooks into web story <head> generation to modify output. * * @return void */ public function web_stories_story_head() { \remove_action( 'web_stories_story_head', 'rel_canonical' ); \add_action( 'web_stories_story_head', [ $this->front_end, 'call_wpseo_head' ], 9 ); } /** * Filters the meta description for stories. * * @param string $description The description sentence. * @param Indexable_Presentation $presentation The presentation of an indexable. * @return string The description sentence. */ public function filter_meta_description( $description, $presentation ) { if ( $description || $presentation->model->object_sub_type !== 'web-story' ) { return $description; } return \get_the_excerpt( $presentation->model->object_id ); } /** * Filters Article type for Web Stories. * * @param string|string[] $type The Article type. * @param Indexable $indexable The indexable. * @return string|string[] Article type. */ public function filter_schema_article_type( $type, $indexable ) { if ( $indexable->object_sub_type !== 'web-story' ) { return $type; } if ( \is_string( $type ) && $type === 'None' ) { return 'Article'; } return $type; } } integrations/third-party/web-stories-post-edit.php 0000644 00000002331 15025657560 0016415 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional; use Yoast\WP\SEO\Conditionals\Web_Stories_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Web Stories integration. */ class Web_Stories_Post_Edit implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Web_Stories_Conditional::class, Post_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'wpseo_admin_l10n', [ $this, 'add_admin_l10n' ] ); } /** * Adds a isWebStoriesIntegrationActive variable to the Adminl10n array. * * @param array $input The array to add the isWebStoriesIntegrationActive to. * * @return array The passed array with the additional isWebStoriesIntegrationActive variable set to 1 if we are editing a web story. */ public function add_admin_l10n( $input ) { if ( \get_post_type() === 'web-story' ) { $input['isWebStoriesIntegrationActive'] = 1; } return $input; } } integrations/third-party/woocommerce-post-edit.php 0000644 00000002740 15025657560 0016475 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use WP_Post; use Yoast\WP\SEO\Conditionals\Admin\Post_Conditional; use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * A WooCommerce integration that runs in the post editor. */ class WooCommerce_Post_Edit implements Integration_Interface { /** * Register the hooks for this integration to work. * * @return void */ public function register_hooks() { \add_filter( 'wpseo_post_edit_values', [ $this, 'remove_meta_description_date' ], 10, 2 ); } /** * Only run this integration when WooCommerce is active and the user is in the post editor. * * @return string[] The conditionals that should be met before this integration is loaded. */ public static function get_conditionals() { return [ WooCommerce_Conditional::class, Post_Conditional::class ]; } /** * Don't show the date in the Google preview for WooCommerce products, * since Google does not show dates for product pages in the search results. * * @param array $values Key-value map of variables we enqueue in the JavaScript of the post editor. * @param WP_Post $post The post currently opened in the editor. * * @return array The values, where the `metaDescriptionDate` is set to the empty string. */ public function remove_meta_description_date( $values, $post ) { if ( $post->post_type === 'product' ) { $values['metaDescriptionDate'] = ''; } return $values; } } integrations/third-party/jetpack.php 0000644 00000001504 15025657560 0013666 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Conditionals\Jetpack_Conditional; use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Jetpack integration. */ class Jetpack implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class, Jetpack_Conditional::class, Open_Graph_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'jetpack_enable_open_graph', '__return_false' ); } } integrations/third-party/exclude-elementor-post-types.php 0000644 00000001642 15025657560 0020016 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional; use Yoast\WP\SEO\Integrations\Abstract_Exclude_Post_Type; /** * Excludes certain Elementor-specific post types from the indexable table. * * Posts with these post types will not be saved to the indexable table. */ class Exclude_Elementor_Post_Types extends Abstract_Exclude_Post_Type { /** * This integration is only active when the Elementor plugin * is installed and activated. * * @return string[] The conditionals. */ public static function get_conditionals() { return [ Elementor_Activated_Conditional::class ]; } /** * Returns the names of the post types to be excluded. * To be used in the wpseo_indexable_excluded_post_types filter. * * @return array The names of the post types. */ public function get_post_type() { return [ 'elementor_library' ]; } } integrations/third-party/bbpress.php 0000644 00000002651 15025657560 0013711 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * BbPress integration. */ class BbPress implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ private $options; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * BbPress constructor. * * @codeCoverageIgnore It only sets dependencies. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { if ( $this->options->get( 'breadcrumbs-enable' ) !== true ) { return; } /** * If breadcrumbs are active (which they supposedly are if the users has enabled this settings, * there's no reason to have bbPress breadcrumbs as well. * * {@internal The class itself is only loaded when the template tag is encountered * via the template tag function in the wpseo-functions.php file.}} */ \add_filter( 'bbp_get_breadcrumb', '__return_false' ); } } integrations/third-party/wpml-wpseo-notification.php 0000644 00000006567 15025657560 0017061 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\WPML_WPSEO_Conditional; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Notification; use Yoast_Notification_Center; /** * Adds a notification to the dashboard if the WPML plugin is installed, * but the Yoast SEO Multilingual plugin (a glue plugin to make Yoast SEO and WPML work nicely together) * is not. */ class WPML_WPSEO_Notification implements Integration_Interface { /** * The notification ID. * * @internal */ public const NOTIFICATION_ID = 'wpml-wpseo-not-installed'; /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * The notification center. * * @var Yoast_Notification_Center */ protected $notification_center; /** * The WPML WPSEO conditional. * * @var WPML_WPSEO_Conditional */ protected $wpml_wpseo_conditional; /** * WPML WPSEO notification constructor. * * @param Short_Link_Helper $short_link_helper The short link helper. * @param Yoast_Notification_Center $notification_center The notification center. * @param WPML_WPSEO_Conditional $wpml_wpseo_conditional The WPML WPSEO conditional. */ public function __construct( Short_Link_Helper $short_link_helper, Yoast_Notification_Center $notification_center, WPML_WPSEO_Conditional $wpml_wpseo_conditional ) { $this->short_link_helper = $short_link_helper; $this->notification_center = $notification_center; $this->wpml_wpseo_conditional = $wpml_wpseo_conditional; } /** * Initializes the integration. * * @return void */ public function register_hooks() { \add_action( 'admin_notices', [ $this, 'notify_not_installed' ] ); } /** * Returns the conditionals based in which this loadable should be active. * * This integration should only be active when WPML is installed and activated. * * @return array The conditionals. */ public static function get_conditionals() { return [ WPML_Conditional::class ]; } /** * Notify the user that the Yoast SEO Multilingual plugin is not installed * (when the WPML plugin is installed). * * Remove the notification again when it is installed. * * @return void */ public function notify_not_installed() { if ( ! $this->wpml_wpseo_conditional->is_met() ) { $this->notification_center->add_notification( $this->get_notification() ); return; } $this->notification_center->remove_notification_by_id( self::NOTIFICATION_ID ); } /** * Generates the notification to show to the user when WPML is installed, * but the Yoast SEO Multilingual plugin is not. * * @return Yoast_Notification The notification. */ protected function get_notification() { return new Yoast_Notification( \sprintf( /* translators: %1$s expands to an opening anchor tag, %2$s expands to an closing anchor tag. */ \__( 'We notice that you have installed WPML. To make sure your canonical URLs are set correctly, %1$sinstall and activate the WPML SEO add-on%2$s as well!', 'wordpress-seo' ), '<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/wpml-yoast-seo' ) ) . '" target="_blank">', '</a>' ), [ 'id' => self::NOTIFICATION_ID, 'type' => Yoast_Notification::WARNING, ] ); } } integrations/third-party/w3-total-cache.php 0000644 00000001474 15025657560 0014766 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Third_Party\W3_Total_Cache_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * W3 Total Cache integration. */ class W3_Total_Cache implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ W3_Total_Cache_Conditional::class ]; } /** * Initializes the integration. * * On successful update/add of the taxonomy meta option, flush the W3TC cache. * * @return void */ public function register_hooks() { \add_action( 'add_option_wpseo_taxonomy_meta', 'w3tc_objectcache_flush' ); \add_action( 'update_option_wpseo_taxonomy_meta', 'w3tc_objectcache_flush' ); } } integrations/third-party/wpml.php 0000644 00000003553 15025657560 0013232 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * WPML integration. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded -- Known false positive with acronyms. Fix expected in YoastCS 3.x. */ class WPML implements Integration_Interface { /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'wpseo_home_url', [ $this, 'filter_home_url_before' ] ); \add_filter( 'home_url', [ $this, 'filter_home_url_after' ], 100 ); } /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ WPML_Conditional::class ]; } /** * Adds a filter to WPML's wpml_get_home_url filter to ensure we get the unmanipulated home URL. * * @return void */ public function filter_home_url_before() { \add_filter( 'wpml_get_home_url', [ $this, 'wpml_get_home_url' ], 10, 2 ); } /** * Removes the wpml_get_home_url filter to return the WPML, language-enriched home URL. * * @param string $home_url The filtered home URL. * * @return string The unfiltered home URL. */ public function filter_home_url_after( $home_url ) { \remove_filter( 'wpml_get_home_url', [ $this, 'wpml_get_home_url' ], 10 ); return $home_url; } /** * Returns the original URL instead of the language-enriched URL. * This method gets automatically triggered by the wpml_get_home_url filter. * * @param string $home_url The url altered by WPML. Unused. * @param string $url The url that isn't altered by WPML. * * @return string The original url. */ public function wpml_get_home_url( $home_url, $url ) { return $url; } } integrations/third-party/amp.php 0000644 00000002723 15025657560 0013026 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Integrations\Front_End_Integration; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * AMP integration. */ class AMP implements Integration_Interface { /** * The front end integration. * * @var Front_End_Integration */ protected $front_end; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Constructs the AMP integration * * @param Front_End_Integration $front_end The front end integration. */ public function __construct( Front_End_Integration $front_end ) { $this->front_end = $front_end; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'amp_post_template_head', [ $this, 'remove_amp_meta_output' ], 0 ); \add_action( 'amp_post_template_head', [ $this->front_end, 'call_wpseo_head' ], 9 ); } /** * Removes amp meta output. * * @return void */ public function remove_amp_meta_output() { \remove_action( 'amp_post_template_head', 'amp_post_template_add_title' ); \remove_action( 'amp_post_template_head', 'amp_post_template_add_canonical' ); \remove_action( 'amp_post_template_head', 'amp_print_schemaorg_metadata' ); } } integrations/third-party/woocommerce-permalinks.php 0000644 00000005714 15025657560 0016736 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * The permalink watcher. */ class Woocommerce_Permalinks implements Integration_Interface { /** * Represents the indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ WooCommerce_Conditional::class, Migrations_Conditional::class ]; } /** * Constructor. * * @param Indexable_Helper $indexable_helper Indexable Helper. */ public function __construct( Indexable_Helper $indexable_helper ) { $this->indexable_helper = $indexable_helper; } /** * Registers the hooks. * * @codeCoverageIgnore * * @return void */ public function register_hooks() { \add_filter( 'wpseo_post_types_reset_permalinks', [ $this, 'filter_product_from_post_types' ] ); \add_action( 'update_option_woocommerce_permalinks', [ $this, 'reset_woocommerce_permalinks' ], 10, 2 ); } /** * Filters the product post type from the post type. * * @param array $post_types The post types to filter. * * @return array The filtered post types. */ public function filter_product_from_post_types( $post_types ) { unset( $post_types['product'] ); return $post_types; } /** * Resets the indexables for WooCommerce based on the changed permalink fields. * * @param array $old_value The old value. * @param array $new_value The new value. * * @return void */ public function reset_woocommerce_permalinks( $old_value, $new_value ) { $changed_options = \array_diff( $old_value, $new_value ); if ( \array_key_exists( 'product_base', $changed_options ) ) { $this->indexable_helper->reset_permalink_indexables( 'post', 'product' ); } if ( \array_key_exists( 'attribute_base', $changed_options ) ) { $attribute_taxonomies = $this->get_attribute_taxonomies(); foreach ( $attribute_taxonomies as $attribute_name ) { $this->indexable_helper->reset_permalink_indexables( 'term', $attribute_name ); } } if ( \array_key_exists( 'category_base', $changed_options ) ) { $this->indexable_helper->reset_permalink_indexables( 'term', 'product_cat' ); } if ( \array_key_exists( 'tag_base', $changed_options ) ) { $this->indexable_helper->reset_permalink_indexables( 'term', 'product_tag' ); } } /** * Retrieves the taxonomies based on the attributes. * * @return array The taxonomies. */ protected function get_attribute_taxonomies() { $taxonomies = []; foreach ( \wc_get_attribute_taxonomies() as $attribute_taxonomy ) { $taxonomies[] = \wc_attribute_taxonomy_name( $attribute_taxonomy->attribute_name ); } $taxonomies = \array_filter( $taxonomies ); return $taxonomies; } } integrations/third-party/elementor.php 0000644 00000061207 15025657560 0014245 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use WP_Post; use WP_Screen; use WPSEO_Admin_Asset_Manager; use WPSEO_Admin_Recommended_Replace_Vars; use WPSEO_Meta; use WPSEO_Metabox_Analysis_Inclusive_Language; use WPSEO_Metabox_Analysis_Readability; use WPSEO_Metabox_Analysis_SEO; use WPSEO_Metabox_Formatter; use WPSEO_Post_Metabox_Formatter; use WPSEO_Replace_Vars; use WPSEO_Utils; use Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional; use Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository; use Yoast\WP\SEO\Elementor\Infrastructure\Request_Post; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Admin\Meta_Fields_Presenter; use Yoast\WP\SEO\Promotions\Application\Promotion_Manager; /** * Integrates the Yoast SEO metabox in the Elementor editor. */ class Elementor implements Integration_Interface { /** * Represents the post. * * @var WP_Post|null */ protected $post; /** * Represents the admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * Represents the options helper. * * @var Options_Helper */ protected $options; /** * Represents the capability helper. * * @var Capability_Helper */ protected $capability; /** * Holds the Request_Post. * * @var Request_Post */ private $request_post; /** * Holds whether the socials are enabled. * * @var bool */ protected $social_is_enabled; /** * Holds whether the advanced settings are enabled. * * @var bool */ protected $is_advanced_metadata_enabled; /** * Helper to determine whether or not the SEO analysis is enabled. * * @var WPSEO_Metabox_Analysis_SEO */ protected $seo_analysis; /** * Helper to determine whether or not the readability analysis is enabled. * * @var WPSEO_Metabox_Analysis_Readability */ protected $readability_analysis; /** * Helper to determine whether or not the inclusive language analysis is enabled. * * @var WPSEO_Metabox_Analysis_Inclusive_Language */ protected $inclusive_language_analysis; /** * Holds the promotion manager. * * @var Promotion_Manager */ protected $promotion_manager; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Elementor_Edit_Conditional::class ]; } /** * Constructor. * * @param WPSEO_Admin_Asset_Manager $asset_manager The asset manager. * @param Options_Helper $options The options helper. * @param Capability_Helper $capability The capability helper. * @param Request_Post $request_post The Request_Post. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Options_Helper $options, Capability_Helper $capability, Request_Post $request_post ) { $this->asset_manager = $asset_manager; $this->options = $options; $this->capability = $capability; $this->request_post = $request_post; $this->seo_analysis = new WPSEO_Metabox_Analysis_SEO(); $this->readability_analysis = new WPSEO_Metabox_Analysis_Readability(); $this->inclusive_language_analysis = new WPSEO_Metabox_Analysis_Inclusive_Language(); $this->social_is_enabled = $this->options->get( 'opengraph', false ) || $this->options->get( 'twitter', false ); $this->is_advanced_metadata_enabled = $this->capability->current_user_can( 'wpseo_edit_advanced_metadata' ) || $this->options->get( 'disableadvanced_meta' ) === false; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'wp_ajax_wpseo_elementor_save', [ $this, 'save_postdata' ] ); // We need to delay the post type lookup to give other plugins a chance to register custom post types. \add_action( 'init', [ $this, 'register_elementor_hooks' ], \PHP_INT_MAX ); } /** * Registers our Elementor hooks. * This is done for pages with metabox on page load and not on ajax request. * * @return void */ public function register_elementor_hooks() { if ( $this->get_metabox_post() === null || ! $this->display_metabox( $this->get_metabox_post()->post_type ) ) { return; } \add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'init' ] ); } /** * Initializes the integration. * * @return void */ public function init() { $this->asset_manager->register_assets(); $this->enqueue(); $this->render_hidden_fields(); } // Below is mostly copied from `class-metabox.php`. That constructor has side-effects we do not need. /** * Determines whether the metabox should be shown for the passed identifier. * * By default, the check is done for post types, but can also be used for taxonomies. * * @param string|null $identifier The identifier to check. * @param string $type The type of object to check. Defaults to post_type. * * @return bool Whether the metabox should be displayed. */ public function display_metabox( $identifier = null, $type = 'post_type' ) { return WPSEO_Utils::is_metabox_active( $identifier, $type ); } /** * Saves the WP SEO metadata for posts. * * Outputs JSON via wp_send_json then stops code execution. * * {@internal $_POST parameters are validated via sanitize_post_meta().}} * * @return void */ public function save_postdata() { global $post; if ( ! isset( $_POST['post_id'] ) || ! \is_string( $_POST['post_id'] ) ) { \wp_send_json_error( 'Bad Request', 400 ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: No sanitization needed because we cast to an integer. $post_id = (int) \wp_unslash( $_POST['post_id'] ); if ( $post_id <= 0 ) { \wp_send_json_error( 'Bad Request', 400 ); } if ( ! \current_user_can( 'edit_post', $post_id ) ) { \wp_send_json_error( 'Forbidden', 403 ); } \check_ajax_referer( 'wpseo_elementor_save', '_wpseo_elementor_nonce' ); // Bail if this is a multisite installation and the site has been switched. if ( \is_multisite() && \ms_is_switched() ) { \wp_send_json_error( 'Switched multisite', 409 ); } \clean_post_cache( $post_id ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. $post = \get_post( $post_id ); if ( ! \is_object( $post ) ) { // Non-existent post. \wp_send_json_error( 'Post not found', 400 ); } \do_action( 'wpseo_save_compare_data', $post ); // Initialize meta, amongst other things it registers sanitization. WPSEO_Meta::init(); $social_fields = []; if ( $this->social_is_enabled ) { $social_fields = WPSEO_Meta::get_meta_field_defs( 'social', $post->post_type ); } // The below methods use the global post so make sure it is setup. \setup_postdata( $post ); $meta_boxes = \apply_filters( 'wpseo_save_metaboxes', [] ); $meta_boxes = \array_merge( $meta_boxes, WPSEO_Meta::get_meta_field_defs( 'general', $post->post_type ), WPSEO_Meta::get_meta_field_defs( 'advanced', $post->post_type ), $social_fields, WPSEO_Meta::get_meta_field_defs( 'schema', $post->post_type ) ); foreach ( $meta_boxes as $key => $meta_box ) { // If analysis is disabled remove that analysis score value from the DB. if ( $this->is_meta_value_disabled( $key ) ) { WPSEO_Meta::delete( $key, $post_id ); continue; } $data = null; $field_name = WPSEO_Meta::$form_prefix . $key; if ( $meta_box['type'] === 'checkbox' ) { $data = isset( $_POST[ $field_name ] ) ? 'on' : 'off'; } else { if ( isset( $_POST[ $field_name ] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Sanitized through sanitize_post_meta. $data = \wp_unslash( $_POST[ $field_name ] ); // For multi-select. if ( \is_array( $data ) ) { $data = \array_map( [ 'WPSEO_Utils', 'sanitize_text_field' ], $data ); } if ( \is_string( $data ) ) { $data = ( $key !== 'canonical' ) ? WPSEO_Utils::sanitize_text_field( $data ) : WPSEO_Utils::sanitize_url( $data ); } } // Reset options when no entry is present with multiselect - only applies to `meta-robots-adv` currently. if ( ! isset( $_POST[ $field_name ] ) && ( $meta_box['type'] === 'multiselect' ) ) { $data = []; } } if ( $data !== null ) { WPSEO_Meta::set_value( $key, $data, $post_id ); } } if ( isset( $_POST[ WPSEO_Meta::$form_prefix . 'slug' ] ) && \is_string( $_POST[ WPSEO_Meta::$form_prefix . 'slug' ] ) ) { $slug = \sanitize_title( \wp_unslash( $_POST[ WPSEO_Meta::$form_prefix . 'slug' ] ) ); if ( $post->post_name !== $slug ) { $post_array = $post->to_array(); $post_array['post_name'] = $slug; $save_successful = \wp_insert_post( $post_array ); if ( \is_wp_error( $save_successful ) ) { \wp_send_json_error( 'Slug not saved', 400 ); } // Update the post object to ensure we have the actual slug. // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- Updating the post is needed to get the current slug. $post = \get_post( $post_id ); if ( ! \is_object( $post ) ) { \wp_send_json_error( 'Updated slug not found', 400 ); } } } \do_action( 'wpseo_saved_postdata' ); // Output the slug, because it is processed by WP and we need the actual slug again. \wp_send_json_success( [ 'slug' => $post->post_name ] ); } /** * Determines if the given meta value key is disabled. * * @param string $key The key of the meta value. * * @return bool Whether the given meta value key is disabled. */ public function is_meta_value_disabled( $key ) { if ( $key === 'linkdex' && ! $this->seo_analysis->is_enabled() ) { return true; } if ( $key === 'content_score' && ! $this->readability_analysis->is_enabled() ) { return true; } if ( $key === 'inclusive_language_score' && ! $this->inclusive_language_analysis->is_enabled() ) { return true; } return false; } /** * Enqueues all the needed JS and CSS. * * @return void */ public function enqueue() { $post_id = \get_queried_object_id(); if ( empty( $post_id ) ) { $post_id = 0; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['post'] ) && \is_string( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended -- Reason: No sanitization needed because we cast to an integer,We are not processing form information. $post_id = (int) \wp_unslash( $_GET['post'] ); } } if ( $post_id !== 0 ) { // Enqueue files needed for upload functionality. \wp_enqueue_media( [ 'post' => $post_id ] ); } $this->asset_manager->enqueue_style( 'admin-global' ); $this->asset_manager->enqueue_style( 'metabox-css' ); $this->asset_manager->enqueue_style( 'scoring' ); $this->asset_manager->enqueue_style( 'monorepo' ); $this->asset_manager->enqueue_style( 'admin-css' ); $this->asset_manager->enqueue_style( 'ai-generator' ); $this->asset_manager->enqueue_style( 'elementor' ); $this->asset_manager->enqueue_script( 'admin-global' ); $this->asset_manager->enqueue_script( 'elementor' ); $this->asset_manager->localize_script( 'elementor', 'wpseoAdminGlobalL10n', \YoastSEO()->helpers->wincher->get_admin_global_links() ); $this->asset_manager->localize_script( 'elementor', 'wpseoAdminL10n', WPSEO_Utils::get_admin_l10n() ); $this->asset_manager->localize_script( 'elementor', 'wpseoFeaturesL10n', WPSEO_Utils::retrieve_enabled_features() ); $plugins_script_data = [ 'replaceVars' => [ 'replace_vars' => $this->get_replace_vars(), 'recommended_replace_vars' => $this->get_recommended_replace_vars(), 'hidden_replace_vars' => $this->get_hidden_replace_vars(), 'scope' => $this->determine_scope(), 'has_taxonomies' => $this->current_post_type_has_taxonomies(), ], 'shortcodes' => [ 'wpseo_shortcode_tags' => $this->get_valid_shortcode_tags(), 'wpseo_filter_shortcodes_nonce' => \wp_create_nonce( 'wpseo-filter-shortcodes' ), ], ]; $worker_script_data = [ 'url' => \YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-analysis-worker' ), 'dependencies' => \YoastSEO()->helpers->asset->get_dependency_urls_by_handle( 'yoast-seo-analysis-worker' ), 'keywords_assessment_url' => \YoastSEO()->helpers->asset->get_asset_url( 'yoast-seo-used-keywords-assessment' ), 'log_level' => WPSEO_Utils::get_analysis_worker_log_level(), // We need to make the feature flags separately available inside of the analysis web worker. 'enabled_features' => WPSEO_Utils::retrieve_enabled_features(), ]; $permalink = $this->get_permalink(); $page_on_front = (int) \get_option( 'page_on_front' ); $homepage_is_page = \get_option( 'show_on_front' ) === 'page'; $is_front_page = $homepage_is_page && $page_on_front === $post_id; $script_data = [ 'metabox' => $this->get_metabox_script_data( $permalink ), 'isPost' => true, 'isBlockEditor' => WP_Screen::get()->is_block_editor(), 'isElementorEditor' => true, 'isAlwaysIntroductionV2' => $this->is_elementor_version_compatible_with_introduction_v2(), 'postStatus' => \get_post_status( $post_id ), 'postType' => \get_post_type( $post_id ), 'analysis' => [ 'plugins' => $plugins_script_data, 'worker' => $worker_script_data, ], 'usedKeywordsNonce' => \wp_create_nonce( 'wpseo-keyword-usage-and-post-types' ), 'isFrontPage' => $is_front_page, ]; /** * The website information repository. * * @var Website_Information_Repository $repo */ $repo = \YoastSEO()->classes->get( Website_Information_Repository::class ); $site_information = $repo->get_post_site_information(); $site_information->set_permalink( $permalink ); $script_data = \array_merge_recursive( $site_information->get_legacy_site_information(), $script_data ); $this->asset_manager->localize_script( 'elementor', 'wpseoScriptData', $script_data ); $this->asset_manager->enqueue_user_language_script(); } /** * Checks whether the current Elementor version is compatible with our introduction v2. * * In version 3.30.0, Elementor removed the experimental flag for the editor v2. * Resulting in the editor v2 being the default. * * @return bool Whether the Elementor version is compatible with introduction v2. */ private function is_elementor_version_compatible_with_introduction_v2(): bool { if ( ! \defined( 'ELEMENTOR_VERSION' ) ) { return false; } // Take the semver version from their version string. $matches = []; $version = ( \preg_match( '/^([0-9]+.[0-9]+.[0-9]+)/', \ELEMENTOR_VERSION, $matches ) > 0 ) ? $matches[1] : \ELEMENTOR_VERSION; // Check if the version is 3.30.0 or higher. This is where the editor v2 was taken out of the experimental into the default state. return \version_compare( $version, '3.30.0', '>=' ); } /** * Renders the metabox hidden fields. * * @return void */ protected function render_hidden_fields() { // Wrap in a form with an action and post_id for the submit. \printf( '<form id="yoast-form" method="post" action="%1$s"><input type="hidden" name="action" value="wpseo_elementor_save" /><input type="hidden" id="post_ID" name="post_id" value="%2$s" />', \esc_url( \admin_url( 'admin-ajax.php' ) ), \esc_attr( $this->get_metabox_post()->ID ) ); \wp_nonce_field( 'wpseo_elementor_save', '_wpseo_elementor_nonce' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'general' ); if ( $this->is_advanced_metadata_enabled ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'advanced' ); } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'schema', $this->get_metabox_post()->post_type ); if ( $this->social_is_enabled ) { // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Reason: Meta_Fields_Presenter->present is considered safe. echo new Meta_Fields_Presenter( $this->get_metabox_post(), 'social' ); } \printf( '<input type="hidden" id="%1$s" name="%1$s" value="%2$s" />', \esc_attr( WPSEO_Meta::$form_prefix . 'slug' ), /** * It is important that this slug value is the same as in the database. * If the DB value is empty we can auto-generate a slug. * But if not empty, we should not touch it anymore. */ \esc_attr( $this->get_metabox_post()->post_name ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Output should be escaped in the filter. echo \apply_filters( 'wpseo_elementor_hidden_fields', '' ); echo '</form>'; } /** * Returns post in metabox context. * * @return WP_Post|null */ protected function get_metabox_post() { if ( $this->post !== null ) { return $this->post; } $this->post = $this->request_post->get_post(); return $this->post; } /** * Passes variables to js for use with the post-scraper. * * @param string $permalink The permalink. * * @return array */ protected function get_metabox_script_data( $permalink ) { $post_formatter = new WPSEO_Metabox_Formatter( new WPSEO_Post_Metabox_Formatter( $this->get_metabox_post(), [], $permalink ) ); $values = $post_formatter->get_values(); /** This filter is documented in admin/filters/class-cornerstone-filter.php. */ $post_types = \apply_filters( 'wpseo_cornerstone_post_types', \YoastSEO()->helpers->post_type->get_accessible_post_types() ); if ( $values['cornerstoneActive'] && ! \in_array( $this->get_metabox_post()->post_type, $post_types, true ) ) { $values['cornerstoneActive'] = false; } $values['elementorMarkerStatus'] = $this->is_highlighting_available() ? 'enabled' : 'hidden'; return $values; } /** * Gets the permalink. * * @return string */ protected function get_permalink(): string { $permalink = ''; if ( \is_object( $this->get_metabox_post() ) ) { $permalink = \get_sample_permalink( $this->get_metabox_post()->ID ); $permalink = $permalink[0]; } return $permalink; } /** * Checks whether the highlighting functionality is available for Elementor: * - in Free it's always available (as an upsell). * - in Premium it's available as long as the version is 21.8-RC0 or above. * * @return bool Whether the highlighting functionality is available. */ private function is_highlighting_available() { $is_premium = \YoastSEO()->helpers->product->is_premium(); $premium_version = \YoastSEO()->helpers->product->get_premium_version(); return ! $is_premium || \version_compare( $premium_version, '21.8-RC0', '>=' ); } /** * Prepares the replace vars for localization. * * @return array Replace vars. */ protected function get_replace_vars() { $cached_replacement_vars = []; $vars_to_cache = [ 'date', 'id', 'sitename', 'sitedesc', 'sep', 'page', 'currentyear', 'currentdate', 'currentmonth', 'currentday', 'tag', 'category', 'category_title', 'primary_category', 'pt_single', 'pt_plural', 'modified', 'name', 'user_description', 'pagetotal', 'pagenumber', 'post_year', 'post_month', 'post_day', 'author_first_name', 'author_last_name', 'permalink', 'post_content', ]; foreach ( $vars_to_cache as $var ) { $cached_replacement_vars[ $var ] = \wpseo_replace_vars( '%%' . $var . '%%', $this->get_metabox_post() ); } // Merge custom replace variables with the WordPress ones. return \array_merge( $cached_replacement_vars, $this->get_custom_replace_vars( $this->get_metabox_post() ) ); } /** * Prepares the recommended replace vars for localization. * * @return array Recommended replacement variables. */ protected function get_recommended_replace_vars() { $recommended_replace_vars = new WPSEO_Admin_Recommended_Replace_Vars(); // What is recommended depends on the current context. $post_type = $recommended_replace_vars->determine_for_post( $this->get_metabox_post() ); return $recommended_replace_vars->get_recommended_replacevars_for( $post_type ); } /** * Returns the list of replace vars that should be hidden inside the editor. * * @return string[] The hidden replace vars. */ protected function get_hidden_replace_vars() { return ( new WPSEO_Replace_Vars() )->get_hidden_replace_vars(); } /** * Gets the custom replace variables for custom taxonomies and fields. * * @param WP_Post $post The post to check for custom taxonomies and fields. * * @return array Array containing all the replacement variables. */ protected function get_custom_replace_vars( $post ) { return [ 'custom_fields' => $this->get_custom_fields_replace_vars( $post ), 'custom_taxonomies' => $this->get_custom_taxonomies_replace_vars( $post ), ]; } /** * Gets the custom replace variables for custom taxonomies. * * @param WP_Post $post The post to check for custom taxonomies. * * @return array Array containing all the replacement variables. */ protected function get_custom_taxonomies_replace_vars( $post ) { $taxonomies = \get_object_taxonomies( $post, 'objects' ); $custom_replace_vars = []; foreach ( $taxonomies as $taxonomy_name => $taxonomy ) { if ( \is_string( $taxonomy ) ) { // If attachment, see https://core.trac.wordpress.org/ticket/37368 . $taxonomy_name = $taxonomy; $taxonomy = \get_taxonomy( $taxonomy_name ); } if ( $taxonomy->_builtin && $taxonomy->public ) { continue; } $custom_replace_vars[ $taxonomy_name ] = [ 'name' => $taxonomy->name, 'description' => $taxonomy->description, ]; } return $custom_replace_vars; } /** * Gets the custom replace variables for custom fields. * * @param WP_Post $post The post to check for custom fields. * * @return array Array containing all the replacement variables. */ protected function get_custom_fields_replace_vars( $post ) { $custom_replace_vars = []; // If no post object is passed, return the empty custom_replace_vars array. if ( ! \is_object( $post ) ) { return $custom_replace_vars; } $custom_fields = \get_post_custom( $post->ID ); // Simply concatenate all fields containing replace vars so we can handle them all with a single regex find. $replace_vars_fields = \implode( ' ', [ \YoastSEO()->meta->for_post( $post->ID )->presentation->title, \YoastSEO()->meta->for_post( $post->ID )->presentation->meta_description, ] ); \preg_match_all( '/%%cf_([A-Za-z0-9_]+)%%/', $replace_vars_fields, $matches ); $fields_to_include = $matches[1]; foreach ( $custom_fields as $custom_field_name => $custom_field ) { // Skip private custom fields. if ( \substr( $custom_field_name, 0, 1 ) === '_' ) { continue; } // Skip custom fields that are not used, new ones will be fetched dynamically. if ( ! \in_array( $custom_field_name, $fields_to_include, true ) ) { continue; } // Skip custom field values that are serialized. if ( \is_serialized( $custom_field[0] ) ) { continue; } $custom_replace_vars[ $custom_field_name ] = $custom_field[0]; } return $custom_replace_vars; } /** * Determines the scope based on the post type. * This can be used by the replacevar plugin to determine if a replacement needs to be executed. * * @return string String describing the current scope. */ protected function determine_scope() { if ( $this->get_metabox_post()->post_type === 'page' ) { return 'page'; } return 'post'; } /** * Determines whether or not the current post type has registered taxonomies. * * @return bool Whether the current post type has taxonomies. */ protected function current_post_type_has_taxonomies() { $post_taxonomies = \get_object_taxonomies( $this->get_metabox_post()->post_type ); return ! empty( $post_taxonomies ); } /** * Returns an array with shortcode tags for all registered shortcodes. * * @return array */ protected function get_valid_shortcode_tags() { $shortcode_tags = []; foreach ( $GLOBALS['shortcode_tags'] as $tag => $description ) { $shortcode_tags[] = $tag; } return $shortcode_tags; } } integrations/third-party/woocommerce.php 0000644 00000022703 15025657560 0014570 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Pagination_Helper; use Yoast\WP\SEO\Helpers\Woocommerce_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * WooCommerce integration. */ class WooCommerce implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ private $options; /** * The WPSEO Replace Vars object. * * @var WPSEO_Replace_Vars */ private $replace_vars; /** * The memoizer for the meta tags context. * * @var Meta_Tags_Context_Memoizer */ protected $context_memoizer; /** * The indexable repository. * * @var Indexable_Repository */ private $repository; /** * The pagination helper. * * @var Pagination_Helper */ protected $pagination_helper; /** * The WooCommerce helper. * * @var Woocommerce_Helper */ private $woocommerce_helper; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ WooCommerce_Conditional::class, Front_End_Conditional::class ]; } /** * WooCommerce constructor. * * @param Options_Helper $options The options helper. * @param WPSEO_Replace_Vars $replace_vars The replace vars helper. * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer. * @param Indexable_Repository $repository The indexable repository. * @param Pagination_Helper $pagination_helper The paginataion helper. * @param Woocommerce_Helper $woocommerce_helper The WooCommerce helper. */ public function __construct( Options_Helper $options, WPSEO_Replace_Vars $replace_vars, Meta_Tags_Context_Memoizer $context_memoizer, Indexable_Repository $repository, Pagination_Helper $pagination_helper, Woocommerce_Helper $woocommerce_helper ) { $this->options = $options; $this->replace_vars = $replace_vars; $this->context_memoizer = $context_memoizer; $this->repository = $repository; $this->pagination_helper = $pagination_helper; $this->woocommerce_helper = $woocommerce_helper; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'wpseo_frontend_page_type_simple_page_id', [ $this, 'get_page_id' ] ); \add_filter( 'wpseo_breadcrumb_indexables', [ $this, 'add_shop_to_breadcrumbs' ] ); \add_filter( 'wpseo_title', [ $this, 'title' ], 10, 2 ); \add_filter( 'wpseo_metadesc', [ $this, 'description' ], 10, 2 ); \add_filter( 'wpseo_canonical', [ $this, 'canonical' ], 10, 2 ); \add_filter( 'wpseo_adjacent_rel_url', [ $this, 'adjacent_rel_url' ], 10, 3 ); } /** * Returns the correct canonical when WooCommerce is enabled. * * @param string $canonical The current canonical. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string The correct canonical. */ public function canonical( $canonical, $presentation = null ) { if ( ! $this->woocommerce_helper->is_shop_page() ) { return $canonical; } $url = $this->get_shop_paginated_link( 'curr', $presentation ); if ( $url ) { return $url; } return $canonical; } /** * Returns correct adjacent pages when WooCommerce is enabled. * * @param string $link The current link. * @param string $rel Link relationship, prev or next. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string The correct link. */ public function adjacent_rel_url( $link, $rel, $presentation = null ) { if ( ! $this->woocommerce_helper->is_shop_page() ) { return $link; } if ( $rel !== 'next' && $rel !== 'prev' ) { return $link; } $url = $this->get_shop_paginated_link( $rel, $presentation ); if ( $url ) { return $url; } return $link; } /** * Adds a breadcrumb for the shop page. * * @param Indexable[] $indexables The array with indexables. * * @return Indexable[] The indexables to be shown in the breadcrumbs, with the shop page added. */ public function add_shop_to_breadcrumbs( $indexables ) { $shop_page_id = $this->woocommerce_helper->get_shop_page_id(); if ( ! \is_int( $shop_page_id ) || $shop_page_id < 1 ) { return $indexables; } foreach ( $indexables as $index => $indexable ) { if ( $indexable->object_type === 'post-type-archive' && $indexable->object_sub_type === 'product' ) { $shop_page_indexable = $this->repository->find_by_id_and_type( $shop_page_id, 'post' ); if ( \is_a( $shop_page_indexable, Indexable::class ) ) { $indexables[ $index ] = $shop_page_indexable; } } } return $indexables; } /** * Returns the ID of the WooCommerce shop page when the currently opened page is the shop page. * * @param int $page_id The page id. * * @return int The Page ID of the shop. */ public function get_page_id( $page_id ) { if ( ! $this->woocommerce_helper->is_shop_page() ) { return $page_id; } return $this->woocommerce_helper->get_shop_page_id(); } /** * Handles the title. * * @param string $title The title. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string The title to use. */ public function title( $title, $presentation = null ) { $presentation = $this->ensure_presentation( $presentation ); if ( $presentation->model->title ) { return $title; } if ( ! $this->woocommerce_helper->is_shop_page() ) { return $title; } if ( ! \is_archive() ) { return $title; } $shop_page_id = $this->woocommerce_helper->get_shop_page_id(); if ( $shop_page_id < 1 ) { return $title; } $product_template_title = $this->get_product_template( 'title-product', $shop_page_id ); if ( $product_template_title ) { return $product_template_title; } return $title; } /** * Handles the meta description. * * @param string $description The title. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string The description to use. */ public function description( $description, $presentation = null ) { $presentation = $this->ensure_presentation( $presentation ); if ( $presentation->model->description ) { return $description; } if ( ! $this->woocommerce_helper->is_shop_page() ) { return $description; } if ( ! \is_archive() ) { return $description; } $shop_page_id = $this->woocommerce_helper->get_shop_page_id(); if ( $shop_page_id < 1 ) { return $description; } $product_template_description = $this->get_product_template( 'metadesc-product', $shop_page_id ); if ( $product_template_description ) { return $product_template_description; } return $description; } /** * Uses template for the given option name and replace the replacement variables on it. * * @param string $option_name The option name to get the template for. * @param string $shop_page_id The page id to retrieve template for. * * @return string The rendered value. */ protected function get_product_template( $option_name, $shop_page_id ) { $template = $this->options->get( $option_name ); $page = \get_post( $shop_page_id ); return $this->replace_vars->replace( $template, $page ); } /** * Get paginated link for shop page. * * @param string $rel Link relationship, prev or next or curr. * @param Indexable_Presentation|null $presentation The indexable presentation. * * @return string|null The link. */ protected function get_shop_paginated_link( $rel, $presentation = null ) { $presentation = $this->ensure_presentation( $presentation ); $permalink = $presentation->permalink; if ( ! $permalink ) { return null; } $current_page = \max( 1, $this->pagination_helper->get_current_archive_page_number() ); if ( $rel === 'curr' && $current_page === 1 ) { return $permalink; } if ( $rel === 'curr' && $current_page > 1 ) { return $this->pagination_helper->get_paginated_url( $permalink, $current_page ); } if ( $rel === 'prev' && $current_page === 2 ) { return $permalink; } if ( $rel === 'prev' && $current_page > 2 ) { return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page - 1 ) ); } if ( $rel === 'next' && $current_page < $this->pagination_helper->get_number_of_archive_pages() ) { return $this->pagination_helper->get_paginated_url( $permalink, ( $current_page + 1 ) ); } return null; } /** * Ensures a presentation is available. * * @param Indexable_Presentation $presentation The indexable presentation. * * @return Indexable_Presentation The presentation, taken from the current page if the input was invalid. */ protected function ensure_presentation( $presentation ) { if ( \is_a( $presentation, Indexable_Presentation::class ) ) { return $presentation; } $context = $this->context_memoizer->for_current_page(); return $context->presentation; } } integrations/third-party/exclude-woocommerce-post-types.php 0000644 00000001605 15025657560 0020342 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Third_Party; use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Integrations\Abstract_Exclude_Post_Type; /** * Excludes certain WooCommerce-specific post types from the indexable table. * * Posts with these post types will not be saved to the indexable table. */ class Exclude_WooCommerce_Post_Types extends Abstract_Exclude_Post_Type { /** * This integration is only active when the WooCommerce plugin * is installed and activated. * * @return string[] The conditionals. */ public static function get_conditionals() { return [ WooCommerce_Conditional::class ]; } /** * Returns the names of the post types to be excluded. * To be used in the wpseo_indexable_excluded_post_types filter. * * @return array The names of the post types. */ public function get_post_type() { return [ 'shop_order' ]; } } integrations/uninstall-integration.php 0000644 00000001756 15025657560 0014341 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Yoast\WP\SEO\Conditionals\No_Conditionals; /** * Class to manage the integration with the WP uninstall flow. */ class Uninstall_Integration implements Integration_Interface { use No_Conditionals; /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'uninstall_' . \WPSEO_BASENAME, [ $this, 'wpseo_uninstall' ] ); } /** * Performs all necessary actions that should happen upon plugin uninstall. * * @return void */ public function wpseo_uninstall() { $this->clear_import_statuses(); } /** * Clears the persistent import statuses. * * @return void */ public function clear_import_statuses() { $yoast_options = \get_site_option( 'wpseo' ); if ( isset( $yoast_options['importing_completed'] ) ) { $yoast_options['importing_completed'] = []; \update_site_option( 'wpseo', $yoast_options ); } } } integrations/feature-flag-integration.php 0000644 00000005672 15025657560 0014673 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Feature_Flag_Conditional; /** * Gathers all feature flags and surfaces them to the JavaScript side of the plugin. */ class Feature_Flag_Integration implements Integration_Interface { /** * The admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * All of the feature flag conditionals. * * @var Feature_Flag_Conditional[] */ protected $feature_flags; /** * Feature_Flag_Integration constructor. * * @param WPSEO_Admin_Asset_Manager $asset_manager The admin asset manager. * @param Feature_Flag_Conditional ...$feature_flags All of the known feature flag conditionals. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Feature_Flag_Conditional ...$feature_flags ) { $this->asset_manager = $asset_manager; $this->feature_flags = $feature_flags; } /** * Returns the conditionals based on which this loadable should be active. * * @return string[] The conditionals based on which this loadable should be active. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Initializes the integration. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'add_feature_flags' ] ); } /** * Gathers all the feature flags and injects them into the JavaScript. * * @return void */ public function add_feature_flags() { $enabled_features = $this->get_enabled_features(); // Localize under both names for BC. $this->asset_manager->localize_script( 'feature-flag-package', 'wpseoFeatureFlags', $enabled_features ); $this->asset_manager->localize_script( 'feature-flag-package', 'wpseoFeaturesL10n', $enabled_features ); } /** * Returns an array of all enabled feature flags. * * @return string[] The array of enabled features. */ public function get_enabled_features() { $enabled_features = []; foreach ( $this->feature_flags as $feature_flag ) { if ( $feature_flag->is_met() ) { $enabled_features[] = $feature_flag->get_feature_name(); } } return $this->filter_enabled_features( $enabled_features ); } /** * Runs the list of enabled feature flags through a filter. * * @param string[] $enabled_features The list of currently enabled feature flags. * * @return string[] The (possibly adapted) list of enabled features. */ protected function filter_enabled_features( $enabled_features ) { /** * Filters the list of currently enabled feature flags. * * @param string[] $enabled_features The current list of enabled feature flags. */ $filtered_enabled_features = \apply_filters( 'wpseo_enable_feature', $enabled_features ); if ( ! \is_array( $filtered_enabled_features ) ) { $filtered_enabled_features = $enabled_features; } return $filtered_enabled_features; } } integrations/front-end/rss-footer-embed.php 0000644 00000012337 15025657560 0015055 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class RSS_Footer_Embed. */ class RSS_Footer_Embed implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ protected $options; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Sets the required helpers. * * @codeCoverageIgnore It only handles dependencies. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'the_content_feed', [ $this, 'embed_rssfooter' ] ); \add_filter( 'the_excerpt_rss', [ $this, 'embed_rssfooter_excerpt' ] ); } /** * Adds the RSS footer (or header) to the full RSS feed item. * * @param string $content Feed item content. * * @return string */ public function embed_rssfooter( $content ) { if ( ! $this->include_rss_footer( 'full' ) ) { return $content; } return $this->embed_rss( $content ); } /** * Adds the RSS footer (or header) to the excerpt RSS feed item. * * @param string $content Feed item excerpt. * * @return string */ public function embed_rssfooter_excerpt( $content ) { if ( ! $this->include_rss_footer( 'excerpt' ) ) { return $content; } return $this->embed_rss( \wpautop( $content ) ); } /** * Checks if the RSS footer should included. * * @param string $context The context of the RSS content. * * @return bool Whether or not the RSS footer should included. */ protected function include_rss_footer( $context ) { if ( ! \is_feed() ) { return false; } /** * Filter: 'wpseo_include_rss_footer' - Allow the RSS footer to be dynamically shown/hidden. * * @param bool $show_embed Indicates if the RSS footer should be shown or not. * @param string $context The context of the RSS content - 'full' or 'excerpt'. */ if ( ! \apply_filters( 'wpseo_include_rss_footer', true, $context ) ) { return false; } return $this->is_configured(); } /** * Checks if the RSS feed fields are configured. * * @return bool True when one of the fields has a value. */ protected function is_configured() { return ( $this->options->get( 'rssbefore', '' ) !== '' || $this->options->get( 'rssafter', '' ) ); } /** * Adds the RSS footer and/or header to an RSS feed item. * * @param string $content Feed item content. * * @return string The content to add. */ protected function embed_rss( $content ) { $before = $this->rss_replace_vars( $this->options->get( 'rssbefore', '' ) ); $after = $this->rss_replace_vars( $this->options->get( 'rssafter', '' ) ); $content = $before . $content . $after; return $content; } /** * Replaces the possible RSS variables with their actual values. * * @param string $content The RSS content that should have the variables replaced. * * @return string */ protected function rss_replace_vars( $content ) { if ( $content === '' ) { return $content; } $replace_vars = $this->get_replace_vars( $this->get_link_template(), \get_post() ); $content = \stripslashes( \trim( $content ) ); $content = \str_ireplace( \array_keys( $replace_vars ), \array_values( $replace_vars ), $content ); return \wpautop( $content ); } /** * Retrieves the replacement variables. * * @codeCoverageIgnore It just contains too much WordPress functions. * * @param string $link_template The link template. * @param mixed $post The post to use. * * @return array The replacement variables. */ protected function get_replace_vars( $link_template, $post ) { $author_link = ''; if ( \is_object( $post ) ) { $author_link = \sprintf( $link_template, \esc_url( \get_author_posts_url( $post->post_author ) ), \esc_html( \get_the_author() ) ); } return [ '%%AUTHORLINK%%' => $author_link, '%%POSTLINK%%' => \sprintf( $link_template, \esc_url( \get_permalink() ), \esc_html( \get_the_title() ) ), '%%BLOGLINK%%' => \sprintf( $link_template, \esc_url( \get_bloginfo( 'url' ) ), \esc_html( \get_bloginfo( 'name' ) ) ), '%%BLOGDESCLINK%%' => \sprintf( $link_template, \esc_url( \get_bloginfo( 'url' ) ), \esc_html( \get_bloginfo( 'name' ) ) . ' - ' . \esc_html( \get_bloginfo( 'description' ) ) ), ]; } /** * Retrieves the link template. * * @return string The link template. */ protected function get_link_template() { /** * Filter: 'nofollow_rss_links' - Allow the developer to determine whether or not to follow the links in * the bits Yoast SEO adds to the RSS feed, defaults to false. * * @since 1.4.20 * * @param bool $unsigned Whether or not to follow the links in RSS feed, defaults to true. */ if ( \apply_filters( 'nofollow_rss_links', false ) ) { return '<a rel="nofollow" href="%1$s">%2$s</a>'; } return '<a href="%1$s">%2$s</a>'; } } integrations/front-end/comment-link-fixer.php 0000644 00000007732 15025657560 0015413 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Redirect_Helper; use Yoast\WP\SEO\Helpers\Robots_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Comment_Link_Fixer. */ class Comment_Link_Fixer implements Integration_Interface { /** * The redirects helper. * * @var Redirect_Helper */ protected $redirect; /** * The robots helper. * * @var Robots_Helper */ protected $robots; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Comment_Link_Fixer constructor. * * @codeCoverageIgnore It only sets depedencies. * * @param Redirect_Helper $redirect The redirect helper. * @param Robots_Helper $robots The robots helper. */ public function __construct( Redirect_Helper $redirect, Robots_Helper $robots ) { $this->redirect = $redirect; $this->robots = $robots; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { if ( $this->clean_reply_to_com() ) { \add_filter( 'comment_reply_link', [ $this, 'remove_reply_to_com' ] ); \add_action( 'template_redirect', [ $this, 'replytocom_redirect' ], 1 ); } // When users view a reply to a comment, this URL parameter is set. These should never be indexed separately. if ( $this->get_replytocom_parameter() !== null ) { \add_filter( 'wpseo_robots_array', [ $this->robots, 'set_robots_no_index' ] ); } } /** * Checks if the url contains the ?replytocom query parameter. * * @codeCoverageIgnore Wraps the filter input. * * @return string|null The value of replytocom or null if it does not exist. */ protected function get_replytocom_parameter() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['replytocom'] ) && \is_string( $_GET['replytocom'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. return \sanitize_text_field( \wp_unslash( $_GET['replytocom'] ) ); } return null; } /** * Removes the ?replytocom variable from the link, replacing it with a #comment-<number> anchor. * * @todo Should this function also allow for relative urls ? * * @param string $link The comment link as a string. * * @return string The modified link. */ public function remove_reply_to_com( $link ) { return \preg_replace( '`href=(["\'])(?:.*(?:\?|&|&)replytocom=(\d+)#respond)`', 'href=$1#comment-$2', $link ); } /** * Redirects out the ?replytocom variables. * * @return bool True when redirect has been done. */ public function replytocom_redirect() { if ( isset( $_GET['replytocom'] ) && \is_singular() ) { $url = \get_permalink( $GLOBALS['post']->ID ); $hash = \sanitize_text_field( \wp_unslash( $_GET['replytocom'] ) ); $query_string = ''; if ( isset( $_SERVER['QUERY_STRING'] ) ) { $query_string = \remove_query_arg( 'replytocom', \sanitize_text_field( \wp_unslash( $_SERVER['QUERY_STRING'] ) ) ); } if ( ! empty( $query_string ) ) { $url .= '?' . $query_string; } $url .= '#comment-' . $hash; $this->redirect->do_safe_redirect( $url, 301 ); return true; } return false; } /** * Checks whether we can allow the feature that removes ?replytocom query parameters. * * @codeCoverageIgnore It just wraps a call to a filter. * * @return bool True to remove, false not to remove. */ private function clean_reply_to_com() { /** * Filter: 'wpseo_remove_reply_to_com' - Allow disabling the feature that removes ?replytocom query parameters. * * @param bool $return True to remove, false not to remove. */ return (bool) \apply_filters( 'wpseo_remove_reply_to_com', true ); } } integrations/front-end/feed-improvements.php 0000644 00000011316 15025657560 0015325 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Surfaces\Meta_Surface; /** * Class Feed_Improvements */ class Feed_Improvements implements Integration_Interface { /** * Holds the options helper. * * @var Options_Helper */ private $options; /** * Holds the meta helper surface. * * @var Meta_Surface */ private $meta; /** * Canonical_Header constructor. * * @codeCoverageIgnore It only sets depedencies. * * @param Options_Helper $options The options helper. * @param Meta_Surface $meta The meta surface. */ public function __construct( Options_Helper $options, Meta_Surface $meta ) { $this->options = $options; $this->meta = $meta; } /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Registers hooks to WordPress. * * @return void */ public function register_hooks() { \add_filter( 'get_bloginfo_rss', [ $this, 'filter_bloginfo_rss' ], 10, 2 ); \add_filter( 'document_title_separator', [ $this, 'filter_document_title_separator' ] ); \add_action( 'do_feed_rss', [ $this, 'handle_rss_feed' ], 9 ); \add_action( 'do_feed_rss2', [ $this, 'send_canonical_header' ], 9 ); \add_action( 'do_feed_rss2', [ $this, 'add_robots_headers' ], 9 ); } /** * Filter `bloginfo_rss` output to give the URL for what's being shown instead of just always the homepage. * * @param string $show The output so far. * @param string $what What is being shown. * * @return string */ public function filter_bloginfo_rss( $show, $what ) { if ( $what === 'url' ) { return $this->get_url_for_queried_object( $show ); } return $show; } /** * Makes sure send canonical header always runs, because this RSS hook does not support the for_comments parameter * * @return void */ public function handle_rss_feed() { $this->send_canonical_header( false ); } /** * Adds a canonical link header to the main canonical URL for the requested feed object. If it is not a comment * feed. * * @param bool $for_comments If the RRS feed is meant for a comment feed. * * @return void */ public function send_canonical_header( $for_comments ) { if ( $for_comments || \headers_sent() ) { return; } $queried_object = \get_queried_object(); // Don't call get_class with null. This gives a warning. $class = ( $queried_object !== null ) ? \get_class( $queried_object ) : null; $url = $this->get_url_for_queried_object( $this->meta->for_home_page()->canonical ); if ( ( ! empty( $url ) && $url !== $this->meta->for_home_page()->canonical ) || $class === null ) { \header( \sprintf( 'Link: <%s>; rel="canonical"', $url ), false ); } } /** * Adds noindex, follow tag for comment feeds. * * @param bool $for_comments If the RSS feed is meant for a comment feed. * * @return void */ public function add_robots_headers( $for_comments ) { if ( $for_comments && ! \headers_sent() ) { \header( 'X-Robots-Tag: noindex, follow', true ); } } /** * Makes sure the title separator set in Yoast SEO is used for all feeds. * * @param string $separator The separator from WordPress. * * @return string The separator from Yoast SEO's settings. */ public function filter_document_title_separator( $separator ) { return \html_entity_decode( $this->options->get_title_separator() ); } /** * Determines the main URL for the queried object. * * @param string $url The URL determined so far. * * @return string The canonical URL for the queried object. */ protected function get_url_for_queried_object( $url = '' ) { $queried_object = \get_queried_object(); // Don't call get_class with null. This gives a warning. $class = ( $queried_object !== null ) ? \get_class( $queried_object ) : null; $meta = false; switch ( $class ) { // Post type archive feeds. case 'WP_Post_Type': $meta = $this->meta->for_post_type_archive( $queried_object->name ); break; // Post comment feeds. case 'WP_Post': $meta = $this->meta->for_post( $queried_object->ID ); break; // Term feeds. case 'WP_Term': $meta = $this->meta->for_term( $queried_object->term_id ); break; // Author feeds. case 'WP_User': $meta = $this->meta->for_author( $queried_object->ID ); break; // This would be NULL on the home page and on date archive feeds. case null: $meta = $this->meta->for_home_page(); break; default: break; } if ( $meta ) { return $meta->canonical; } return $url; } } integrations/front-end/indexing-controls.php 0000644 00000005253 15025657560 0015345 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Robots_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Indexing_Controls. */ class Indexing_Controls implements Integration_Interface { /** * The robots helper. * * @var Robots_Helper */ protected $robots; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * The constructor. * * @codeCoverageIgnore Sets the dependencies. * * @param Robots_Helper $robots The robots helper. */ public function __construct( Robots_Helper $robots ) { $this->robots = $robots; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @codeCoverageIgnore * * @return void */ public function register_hooks() { // The option `blog_public` is set in Settings > Reading > Search Engine Visibility. if ( (string) \get_option( 'blog_public' ) === '0' ) { \add_filter( 'wpseo_robots_array', [ $this->robots, 'set_robots_no_index' ] ); } \add_action( 'template_redirect', [ $this, 'noindex_robots' ] ); \add_filter( 'loginout', [ $this, 'nofollow_link' ] ); \add_filter( 'register', [ $this, 'nofollow_link' ] ); // Remove actions that we will handle through our wpseo_head call, and probably change the output of. \remove_action( 'wp_head', 'rel_canonical' ); \remove_action( 'wp_head', 'index_rel_link' ); \remove_action( 'wp_head', 'start_post_rel_link' ); \remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head' ); \remove_action( 'wp_head', 'noindex', 1 ); } /** * Sends a Robots HTTP header preventing URL from being indexed in the search results while allowing search engines * to follow the links in the object at the URL. * * @return bool Boolean indicating whether the noindex header was sent. */ public function noindex_robots() { if ( ! \is_robots() ) { return false; } return $this->set_robots_header(); } /** * Adds rel="nofollow" to a link, only used for login / registration links. * * @param string $input The link element as a string. * * @return string */ public function nofollow_link( $input ) { return \str_replace( '<a ', '<a rel="nofollow" ', $input ); } /** * Sets the x-robots-tag to noindex follow. * * @codeCoverageIgnore Too difficult to test. * * @return bool */ protected function set_robots_header() { if ( \headers_sent() === false ) { \header( 'X-Robots-Tag: noindex, follow', true ); return true; } return false; } } integrations/front-end/backwards-compatibility.php 0000644 00000003301 15025657560 0016477 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Adds actions that were previously called and are now deprecated. */ class Backwards_Compatibility implements Integration_Interface { /** * Represents the options helper. * * @var Options_Helper */ protected $options; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Backwards_Compatibility constructor * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { if ( $this->options->get( 'opengraph' ) === true ) { \add_action( 'wpseo_head', [ $this, 'call_wpseo_opengraph' ], 30 ); } if ( $this->options->get( 'twitter' ) === true && \apply_filters( 'wpseo_output_twitter_card', true ) !== false ) { \add_action( 'wpseo_head', [ $this, 'call_wpseo_twitter' ], 40 ); } } /** * Calls the old wpseo_opengraph action. * * @return void */ public function call_wpseo_opengraph() { \do_action_deprecated( 'wpseo_opengraph', [], '14.0', 'wpseo_frontend_presenters' ); } /** * Calls the old wpseo_twitter action. * * @return void */ public function call_wpseo_twitter() { \do_action_deprecated( 'wpseo_twitter', [], '14.0', 'wpseo_frontend_presenters' ); } } integrations/front-end/open-graph-oembed.php 0000644 00000006434 15025657560 0015172 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use WP_Post; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Conditionals\Open_Graph_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Surfaces\Meta_Surface; /** * Class Open_Graph_OEmbed. */ class Open_Graph_OEmbed implements Integration_Interface { /** * The meta surface. * * @var Meta_Surface */ private $meta; /** * The oEmbed data. * * @var array */ private $data; /** * The post ID for the current post. * * @var int */ private $post_id; /** * The post meta. * * @var Meta|false */ private $post_meta; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class, Open_Graph_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'oembed_response_data', [ $this, 'set_oembed_data' ], 10, 2 ); } /** * Open_Graph_OEmbed constructor. * * @param Meta_Surface $meta The meta surface. */ public function __construct( Meta_Surface $meta ) { $this->meta = $meta; } /** * Callback function to pass to the oEmbed's response data that will enable * support for using the image and title set by the WordPress SEO plugin's fields. This * address the concern where some social channels/subscribed use oEmebed data over Open Graph data * if both are present. * * @link https://developer.wordpress.org/reference/hooks/oembed_response_data/ for hook info. * * @param array $data The oEmbed data. * @param WP_Post $post The current Post object. * * @return array An array of oEmbed data with modified values where appropriate. */ public function set_oembed_data( $data, $post ) { // Data to be returned. $this->data = $data; $this->post_id = $post->ID; $this->post_meta = $this->meta->for_post( $this->post_id ); if ( ! empty( $this->post_meta ) ) { $this->set_title(); $this->set_description(); $this->set_image(); } return $this->data; } /** * Sets the OpenGraph title if configured. * * @return void */ protected function set_title() { $opengraph_title = $this->post_meta->open_graph_title; if ( ! empty( $opengraph_title ) ) { $this->data['title'] = $opengraph_title; } } /** * Sets the OpenGraph description if configured. * * @return void */ protected function set_description() { $opengraph_description = $this->post_meta->open_graph_description; if ( ! empty( $opengraph_description ) ) { $this->data['description'] = $opengraph_description; } } /** * Sets the image if it has been configured. * * @return void */ protected function set_image() { $images = $this->post_meta->open_graph_images; if ( ! \is_array( $images ) ) { return; } $image = \reset( $images ); if ( empty( $image ) || ! isset( $image['url'] ) ) { return; } $this->data['thumbnail_url'] = $image['url']; if ( isset( $image['width'] ) ) { $this->data['thumbnail_width'] = $image['width']; } if ( isset( $image['height'] ) ) { $this->data['thumbnail_height'] = $image['height']; } } } integrations/front-end/wp-robots-integration.php 0000644 00000012742 15025657560 0016155 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Conditionals\WP_Robots_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer; use Yoast\WP\SEO\Presenters\Robots_Presenter; /** * Class WP_Robots_Integration * * @package Yoast\WP\SEO\Integrations\Front_End */ class WP_Robots_Integration implements Integration_Interface { /** * The meta tags context memoizer. * * @var Meta_Tags_Context_Memoizer */ protected $context_memoizer; /** * Sets the dependencies for this integration. * * @param Meta_Tags_Context_Memoizer $context_memoizer The meta tags context memoizer. */ public function __construct( Meta_Tags_Context_Memoizer $context_memoizer ) { $this->context_memoizer = $context_memoizer; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { /** * Allow control of the `wp_robots` filter by prioritizing our hook 10 less than max. * Use the `wpseo_robots` filter to filter the Yoast robots output, instead of WordPress core. */ \add_filter( 'wp_robots', [ $this, 'add_robots' ], ( \PHP_INT_MAX - 10 ) ); } /** * Returns the conditionals based on which this loadable should be active. * * @return array The conditionals. */ public static function get_conditionals() { return [ Front_End_Conditional::class, WP_Robots_Conditional::class, ]; } /** * Adds our robots tag value to the WordPress robots tag output. * * @param array $robots The current robots data. * * @return array The robots data. */ public function add_robots( $robots ) { if ( ! \is_array( $robots ) ) { return $this->get_robots_value(); } $merged_robots = \array_merge( $robots, $this->get_robots_value() ); $filtered_robots = $this->enforce_robots_congruence( $merged_robots ); $sorted_robots = $this->sort_robots( $filtered_robots ); // Filter all falsy-null robot values. return \array_filter( $sorted_robots ); } /** * Retrieves the robots key-value pairs. * * @return array The robots key-value pairs. */ protected function get_robots_value() { $context = $this->context_memoizer->for_current_page(); $robots_presenter = new Robots_Presenter(); $robots_presenter->presentation = $context->presentation; return $this->format_robots( $robots_presenter->get() ); } /** * Formats our robots fields, to match the pattern WordPress is using. * * Our format: `[ 'index' => 'noindex', 'max-image-preview' => 'max-image-preview:large', ... ]` * WordPress format: `[ 'noindex' => true, 'max-image-preview' => 'large', ... ]` * * @param array $robots Our robots value. * * @return array The formatted robots. */ protected function format_robots( $robots ) { foreach ( $robots as $key => $value ) { // When the entry represents for example: max-image-preview:large. $colon_position = \strpos( $value, ':' ); if ( $colon_position !== false ) { $robots[ $key ] = \substr( $value, ( $colon_position + 1 ) ); continue; } // When index => noindex, we want a separate noindex as entry in array. if ( \strpos( $value, 'no' ) === 0 ) { $robots[ $key ] = false; $robots[ $value ] = true; continue; } // When the key is equal to the value, just make its value a boolean. if ( $key === $value ) { $robots[ $key ] = true; } } return $robots; } /** * Ensures all other possible robots values are congruent with nofollow and or noindex. * * WordPress might add some robot values again. * When the page is set to noindex we want to filter out these values. * * @param array $robots The robots. * * @return array The filtered robots. */ protected function enforce_robots_congruence( $robots ) { if ( ! empty( $robots['nofollow'] ) ) { $robots['follow'] = null; } if ( ! empty( $robots['noarchive'] ) ) { $robots['archive'] = null; } if ( ! empty( $robots['noimageindex'] ) ) { $robots['imageindex'] = null; // `max-image-preview` should set be to `none` when `noimageindex` is present. // Using `isset` rather than `! empty` here so that in the rare case of `max-image-preview` // being equal to an empty string due to filtering, its value would still be set to `none`. if ( isset( $robots['max-image-preview'] ) ) { $robots['max-image-preview'] = 'none'; } } if ( ! empty( $robots['nosnippet'] ) ) { $robots['snippet'] = null; } if ( ! empty( $robots['noindex'] ) ) { $robots['index'] = null; $robots['imageindex'] = null; $robots['noimageindex'] = null; $robots['archive'] = null; $robots['noarchive'] = null; $robots['snippet'] = null; $robots['nosnippet'] = null; $robots['max-snippet'] = null; $robots['max-image-preview'] = null; $robots['max-video-preview'] = null; } return $robots; } /** * Sorts the robots array. * * @param array $robots The robots array. * * @return array The sorted robots array. */ protected function sort_robots( $robots ) { \uksort( $robots, static function ( $a, $b ) { $order = [ 'index' => 0, 'noindex' => 1, 'follow' => 2, 'nofollow' => 3, ]; $ai = ( $order[ $a ] ?? 4 ); $bi = ( $order[ $b ] ?? 4 ); return ( $ai - $bi ); } ); return $robots; } } integrations/front-end/redirects.php 0000644 00000016037 15025657560 0013665 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Meta_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Redirect_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Redirects. */ class Redirects implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ protected $options; /** * The meta helper. * * @var Meta_Helper */ protected $meta; /** * The current page helper. * * @var Current_Page_Helper */ protected $current_page; /** * The redirect helper. * * @var Redirect_Helper */ private $redirect; /** * The URL helper. * * @var Url_Helper */ private $url; /** * Holds the WP_Query variables we should get rid of. * * @var string[] */ private $date_query_variables = [ 'year', 'm', 'monthnum', 'day', 'hour', 'minute', 'second', ]; /** * Sets the helpers. * * @codeCoverageIgnore * * @param Options_Helper $options Options helper. * @param Meta_Helper $meta Meta helper. * @param Current_Page_Helper $current_page The current page helper. * @param Redirect_Helper $redirect The redirect helper. * @param Url_Helper $url The URL helper. */ public function __construct( Options_Helper $options, Meta_Helper $meta, Current_Page_Helper $current_page, Redirect_Helper $redirect, Url_Helper $url ) { $this->options = $options; $this->meta = $meta; $this->current_page = $current_page; $this->redirect = $redirect; $this->url = $url; } /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'wp', [ $this, 'archive_redirect' ] ); \add_action( 'wp', [ $this, 'page_redirect' ], 99 ); \add_action( 'wp', [ $this, 'category_redirect' ] ); \add_action( 'template_redirect', [ $this, 'attachment_redirect' ], 1 ); \add_action( 'template_redirect', [ $this, 'disable_date_queries' ] ); } /** * Disable date queries, if they're disabled in Yoast SEO settings, to prevent indexing the wrong things. * * @return void */ public function disable_date_queries() { if ( $this->options->get( 'disable-date', false ) ) { $exploded_url = \explode( '?', $this->url->recreate_current_url(), 2 ); list( $base_url, $query_string ) = \array_pad( $exploded_url, 2, '' ); \parse_str( $query_string, $query_vars ); foreach ( $this->date_query_variables as $variable ) { if ( \in_array( $variable, \array_keys( $query_vars ), true ) ) { $this->do_date_redirect( $query_vars, $base_url ); } } } } /** * When certain archives are disabled, this redirects those to the homepage. * * @return void */ public function archive_redirect() { if ( $this->need_archive_redirect() ) { $this->redirect->do_safe_redirect( \get_bloginfo( 'url' ), 301 ); } } /** * Based on the redirect meta value, this function determines whether it should redirect the current post / page. * * @return void */ public function page_redirect() { if ( ! $this->current_page->is_simple_page() ) { return; } $post = \get_post(); if ( ! \is_object( $post ) ) { return; } $redirect = $this->meta->get_value( 'redirect', $post->ID ); if ( $redirect === '' ) { return; } $this->redirect->do_safe_redirect( $redirect, 301 ); } /** * If the option to disable attachment URLs is checked, this performs the redirect to the attachment. * * @return void */ public function attachment_redirect() { if ( ! $this->current_page->is_attachment() ) { return; } if ( $this->options->get( 'disable-attachment', false ) === false ) { return; } $url = $this->get_attachment_url(); if ( empty( $url ) ) { return; } $this->redirect->do_unsafe_redirect( $url, 301 ); } /** * Checks if certain archive pages are disabled to determine if a archive redirect is needed. * * @codeCoverageIgnore * * @return bool Whether or not to redirect an archive page. */ protected function need_archive_redirect() { if ( $this->options->get( 'disable-date', false ) && $this->current_page->is_date_archive() ) { return true; } if ( $this->options->get( 'disable-author', false ) && $this->current_page->is_author_archive() ) { return true; } if ( $this->options->get( 'disable-post_format', false ) && $this->current_page->is_post_format_archive() ) { return true; } return false; } /** * Retrieves the attachment url for the current page. * * @codeCoverageIgnore It wraps WordPress functions. * * @return string The attachment url. */ protected function get_attachment_url() { /** * Allows the developer to change the target redirection URL for attachments. * * @since 7.5.3 * * @param string $attachment_url The attachment URL for the queried object. * @param object $queried_object The queried object. */ return \apply_filters( 'wpseo_attachment_redirect_url', \wp_get_attachment_url( \get_queried_object_id() ), \get_queried_object() ); } /** * Redirects away query variables that shouldn't work. * * @param array $query_vars The query variables in the current URL. * @param string $base_url The base URL without query string. * * @return void */ private function do_date_redirect( $query_vars, $base_url ) { foreach ( $this->date_query_variables as $variable ) { unset( $query_vars[ $variable ] ); } $url = $base_url; if ( \count( $query_vars ) > 0 ) { $url .= '?' . \http_build_query( $query_vars ); } $this->redirect->do_safe_redirect( $url, 301 ); } /** * Strips `cat=-1` from the URL and redirects to the resulting URL. * * @return void */ public function category_redirect() { /** * Allows the developer to keep cat=-1 GET parameters * * @since 19.9 * * @param bool $remove_cat_parameter Whether to remove the `cat=-1` GET parameter. Default true. */ $should_remove_parameter = \apply_filters( 'wpseo_remove_cat_parameter', true ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved. if ( $should_remove_parameter && isset( $_GET['cat'] ) && $_GET['cat'] === '-1' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Data is not processed or saved. unset( $_GET['cat'] ); if ( isset( $_SERVER['REQUEST_URI'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- This is just a replace and the data is never saved. $_SERVER['REQUEST_URI'] = \remove_query_arg( 'cat' ); } $this->redirect->do_safe_redirect( $this->url->recreate_current_url(), 301, 'Stripping cat=-1 from the URL' ); } } } integrations/front-end/handle-404.php 0000644 00000005201 15025657560 0013430 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper; /** * Handles intercepting requests. */ class Handle_404 implements Integration_Interface { /** * The WP Query wrapper. * * @var WP_Query_Wrapper */ private $query_wrapper; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'pre_handle_404', [ $this, 'handle_404' ] ); } /** * Handle_404 constructor. * * @codeCoverageIgnore Handles dependencies. * * @param WP_Query_Wrapper $query_wrapper The query wrapper. */ public function __construct( WP_Query_Wrapper $query_wrapper ) { $this->query_wrapper = $query_wrapper; } /** * Handles the 404 status code. * * @param bool $handled Whether we've handled the request. * * @return bool True if it's 404. */ public function handle_404( $handled ) { if ( ! $this->is_feed_404() ) { return $handled; } $this->set_404(); $this->set_headers(); \add_filter( 'old_slug_redirect_url', '__return_false' ); \add_filter( 'redirect_canonical', '__return_false' ); return true; } /** * If there are no posts in a feed, make it 404 instead of sending an empty RSS feed. * * @return bool True if it's 404. */ protected function is_feed_404() { if ( ! \is_feed() ) { return false; } $wp_query = $this->query_wrapper->get_query(); // Don't 404 if the query contains post(s) or an object. if ( $wp_query->posts || $wp_query->get_queried_object() ) { return false; } // Don't 404 if it isn't archive or singular. if ( ! $wp_query->is_archive() && ! $wp_query->is_singular() ) { return false; } return true; } /** * Sets the 404 status code. * * @return void */ protected function set_404() { $wp_query = $this->query_wrapper->get_query(); $wp_query->is_feed = false; $wp_query->set_404(); $this->query_wrapper->set_query( $wp_query ); } /** * Sets the headers for http. * * @codeCoverageIgnore * * @return void */ protected function set_headers() { // Overwrite Content-Type header. if ( ! \headers_sent() ) { \header( 'Content-Type: ' . \get_option( 'html_type' ) . '; charset=' . \get_option( 'blog_charset' ) ); } \status_header( 404 ); \nocache_headers(); } } integrations/front-end/force-rewrite-title.php 0000644 00000010702 15025657560 0015566 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper; /** * Class Force_Rewrite_Title. */ class Force_Rewrite_Title implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ private $options; /** * Toggle indicating whether output buffering has been started. * * @var bool */ private $ob_started = false; /** * The WP Query wrapper. * * @var WP_Query_Wrapper */ private $wp_query; /** * Sets the helpers. * * @codeCoverageIgnore It just handles dependencies. * * @param Options_Helper $options Options helper. * @param WP_Query_Wrapper $wp_query WP query wrapper. */ public function __construct( Options_Helper $options, WP_Query_Wrapper $wp_query ) { $this->options = $options; $this->wp_query = $wp_query; } /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @codeCoverageIgnore * * @return void */ public function register_hooks() { // When the option is disabled. if ( ! $this->options->get( 'forcerewritetitle', false ) ) { return; } // For WordPress versions below 4.4. if ( \current_theme_supports( 'title-tag' ) ) { return; } \add_action( 'template_redirect', [ $this, 'force_rewrite_output_buffer' ], 99999 ); \add_action( 'wp_footer', [ $this, 'flush_cache' ], -1 ); } /** * Used in the force rewrite functionality this retrieves the output, replaces the title with the proper SEO * title and then flushes the output. * * @return bool */ public function flush_cache() { if ( $this->ob_started !== true ) { return false; } $content = $this->get_buffered_output(); $old_wp_query = $this->wp_query->get_query(); \wp_reset_query(); // When the file has the debug mark. if ( \preg_match( '/(?\'before\'.*)<!-- This site is optimized with the Yoast SEO.*<!-- \/ Yoast SEO( Premium)? plugin. -->(?\'after\'.*)/is', $content, $matches ) ) { $content = $this->replace_titles_from_content( $content, $matches ); unset( $matches ); } // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- The query gets reset here with the original query. $GLOBALS['wp_query'] = $old_wp_query; // phpcs:ignore WordPress.Security.EscapeOutput -- The output should already have been escaped, we are only filtering it. echo $content; return true; } /** * Starts the output buffer so it can later be fixed by flush_cache(). * * @return void */ public function force_rewrite_output_buffer() { $this->ob_started = true; $this->start_output_buffering(); } /** * Replaces the titles from the parts that contains a title. * * @param string $content The content to remove the titles from. * @param array $parts_with_title The parts containing a title. * * @return string The modified content. */ protected function replace_titles_from_content( $content, $parts_with_title ) { if ( isset( $parts_with_title['before'] ) && \is_string( $parts_with_title['before'] ) ) { $content = $this->replace_title( $parts_with_title['before'], $content ); } if ( isset( $parts_with_title['after'] ) ) { $content = $this->replace_title( $parts_with_title['after'], $content ); } return $content; } /** * Removes the title from the part that contains the title and put this modified part back * into the content. * * @param string $part_with_title The part with the title that needs to be replaced. * @param string $content The entire content. * * @return string The altered content. */ protected function replace_title( $part_with_title, $content ) { $part_without_title = \preg_replace( '/<title.*?\/title>/i', '', $part_with_title ); return \str_replace( $part_with_title, $part_without_title, $content ); } /** * Starts the output buffering. * * @codeCoverageIgnore * * @return void */ protected function start_output_buffering() { \ob_start(); } /** * Retrieves the buffered output. * * @codeCoverageIgnore * * @return string|false The buffered output. */ protected function get_buffered_output() { return \ob_get_clean(); } } integrations/front-end/schema-accessibility-feature.php 0000644 00000004462 15025657560 0017416 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Generators\Schema\Abstract_Schema_Piece; use Yoast\WP\SEO\Generators\Schema\Article; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Adds the table of contents accessibility feature to the article piece with a fallback to the webpage piece. */ class Schema_Accessibility_Feature implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'wpseo_schema_webpage', [ $this, 'maybe_add_accessibility_feature' ], 10, 4 ); \add_filter( 'wpseo_schema_article', [ $this, 'add_accessibility_feature' ], 10, 2 ); } /** * Adds the accessibility feature to the webpage if there is no article. * * @param array $piece The graph piece. * @param Meta_Tags_Context $context The context. * @param Abstract_Schema_Piece $the_generator The current schema generator. * @param Abstract_Schema_Piece[] $generators The schema generators. * * @return array The graph piece. */ public function maybe_add_accessibility_feature( $piece, $context, $the_generator, $generators ) { foreach ( $generators as $generator ) { if ( \is_a( $generator, Article::class ) && $generator->is_needed() ) { return $piece; } } return $this->add_accessibility_feature( $piece, $context ); } /** * Adds the accessibility feature to a schema graph piece. * * @param array $piece The schema piece. * @param Meta_Tags_Context $context The context. * * @return array The graph piece. */ public function add_accessibility_feature( $piece, $context ) { if ( empty( $context->blocks['yoast-seo/table-of-contents'] ) ) { return $piece; } if ( isset( $piece['accessibilityFeature'] ) ) { $piece['accessibilityFeature'][] = 'tableOfContents'; } else { $piece['accessibilityFeature'] = [ 'tableOfContents', ]; } return $piece; } } integrations/front-end/robots-txt-integration.php 0000644 00000017576 15025657560 0016360 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use WPSEO_Sitemaps_Router; use Yoast\WP\SEO\Conditionals\Robots_Txt_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Robots_Txt_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Presenters\Robots_Txt_Presenter; /** * Handles adding the sitemap to the `robots.txt`. */ class Robots_Txt_Integration implements Integration_Interface { /** * Holds the options helper. * * @var Options_Helper */ protected $options_helper; /** * Holds the robots txt helper. * * @var Robots_Txt_Helper */ protected $robots_txt_helper; /** * Holds the robots txt presenter. * * @var Robots_Txt_Presenter */ protected $robots_txt_presenter; /** * Sets the helpers. * * @param Options_Helper $options_helper Options helper. * @param Robots_Txt_Helper $robots_txt_helper Robots txt helper. * @param Robots_Txt_Presenter $robots_txt_presenter Robots txt presenter. */ public function __construct( Options_Helper $options_helper, Robots_Txt_Helper $robots_txt_helper, Robots_Txt_Presenter $robots_txt_presenter ) { $this->options_helper = $options_helper; $this->robots_txt_helper = $robots_txt_helper; $this->robots_txt_presenter = $robots_txt_presenter; } /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Robots_Txt_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'robots_txt', [ $this, 'filter_robots' ], 99999 ); if ( $this->options_helper->get( 'deny_search_crawling' ) && ! \is_multisite() ) { \add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_search_to_robots' ], 10, 1 ); } if ( $this->options_helper->get( 'deny_wp_json_crawling' ) && ! \is_multisite() ) { \add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_wp_json_to_robots' ], 10, 1 ); } if ( $this->options_helper->get( 'deny_adsbot_crawling' ) && ! \is_multisite() ) { \add_action( 'Yoast\WP\SEO\register_robots_rules', [ $this, 'add_disallow_adsbot' ], 10, 1 ); } } /** * Filters the robots.txt output. * * @param string $robots_txt The robots.txt output from WordPress. * * @return string Filtered robots.txt output. */ public function filter_robots( $robots_txt ) { $robots_txt = $this->remove_default_robots( $robots_txt ); $this->maybe_add_xml_sitemap(); /** * Filter: 'wpseo_should_add_subdirectory_multisite_xml_sitemaps' - Disabling this filter removes subdirectory sites from xml sitemaps. * * @since 19.8 * * @param bool $show Whether to display multisites in the xml sitemaps. */ if ( \apply_filters( 'wpseo_should_add_subdirectory_multisite_xml_sitemaps', true ) ) { $this->add_subdirectory_multisite_xml_sitemaps(); } /** * Allow registering custom robots rules to be outputted within the Yoast content block in robots.txt. * * @param Robots_Txt_Helper $robots_txt_helper The Robots_Txt_Helper object. */ \do_action( 'Yoast\WP\SEO\register_robots_rules', $this->robots_txt_helper ); return \trim( $robots_txt . \PHP_EOL . $this->robots_txt_presenter->present() . \PHP_EOL ); } /** * Add a disallow rule for search to robots.txt. * * @param Robots_Txt_Helper $robots_txt_helper The robots txt helper. * * @return void */ public function add_disallow_search_to_robots( Robots_Txt_Helper $robots_txt_helper ) { $robots_txt_helper->add_disallow( '*', '/?s=' ); $robots_txt_helper->add_disallow( '*', '/page/*/?s=' ); $robots_txt_helper->add_disallow( '*', '/search/' ); } /** * Add a disallow rule for /wp-json/ to robots.txt. * * @param Robots_Txt_Helper $robots_txt_helper The robots txt helper. * * @return void */ public function add_disallow_wp_json_to_robots( Robots_Txt_Helper $robots_txt_helper ) { $robots_txt_helper->add_disallow( '*', '/wp-json/' ); $robots_txt_helper->add_disallow( '*', '/?rest_route=' ); } /** * Add a disallow rule for AdsBot agents to robots.txt. * * @param Robots_Txt_Helper $robots_txt_helper The robots txt helper. * * @return void */ public function add_disallow_adsbot( Robots_Txt_Helper $robots_txt_helper ) { $robots_txt_helper->add_disallow( 'AdsBot', '/' ); } /** * Replaces the default WordPress robots.txt output. * * @param string $robots_txt Input robots.txt. * * @return string */ protected function remove_default_robots( $robots_txt ) { return \preg_replace( '`User-agent: \*[\r\n]+Disallow: /wp-admin/[\r\n]+Allow: /wp-admin/admin-ajax\.php[\r\n]+`', '', $robots_txt ); } /** * Adds XML sitemap reference to robots.txt. * * @return void */ protected function maybe_add_xml_sitemap() { // If the XML sitemap is disabled, bail. if ( ! $this->options_helper->get( 'enable_xml_sitemap', false ) ) { return; } $this->robots_txt_helper->add_sitemap( \esc_url( WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ) ) ); } /** * Adds subdomain multisite' XML sitemap references to robots.txt. * * @return void */ protected function add_subdirectory_multisite_xml_sitemaps() { // If not on a multisite subdirectory, bail. if ( ! \is_multisite() || \is_subdomain_install() ) { return; } $sitemaps_enabled = $this->get_xml_sitemaps_enabled(); foreach ( $sitemaps_enabled as $blog_id => $is_sitemap_enabled ) { if ( ! $is_sitemap_enabled ) { continue; } $this->robots_txt_helper->add_sitemap( \esc_url( \get_home_url( $blog_id, 'sitemap_index.xml' ) ) ); } } /** * Retrieves whether the XML sitemaps are enabled, keyed by blog ID. * * @return array */ protected function get_xml_sitemaps_enabled() { $is_allowed = $this->is_sitemap_allowed(); $blog_ids = $this->get_blog_ids(); $is_enabled = []; foreach ( $blog_ids as $blog_id ) { $is_enabled[ $blog_id ] = $is_allowed && $this->is_sitemap_enabled_for( $blog_id ); } return $is_enabled; } /** * Retrieves whether the sitemap is allowed on a sub site. * * @return bool */ protected function is_sitemap_allowed() { $options = \get_network_option( null, 'wpseo_ms' ); if ( ! $options || ! isset( $options['allow_enable_xml_sitemap'] ) ) { // Default is enabled. return true; } return (bool) $options['allow_enable_xml_sitemap']; } /** * Retrieves whether the sitemap is enabled on a site. * * @param int $blog_id The blog ID. * * @return bool */ protected function is_sitemap_enabled_for( $blog_id ) { if ( ! $this->is_yoast_active_on( $blog_id ) ) { return false; } $options = \get_blog_option( $blog_id, 'wpseo' ); if ( ! $options || ! isset( $options['enable_xml_sitemap'] ) ) { // Default is enabled. return true; } return (bool) $options['enable_xml_sitemap']; } /** * Determines whether Yoast SEO is active. * * @param int $blog_id The blog ID. * * @return bool */ protected function is_yoast_active_on( $blog_id ) { return \in_array( 'wordpress-seo/wp-seo.php', (array) \get_blog_option( $blog_id, 'active_plugins', [] ), true ) || $this->is_yoast_active_for_network(); } /** * Determines whether Yoast SEO is active for the entire network. * * @return bool */ protected function is_yoast_active_for_network() { $plugins = \get_network_option( null, 'active_sitewide_plugins' ); if ( isset( $plugins['wordpress-seo/wp-seo.php'] ) ) { return true; } return false; } /** * Retrieves the blog IDs of public, "active" sites on the network. * * @return array */ protected function get_blog_ids() { $criteria = [ 'archived' => 0, 'deleted' => 0, 'public' => 1, 'spam' => 0, 'fields' => 'ids', 'network_id' => \get_current_network_id(), ]; return \get_sites( $criteria ); } } integrations/front-end/crawl-cleanup-basic.php 0000644 00000007136 15025657560 0015515 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Crawl_Cleanup_Basic. */ class Crawl_Cleanup_Basic implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * Crawl Cleanup Basic integration constructor. * * @param Options_Helper $options_helper The option helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { // Remove HTTP headers we don't want. \add_action( 'wp', [ $this, 'clean_headers' ], 0 ); if ( $this->is_true( 'remove_shortlinks' ) ) { // Remove shortlinks. \remove_action( 'wp_head', 'wp_shortlink_wp_head' ); \remove_action( 'template_redirect', 'wp_shortlink_header', 11 ); } if ( $this->is_true( 'remove_rest_api_links' ) ) { // Remove REST API links. \remove_action( 'wp_head', 'rest_output_link_wp_head' ); \remove_action( 'template_redirect', 'rest_output_link_header', 11 ); } if ( $this->is_true( 'remove_rsd_wlw_links' ) ) { // Remove RSD and WLW Manifest links. \remove_action( 'wp_head', 'rsd_link' ); \remove_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' ); \remove_action( 'wp_head', 'wlwmanifest_link' ); } if ( $this->is_true( 'remove_oembed_links' ) ) { // Remove JSON+XML oEmbed links. \remove_action( 'wp_head', 'wp_oembed_add_discovery_links' ); } if ( $this->is_true( 'remove_generator' ) ) { \remove_action( 'wp_head', 'wp_generator' ); } if ( $this->is_true( 'remove_emoji_scripts' ) ) { // Remove emoji scripts and additional stuff they cause. \remove_action( 'wp_head', 'print_emoji_detection_script', 7 ); \remove_action( 'wp_print_styles', 'print_emoji_styles' ); \remove_action( 'admin_print_scripts', 'print_emoji_detection_script' ); \remove_action( 'admin_print_styles', 'print_emoji_styles' ); \add_filter( 'wp_resource_hints', [ $this, 'resource_hints_plain_cleanup' ], 1 ); } } /** * Returns the conditionals based in which this loadable should be active. * * @return array The array of conditionals. */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Removes X-Pingback and X-Powered-By headers as they're unneeded. * * @return void */ public function clean_headers() { if ( \headers_sent() ) { return; } if ( $this->is_true( 'remove_powered_by_header' ) ) { \header_remove( 'X-Powered-By' ); } if ( $this->is_true( 'remove_pingback_header' ) ) { \header_remove( 'X-Pingback' ); } } /** * Remove the core s.w.org hint as it's only used for emoji stuff we don't use. * * @param array $hints The hints we're adding to. * * @return array */ public function resource_hints_plain_cleanup( $hints ) { foreach ( $hints as $key => $hint ) { if ( \is_array( $hint ) && isset( $hint['href'] ) ) { if ( \strpos( $hint['href'], '//s.w.org' ) !== false ) { unset( $hints[ $key ] ); } } elseif ( \strpos( $hint, '//s.w.org' ) !== false ) { unset( $hints[ $key ] ); } } return $hints; } /** * Checks if the value of an option is set to true. * * @param string $option_name The option name. * * @return bool */ private function is_true( $option_name ) { return $this->options_helper->get( $option_name ) === true; } } integrations/front-end/crawl-cleanup-searches.php 0000644 00000013005 15025657560 0016221 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use WP_Query; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Redirect_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Class Crawl_Cleanup_Searches. */ class Crawl_Cleanup_Searches implements Integration_Interface { /** * Patterns to match against to find spam. * * @var array */ private $patterns = [ '/[:()【】[]]+/u', '/(TALK|QQ)\:/iu', ]; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The redirect helper. * * @var Redirect_Helper */ private $redirect_helper; /** * Crawl_Cleanup_Searches integration constructor. * * @param Options_Helper $options_helper The option helper. * @param Redirect_Helper $redirect_helper The redirect helper. */ public function __construct( Options_Helper $options_helper, Redirect_Helper $redirect_helper ) { $this->options_helper = $options_helper; $this->redirect_helper = $redirect_helper; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { if ( $this->options_helper->get( 'search_cleanup' ) ) { \add_filter( 'pre_get_posts', [ $this, 'validate_search' ] ); } if ( $this->options_helper->get( 'redirect_search_pretty_urls' ) && ! empty( \get_option( 'permalink_structure' ) ) ) { \add_action( 'template_redirect', [ $this, 'maybe_redirect_searches' ], 2 ); } } /** * Returns the conditionals based in which this loadable should be active. * * @return array The array of conditionals. */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Check if we want to allow this search to happen. * * @param WP_Query $query The main query. * * @return WP_Query */ public function validate_search( WP_Query $query ) { if ( ! $query->is_search() ) { return $query; } // First check against emoji and patterns we might not want. $this->check_unwanted_patterns( $query ); // Then limit characters if still needed. $this->limit_characters(); return $query; } /** * Redirect pretty search URLs to the "raw" equivalent * * @return void */ public function maybe_redirect_searches() { if ( ! \is_search() ) { return; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput if ( isset( $_SERVER['REQUEST_URI'] ) && \stripos( $_SERVER['REQUEST_URI'], '/search/' ) === 0 ) { $args = []; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $parsed = \wp_parse_url( $_SERVER['REQUEST_URI'] ); if ( ! empty( $parsed['query'] ) ) { \wp_parse_str( $parsed['query'], $args ); } $args['s'] = \get_search_query(); $proper_url = \home_url( '/' ); if ( \intval( \get_query_var( 'paged' ) ) > 1 ) { $proper_url .= \sprintf( 'page/%s/', \get_query_var( 'paged' ) ); unset( $args['paged'] ); } $proper_url = \add_query_arg( \array_map( 'rawurlencode_deep', $args ), $proper_url ); if ( ! empty( $parsed['fragment'] ) ) { $proper_url .= '#' . \rawurlencode( $parsed['fragment'] ); } $this->redirect_away( 'We redirect pretty URLs to the raw format.', $proper_url ); } } /** * Check query against unwanted search patterns. * * @param WP_Query $query The main WordPress query. * * @return void */ private function check_unwanted_patterns( WP_Query $query ) { $s = \rawurldecode( $query->query_vars['s'] ); if ( $this->options_helper->get( 'search_cleanup_emoji' ) && $this->has_emoji( $s ) ) { $this->redirect_away( 'We don\'t allow searches with emojis and other special characters.' ); } if ( ! $this->options_helper->get( 'search_cleanup_patterns' ) ) { return; } foreach ( $this->patterns as $pattern ) { $outcome = \preg_match( $pattern, $s, $matches ); if ( $outcome && $matches !== [] ) { $this->redirect_away( 'Your search matched a common spam pattern.' ); } } } /** * Redirect to the homepage for invalid searches. * * @param string $reason The reason for redirecting away. * @param string $to_url The URL to redirect to. * * @return void */ private function redirect_away( $reason, $to_url = '' ) { if ( empty( $to_url ) ) { $to_url = \get_home_url(); } $this->redirect_helper->do_safe_redirect( $to_url, 301, 'Yoast Search Filtering: ' . $reason ); } /** * Limits the number of characters in the search query. * * @return void */ private function limit_characters() { // We retrieve the search term unescaped because we want to count the characters properly. We make sure to escape it afterwards, if we do something with it. $unescaped_s = \get_search_query( false ); // We then unslash the search term, again because we want to count the characters properly. We make sure to slash it afterwards, if we do something with it. $raw_s = \wp_unslash( $unescaped_s ); if ( \mb_strlen( $raw_s, 'UTF-8' ) > $this->options_helper->get( 'search_character_limit' ) ) { $new_s = \mb_substr( $raw_s, 0, $this->options_helper->get( 'search_character_limit' ), 'UTF-8' ); \set_query_var( 's', \wp_slash( \esc_attr( $new_s ) ) ); } } /** * Determines if a text string contains an emoji or not. * * @param string $text The text string to detect emoji in. * * @return bool */ private function has_emoji( $text ) { $emojis_regex = '/([^-\p{L}\x00-\x7F]+)/u'; \preg_match( $emojis_regex, $text, $matches ); return ! empty( $matches ); } } integrations/front-end/category-term-description.php 0000644 00000002434 15025657560 0017000 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Adds support for shortcodes to category and term descriptions. */ class Category_Term_Description implements Integration_Interface { /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_filter( 'category_description', [ $this, 'add_shortcode_support' ] ); \add_filter( 'term_description', [ $this, 'add_shortcode_support' ] ); } /** * Adds shortcode support to category and term descriptions. * * This methods wrap in output buffering to prevent shortcodes that echo stuff * instead of return from breaking things. * * @param string $description String to add shortcodes in. * * @return string Content with shortcodes filtered out. */ public function add_shortcode_support( $description ) { \ob_start(); $description = \do_shortcode( $description ); \ob_end_clean(); return $description; } } integrations/front-end/crawl-cleanup-rss.php 0000644 00000014671 15025657560 0015245 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations\Front_End; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Adds actions that cleanup unwanted rss feed links. */ class Crawl_Cleanup_Rss implements Integration_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * Crawl Cleanup RSS integration constructor. * * @param Options_Helper $options_helper The option helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> The conditionals. */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Register our RSS related hooks. * * @return void */ public function register_hooks() { if ( $this->is_true( 'remove_feed_global' ) ) { \add_filter( 'feed_links_show_posts_feed', '__return_false' ); } if ( $this->is_true( 'remove_feed_global_comments' ) ) { \add_filter( 'feed_links_show_comments_feed', '__return_false' ); } \add_action( 'wp', [ $this, 'maybe_disable_feeds' ] ); \add_action( 'wp', [ $this, 'maybe_redirect_feeds' ], -10000 ); } /** * Disable feeds on selected cases. * * @return void */ public function maybe_disable_feeds() { if ( $this->is_true( 'remove_feed_post_comments' ) ) { \add_filter( 'feed_links_extra_show_post_comments_feed', '__return_false' ); } if ( $this->is_true( 'remove_feed_authors' ) ) { \add_filter( 'feed_links_extra_show_author_feed', '__return_false' ); } if ( $this->is_true( 'remove_feed_categories' ) ) { \add_filter( 'feed_links_extra_show_category_feed', '__return_false' ); } if ( $this->is_true( 'remove_feed_tags' ) ) { \add_filter( 'feed_links_extra_show_tag_feed', '__return_false' ); } if ( $this->is_true( 'remove_feed_custom_taxonomies' ) ) { \add_filter( 'feed_links_extra_show_tax_feed', '__return_false' ); } if ( $this->is_true( 'remove_feed_post_types' ) ) { \add_filter( 'feed_links_extra_show_post_type_archive_feed', '__return_false' ); } if ( $this->is_true( 'remove_feed_search' ) ) { \add_filter( 'feed_links_extra_show_search_feed', '__return_false' ); } } /** * Redirect feeds we don't want away. * * @return void */ public function maybe_redirect_feeds() { global $wp_query; if ( ! \is_feed() ) { return; } if ( \in_array( \get_query_var( 'feed' ), [ 'atom', 'rdf' ], true ) && $this->is_true( 'remove_atom_rdf_feeds' ) ) { $this->redirect_feed( \home_url(), 'We disable Atom/RDF feeds for performance reasons.' ); } // Only if we're on the global feed, the query is _just_ `'feed' => 'feed'`, hence this check. if ( ( $wp_query->query === [ 'feed' => 'feed' ] || $wp_query->query === [ 'feed' => 'atom' ] || $wp_query->query === [ 'feed' => 'rdf' ] ) && $this->is_true( 'remove_feed_global' ) ) { $this->redirect_feed( \home_url(), 'We disable the RSS feed for performance reasons.' ); } if ( \is_comment_feed() && ! ( \is_singular() || \is_attachment() ) && $this->is_true( 'remove_feed_global_comments' ) ) { $this->redirect_feed( \home_url(), 'We disable comment feeds for performance reasons.' ); } elseif ( \is_comment_feed() && \is_singular() && ( $this->is_true( 'remove_feed_post_comments' ) || $this->is_true( 'remove_feed_global_comments' ) ) ) { $url = \get_permalink( \get_queried_object() ); $this->redirect_feed( $url, 'We disable post comment feeds for performance reasons.' ); } if ( \is_author() && $this->is_true( 'remove_feed_authors' ) ) { $author_id = (int) \get_query_var( 'author' ); $url = \get_author_posts_url( $author_id ); $this->redirect_feed( $url, 'We disable author feeds for performance reasons.' ); } if ( ( \is_category() && $this->is_true( 'remove_feed_categories' ) ) || ( \is_tag() && $this->is_true( 'remove_feed_tags' ) ) || ( \is_tax() && $this->is_true( 'remove_feed_custom_taxonomies' ) ) ) { $term = \get_queried_object(); $url = \get_term_link( $term, $term->taxonomy ); if ( \is_wp_error( $url ) ) { $url = \home_url(); } $this->redirect_feed( $url, 'We disable taxonomy feeds for performance reasons.' ); } if ( ( \is_post_type_archive() ) && $this->is_true( 'remove_feed_post_types' ) ) { $url = \get_post_type_archive_link( $this->get_queried_post_type() ); $this->redirect_feed( $url, 'We disable post type feeds for performance reasons.' ); } if ( \is_search() && $this->is_true( 'remove_feed_search' ) ) { $url = \trailingslashit( \home_url() ) . '?s=' . \get_search_query(); $this->redirect_feed( $url, 'We disable search RSS feeds for performance reasons.' ); } } /** * Sends a cache control header. * * @param int $expiration The expiration time. * * @return void */ public function cache_control_header( $expiration ) { \header_remove( 'Expires' ); // The cacheability of the current request. 'public' allows caching, 'private' would not allow caching by proxies like CloudFlare. $cacheability = 'public'; $format = '%1$s, max-age=%2$d, s-maxage=%2$d, stale-while-revalidate=120, stale-if-error=14400'; if ( \is_user_logged_in() ) { $expiration = 0; $cacheability = 'private'; $format = '%1$s, max-age=%2$d'; } \header( \sprintf( 'Cache-Control: ' . $format, $cacheability, $expiration ), true ); } /** * Redirect a feed result to somewhere else. * * @param string $url The location we're redirecting to. * @param string $reason The reason we're redirecting. * * @return void */ private function redirect_feed( $url, $reason ) { \header_remove( 'Content-Type' ); \header_remove( 'Last-Modified' ); $this->cache_control_header( 7 * \DAY_IN_SECONDS ); \wp_safe_redirect( $url, 301, 'Yoast SEO: ' . $reason ); exit; } /** * Retrieves the queried post type. * * @return string The queried post type. */ private function get_queried_post_type() { $post_type = \get_query_var( 'post_type' ); if ( \is_array( $post_type ) ) { $post_type = \reset( $post_type ); } return $post_type; } /** * Checks if the value of an option is set to true. * * @param string $option_name The option name. * * @return bool */ private function is_true( $option_name ) { return $this->options_helper->get( $option_name ) === true; } } integrations/exclude-oembed-cache-post-type.php 0000644 00000001460 15025657560 0015664 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use Yoast\WP\SEO\Conditionals\Migrations_Conditional; /** * Excludes certain oEmbed Cache-specific post types from the indexable table. * * Posts with these post types will not be saved to the indexable table. */ class Exclude_Oembed_Cache_Post_Type extends Abstract_Exclude_Post_Type { /** * This integration is only active when the database migrations have been run. * * @return string[] The conditionals. */ public static function get_conditionals() { return [ Migrations_Conditional::class ]; } /** * Returns the names of the post types to be excluded. * To be used in the wpseo_indexable_excluded_post_types filter. * * @return array The names of the post types. */ public function get_post_type() { return [ 'oembed_cache' ]; } } integrations/academy-integration.php 0000644 00000011007 15025657560 0013721 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; /** * Class Academy_Integration. */ class Academy_Integration implements Integration_Interface { public const PAGE = 'wpseo_page_academy'; /** * Holds the WPSEO_Admin_Asset_Manager. * * @var WPSEO_Admin_Asset_Manager */ private $asset_manager; /** * Holds the Current_Page_Helper. * * @var Current_Page_Helper */ private $current_page_helper; /** * Holds the Product_Helper. * * @var Product_Helper */ private $product_helper; /** * Holds the Short_Link_Helper. * * @var Short_Link_Helper */ private $shortlink_helper; /** * Constructs Academy_Integration. * * @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager. * @param Current_Page_Helper $current_page_helper The Current_Page_Helper. * @param Product_Helper $product_helper The Product_Helper. * @param Short_Link_Helper $shortlink_helper The Short_Link_Helper. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Current_Page_Helper $current_page_helper, Product_Helper $product_helper, Short_Link_Helper $shortlink_helper ) { $this->asset_manager = $asset_manager; $this->current_page_helper = $current_page_helper; $this->product_helper = $product_helper; $this->shortlink_helper = $shortlink_helper; } /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Admin_Conditional::class, User_Can_Manage_Wpseo_Options_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { // Add page. \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] ); // Are we on the settings page? if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) { \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); \add_action( 'in_admin_header', [ $this, 'remove_notices' ], \PHP_INT_MAX ); } } /** * Adds the page. * * @param array $pages The pages. * * @return array The pages. */ public function add_page( $pages ) { \array_splice( $pages, 3, 0, [ [ 'wpseo_dashboard', '', \__( 'Academy', 'wordpress-seo' ), 'wpseo_manage_options', self::PAGE, [ $this, 'display_page' ], ], ] ); return $pages; } /** * Displays the page. * * @return void */ public function display_page() { echo '<div id="yoast-seo-academy"></div>'; } /** * Enqueues the assets. * * @return void */ 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' ); \wp_enqueue_media(); $this->asset_manager->enqueue_script( 'academy' ); $this->asset_manager->enqueue_style( 'academy' ); $this->asset_manager->localize_script( 'academy', 'wpseoScriptData', $this->get_script_data() ); } /** * Removes all current WP notices. * * @return void */ 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. */ public function get_script_data() { $addon_manager = new WPSEO_Addon_Manager(); $woocommerce_seo_active = $addon_manager->is_installed( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ); $local_seo_active = $addon_manager->is_installed( WPSEO_Addon_Manager::LOCAL_SLUG ); return [ 'preferences' => [ 'isPremium' => $this->product_helper->is_premium(), 'isWooActive' => $woocommerce_seo_active, 'isLocalActive' => $local_seo_active, 'isRtl' => \is_rtl(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'upsellSettings' => [ 'actionId' => 'load-nfd-ctb', 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', ], ], 'linkParams' => $this->shortlink_helper->get_query_params(), ]; } } integrations/primary-category.php 0000644 00000003716 15025657560 0013303 0 ustar 00 <?php namespace Yoast\WP\SEO\Integrations; use stdClass; use WP_Error; use WP_Post; use WPSEO_Primary_Term; use Yoast\WP\SEO\Conditionals\Primary_Category_Conditional; /** * Adds customizations to the front end for the primary category. */ class Primary_Category implements Integration_Interface { /** * Returns the conditionals based on which this loadable should be active. * * In this case only when on the frontend, the post overview, post edit or new post admin page. * * @return array The conditionals. */ public static function get_conditionals() { return [ Primary_Category_Conditional::class ]; } /** * Registers a filter to change a post's primary category. * * @return void */ public function register_hooks() { \add_filter( 'post_link_category', [ $this, 'post_link_category' ], 10, 3 ); } /** * Filters post_link_category to change the category to the chosen category by the user. * * @param stdClass $category The category that is now used for the post link. * @param array|null $categories This parameter is not used. * @param WP_Post|null $post The post in question. * * @return array|object|WP_Error|null The category we want to use for the post link. */ public function post_link_category( $category, $categories = null, $post = null ) { $post = \get_post( $post ); if ( $post === null ) { return $category; } $primary_category = $this->get_primary_category( $post ); if ( $primary_category !== false && $primary_category !== $category->cat_ID ) { $category = \get_category( $primary_category ); } return $category; } /** * Get the id of the primary category. * * @codeCoverageIgnore It justs wraps a dependency. * * @param WP_Post $post The post in question. * * @return int Primary category id. */ protected function get_primary_category( $post ) { $primary_term = new WPSEO_Primary_Term( 'category', $post->ID ); return $primary_term->get_primary_term(); } } functions.php 0000644 00000001271 15025657560 0007301 0 ustar 00 <?php /** * WPSEO plugin file. * * @package WPSEO\Internals */ if ( ! defined( 'WPSEO_VERSION' ) ) { header( 'Status: 403 Forbidden' ); header( 'HTTP/1.1 403 Forbidden' ); exit(); } use Yoast\WP\SEO\Main; if ( is_dir( WPSEO_PATH . YOAST_VENDOR_PREFIX_DIRECTORY ) ) { require_once WPSEO_PATH . YOAST_VENDOR_PREFIX_DIRECTORY . '/guzzlehttp/guzzle/src/functions.php'; } /** * Retrieves the main instance. * * @phpcs:disable WordPress.NamingConventions -- Should probably be renamed, but leave for now. * * @return Main The main instance. */ function YoastSEO() { // phpcs:enable static $main; if ( $main === null ) { $main = new Main(); $main->load(); } return $main; } helpers/date-helper.php 0000644 00000006210 15025657560 0011123 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use DateTime; use DateTimeZone; use Exception; /** * A helper object for dates. */ class Date_Helper { /** * Convert given date string to the W3C format. * * If $translate is true then the given date and format string will * be passed to date_i18n() for translation. * * @param string $date Date string to convert. * @param bool $translate Whether the return date should be translated. Default false. * * @return string Formatted date string. */ public function mysql_date_to_w3c_format( $date, $translate = false ) { return \mysql2date( \DATE_W3C, $date, $translate ); } /** * Formats a given date in UTC TimeZone format. * * @param string $date String representing the date / time. * @param string $format The format that the passed date should be in. * * @return string The formatted date. */ public function format( $date, $format = \DATE_W3C ) { if ( ! \is_string( $date ) ) { return $date; } $immutable_date = \date_create_immutable_from_format( 'Y-m-d H:i:s', $date, new DateTimeZone( 'UTC' ) ); if ( ! $immutable_date ) { return $date; } return $immutable_date->format( $format ); } /** * Formats the given timestamp to the needed format. * * @param int $timestamp The timestamp to use for the formatting. * @param string $format The format that the passed date should be in. * * @return string The formatted date. */ public function format_timestamp( $timestamp, $format = \DATE_W3C ) { if ( ! \is_string( $timestamp ) && ! \is_int( $timestamp ) ) { return $timestamp; } $immutable_date = \date_create_immutable_from_format( 'U', $timestamp, new DateTimeZone( 'UTC' ) ); if ( ! $immutable_date ) { return $timestamp; } return $immutable_date->format( $format ); } /** * Formats a given date in UTC TimeZone format and translate it to the set language. * * @param string $date String representing the date / time. * @param string $format The format that the passed date should be in. * * @return string The formatted and translated date. */ public function format_translated( $date, $format = \DATE_W3C ) { return \date_i18n( $format, $this->format( $date, 'U' ) ); } /** * Returns the current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT). * * @return int The current time measured in the number of seconds since the Unix Epoch (January 1 1970 00:00:00 GMT). */ public function current_time() { return \time(); } /** * Check if a string is a valid datetime. * * @param string $datetime String input to check as valid input for DateTime class. * * @return bool True when datetime is valid. */ public function is_valid_datetime( $datetime ) { if ( $datetime === null ) { /* * While not "officially" supported, `null` will be handled as `"now"` until PHP 9.0. * @link https://3v4l.org/tYp2k */ return true; } if ( \is_string( $datetime ) && \substr( $datetime, 0, 1 ) === '-' ) { return false; } try { return new DateTime( $datetime ) !== false; } catch ( Exception $exception ) { return false; } } } helpers/sanitization-helper.php 0000644 00000002040 15025657560 0012717 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WPSEO_Utils; /** * A helper object for sanitization. */ class Sanitization_Helper { /** * Emulate the WP native sanitize_text_field function in a %%variable%% safe way. * * @codeCoverageIgnore We have to write test when this method contains own code. * * @param string $value String value to sanitize. * * @return string The sanitized string. */ public function sanitize_text_field( $value ) { return WPSEO_Utils::sanitize_text_field( $value ); } /** * Sanitize a url for saving to the database. * Not to be confused with the old native WP function. * * @codeCoverageIgnore We have to write test when this method contains own code. * * @param string $value String URL value to sanitize. * @param array $allowed_protocols Optional set of allowed protocols. * * @return string The sanitized URL. */ public function sanitize_url( $value, $allowed_protocols = [ 'http', 'https' ] ) { return WPSEO_Utils::sanitize_url( $value, $allowed_protocols ); } } helpers/robots-helper.php 0000644 00000003361 15025657560 0011522 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Models\Indexable; /** * A helper object for the robots meta tag. */ class Robots_Helper { /** * Holds the Post_Type_Helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * Holds the Taxonomy_Helper. * * @var Taxonomy_Helper */ protected $taxonomy_helper; /** * Constructs a Score_Helper. * * @param Post_Type_Helper $post_type_helper The Post_Type_Helper. * @param Taxonomy_Helper $taxonomy_helper The Taxonomy_Helper. */ public function __construct( Post_Type_Helper $post_type_helper, Taxonomy_Helper $taxonomy_helper ) { $this->post_type_helper = $post_type_helper; $this->taxonomy_helper = $taxonomy_helper; } /** * Retrieves whether the Indexable is indexable. * * @param Indexable $indexable The Indexable. * * @return bool Whether the Indexable is indexable. */ public function is_indexable( Indexable $indexable ) { if ( $indexable->is_robots_noindex === null ) { // No individual value set, check the global setting. switch ( $indexable->object_type ) { case 'post': return $this->post_type_helper->is_indexable( $indexable->object_sub_type ); case 'term': return $this->taxonomy_helper->is_indexable( $indexable->object_sub_type ); } } return $indexable->is_robots_noindex === false; } /** * Sets the robots index to noindex. * * @param array $robots The current robots value. * * @return array The altered robots string. */ public function set_robots_no_index( $robots ) { if ( ! \is_array( $robots ) ) { \_deprecated_argument( __METHOD__, '14.1', '$robots has to be a key-value paired array.' ); return $robots; } $robots['index'] = 'noindex'; return $robots; } } helpers/author-archive-helper.php 0000644 00000012377 15025657560 0013142 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WP_Query; use Yoast\WP\Lib\Model; /** * A helper object for author archives. */ class Author_Archive_Helper { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The post type helper. * * @var Post_Type_Helper */ private $post_type_helper; /** * Creates a new author archive helper. * * @param Options_Helper $options_helper The options helper. * @param Post_Type_Helper $post_type_helper The post type helper. */ public function __construct( Options_Helper $options_helper, Post_Type_Helper $post_type_helper ) { $this->options_helper = $options_helper; $this->post_type_helper = $post_type_helper; } /** * Gets the array of post types that are shown on an author's archive. * * @return array The post types that are shown on an author's archive. */ public function get_author_archive_post_types() { /** * Filters the array of post types that are shown on an author's archive. * * @param array $args The post types that are shown on an author archive. */ return \apply_filters( 'wpseo_author_archive_post_types', [ 'post' ] ); } /** * Returns whether the author has at least one public post. * * @param int $author_id The author ID. * * @return bool|null Whether the author has at least one public post. */ public function author_has_public_posts( $author_id ) { // First check if the author has at least one public post. $has_public_post = $this->author_has_a_public_post( $author_id ); if ( $has_public_post ) { return true; } // Then check if the author has at least one post where the status is the same as the global setting. $has_public_post_depending_on_the_global_setting = $this->author_has_a_post_with_is_public_null( $author_id ); if ( $has_public_post_depending_on_the_global_setting ) { return null; } return false; } /** * Returns whether the author has at least one public post. * * **Note**: It uses WP_Query to determine the number of posts, * not the indexables table. * * @param string $user_id The user ID. * * @return bool Whether the author has at least one public post. */ public function author_has_public_posts_wp( $user_id ) { $post_types = \array_intersect( $this->get_author_archive_post_types(), $this->post_type_helper->get_indexable_post_types() ); $public_post_stati = \array_values( \array_filter( \get_post_stati(), 'is_post_status_viewable' ) ); $args = [ 'post_type' => $post_types, 'post_status' => $public_post_stati, 'author' => $user_id, 'update_post_term_cache' => false, 'update_post_meta_cache' => false, 'no_found_rows' => true, 'fields' => 'ids', 'posts_per_page' => 1, ]; $query = new WP_Query( $args ); if ( $query->have_posts() ) { return true; } return false; } /** * Checks whether author archives are disabled. * * @return bool `true` if author archives are disabled, `false` if not. */ public function are_disabled() { return $this->options_helper->get( 'disable-author' ); } /** * Returns whether the author has at least one public post. * * @codeCoverageIgnore It looks for the first ID through the ORM and converts it to a boolean. * * @param int $author_id The author ID. * * @return bool Whether the author has at least one public post. */ protected function author_has_a_public_post( $author_id ) { $cache_key = 'author_has_a_public_post_' . $author_id; $indexable_exists = \wp_cache_get( $cache_key ); if ( $indexable_exists === false ) { $indexable_exists = Model::of_type( 'Indexable' ) ->select( 'id' ) ->where( 'object_type', 'post' ) ->where_in( 'object_sub_type', $this->get_author_archive_post_types() ) ->where( 'author_id', $author_id ) ->where( 'is_public', 1 ) ->find_one(); if ( $indexable_exists === false ) { // Cache no results to prevent full table scanning on authors with no public posts. \wp_cache_set( $cache_key, 0, '', \wp_rand( ( 2 * \HOUR_IN_SECONDS ), ( 4 * \HOUR_IN_SECONDS ) ) ); } } return (bool) $indexable_exists; } /** * Returns whether the author has at least one post with the is public null. * * @codeCoverageIgnore It looks for the first ID through the ORM and converts it to a boolean. * * @param int $author_id The author ID. * * @return bool Whether the author has at least one post with the is public null. */ protected function author_has_a_post_with_is_public_null( $author_id ) { $cache_key = 'author_has_a_post_with_is_public_null_' . $author_id; $indexable_exists = \wp_cache_get( $cache_key ); if ( $indexable_exists === false ) { $indexable_exists = Model::of_type( 'Indexable' ) ->select( 'id' ) ->where( 'object_type', 'post' ) ->where_in( 'object_sub_type', $this->get_author_archive_post_types() ) ->where( 'author_id', $author_id ) ->where_null( 'is_public' ) ->find_one(); if ( $indexable_exists === false ) { // Cache no results to prevent full table scanning on authors with no is public null posts. \wp_cache_set( $cache_key, 0, '', \wp_rand( ( 2 * \HOUR_IN_SECONDS ), ( 4 * \HOUR_IN_SECONDS ) ) ); } } return (bool) $indexable_exists; } } helpers/current-page-helper.php 0000644 00000031236 15025657560 0012610 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WP_Post; use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper; /** * A helper object for WordPress posts. */ class Current_Page_Helper { /** * The WP Query wrapper. * * @var WP_Query_Wrapper */ private $wp_query_wrapper; /** * Current_Page_Helper constructor. * * @codeCoverageIgnore It only sets dependencies. * * @param WP_Query_Wrapper $wp_query_wrapper The wrapper for WP_Query. */ public function __construct( WP_Query_Wrapper $wp_query_wrapper ) { $this->wp_query_wrapper = $wp_query_wrapper; } /** * Returns the page type for the current request. * * @codeCoverageIgnore It just depends on other functions for its result. * * @return string Page type. */ public function get_page_type() { switch ( true ) { case $this->is_search_result(): return 'Search_Result_Page'; case $this->is_static_posts_page(): return 'Static_Posts_Page'; case $this->is_home_static_page(): return 'Static_Home_Page'; case $this->is_home_posts_page(): return 'Home_Page'; case $this->is_simple_page(): return 'Post_Type'; case $this->is_post_type_archive(): return 'Post_Type_Archive'; case $this->is_term_archive(): return 'Term_Archive'; case $this->is_author_archive(): return 'Author_Archive'; case $this->is_date_archive(): return 'Date_Archive'; case $this->is_404(): return 'Error_Page'; } return 'Fallback'; } /** * Checks if the currently opened page is a simple page. * * @return bool Whether the currently opened page is a simple page. */ public function is_simple_page() { return $this->get_simple_page_id() > 0; } /** * Returns the id of the currently opened page. * * @return int The id of the currently opened page. */ public function get_simple_page_id() { if ( \is_singular() ) { return \get_the_ID(); } if ( $this->is_posts_page() ) { return \get_option( 'page_for_posts' ); } /** * Filter: Allow changing the default page id. * * @param int $page_id The default page id. */ return \apply_filters( 'wpseo_frontend_page_type_simple_page_id', 0 ); } /** * Returns the id of the currently opened author archive. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return int The id of the currently opened author archive. */ public function get_author_id() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->get( 'author' ); } /** * Returns the id of the front page. * * @return int The id of the front page. 0 if the front page is not a static page. */ public function get_front_page_id() { if ( \get_option( 'show_on_front' ) !== 'page' ) { return 0; } return (int) \get_option( 'page_on_front' ); } /** * Returns the id of the currently opened term archive. * * @return int The id of the currently opened term archive. */ public function get_term_id() { $wp_query = $this->wp_query_wrapper->get_main_query(); if ( $wp_query->is_tax() || $wp_query->is_tag() || $wp_query->is_category() ) { $queried_object = $wp_query->get_queried_object(); if ( $queried_object && ! \is_wp_error( $queried_object ) ) { return $queried_object->term_id; } } return 0; } /** * Returns the post type of the main query. * * @return string The post type of the main query. */ public function get_queried_post_type() { $wp_query = $this->wp_query_wrapper->get_main_query(); $post_type = $wp_query->get( 'post_type' ); if ( \is_array( $post_type ) ) { $post_type = \reset( $post_type ); } return $post_type; } /** * Returns the permalink of the currently opened date archive. * If the permalink was cached, it returns this permalink. * If not, we call another function to get the permalink through wp_query. * * @return string The permalink of the currently opened date archive. */ public function get_date_archive_permalink() { static $date_archive_permalink; if ( isset( $date_archive_permalink ) ) { return $date_archive_permalink; } $date_archive_permalink = $this->get_non_cached_date_archive_permalink(); return $date_archive_permalink; } /** * Determine whether this is the homepage and shows posts. * * @return bool Whether or not the current page is the homepage that displays posts. */ public function is_home_posts_page() { $wp_query = $this->wp_query_wrapper->get_main_query(); if ( ! $wp_query->is_home() ) { return false; } /* * Whether the static page's `Homepage` option is actually not set to a page. * Otherwise WordPress proceeds to handle the homepage as a `Your latest posts` page. */ if ( (int) \get_option( 'page_on_front' ) === 0 ) { return true; } return \get_option( 'show_on_front' ) === 'posts'; } /** * Determine whether this is the static frontpage. * * @return bool Whether or not the current page is a static frontpage. */ public function is_home_static_page() { $wp_query = $this->wp_query_wrapper->get_main_query(); if ( ! $wp_query->is_front_page() ) { return false; } if ( \get_option( 'show_on_front' ) !== 'page' ) { return false; } return $wp_query->is_page( \get_option( 'page_on_front' ) ); } /** * Determine whether this is the static posts page. * * @return bool Whether or not the current page is a static posts page. */ public function is_static_posts_page() { $wp_query = $this->wp_query_wrapper->get_main_query(); $queried_object = $wp_query->get_queried_object(); return ( $wp_query->is_posts_page && \is_a( $queried_object, WP_Post::class ) && $queried_object->post_type === 'page' ); } /** * Determine whether this is the statically set posts page, when it's not the frontpage. * * @return bool Whether or not it's a non-frontpage, statically set posts page. */ public function is_posts_page() { $wp_query = $this->wp_query_wrapper->get_main_query(); if ( ! $wp_query->is_home() ) { return false; } return \get_option( 'show_on_front' ) === 'page'; } /** * Determine whether this is a post type archive. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether nor not the current page is a post type archive. */ public function is_post_type_archive() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_post_type_archive(); } /** * Determine whether this is a term archive. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether nor not the current page is a term archive. */ public function is_term_archive() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_tax || $wp_query->is_tag || $wp_query->is_category; } /** * Determine whether this is an attachment page. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether nor not the current page is an attachment page. */ public function is_attachment() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_attachment; } /** * Determine whether this is an author archive. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether nor not the current page is an author archive. */ public function is_author_archive() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_author(); } /** * Determine whether this is an date archive. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether nor not the current page is an date archive. */ public function is_date_archive() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_date(); } /** * Determine whether this is a search result. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether nor not the current page is a search result. */ public function is_search_result() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_search(); } /** * Determine whether this is a 404 page. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether nor not the current page is a 404 page. */ public function is_404() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_404(); } /** * Checks if the current page is the post format archive. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether or not the current page is the post format archive. */ public function is_post_format_archive() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_tax( 'post_format' ); } /** * Determine whether this page is an taxonomy archive page for multiple terms (url: /term-1,term2/). * * @return bool Whether or not the current page is an archive page for multiple terms. */ public function is_multiple_terms_page() { if ( ! $this->is_term_archive() ) { return false; } return $this->count_queried_terms() > 1; } /** * Checks whether the current page is paged. * * @codeCoverageIgnore This method only calls a WordPress function. * * @return bool Whether the current page is paged. */ public function is_paged() { return \is_paged(); } /** * Checks if the current page is the front page. * * @codeCoverageIgnore It wraps WordPress functionality. * * @return bool Whether or not the current page is the front page. */ public function is_front_page() { $wp_query = $this->wp_query_wrapper->get_main_query(); return $wp_query->is_front_page(); } /** * Retrieves the current admin page. * * @codeCoverageIgnore It only wraps a global WordPress variable. * * @return string The current page. */ public function get_current_admin_page() { global $pagenow; return $pagenow; } /** * Check if the current opened page is a Yoast SEO page. * * @return bool True when current page is a yoast seo plugin page. */ public function is_yoast_seo_page() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only using the variable in the strpos function. $current_page = \wp_unslash( $_GET['page'] ); return \strpos( $current_page, 'wpseo_' ) === 0; } return false; } /** * Returns the current Yoast SEO page. * (E.g. the `page` query variable in the URL). * * @return string The current Yoast SEO page. */ public function get_current_yoast_seo_page() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. return \sanitize_text_field( \wp_unslash( $_GET['page'] ) ); } return ''; } /** * Checks if the current global post is the privacy policy page. * * @return bool current global post is set as privacy page */ public function current_post_is_privacy_policy() { global $post; if ( ! isset( $post->ID ) ) { return false; } return \intval( $post->ID ) === \intval( \get_option( 'wp_page_for_privacy_policy', false ) ); } /** * Returns the permalink of the currently opened date archive. * * @return string The permalink of the currently opened date archive. */ protected function get_non_cached_date_archive_permalink() { $date_archive_permalink = ''; $wp_query = $this->wp_query_wrapper->get_main_query(); if ( $wp_query->is_day() ) { $date_archive_permalink = \get_day_link( $wp_query->get( 'year' ), $wp_query->get( 'monthnum' ), $wp_query->get( 'day' ) ); } if ( $wp_query->is_month() ) { $date_archive_permalink = \get_month_link( $wp_query->get( 'year' ), $wp_query->get( 'monthnum' ) ); } if ( $wp_query->is_year() ) { $date_archive_permalink = \get_year_link( $wp_query->get( 'year' ) ); } return $date_archive_permalink; } /** * Counts the total amount of queried terms. * * @codeCoverageIgnore This relies too much on WordPress dependencies. * * @return int The amoumt of queried terms. */ protected function count_queried_terms() { $wp_query = $this->wp_query_wrapper->get_main_query(); $term = $wp_query->get_queried_object(); $queried_terms = $wp_query->tax_query->queried_terms; if ( $term === null || empty( $queried_terms[ $term->taxonomy ]['terms'] ) ) { return 0; } return \count( $queried_terms[ $term->taxonomy ]['terms'] ); } } helpers/robots-txt-helper.php 0000644 00000005242 15025657560 0012337 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Values\Robots\User_Agent_List; /** * A helper object for the robots txt file. */ class Robots_Txt_Helper { /** * Holds a list of user agents with directives. * * @var User_Agent_List */ protected $robots_txt_user_agents; /** * Holds an array with absolute URLs of sitemaps. * * @var array */ protected $robots_txt_sitemaps; /** * Constructor for Robots_Txt_Helper. */ public function __construct() { $this->robots_txt_user_agents = new User_Agent_List(); $this->robots_txt_sitemaps = []; } /** * Add a disallow rule for a specific user agent if it does not exist yet. * * @param string $user_agent The user agent to add the disallow rule to. * @param string $path The path to add as a disallow rule. * * @return void */ public function add_disallow( $user_agent, $path ) { $user_agent_container = $this->robots_txt_user_agents->get_user_agent( $user_agent ); $user_agent_container->add_disallow_directive( $path ); } /** * Add an allow rule for a specific user agent if it does not exist yet. * * @param string $user_agent The user agent to add the allow rule to. * @param string $path The path to add as a allow rule. * * @return void */ public function add_allow( $user_agent, $path ) { $user_agent_container = $this->robots_txt_user_agents->get_user_agent( $user_agent ); $user_agent_container->add_allow_directive( $path ); } /** * Add sitemap to robots.txt if it does not exist yet. * * @param string $absolute_path The absolute path to the sitemap to add. * * @return void */ public function add_sitemap( $absolute_path ) { if ( ! \in_array( $absolute_path, $this->robots_txt_sitemaps, true ) ) { $this->robots_txt_sitemaps[] = $absolute_path; } } /** * Get all registered disallow directives per user agent. * * @return array The registered disallow directives per user agent. */ public function get_disallow_directives() { return $this->robots_txt_user_agents->get_disallow_directives(); } /** * Get all registered allow directives per user agent. * * @return array The registered allow directives per user agent. */ public function get_allow_directives() { return $this->robots_txt_user_agents->get_allow_directives(); } /** * Get all registered sitemap rules. * * @return array The registered sitemap rules. */ public function get_sitemap_rules() { return $this->robots_txt_sitemaps; } /** * Get all registered user agents * * @return array The registered user agents. */ public function get_robots_txt_user_agents() { return $this->robots_txt_user_agents->get_user_agents(); } } helpers/permalink-helper.php 0000644 00000002406 15025657560 0012173 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Models\Indexable; /** * A helper object for permalinks. */ class Permalink_Helper { /** * Retrieves the permalink for an indexable. * * @param Indexable $indexable The indexable. * * @return string|null The permalink. */ public function get_permalink_for_indexable( $indexable ) { switch ( true ) { case $indexable->object_type === 'post': if ( $indexable->object_sub_type === 'attachment' ) { return \wp_get_attachment_url( $indexable->object_id ); } return \get_permalink( $indexable->object_id ); case $indexable->object_type === 'home-page': return \home_url( '/' ); case $indexable->object_type === 'term': $term = \get_term( $indexable->object_id ); if ( $term === null || \is_wp_error( $term ) ) { return null; } return \get_term_link( $term, $term->taxonomy ); case $indexable->object_type === 'system-page' && $indexable->object_sub_type === 'search-page': return \get_search_link(); case $indexable->object_type === 'post-type-archive': return \get_post_type_archive_link( $indexable->object_sub_type ); case $indexable->object_type === 'user': return \get_author_posts_url( $indexable->object_id ); } return null; } } helpers/post-helper.php 0000644 00000012661 15025657560 0011202 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WP_Post; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * A helper object for post related things. */ class Post_Helper { /** * Holds the String_Helper instance. * * @var String_Helper */ private $string; /** * Holds the Post_Type_Helper instance. * * @var Post_Type_Helper */ private $post_type; /** * Represents the indexables repository. * * @var Indexable_Repository */ private $repository; /** * Post_Helper constructor. * * @codeCoverageIgnore It only sets dependencies. * * @param String_Helper $string_helper The string helper. * @param Post_Type_Helper $post_type_helper The string helper. */ public function __construct( String_Helper $string_helper, Post_Type_Helper $post_type_helper ) { $this->string = $string_helper; $this->post_type = $post_type_helper; } /** * Sets the indexable repository. Done to avoid circular dependencies. * * @required * * @param Indexable_Repository $repository The indexable repository. * * @return void */ public function set_indexable_repository( Indexable_Repository $repository ) { $this->repository = $repository; } /** * Removes all shortcode tags from the given content. * * @codeCoverageIgnore It only wraps a WordPress function. * * @param string $content Content to remove all the shortcode tags from. * * @return string Content without shortcode tags. */ public function strip_shortcodes( $content ) { return \strip_shortcodes( $content ); } /** * Retrieves the post excerpt (without tags). * * @codeCoverageIgnore It only wraps another helper method. * * @param int $post_id Post ID. * * @return string Post excerpt (without tags). */ public function get_the_excerpt( $post_id ) { return $this->string->strip_all_tags( \get_the_excerpt( $post_id ) ); } /** * Retrieves the post type of the current post. * * @codeCoverageIgnore It only wraps a WordPress function. * * @param WP_Post|null $post The post. * * @return string|false Post type on success, false on failure. */ public function get_post_type( $post = null ) { return \get_post_type( $post ); } /** * Retrieves the post title with fallback to `No title`. * * @param int $post_id Optional. Post ID. * * @return string The post title with fallback to `No title`. */ public function get_post_title_with_fallback( $post_id = 0 ) { $post_title = \get_the_title( $post_id ); if ( $post_title ) { return $post_title; } return \__( 'No title', 'wordpress-seo' ); } /** * Retrieves post data given a post ID. * * @codeCoverageIgnore It wraps a WordPress function. * * @param int $post_id Post ID. * * @return WP_Post|null The post. */ public function get_post( $post_id ) { return \get_post( $post_id ); } /** * Updates the has_public_posts field on attachments for a post_parent. * * An attachment is represented by their post parent when: * - The attachment has a post parent. * - The attachment inherits the post status. * * @codeCoverageIgnore It relies too much on dependencies. * * @param int $post_parent Post ID. * @param int $has_public_posts Whether the parent is public. * * @return bool Whether the update was successful. */ public function update_has_public_posts_on_attachments( $post_parent, $has_public_posts ) { $query = $this->repository->query() ->select( 'id' ) ->where( 'object_type', 'post' ) ->where( 'object_sub_type', 'attachment' ) ->where( 'post_status', 'inherit' ) ->where( 'post_parent', $post_parent ); if ( $has_public_posts !== null ) { $query->where_raw( '( has_public_posts IS NULL OR has_public_posts <> %s )', [ $has_public_posts ] ); } else { $query->where_not_null( 'has_public_posts' ); } $results = $query->find_array(); if ( empty( $results ) ) { return true; } $updated = $this->repository->query() ->set( 'has_public_posts', $has_public_posts ) ->where_id_in( \wp_list_pluck( $results, 'id' ) ) ->update_many(); return $updated !== false; } /** * Determines if the post can be indexed. * * @param int $post_id Post ID to check. * * @return bool True if the post can be indexed. */ public function is_post_indexable( $post_id ) { // Don't index posts which are not public (i.e. viewable). $post_type = \get_post_type( $post_id ); if ( ! $this->post_type->is_of_indexable_post_type( $post_type ) ) { return false; } // Don't index excluded post statuses. if ( \in_array( \get_post_status( $post_id ), $this->get_excluded_post_statuses(), true ) ) { return false; } // Don't index revisions of posts. if ( \wp_is_post_revision( $post_id ) ) { return false; } // Don't index autosaves that are not caught by the auto-draft check. if ( \wp_is_post_autosave( $post_id ) ) { return false; } return true; } /** * Retrieves the list of excluded post statuses. * * @return array The excluded post statuses. */ public function get_excluded_post_statuses() { return [ 'auto-draft' ]; } /** * Retrieves the list of public posts statuses. * * @return array The public post statuses. */ public function get_public_post_statuses() { /** * Filter: 'wpseo_public_post_statuses' - List of public post statuses. * * @param array $post_statuses Post status list, defaults to array( 'publish' ). */ return \apply_filters( 'wpseo_public_post_statuses', [ 'publish' ] ); } } helpers/import-helper.php 0000644 00000001314 15025657560 0011520 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * The Import Helper. */ class Import_Helper { /** * Flattens a multidimensional array of settings. Recursive. * * @param array $array_to_flatten The array to be flattened. * @param string $key_prefix The key to be used as a prefix. * * @return array The flattened array. */ public function flatten_settings( $array_to_flatten, $key_prefix = '' ) { $result = []; foreach ( $array_to_flatten as $key => $value ) { if ( \is_array( $value ) ) { $result = \array_merge( $result, $this->flatten_settings( $value, $key_prefix . '/' . $key ) ); } else { $result[ $key_prefix . '/' . $key ] = $value; } } return $result; } } helpers/first-time-configuration-notice-helper.php 0000644 00000012244 15025657560 0016421 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper to determine the status of ftc and front-end related configuration. */ class First_Time_Configuration_Notice_Helper { /** * The options' helper. * * @var Options_Helper */ private $options_helper; /** * The indexing helper. * * @var Indexing_Helper */ private $indexing_helper; /** * Whether we show the alternate mesage. * * @var bool */ private $show_alternate_message; /** * First_Time_Configuration_Notice_Integration constructor. * * @param Options_Helper $options_helper The options helper. * @param Indexing_Helper $indexing_helper The indexing helper. */ public function __construct( Options_Helper $options_helper, Indexing_Helper $indexing_helper ) { $this->options_helper = $options_helper; $this->indexing_helper = $indexing_helper; $this->show_alternate_message = false; } /** * Determines whether and where the "First-time SEO Configuration" admin notice should be displayed. * * @return bool Whether the "First-time SEO Configuration" admin notice should be displayed. */ public function should_display_first_time_configuration_notice() { if ( ! $this->options_helper->get( 'dismiss_configuration_workout_notice', false ) === false ) { return false; } if ( ! $this->on_wpseo_admin_page_or_dashboard() ) { return false; } return $this->first_time_configuration_not_finished(); } /** * Gets the first time configuration title based on the show_alternate_message boolean * * @return string */ public function get_first_time_configuration_title() { return ( ! $this->show_alternate_message ) ? \__( 'First-time SEO configuration', 'wordpress-seo' ) : \__( 'SEO configuration', 'wordpress-seo' ); } /** * Determines if the first time configuration is completely finished. * * @return bool */ public function first_time_configuration_not_finished() { if ( ! $this->user_can_do_first_time_configuration() ) { return false; } if ( $this->is_first_time_configuration_finished() ) { return false; } if ( $this->options_helper->get( 'first_time_install', false ) !== false ) { return ! $this->are_site_representation_name_and_logo_set() || $this->indexing_helper->get_unindexed_count() > 0; } if ( $this->indexing_helper->is_initial_indexing() === false ) { return false; } if ( $this->indexing_helper->is_finished_indexables_indexing() === true ) { return false; } $this->show_alternate_message = true; return ! $this->are_site_representation_name_and_logo_set(); } /** * Whether the user can do the first-time configuration. * * @return bool Whether the current user can do the first-time configuration. */ private function user_can_do_first_time_configuration() { return \current_user_can( 'wpseo_manage_options' ); } /** * Whether the user is currently visiting one of our admin pages or the WordPress dashboard. * * @return bool Whether the current page is a Yoast SEO admin page */ private function on_wpseo_admin_page_or_dashboard() { $pagenow = $GLOBALS['pagenow']; // Show on the WP Dashboard. if ( $pagenow === 'index.php' ) { return true; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information and only comparing the variable in a condition. $page_from_get = \wp_unslash( $_GET['page'] ); // Show on Yoast SEO pages, with some exceptions. if ( $pagenow === 'admin.php' && \strpos( $page_from_get, 'wpseo' ) === 0 ) { $exceptions = [ 'wpseo_installation_successful', 'wpseo_installation_successful_free', ]; if ( ! \in_array( $page_from_get, $exceptions, true ) ) { return true; } } } return false; } /** * Whether all steps of the first-time configuration have been finished. * * @return bool Whether the first-time configuration has been finished. */ private function is_first_time_configuration_finished() { $configuration_finished_steps = $this->options_helper->get( 'configuration_finished_steps', [] ); return \count( $configuration_finished_steps ) === 3; } /** * Whether the site representation name and logo have been set. * * @return bool Whether the site representation name and logo have been set. */ private function are_site_representation_name_and_logo_set() { $company_or_person = $this->options_helper->get( 'company_or_person', '' ); if ( $company_or_person === '' ) { return false; } if ( $company_or_person === 'company' ) { return ! empty( $this->options_helper->get( 'company_name' ) ) && ! empty( $this->options_helper->get( 'company_logo', '' ) ); } return ! empty( $this->options_helper->get( 'company_or_person_user_id' ) ) && ! empty( $this->options_helper->get( 'person_logo', '' ) ); } /** * Getter for the show alternate message boolean. * * @return bool */ public function should_show_alternate_message() { return $this->show_alternate_message; } } helpers/schema/language-helper.php 0000644 00000001544 15025657560 0013236 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Schema; /** * Class Language_Helper. */ class Language_Helper { /** * Adds the `inLanguage` property to a Schema piece. * * Must use one of the language codes from the IETF BCP 47 standard. The * language tag syntax is made of one or more subtags separated by a hyphen * e.g. "en", "en-US", "zh-Hant-CN". * * @param array $data The Schema piece data. * * @return array The Schema piece data with added language property */ public function add_piece_language( $data ) { /** * Filter: 'wpseo_schema_piece_language' - Allow changing the Schema piece language. * * @param string $type The Schema piece language. * @param array $data The Schema piece data. */ $data['inLanguage'] = \apply_filters( 'wpseo_schema_piece_language', \get_bloginfo( 'language' ), $data ); return $data; } } helpers/schema/article-helper.php 0000644 00000001552 15025657560 0013075 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Schema; /** * Class Article_Helper. */ class Article_Helper { /** * Determines whether a given post type should have Article schema. * * @param string|null $post_type Post type to check. * * @return bool True if it has Article schema, false if not. */ public function is_article_post_type( $post_type = null ) { if ( $post_type === null ) { $post_type = \get_post_type(); } return $this->is_author_supported( $post_type ); } /** * Checks whether author is supported for the passed object sub type. * * @param string $object_sub_type The sub type of the object to check author support for. * * @return bool True if author is supported for the passed object sub type. */ public function is_author_supported( $object_sub_type ) { return \post_type_supports( $object_sub_type, 'author' ); } } helpers/schema/replace-vars-helper.php 0000644 00000006764 15025657560 0014050 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Schema; use Closure; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Config\Schema_IDs; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Helpers\Date_Helper; use Yoast\WP\SEO\Presentations\Indexable_Presentation; /** * Registers the Schema replace variables and exposes a method to replace variables on a Schema graph. */ class Replace_Vars_Helper { use No_Conditionals; /** * The replace vars. * * @var WPSEO_Replace_Vars */ protected $replace_vars; /** * The Schema ID helper. * * @var ID_Helper */ protected $id_helper; /** * The date helper. * * @var Date_Helper */ protected $date_helper; /** * Replace_Vars_Helper constructor. * * @param WPSEO_Replace_Vars $replace_vars The replace vars. * @param ID_Helper $id_helper The Schema ID helper. * @param Date_Helper $date_helper The date helper. */ public function __construct( WPSEO_Replace_Vars $replace_vars, ID_Helper $id_helper, Date_Helper $date_helper ) { $this->replace_vars = $replace_vars; $this->id_helper = $id_helper; $this->date_helper = $date_helper; } /** * Replaces the variables. * * @param array $schema_data The Schema data. * @param Indexable_Presentation $presentation The indexable presentation. * * @return array The array with replaced vars. */ public function replace( array $schema_data, Indexable_Presentation $presentation ) { foreach ( $schema_data as $key => $value ) { if ( \is_array( $value ) ) { $schema_data[ $key ] = $this->replace( $value, $presentation ); continue; } $schema_data[ $key ] = $this->replace_vars->replace( $value, $presentation->source ); } return $schema_data; } /** * Registers the Schema-related replace vars. * * @param Meta_Tags_Context $context The meta tags context. * * @return void */ public function register_replace_vars( $context ) { $replace_vars = [ 'main_schema_id' => $context->main_schema_id, 'author_id' => $this->id_helper->get_user_schema_id( $context->indexable->author_id, $context ), 'person_id' => $context->site_url . Schema_IDs::PERSON_HASH, 'primary_image_id' => $context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH, 'webpage_id' => $context->main_schema_id, 'website_id' => $context->site_url . Schema_IDs::WEBSITE_HASH, 'organization_id' => $context->site_url . Schema_IDs::ORGANIZATION_HASH, ]; if ( $context->post ) { // Post does not always exist, e.g. on term pages. $replace_vars['post_date'] = $this->date_helper->format( $context->post->post_date, \DATE_ATOM ); } foreach ( $replace_vars as $var => $value ) { $this->register_replacement( $var, $value ); } } /** * Registers a replace var and its value. * * @param string $variable The replace variable. * @param string $value The value that the variable should be replaced with. * * @return void */ protected function register_replacement( $variable, $value ) { $this->replace_vars->safe_register_replacement( $variable, $this->get_identity_function( $value ) ); } /** * Returns an anonymous function that in turn just returns the given value. * * @param mixed $value The value that the function should return. * * @return Closure A function that returns the given value. */ protected function get_identity_function( $value ) { return static function () use ( $value ) { return $value; }; } } helpers/schema/id-helper.php 0000644 00000001263 15025657560 0012045 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Schema; use Yoast\WP\SEO\Config\Schema_IDs; use Yoast\WP\SEO\Context\Meta_Tags_Context; /** * Schema utility functions. */ class ID_Helper { /** * Retrieve a users Schema ID. * * @param int $user_id The ID of the User you need a Schema ID for. * @param Meta_Tags_Context $context A value object with context variables. * * @return string The user's schema ID. */ public function get_user_schema_id( $user_id, $context ) { $user = \get_userdata( $user_id ); if ( \is_a( $user, 'WP_User' ) ) { return $context->site_url . Schema_IDs::PERSON_HASH . \wp_hash( $user->user_login . $user_id ); } return ''; } } helpers/schema/html-helper.php 0000644 00000003756 15025657560 0012426 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Schema; /** * Class HTML_Helper. */ class HTML_Helper { /** * Sanitizes a HTML string by stripping all tags except headings, breaks, lists, links, paragraphs and formatting. * * @param string $html The original HTML. * * @return string The sanitized HTML. */ public function sanitize( $html ) { if ( ! $this->is_non_empty_string_or_stringable( $html ) ) { if ( \is_int( $html ) || \is_float( $html ) ) { return (string) $html; } return ''; } return \strip_tags( $html, '<h1><h2><h3><h4><h5><h6><br><ol><ul><li><a><p><b><strong><i><em>' ); } /** * Strips the tags in a smart way. * * @param string $html The original HTML. * * @return string The sanitized HTML. */ public function smart_strip_tags( $html ) { if ( ! $this->is_non_empty_string_or_stringable( $html ) ) { if ( \is_int( $html ) || \is_float( $html ) ) { return (string) $html; } return ''; } // Replace all new lines with spaces. $html = \preg_replace( '/(\r|\n)/', ' ', $html ); // Replace <br> tags with spaces. $html = \preg_replace( '/<br(\s*)?\/?>/i', ' ', $html ); // Replace closing </p> and other tags with the same tag with a space after it, so we don't end up connecting words when we remove them later. $html = \preg_replace( '/<\/(p|div|h\d)>/i', '</$1> ', $html ); // Replace list items with list identifiers so it still looks natural. $html = \preg_replace( '/(<li[^>]*>)/i', '$1• ', $html ); // Strip tags. $html = \wp_strip_all_tags( $html ); // Replace multiple spaces with one space. $html = \preg_replace( '!\s+!', ' ', $html ); return \trim( $html ); } /** * Verifies that the received input is either a string or stringable object. * * @param string $html The original HTML. * * @return bool */ private function is_non_empty_string_or_stringable( $html ) { return ( \is_string( $html ) || ( \is_object( $html ) && \method_exists( $html, '__toString' ) ) ) && ! empty( $html ); } } helpers/schema/image-helper.php 0000644 00000014022 15025657560 0012530 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Schema; use Yoast\WP\SEO\Helpers\Image_Helper as Main_Image_Helper; /** * Class Image_Helper. */ class Image_Helper { /** * The HTML helper. * * @var HTML_Helper */ private $html; /** * The language helper. * * @var Language_Helper */ private $language; /** * Represents the main image helper. * * @var Main_Image_Helper */ private $image; /** * Image_Helper constructor. * * @codeCoverageIgnore It handles dependencies. * * @param HTML_Helper $html The HTML helper. * @param Language_Helper $language The language helper. * @param Main_Image_Helper $image The 'main' image helper. */ public function __construct( HTML_Helper $html, Language_Helper $language, Main_Image_Helper $image ) { $this->html = $html; $this->language = $language; $this->image = $image; } /** * Find an image based on its URL and generate a Schema object for it. * * @param string $schema_id The `@id` to use for the returned image. * @param string $url The image URL to base our object on. * @param string $caption An optional caption. * @param bool $add_hash Whether a hash will be added as a suffix in the @id. * @param bool $use_link_table Whether the SEO Links table will be used to retrieve the id. * * @return array Schema ImageObject array. */ public function generate_from_url( $schema_id, $url, $caption = '', $add_hash = false, $use_link_table = true ) { $attachment_id = $this->image->get_attachment_by_url( $url, $use_link_table ); if ( $attachment_id > 0 ) { return $this->generate_from_attachment_id( $schema_id, $attachment_id, $caption, $add_hash ); } return $this->simple_image_object( $schema_id, $url, $caption, $add_hash ); } /** * Retrieve data about an image from the database and use it to generate a Schema object. * * @param string $schema_id The `@id` to use for the returned image. * @param int $attachment_id The attachment to retrieve data from. * @param string $caption The caption string, if there is one. * @param bool $add_hash Whether a hash will be added as a suffix in the @id. * * @return array Schema ImageObject array. */ public function generate_from_attachment_id( $schema_id, $attachment_id, $caption = '', $add_hash = false ) { $data = $this->generate_object(); $url = $this->image->get_attachment_image_url( $attachment_id, 'full' ); $id_suffix = ( $add_hash ) ? \md5( $url ) : ''; $data['@id'] = $schema_id . $id_suffix; $data['url'] = $url; $data['contentUrl'] = $url; $data = $this->add_image_size( $data, $attachment_id ); $data = $this->add_caption( $data, $attachment_id, $caption ); return $data; } /** * Retrieve data about an image from the database and use it to generate a Schema object. * * @param string $schema_id The `@id` to use for the returned image. * @param array $attachment_meta The attachment metadata. * @param string $caption The caption string, if there is one. * @param bool $add_hash Whether a hash will be added as a suffix in the @id. * * @return array Schema ImageObject array. */ public function generate_from_attachment_meta( $schema_id, $attachment_meta, $caption = '', $add_hash = false ) { $data = $this->generate_object(); $id_suffix = ( $add_hash ) ? \md5( $attachment_meta['url'] ) : ''; $data['@id'] = $schema_id . $id_suffix; $data['url'] = $attachment_meta['url']; $data['contentUrl'] = $data['url']; $data['width'] = $attachment_meta['width']; $data['height'] = $attachment_meta['height']; if ( ! empty( $caption ) ) { $data['caption'] = $this->html->smart_strip_tags( $caption ); } return $data; } /** * If we can't find $url in our database, we output a simple ImageObject. * * @param string $schema_id The `@id` to use for the returned image. * @param string $url The image URL. * @param string $caption A caption, if set. * @param bool $add_hash Whether a hash will be added as a suffix in the @id. * * @return array Schema ImageObject array. */ public function simple_image_object( $schema_id, $url, $caption = '', $add_hash = false ) { $data = $this->generate_object(); $id_suffix = ( $add_hash ) ? \md5( $url ) : ''; $data['@id'] = $schema_id . $id_suffix; $data['url'] = $url; $data['contentUrl'] = $url; if ( ! empty( $caption ) ) { $data['caption'] = $this->html->smart_strip_tags( $caption ); } return $data; } /** * Retrieves an image's caption if set, or uses the alt tag if that's set. * * @param array $data An ImageObject Schema array. * @param int $attachment_id Attachment ID. * @param string $caption The caption string, if there is one. * * @return array An imageObject with width and height set if available. */ private function add_caption( $data, $attachment_id, $caption = '' ) { if ( $caption !== '' ) { $data['caption'] = $caption; return $data; } $caption = $this->image->get_caption( $attachment_id ); if ( ! empty( $caption ) ) { $data['caption'] = $this->html->smart_strip_tags( $caption ); return $data; } return $data; } /** * Generates our bare bone ImageObject. * * @return array an empty ImageObject */ private function generate_object() { $data = [ '@type' => 'ImageObject', ]; $data = $this->language->add_piece_language( $data ); return $data; } /** * Adds image's width and height. * * @param array $data An ImageObject Schema array. * @param int $attachment_id Attachment ID. * * @return array An imageObject with width and height set if available. */ private function add_image_size( $data, $attachment_id ) { $image_meta = $this->image->get_metadata( $attachment_id ); if ( empty( $image_meta['width'] ) || empty( $image_meta['height'] ) ) { return $data; } $data['width'] = $image_meta['width']; $data['height'] = $image_meta['height']; return $data; } } helpers/language-helper.php 0000644 00000005234 15025657560 0011776 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WPSEO_Language_Utils; use Yoast\WP\SEO\Config\Researcher_Languages; /** * A helper object for language features. */ class Language_Helper { /** * The languages with inclusive language analysis support. * * @var string[] */ public static $languages_with_inclusive_language_support = [ 'en' ]; /** * Checks whether word form recognition is active for the used language. * * @param string $language The used language. * * @return bool Whether word form recognition is active for the used language. */ public function is_word_form_recognition_active( $language ) { $supported_languages = [ 'de', 'en', 'es', 'fr', 'it', 'nl', 'ru', 'id', 'pt', 'pl', 'ar', 'sv', 'he', 'hu', 'nb', 'tr', 'cs', 'sk', 'el', 'ja' ]; return \in_array( $language, $supported_languages, true ); } /** * Checks whether the given language has function word support. * (E.g. function words are used or filtered out for this language when doing some SEO and readability assessments). * * @param string $language The language to check. * * @return bool Whether the language has function word support. */ public function has_function_word_support( $language ) { $supported_languages = [ 'en', 'de', 'nl', 'fr', 'es', 'it', 'pt', 'ru', 'pl', 'sv', 'id', 'he', 'ar', 'hu', 'nb', 'tr', 'cs', 'sk', 'fa', 'el', 'ja' ]; return \in_array( $language, $supported_languages, true ); } /** * Checks whether the given language has inclusive language support. * * @param string $language The language to check if inclusive language is supported. * * @return bool Whether the language has inclusive language support. */ public function has_inclusive_language_support( $language ) { return \in_array( $language, self::$languages_with_inclusive_language_support, true ); } /** * Checks whether we have a specific researcher for the current locale and returns that language. * If there is no researcher for the current locale, returns default as the researcher. * * @return string The language to use to select a researcher. */ public function get_researcher_language() { $researcher_language = WPSEO_Language_Utils::get_language( \get_locale() ); $supported_languages = Researcher_Languages::SUPPORTED_LANGUAGES; if ( ! \in_array( $researcher_language, $supported_languages, true ) ) { $researcher_language = 'default'; } return $researcher_language; } /** * Returns The site language code without region * (e.g. 'en' for 'en_US' or 'en_GB'). * * @return string The site language code without region. */ public function get_language() { return WPSEO_Language_Utils::get_language( \get_locale() ); } } helpers/url-helper.php 0000644 00000020245 15025657560 0011014 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Models\SEO_Links; /** * A helper object for URLs. */ class Url_Helper { /** * Retrieve home URL with proper trailing slash. * * @param string $path Path relative to home URL. * @param string|null $scheme Scheme to apply. * * @return string Home URL with optional path, appropriately slashed if not. */ public function home( $path = '', $scheme = null ) { $home_url = \home_url( $path, $scheme ); if ( ! empty( $path ) ) { return $home_url; } $home_path = \wp_parse_url( $home_url, \PHP_URL_PATH ); if ( $home_path === '/' ) { // Home at site root, already slashed. return $home_url; } if ( $home_path === null ) { // Home at site root, always slash. return \trailingslashit( $home_url ); } if ( \is_string( $home_path ) ) { // Home in subdirectory, slash if permalink structure has slash. return \user_trailingslashit( $home_url ); } return $home_url; } /** * Determines whether the plugin is active for the entire network. * * @return bool Whether or not the plugin is network-active. */ public function is_plugin_network_active() { static $network_active = null; if ( ! \is_multisite() ) { return false; } // If a cached result is available, bail early. if ( $network_active !== null ) { return $network_active; } $network_active_plugins = \wp_get_active_network_plugins(); // Consider MU plugins and network-activated plugins as network-active. $network_active = \strpos( \wp_normalize_path( \WPSEO_FILE ), \wp_normalize_path( \WPMU_PLUGIN_DIR ) ) === 0 || \in_array( \WP_PLUGIN_DIR . '/' . \WPSEO_BASENAME, $network_active_plugins, true ); return $network_active; } /** * Retrieve network home URL if plugin is network-activated, or home url otherwise. * * @return string Home URL with optional path, appropriately slashed if not. */ public function network_safe_home_url() { /** * Action: 'wpseo_home_url' - Allows overriding of the home URL. */ \do_action( 'wpseo_home_url' ); // If the plugin is network-activated, use the network home URL. if ( self::is_plugin_network_active() ) { return \network_home_url(); } return \home_url(); } /** * Check whether a url is relative. * * @param string $url URL string to check. * * @return bool True when url is relative. */ public function is_relative( $url ) { return ( \strpos( $url, 'http' ) !== 0 && \strpos( $url, '//' ) !== 0 ); } /** * Gets the path from the passed URL. * * @param string $url The URL to get the path from. * * @return string The path of the URL. Returns an empty string if URL parsing fails. */ public function get_url_path( $url ) { if ( \is_string( $url ) === false && ( \is_object( $url ) === false || \method_exists( $url, '__toString' ) === false ) ) { return ''; } return (string) \wp_parse_url( $url, \PHP_URL_PATH ); } /** * Gets the host from the passed URL. * * @param string $url The URL to get the host from. * * @return string The host of the URL. Returns an empty string if URL parsing fails. */ public function get_url_host( $url ) { if ( \is_string( $url ) === false && ( \is_object( $url ) === false || \method_exists( $url, '__toString' ) === false ) ) { return ''; } return (string) \wp_parse_url( $url, \PHP_URL_HOST ); } /** * Determines the file extension of the given url. * * @param string $url The URL. * * @return string The extension. */ public function get_extension_from_url( $url ) { $path = $this->get_url_path( $url ); if ( $path === '' ) { return ''; } $parts = \explode( '.', $path ); if ( empty( $parts ) || \count( $parts ) === 1 ) { return ''; } return \end( $parts ); } /** * Ensures that the given url is an absolute url. * * @param string $url The url that needs to be absolute. * * @return string The absolute url. */ public function ensure_absolute_url( $url ) { if ( ! \is_string( $url ) || $url === '' ) { return $url; } if ( $this->is_relative( $url ) === true ) { return $this->build_absolute_url( $url ); } return $url; } /** * Parse the home URL setting to find the base URL for relative URLs. * * @param string|null $path Optional path string. * * @return string */ public function build_absolute_url( $path = null ) { $path = \wp_parse_url( $path, \PHP_URL_PATH ); $url_parts = \wp_parse_url( \home_url() ); $base_url = \trailingslashit( $url_parts['scheme'] . '://' . $url_parts['host'] ); if ( \is_string( $path ) ) { $base_url .= \ltrim( $path, '/' ); } return $base_url; } /** * Returns the link type. * * @param array $url The URL, as parsed by wp_parse_url. * @param array|null $home_url Optional. The home URL, as parsed by wp_parse_url. Used to avoid reparsing the home_url. * @param bool $is_image Whether or not the link is an image. * * @return string The link type. */ public function get_link_type( $url, $home_url = null, $is_image = false ) { // If there is no scheme and no host the link is always internal. // Beware, checking just the scheme isn't enough as a link can be //yoast.com for instance. if ( empty( $url['scheme'] ) && empty( $url['host'] ) ) { return ( $is_image ) ? SEO_Links::TYPE_INTERNAL_IMAGE : SEO_Links::TYPE_INTERNAL; } // If there is a scheme but it's not http(s) then the link is always external. if ( \array_key_exists( 'scheme', $url ) && ! \in_array( $url['scheme'], [ 'http', 'https' ], true ) ) { return ( $is_image ) ? SEO_Links::TYPE_EXTERNAL_IMAGE : SEO_Links::TYPE_EXTERNAL; } if ( $home_url === null ) { $home_url = \wp_parse_url( \home_url() ); } // When the base host is equal to the host. if ( isset( $url['host'] ) && $url['host'] !== $home_url['host'] ) { return ( $is_image ) ? SEO_Links::TYPE_EXTERNAL_IMAGE : SEO_Links::TYPE_EXTERNAL; } // There is no base path and thus all URLs of the same domain are internal. if ( empty( $home_url['path'] ) ) { return ( $is_image ) ? SEO_Links::TYPE_INTERNAL_IMAGE : SEO_Links::TYPE_INTERNAL; } // When there is a path and it matches the start of the url. if ( isset( $url['path'] ) && \strpos( $url['path'], $home_url['path'] ) === 0 ) { return ( $is_image ) ? SEO_Links::TYPE_INTERNAL_IMAGE : SEO_Links::TYPE_INTERNAL; } return ( $is_image ) ? SEO_Links::TYPE_EXTERNAL_IMAGE : SEO_Links::TYPE_EXTERNAL; } /** * Recreate current URL. * * @param bool $with_request_uri Whether we want the REQUEST_URI appended. * * @return string */ public function recreate_current_url( $with_request_uri = true ) { $current_url = 'http'; if ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ) { $current_url .= 's'; } $current_url .= '://'; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary. $suffix = ( $with_request_uri && isset( $_SERVER['REQUEST_URI'] ) ) ? $_SERVER['REQUEST_URI'] : ''; if ( isset( $_SERVER['SERVER_NAME'] ) && ! empty( $_SERVER['SERVER_NAME'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary. $server_name = $_SERVER['SERVER_NAME']; } else { // Early return with just the path. return $suffix; } $server_port = ''; if ( isset( $_SERVER['SERVER_PORT'] ) && $_SERVER['SERVER_PORT'] !== '80' && $_SERVER['SERVER_PORT'] !== '443' ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary. $server_port = $_SERVER['SERVER_PORT']; } if ( ! empty( $server_port ) ) { $current_url .= $server_name . ':' . $server_port . $suffix; } else { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- We know this is scary. $current_url .= $server_name . $suffix; } return $current_url; } /** * Parses a URL and returns its components, this wrapper function was created to support unit tests. * * @param string $parsed_url The URL to parse. * @return array The parsed components of the URL. */ public function parse_str_params( $parsed_url ) { $array = []; // @todo parse_str changes spaces in param names into `_`, we should find a better way to support them. \wp_parse_str( $parsed_url, $array ); return $array; } } helpers/woocommerce-helper.php 0000644 00000002440 15025657560 0012526 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * Represents helper methods for WooCommerce. */ class Woocommerce_Helper { /** * Checks if WooCommerce is active. * * @return bool Is WooCommerce active. */ public function is_active() { return \class_exists( 'WooCommerce' ); } /** * Returns the id of the set WooCommerce shop page. * * @return int The ID of the set page. */ public function get_shop_page_id() { if ( ! \function_exists( 'wc_get_page_id' ) ) { return -1; } return \wc_get_page_id( 'shop' ); } /** * Checks if the current page is a WooCommerce shop page. * * @return bool True when the page is a shop page. */ public function is_shop_page() { if ( ! \function_exists( 'is_shop' ) ) { return false; } if ( ! \is_shop() ) { return false; } if ( \is_search() ) { return false; } return true; } /** * Checks if the current page is a WooCommerce shop page. * * @return bool True when the page is a shop page. */ public function current_post_is_terms_and_conditions_page() { if ( ! \function_exists( 'wc_terms_and_conditions_page_id' ) ) { return false; } global $post; if ( ! isset( $post->ID ) ) { return false; } return \intval( $post->ID ) === \intval( \wc_terms_and_conditions_page_id() ); } } helpers/environment-helper.php 0000644 00000001431 15025657560 0012552 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for site environment. */ class Environment_Helper { /** * Determines if the site is running on production. * * @return bool True if WordPress is currently running on production, false for all other environments. */ public function is_production_mode() { // phpcs:ignore Yoast.NamingConventions.ValidHookName.WrongPrefix -- existing hook. $override = \apply_filters( 'yoast_seo_development_mode', false ); if ( $override ) { return true; } return $this->get_wp_environment() === 'production'; } /** * Determines on which environment WordPress is running. * * @return string The current WordPress environment. */ public function get_wp_environment() { return \wp_get_environment_type(); } } helpers/asset-helper.php 0000644 00000005014 15025657560 0011326 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for author archives. */ class Asset_Helper { /** * Recursively retrieves all dependency urls of a given handle. * * @param string $handle The handle. * * @return string[] All dependency urls of the given handle. */ public function get_dependency_urls_by_handle( $handle ) { $urls = []; foreach ( $this->get_dependency_handles( $handle ) as $other_handle ) { $urls[ $other_handle ] = $this->get_asset_url( $other_handle ); } return $urls; } /** * Recursively retrieves all dependencies of a given handle. * * @param string $handle The handle. * * @return string[] All dependencies of the given handle. */ public function get_dependency_handles( $handle ) { $scripts = \wp_scripts(); if ( ! isset( $scripts->registered[ $handle ] ) ) { return []; } $obj = $scripts->registered[ $handle ]; $deps = $obj->deps; foreach ( $obj->deps as $other_handle ) { $nested_deps = $this->get_dependency_handles( $other_handle ); if ( ! $nested_deps ) { continue; } // Place nested dependencies before primary dependencies, they need to be loaded first. $deps = \array_merge( $nested_deps, $deps ); } // Array unique keeps the first of each element so dependencies will be as early as they're required. return \array_values( \array_unique( $deps ) ); } /** * Gets the URL of a given asset. * * This logic is copied from WP_Scripts::do_item as unfortunately that logic is not properly isolated. * * @param string $handle The handle of the asset. * * @return string|false The URL of the asset or false if the asset does not exist. */ public function get_asset_url( $handle ) { $scripts = \wp_scripts(); if ( ! isset( $scripts->registered[ $handle ] ) ) { return false; } $obj = $scripts->registered[ $handle ]; if ( $obj->ver === null ) { $ver = ''; } else { $ver = ( $obj->ver ) ? $obj->ver : $scripts->default_version; } if ( isset( $scripts->args[ $handle ] ) ) { $ver = ( $ver ) ? $ver . '&' . $scripts->args[ $handle ] : $scripts->args[ $handle ]; } $src = $obj->src; if ( ! \preg_match( '|^(https?:)?//|', $src ) && ! ( $scripts->content_url && \strpos( $src, $scripts->content_url ) === 0 ) ) { $src = $scripts->base_url . $src; } if ( ! empty( $ver ) ) { $src = \add_query_arg( 'ver', $ver, $src ); } /** This filter is documented in wp-includes/class.wp-scripts.php */ return \esc_url( \apply_filters( 'script_loader_src', $src, $handle ) ); } } helpers/user-helper.php 0000644 00000007672 15025657560 0011201 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for the user. */ class User_Helper { /** * Retrieves user meta field for a user. * * @param int $user_id User ID. * @param string $key Optional. The meta key to retrieve. By default, returns data for all keys. * @param bool $single Whether to return a single value. * * @return mixed Will be an array if $single is false. Will be value of meta data field if $single is true. */ public function get_meta( $user_id, $key = '', $single = false ) { return \get_user_meta( $user_id, $key, $single ); } /** * Counts the number of posts the user has written in this post type. * * @param int $user_id User ID. * @param array|string $post_type Optional. Single post type or array of post types to count the number of posts * for. Default 'post'. * * @return int The number of posts the user has written in this post type. */ public function count_posts( $user_id, $post_type = 'post' ) { return (int) \count_user_posts( $user_id, $post_type, true ); } /** * Retrieves the requested data of the author. * * @param string $field The user field to retrieve. * @param int|false $user_id User ID. * * @return string The author's field from the current author's DB object. */ public function get_the_author_meta( $field, $user_id ) { return \get_the_author_meta( $field, $user_id ); } /** * Retrieves the archive url of the user. * * @param int|false $user_id User ID. * * @return string The author's archive url. */ public function get_the_author_posts_url( $user_id ) { return \get_author_posts_url( $user_id ); } /** * Retrieves the current user ID. * * @return int The current user's ID, or 0 if no user is logged in. */ public function get_current_user_id() { return \get_current_user_id(); } /** * Returns the current users display_name. * * @return string */ public function get_current_user_display_name(): string { $user = \wp_get_current_user(); if ( $user && $user->display_name ) { return $user->display_name; } return ''; } /** * Updates user meta field for a user. * * Use the $prev_value parameter to differentiate between meta fields with the * same key and user ID. * * If the meta field for the user does not exist, it will be added. * * @param int $user_id User ID. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param mixed $prev_value Optional. Previous value to check before updating. * If specified, only update existing metadata entries with * this value. Otherwise, update all entries. Default empty. * * @return int|bool Meta ID if the key didn't exist, true on successful update, * false on failure or if the value passed to the function * is the same as the one that is already in the database. */ public function update_meta( $user_id, $meta_key, $meta_value, $prev_value = '' ) { return \update_user_meta( $user_id, $meta_key, $meta_value, $prev_value ); } /** * Removes metadata matching criteria from a user. * * You can match based on the key, or key and value. Removing based on key and * value, will keep from removing duplicate metadata with the same key. It also * allows removing all metadata matching key, if needed. * * @param int $user_id User ID. * @param string $meta_key Metadata name. * @param mixed $meta_value Optional. Metadata value. If provided, * rows will only be removed that match the value. * Must be serializable if non-scalar. Default empty. * * @return bool True on success, false on failure. */ public function delete_meta( $user_id, $meta_key, $meta_value = '' ) { return \delete_user_meta( $user_id, $meta_key, $meta_value ); } } helpers/notification-helper.php 0000644 00000003676 15025657560 0012711 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast_Notification; use Yoast_Notification_Center; /** * A helper object for notifications. */ class Notification_Helper { /** * Restores a notification (wrapper function). * * @codeCoverageIgnore * * @param Yoast_Notification $notification The notification to restore. * * @return bool True if restored, false otherwise. */ public function restore_notification( Yoast_Notification $notification ) { return Yoast_Notification_Center::restore_notification( $notification ); } /** * Return the notifications sorted on type and priority. (wrapper function) * * @codeCoverageIgnore * * @return Yoast_Notification[] Sorted Notifications */ public function get_sorted_notifications() { $notification_center = Yoast_Notification_Center::get(); return $notification_center->get_sorted_notifications(); } /** * Check if the user has dismissed a notification. (wrapper function) * * @codeCoverageIgnore * * @param Yoast_Notification $notification The notification to check for dismissal. * @param int|null $user_id User ID to check on. * * @return bool */ private function is_notification_dismissed( Yoast_Notification $notification, $user_id = null ) { return Yoast_Notification_Center::is_notification_dismissed( $notification, $user_id ); } /** * Parses all the notifications to an array with just id, message, nonce, type and dismissed. * * @return array<string, string|bool> */ public function get_alerts(): array { $all_notifications = $this->get_sorted_notifications(); return \array_map( function ( $notification ) { return [ 'id' => $notification->get_id(), 'message' => $notification->get_message(), 'nonce' => $notification->get_nonce(), 'type' => $notification->get_type(), 'dismissed' => $this->is_notification_dismissed( $notification ), ]; }, $all_notifications ); } } helpers/pagination-helper.php 0000644 00000013267 15025657560 0012351 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper; use Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper; /** * A helper object for pagination. * * Used for the canonical URL and the rel "next" and "prev" meta tags. */ class Pagination_Helper { /** * Holds the WP rewrite wrapper instance. * * @var WP_Rewrite_Wrapper WP_Rewrite wrapper. */ protected $wp_rewrite_wrapper; /** * Holds the WP query wrapper instance. * * @var WP_Query_Wrapper WP_Query wrapper. */ protected $wp_query_wrapper; /** * Pagination_Helper constructor. * * @param WP_Rewrite_Wrapper $wp_rewrite_wrapper The rewrite wrapper. * @param WP_Query_Wrapper $wp_query_wrapper The query wrapper. */ public function __construct( WP_Rewrite_Wrapper $wp_rewrite_wrapper, WP_Query_Wrapper $wp_query_wrapper ) { $this->wp_rewrite_wrapper = $wp_rewrite_wrapper; $this->wp_query_wrapper = $wp_query_wrapper; } /** * Checks whether adjacent rel links are disabled. * * @return bool Whether adjacent rel links are disabled or not. */ public function is_rel_adjacent_disabled() { /** * Filter: 'wpseo_disable_adjacent_rel_links' - Allows disabling of Yoast adjacent links if this is being handled by other code. * * @param bool $links_generated Indicates if other code has handled adjacent links. */ return \apply_filters( 'wpseo_disable_adjacent_rel_links', false ); } /** * Builds a paginated URL. * * @param string $url The un-paginated URL of the current archive. * @param string $page The page number to add on to $url for the $link tag. * @param bool $add_pagination_base Optional. Whether to add the pagination base (`page`) to the url. * @param string $pagination_query_name Optional. The name of the query argument that holds the current page. * * @return string The paginated URL. */ public function get_paginated_url( $url, $page, $add_pagination_base = true, $pagination_query_name = 'page' ) { $wp_rewrite = $this->wp_rewrite_wrapper->get(); $key_query_loop = $this->get_key_query_loop(); if ( $key_query_loop ) { $pagination_query_name = $key_query_loop; } if ( $wp_rewrite->using_permalinks() && ! $key_query_loop ) { $url_parts = \wp_parse_url( $url ); $has_url_params = \array_key_exists( 'query', $url_parts ); if ( $has_url_params ) { // We need to first remove the query params, before potentially adding the pagination parts. \wp_parse_str( $url_parts['query'], $query_parts ); $url = \trailingslashit( \remove_query_arg( \array_keys( $query_parts ), $url ) ); if ( $add_pagination_base ) { $url .= \trailingslashit( $wp_rewrite->pagination_base ); } // We can now re-add the query params, after appending the last pagination parts. return \add_query_arg( $query_parts, \user_trailingslashit( $url . $page ) ); } $url = \trailingslashit( $url ); if ( $add_pagination_base ) { $url .= \trailingslashit( $wp_rewrite->pagination_base ); } return \user_trailingslashit( $url . $page ); } return \add_query_arg( $pagination_query_name, $page, \user_trailingslashit( $url ) ); } /** * Gets the number of archive pages. * * @return int The number of archive pages. */ public function get_number_of_archive_pages() { $wp_query = $this->wp_query_wrapper->get_query(); return (int) $wp_query->max_num_pages; } /** * Returns the current page for paged archives. * * @return int The current archive page. */ public function get_current_archive_page_number() { $wp_query = $this->wp_query_wrapper->get_main_query(); $page_number = (int) $wp_query->get( 'paged' ); if ( $page_number > 1 ) { return $page_number; } $query_loop_page_number = $this->get_page_number_from_query_loop(); if ( $query_loop_page_number ) { return $query_loop_page_number; } return 0; } /** * Returns the current page for paged post types. * * @return int The current post page. */ public function get_current_post_page_number() { $wp_query = $this->wp_query_wrapper->get_main_query(); $query_loop_page_number = $this->get_page_number_from_query_loop(); if ( $query_loop_page_number ) { return $query_loop_page_number; } return (int) $wp_query->get( 'page' ); } /** * Returns the current page number. * * @return int The current page number. */ public function get_current_page_number() { // Get the page number for an archive page. $page_number = \get_query_var( 'paged', 1 ); if ( $page_number > 1 ) { return $page_number; } $query_loop_page_number = $this->get_page_number_from_query_loop(); if ( $query_loop_page_number ) { return $query_loop_page_number; } // Get the page number for a page in a paginated post. return \get_query_var( 'page', 1 ); } /** * Returns the key of the query loop. * * @return string The key of the query loop. */ public function get_key_query_loop() { $regex_pattern = '/^query-\d+-page$/'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- not form data. foreach ( $_GET as $key => $value ) { if ( \preg_match( $regex_pattern, $key ) ) { return $key; } } return ''; } /** * Returns the page number from the query loop. * * @return string The page number from the query loop. */ public function get_page_number_from_query_loop() { $key_query_loop = $this->get_key_query_loop(); if ( $key_query_loop ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotValidated,WordPress.Security.NonceVerification.Recommended -- Validated in get_key_query_loop(). $page_number = (int) $_GET[ $key_query_loop ]; if ( $page_number > 1 ) { return $page_number; } } return ''; } } helpers/attachment-cleanup-helper.php 0000644 00000004366 15025657560 0013775 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\Lib\Model; /** * A helper object for the cleanup of attachments. */ class Attachment_Cleanup_Helper { /** * Removes all indexables for attachments. * * @param bool $suppress_errors Whether to suppress db errors when running the cleanup query. * * @return void */ public function remove_attachment_indexables( $suppress_errors ) { global $wpdb; if ( $suppress_errors ) { // If migrations haven't been completed successfully the following may give false errors. So suppress them. $show_errors = $wpdb->show_errors; $wpdb->show_errors = false; } $indexable_table = Model::get_table_name( 'Indexable' ); $delete_query = "DELETE FROM $indexable_table WHERE object_type = 'post' AND object_sub_type = 'attachment'"; // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. $wpdb->query( $delete_query ); // phpcs:enable if ( $suppress_errors ) { $wpdb->show_errors = $show_errors; } } /** * Cleans all attachment links in the links table from target indexable ids. * * @param bool $suppress_errors Whether to suppress db errors when running the cleanup query. * * @return void */ public function clean_attachment_links_from_target_indexable_ids( $suppress_errors ) { global $wpdb; if ( $suppress_errors ) { // If migrations haven't been completed successfully the following may give false errors. So suppress them. $show_errors = $wpdb->show_errors; $wpdb->show_errors = false; } $links_table = Model::get_table_name( 'SEO_Links' ); $query = "UPDATE $links_table SET target_indexable_id = NULL WHERE type = 'image-in'"; // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. $wpdb->query( $query ); // phpcs:enable if ( $suppress_errors ) { $wpdb->show_errors = $show_errors; } } } helpers/blocks-helper.php 0000644 00000004512 15025657560 0011466 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WP_Block_Parser_Block; /** * A helper object for blocks. */ class Blocks_Helper { /** * Holds the Post_Helper instance. * * @var Post_Helper */ private $post; /** * Constructs a Blocks_Helper instance. * * @codeCoverageIgnore It handles dependencies. * * @param Post_Helper $post The post helper. */ public function __construct( Post_Helper $post ) { $this->post = $post; } /** * Returns all blocks in a given post. * * @param int $post_id The post id. * * @return array The blocks in a block-type => WP_Block_Parser_Block[] format. */ public function get_all_blocks_from_post( $post_id ) { if ( ! $this->has_blocks_support() ) { return []; } $post = $this->post->get_post( $post_id ); return $this->get_all_blocks_from_content( $post->post_content ); } /** * Returns all blocks in a given content. * * @param string $content The content. * * @return array The blocks in a block-type => WP_Block_Parser_Block[] format. */ public function get_all_blocks_from_content( $content ) { if ( ! $this->has_blocks_support() ) { return []; } $collection = []; $blocks = \parse_blocks( $content ); return $this->collect_blocks( $blocks, $collection ); } /** * Checks if the installation has blocks support. * * @codeCoverageIgnore It only checks if a WordPress function exists. * * @return bool True when function parse_blocks exists. */ protected function has_blocks_support() { return \function_exists( 'parse_blocks' ); } /** * Collects an array of blocks into an organised collection. * * @param WP_Block_Parser_Block[] $blocks The blocks. * @param array $collection The collection. * * @return array The blocks in a block-type => WP_Block_Parser_Block[] format. */ private function collect_blocks( $blocks, $collection ) { foreach ( $blocks as $block ) { if ( empty( $block['blockName'] ) ) { continue; } if ( ! isset( $collection[ $block['blockName'] ] ) || ! \is_array( $collection[ $block['blockName'] ] ) ) { $collection[ $block['blockName'] ] = []; } $collection[ $block['blockName'] ][] = $block; if ( isset( $block['innerBlocks'] ) ) { $collection = $this->collect_blocks( $block['innerBlocks'], $collection ); } } return $collection; } } helpers/indexable-to-postmeta-helper.php 0000644 00000015401 15025657560 0014415 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Models\Indexable; /** * A helper object to map indexable data to postmeta. */ class Indexable_To_Postmeta_Helper { /** * The Meta helper. * * @var Meta_Helper */ public $meta; /** * The map of yoast to post meta. * * @var array */ protected $yoast_to_postmeta = [ 'title' => [ 'post_meta_key' => 'title', 'map_method' => 'simple_map', ], 'description' => [ 'post_meta_key' => 'metadesc', 'map_method' => 'simple_map', ], 'open_graph_title' => [ 'post_meta_key' => 'opengraph-title', 'map_method' => 'simple_map', ], 'open_graph_description' => [ 'post_meta_key' => 'opengraph-description', 'map_method' => 'simple_map', ], 'twitter_title' => [ 'post_meta_key' => 'twitter-title', 'map_method' => 'simple_map', ], 'twitter_description' => [ 'post_meta_key' => 'twitter-description', 'map_method' => 'simple_map', ], 'canonical' => [ 'post_meta_key' => 'canonical', 'map_method' => 'simple_map', ], 'primary_focus_keyword' => [ 'post_meta_key' => 'focuskw', 'map_method' => 'simple_map', ], 'open_graph_image' => [ 'post_meta_key' => 'opengraph-image', 'map_method' => 'social_image_map', ], 'open_graph_image_id' => [ 'post_meta_key' => 'opengraph-image-id', 'map_method' => 'social_image_map', ], 'twitter_image' => [ 'post_meta_key' => 'twitter-image', 'map_method' => 'social_image_map', ], 'twitter_image_id' => [ 'post_meta_key' => 'twitter-image-id', 'map_method' => 'social_image_map', ], 'is_robots_noindex' => [ 'post_meta_key' => 'meta-robots-noindex', 'map_method' => 'noindex_map', ], 'is_robots_nofollow' => [ 'post_meta_key' => 'meta-robots-nofollow', 'map_method' => 'nofollow_map', ], 'meta_robots_adv' => [ 'post_meta_key' => 'meta-robots-adv', 'map_method' => 'robots_adv_map', ], ]; /** * Indexable_To_Postmeta_Helper constructor. * * @param Meta_Helper $meta The Meta helper. */ public function __construct( Meta_Helper $meta ) { $this->meta = $meta; } /** * Creates postmeta from a Yoast indexable. * * @param Indexable $indexable The Yoast indexable. * * @return void */ public function map_to_postmeta( $indexable ) { foreach ( $this->yoast_to_postmeta as $indexable_column => $map_info ) { \call_user_func( [ $this, $map_info['map_method'] ], $indexable, $map_info['post_meta_key'], $indexable_column ); } } /** * Uses a simple set_value for non-empty data. * * @param Indexable $indexable The Yoast indexable. * @param string $post_meta_key The post_meta key that will be populated. * @param string $indexable_column The indexable data that will be mapped to post_meta. * * @return void */ public function simple_map( $indexable, $post_meta_key, $indexable_column ) { if ( empty( $indexable->{$indexable_column} ) ) { return; } $this->meta->set_value( $post_meta_key, $indexable->{$indexable_column}, $indexable->object_id ); } /** * Map social image data only if social image is explicitly set. * * @param Indexable $indexable The Yoast indexable. * @param string $post_meta_key The post_meta key that will be populated. * @param string $indexable_column The indexable data that will be mapped to post_meta. * * @return void */ public function social_image_map( $indexable, $post_meta_key, $indexable_column ) { if ( empty( $indexable->{$indexable_column} ) ) { return; } switch ( $indexable_column ) { case 'open_graph_image': case 'open_graph_image_id': $source = $indexable->open_graph_image_source; break; case 'twitter_image': case 'twitter_image_id': $source = $indexable->twitter_image_source; break; } // Map the social image data only when the social image is explicitly set. if ( $source === 'set-by-user' || $source === 'imported' ) { $value = (string) $indexable->{$indexable_column}; $this->meta->set_value( $post_meta_key, $value, $indexable->object_id ); } } /** * Deletes the noindex post_meta key if no noindex in the indexable. Populates the post_meta key appropriately if there is noindex in the indexable. * * @param Indexable $indexable The Yoast indexable. * @param string $post_meta_key The post_meta key that will be populated. * * @return void */ public function noindex_map( $indexable, $post_meta_key ) { if ( $indexable->is_robots_noindex === null ) { $this->meta->delete( $post_meta_key, $indexable->object_id ); return; } if ( $indexable->is_robots_noindex === false ) { $this->meta->set_value( $post_meta_key, 2, $indexable->object_id ); } if ( $indexable->is_robots_noindex === true ) { $this->meta->set_value( $post_meta_key, 1, $indexable->object_id ); } } /** * Deletes the nofollow post_meta key if no nofollow in the indexable or if nofollow is false. Populates the post_meta key appropriately if there is a true nofollow in the indexable. * * @param Indexable $indexable The Yoast indexable. * @param string $post_meta_key The post_meta key that will be populated. * * @return void */ public function nofollow_map( $indexable, $post_meta_key ) { if ( $indexable->is_robots_nofollow === null || $indexable->is_robots_nofollow === false ) { $this->meta->delete( $post_meta_key, $indexable->object_id ); } if ( $indexable->is_robots_nofollow === true ) { $this->meta->set_value( $post_meta_key, 1, $indexable->object_id ); } } /** * Deletes the nofollow post_meta key if no nofollow in the indexable or if nofollow is false. Populates the post_meta key appropriately if there is a true nofollow in the indexable. * * @param Indexable $indexable The Yoast indexable. * @param string $post_meta_key The post_meta key that will be populated. * * @return void */ public function robots_adv_map( $indexable, $post_meta_key ) { $adv_settings_to_be_imported = []; $no_adv_settings = true; if ( $indexable->is_robots_noimageindex === true ) { $adv_settings_to_be_imported[] = 'noimageindex'; $no_adv_settings = false; } if ( $indexable->is_robots_noarchive === true ) { $adv_settings_to_be_imported[] = 'noarchive'; $no_adv_settings = false; } if ( $indexable->is_robots_nosnippet === true ) { $adv_settings_to_be_imported[] = 'nosnippet'; $no_adv_settings = false; } if ( $no_adv_settings === true ) { $this->meta->delete( $post_meta_key, $indexable->object_id ); return; } $this->meta->set_value( $post_meta_key, \implode( ',', $adv_settings_to_be_imported ), $indexable->object_id ); } } helpers/require-file-helper.php 0000644 00000000506 15025657560 0012601 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * Represents a file helper. */ class Require_File_Helper { /** * Activates the plugin based on the given plugin file. * * @param string $path The path to the required file. * * @return void */ public function require_file_once( $path ) { require_once $path; } } helpers/product-helper.php 0000644 00000002117 15025657560 0011670 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for the Yoast products. */ class Product_Helper { /** * Gets the product name. * * @return string */ public function get_product_name() { if ( $this->is_premium() ) { return 'Yoast SEO Premium'; } return 'Yoast SEO'; } /** * Gets the product name in the head section. * * @return string */ public function get_name() { return $this->get_product_name() . ' plugin'; } /** * Checks if the installed version is Yoast SEO Premium. * * @return bool True when is premium. */ public function is_premium() { return \defined( 'WPSEO_PREMIUM_FILE' ); } /** * Gets the Premium version if defined, returns null otherwise. * * @return string|null The Premium version or null when premium version is not defined. */ public function get_premium_version() { if ( \defined( 'WPSEO_PREMIUM_VERSION' ) ) { return \WPSEO_PREMIUM_VERSION; } return null; } /** * Gets the version. * * @return string The version. */ public function get_version() { return \WPSEO_VERSION; } } helpers/post-type-helper.php 0000644 00000016265 15025657560 0012165 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WP_Post_Type; /** * A helper object for post types. */ class Post_Type_Helper { /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * Post_Type_Helper constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Checks if the request post type is public and indexable. * * @codeCoverageIgnore We have to write test when this method contains own code. * * @param string $post_type_name The name of the post type to lookup. * * @return bool True when post type is set to index. */ public function is_indexable( $post_type_name ) { if ( $this->options_helper->get( 'disable-' . $post_type_name, false ) ) { return false; } return ( $this->options_helper->get( 'noindex-' . $post_type_name, false ) === false ); } /** * Checks if the request post type has the Yoast Metabox enabled. * * @param string $post_type_name The name of the post type to lookup. * * @return bool True if metabox is enabled. */ public function has_metabox( $post_type_name ) { return ( $this->options_helper->get( 'display-metabox-pt-' . $post_type_name, true ) === true ); } /** * Returns an array with the public post types. * * @codeCoverageIgnore It only wraps a WordPress function. * * @param string $output The output type to use. * * @return array Array with all the public post_types. */ public function get_public_post_types( $output = 'names' ) { return \get_post_types( [ 'public' => true ], $output ); } /** * Returns an array with the accessible post types. * * An accessible post type is a post type that is public and isn't set as no-index (robots). * * @return array Array with all the accessible post_types. */ public function get_accessible_post_types() { $post_types = \get_post_types( [ 'public' => true ] ); $post_types = \array_filter( $post_types, 'is_post_type_viewable' ); /** * Filter: 'wpseo_accessible_post_types' - Allow changing the accessible post types. * * @param array $post_types The public post types. */ $post_types = \apply_filters( 'wpseo_accessible_post_types', $post_types ); // When the array gets messed up somewhere. if ( ! \is_array( $post_types ) ) { return []; } return $post_types; } /** * Returns an array of post types that are excluded from being indexed for the * indexables. * * @return array The excluded post types. */ public function get_excluded_post_types_for_indexables() { /** * Filter: 'wpseo_indexable_excluded_post_types' - Allows excluding posts of a certain post * type from being saved to the indexable table. * * @param array $excluded_post_types The currently excluded post types that indexables will not be created for. */ $excluded_post_types = \apply_filters( 'wpseo_indexable_excluded_post_types', [] ); // Failsafe, to always make sure that `excluded_post_types` is an array. if ( ! \is_array( $excluded_post_types ) ) { return []; } return $excluded_post_types; } /** * Checks if the post type is excluded. * * @param string $post_type The post type to check. * * @return bool If the post type is exclude. */ public function is_excluded( $post_type ) { return \in_array( $post_type, $this->get_excluded_post_types_for_indexables(), true ); } /** * Checks if the post type with the given name has an archive page. * * @param WP_Post_Type|string $post_type The name of the post type to check. * * @return bool True when the post type has an archive page. */ public function has_archive( $post_type ) { if ( \is_string( $post_type ) ) { $post_type = \get_post_type_object( $post_type ); } return ( ! empty( $post_type->has_archive ) ); } /** * Returns the post types that should be indexed. * * @return array The post types that should be indexed. */ public function get_indexable_post_types() { $public_post_types = $this->get_public_post_types(); $excluded_post_types = $this->get_excluded_post_types_for_indexables(); $included_post_types = \array_diff( $public_post_types, $excluded_post_types ); return $this->filter_included_post_types( $included_post_types ); } /** * Returns all indexable post types with archive pages. * * @return array All post types which are indexable and have archive pages. */ public function get_indexable_post_archives() { return \array_filter( $this->get_indexable_post_type_objects(), [ $this, 'has_archive' ] ); } /** * Filters the post types that are included to be indexed. * * @param array $included_post_types The post types that are included to be indexed. * * @return array The filtered post types that are included to be indexed. */ protected function filter_included_post_types( $included_post_types ) { /** * Filter: 'wpseo_indexable_forced_included_post_types' - Allows force including posts of a certain post * type to be saved to the indexable table. * * @param array $included_post_types The currently included post types that indexables will be created for. */ $filtered_included_post_types = \apply_filters( 'wpseo_indexable_forced_included_post_types', $included_post_types ); if ( ! \is_array( $filtered_included_post_types ) ) { // If the filter got misused, let's return the unfiltered array. return \array_values( $included_post_types ); } // Add sanity check to make sure everything is an actual post type. foreach ( $filtered_included_post_types as $key => $post_type ) { if ( ! \post_type_exists( $post_type ) ) { unset( $filtered_included_post_types[ $key ] ); } } // `array_values`, to make sure that the keys are reset. return \array_values( $filtered_included_post_types ); } /** * Checks if the given post type should be indexed. * * @param string $post_type The post type that is checked. * * @return bool */ public function is_of_indexable_post_type( $post_type ) { $public_types = $this->get_indexable_post_types(); if ( ! \in_array( $post_type, $public_types, true ) ) { return false; } return true; } /** * Checks if the archive of a post type is indexable. * * @param string $post_type The post type to check. * * @return bool if the archive is indexable. */ public function is_post_type_archive_indexable( $post_type ) { $public_type_objects = $this->get_indexable_post_archives(); $public_types = \array_map( static function ( $post_type_object ) { return $post_type_object->name; }, $public_type_objects ); return \in_array( $post_type, $public_types, true ); } /** * Returns an array of complete post type objects for all indexable post types. * * @return array List of indexable post type objects. */ public function get_indexable_post_type_objects() { $post_type_objects = []; $indexable_post_types = $this->get_indexable_post_types(); foreach ( $indexable_post_types as $post_type ) { $post_type_object = \get_post_type_object( $post_type ); if ( ! empty( $post_type_object ) ) { $post_type_objects[ $post_type ] = $post_type_object; } } return $post_type_objects; } } helpers/options-helper.php 0000644 00000010030 15025657560 0011674 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WPSEO_Option_Social; use WPSEO_Option_Titles; use WPSEO_Options; /** * A helper object for options. */ class Options_Helper { /** * Retrieves a single field from any option for the SEO plugin. Keys are always unique. * * @codeCoverageIgnore We have to write test when this method contains own code. * * @param string $key The key it should return. * @param mixed $default_value The default value that should be returned if the key isn't set. * * @return mixed|null Returns value if found, $default_value if not. */ public function get( $key, $default_value = null ) { return WPSEO_Options::get( $key, $default_value ); } /** * Sets a single field to the options. * * @param string $key The key to set. * @param mixed $value The value to set. * @param string $option_group The lookup table which represents the option_group where the key is stored. * * @return mixed|null Returns value if found. */ public function set( $key, $value, $option_group = '' ) { return WPSEO_Options::set( $key, $value, $option_group ); } /** * Get a specific default value for an option. * * @param string $option_name The option for which you want to retrieve a default. * @param string $key The key within the option who's default you want. * * @return mixed The default value. */ public function get_default( $option_name, $key ) { return WPSEO_Options::get_default( $option_name, $key ); } /** * Retrieves the title separator. * * @return string The title separator. */ public function get_title_separator() { $default = $this->get_default( 'wpseo_titles', 'separator' ); // Get the titles option and the separator options. $separator = $this->get( 'separator' ); $seperator_options = $this->get_separator_options(); // This should always be set, but just to be sure. if ( isset( $seperator_options[ $separator ] ) ) { // Set the new replacement. $replacement = $seperator_options[ $separator ]; } elseif ( isset( $seperator_options[ $default ] ) ) { $replacement = $seperator_options[ $default ]; } else { $replacement = \reset( $seperator_options ); } /** * Filter: 'wpseo_replacements_filter_sep' - Allow customization of the separator character(s). * * @param string $replacement The current separator. */ return \apply_filters( 'wpseo_replacements_filter_sep', $replacement ); } /** * Retrieves a default value from the option titles. * * @param string $option_titles_key The key of the option title you wish to get. * * @return string The option title. */ public function get_title_default( $option_titles_key ) { $default_titles = $this->get_title_defaults(); if ( ! empty( $default_titles[ $option_titles_key ] ) ) { return $default_titles[ $option_titles_key ]; } return ''; } /** * Retrieves the default option titles. * * @codeCoverageIgnore We have to write test when this method contains own code. * * @return array The title defaults. */ protected function get_title_defaults() { return WPSEO_Option_Titles::get_instance()->get_defaults(); } /** * Get the available separator options. * * @return array */ protected function get_separator_options() { return WPSEO_Option_Titles::get_instance()->get_separator_options(); } /** * Checks whether a social URL is valid, with empty strings being valid social URLs. * * @param string $url The url to be checked. * * @return bool Whether the URL is valid. */ public function is_social_url_valid( $url ) { return $url === '' || WPSEO_Option_Social::get_instance()->validate_social_url( $url ); } /** * Checks whether a twitter id is valid, with empty strings being valid twitter id. * * @param string $twitter_id The twitter id to be checked. * * @return bool Whether the twitter id is valid. */ public function is_twitter_id_valid( $twitter_id ) { return empty( $twitter_id ) || WPSEO_Option_Social::get_instance()->validate_twitter_id( $twitter_id, false ); } } helpers/crawl-cleanup-helper.php 0000644 00000020145 15025657560 0012746 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * Class Crawl_Cleanup_Helper. * * Used by the Crawl_Cleanup_Permalinks class. */ class Crawl_Cleanup_Helper { /** * The current page helper * * @var Current_Page_Helper */ private $current_page_helper; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The URL helper. * * @var Url_Helper */ private $url_helper; /** * The Redirect Helper. * * @var Redirect_Helper */ private $redirect_helper; /** * Crawl Cleanup Basic integration constructor. * * @param Current_Page_Helper $current_page_helper The current page helper. * @param Options_Helper $options_helper The option helper. * @param Url_Helper $url_helper The URL helper. * @param Redirect_Helper $redirect_helper The Redirect Helper. */ public function __construct( Current_Page_Helper $current_page_helper, Options_Helper $options_helper, Url_Helper $url_helper, Redirect_Helper $redirect_helper ) { $this->current_page_helper = $current_page_helper; $this->options_helper = $options_helper; $this->url_helper = $url_helper; $this->redirect_helper = $redirect_helper; } /** * Checks if the current URL is not robots, sitemap, empty or user is logged in. * * @return bool True if the current URL is a valid URL. */ public function should_avoid_redirect() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We're not processing anything yet... if ( \is_robots() || \get_query_var( 'sitemap' ) || empty( $_GET ) || \is_user_logged_in() ) { return true; } return false; } /** * Returns the list of the allowed extra vars. * * @return array The list of the allowed extra vars. */ public function get_allowed_extravars() { $default_allowed_extravars = [ 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'gclid', 'gtm_debug', ]; /** * Filter: 'Yoast\WP\SEO\allowlist_permalink_vars' - Allows plugins to register their own variables not to clean. * * @since 19.2.0 * * @param array $allowed_extravars The list of the allowed vars (empty by default). */ $allowed_extravars = \apply_filters( 'Yoast\WP\SEO\allowlist_permalink_vars', $default_allowed_extravars ); $clean_permalinks_extra_variables = $this->options_helper->get( 'clean_permalinks_extra_variables' ); if ( $clean_permalinks_extra_variables !== '' ) { $allowed_extravars = \array_merge( $allowed_extravars, \explode( ',', $clean_permalinks_extra_variables ) ); } return $allowed_extravars; } /** * Gets the allowed query vars from the current URL. * * @param string $current_url The current URL. * @return array is_allowed and allowed_query. */ public function allowed_params( $current_url ) { // This is a Premium plugin-only function: Allows plugins to register their own variables not to clean. $allowed_extravars = $this->get_allowed_extravars(); $allowed_query = []; $parsed_url = \wp_parse_url( $current_url, \PHP_URL_QUERY ); $query = $this->url_helper->parse_str_params( $parsed_url ); if ( ! empty( $allowed_extravars ) ) { foreach ( $allowed_extravars as $get ) { $get = \trim( $get ); if ( isset( $query[ $get ] ) ) { $allowed_query[ $get ] = \rawurlencode_deep( $query[ $get ] ); unset( $query[ $get ] ); } } } return [ 'query' => $query, 'allowed_query' => $allowed_query, ]; } /** * Returns the proper URL for singular pages. * * @return string The proper URL. */ public function singular_url() { global $post; $proper_url = \get_permalink( $post->ID ); $page = \get_query_var( 'page' ); if ( $page && $page !== 1 ) { $the_post = \get_post( $post->ID ); $page_count = \substr_count( $the_post->post_content, '<!--nextpage-->' ); $proper_url = \user_trailingslashit( \trailingslashit( $proper_url ) . $page ); if ( $page > ( $page_count + 1 ) ) { $proper_url = \user_trailingslashit( \trailingslashit( $proper_url ) . ( $page_count + 1 ) ); } } // Fix reply to comment links, whoever decided this should be a GET variable? // phpcs:ignore WordPress.Security -- We know this is scary. if ( isset( $_SERVER['REQUEST_URI'] ) && \preg_match( '`(\?replytocom=[^&]+)`', \sanitize_text_field( $_SERVER['REQUEST_URI'] ), $matches ) ) { $proper_url .= \str_replace( '?replytocom=', '#comment-', $matches[0] ); } unset( $matches ); return $proper_url; } /** * Returns the proper URL for front page. * * @return string The proper URL. */ public function front_page_url() { if ( $this->current_page_helper->is_home_posts_page() ) { return \home_url( '/' ); } if ( $this->current_page_helper->is_home_static_page() ) { return \get_permalink( $GLOBALS['post']->ID ); } return ''; } /** * Returns the proper URL for 404 page. * * @param string $current_url The current URL. * @return string The proper URL. */ public function page_not_found_url( $current_url ) { if ( ! \is_multisite() || \is_subdomain_install() || ! \is_main_site() ) { return ''; } if ( $current_url !== \home_url() . '/blog/' && $current_url !== \home_url() . '/blog' ) { return ''; } if ( $this->current_page_helper->is_home_static_page() ) { return \get_permalink( \get_option( 'page_for_posts' ) ); } return \home_url(); } /** * Returns the proper URL for taxonomy page. * * @return string The proper URL. */ public function taxonomy_url() { global $wp_query; $term = $wp_query->get_queried_object(); if ( \is_feed() ) { return \get_term_feed_link( $term->term_id, $term->taxonomy ); } return \get_term_link( $term, $term->taxonomy ); } /** * Returns the proper URL for search page. * * @return string The proper URL. */ public function search_url() { $s = \get_search_query(); return \home_url() . '/?s=' . \rawurlencode( $s ); } /** * Returns the proper URL for url with page param. * * @param string $proper_url The proper URL. * @return string The proper URL. */ public function query_var_page_url( $proper_url ) { global $wp_query; if ( \is_search( $proper_url ) ) { return \home_url() . '/page/' . $wp_query->query_vars['paged'] . '/?s=' . \rawurlencode( \get_search_query() ); } return \user_trailingslashit( \trailingslashit( $proper_url ) . 'page/' . $wp_query->query_vars['paged'] ); } /** * Returns true if query is with page param. * * @param string $proper_url The proper URL. * @return bool is query with page param. */ public function is_query_var_page( $proper_url ) { global $wp_query; if ( empty( $proper_url ) || $wp_query->query_vars['paged'] === 0 || $wp_query->post_count === 0 ) { return false; } return true; } /** * Redirects clean permalink. * * @param string $proper_url The proper URL. * @return void */ public function do_clean_redirect( $proper_url ) { $this->redirect_helper->set_header( 'Content-Type: redirect', true ); $this->redirect_helper->remove_header( 'Content-Type' ); $this->redirect_helper->remove_header( 'Last-Modified' ); $this->redirect_helper->remove_header( 'X-Pingback' ); $message = \sprintf( /* translators: %1$s: Yoast SEO */ \__( '%1$s: unregistered URL parameter removed. See %2$s', 'wordpress-seo' ), 'Yoast SEO', 'https://yoa.st/advanced-crawl-settings' ); $this->redirect_helper->do_safe_redirect( $proper_url, 301, $message ); } /** * Gets the type of URL. * * @return string The type of URL. */ public function get_url_type() { if ( \is_singular() ) { return 'singular_url'; } if ( \is_front_page() ) { return 'front_page_url'; } if ( $this->current_page_helper->is_posts_page() ) { return 'page_for_posts_url'; } if ( \is_category() || \is_tag() || \is_tax() ) { return 'taxonomy_url'; } if ( \is_search() ) { return 'search_url'; } if ( \is_404() ) { return 'page_not_found_url'; } return ''; } /** * Returns the proper URL for posts page. * * @return string The proper URL. */ public function page_for_posts_url() { return \get_permalink( \get_option( 'page_for_posts' ) ); } } helpers/aioseo-helper.php 0000644 00000002362 15025657560 0011471 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use wpdb; /** * The AIOSEO Helper. */ class Aioseo_Helper { /** * The WordPress database instance. * * @var wpdb */ protected $wpdb; /** * The wpdb helper. * * @var Wpdb_Helper */ protected $wpdb_helper; /** * Class constructor. * * @param wpdb $wpdb The WordPress database instance. * @param Wpdb_Helper $wpdb_helper The wpdb helper. */ public function __construct( wpdb $wpdb, Wpdb_Helper $wpdb_helper ) { $this->wpdb = $wpdb; $this->wpdb_helper = $wpdb_helper; } /** * Retrieves the AIOSEO table name along with the db prefix. * * @return string The AIOSEO table name along with the db prefix. */ public function get_table() { return $this->wpdb->prefix . 'aioseo_posts'; } /** * Determines if the AIOSEO database table exists. * * @return bool True if the table is found. */ public function aioseo_exists() { return $this->wpdb_helper->table_exists( $this->get_table() ) === true; } /** * Retrieves the option where the global settings exist. * * @return array The option where the global settings exist. */ public function get_global_option() { return \json_decode( \get_option( 'aioseo_options', '' ), true ); } } helpers/twitter/image-helper.php 0000644 00000002312 15025657560 0012771 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Twitter; use Yoast\WP\SEO\Helpers\Image_Helper as Base_Image_Helper; /** * A helper object for Twitter images. */ class Image_Helper { /** * The base image helper. * * @var Base_Image_Helper */ private $image; /** * Image_Helper constructor. * * @codeCoverageIgnore * * @param Base_Image_Helper $image The image helper. */ public function __construct( Base_Image_Helper $image ) { $this->image = $image; } /** * The image size to use for Twitter. * * @return string Image size string. */ public function get_image_size() { /** * Filter: 'wpseo_twitter_image_size' - Allow changing the Twitter Card image size. * * @param string $featured_img Image size string. */ return (string) \apply_filters( 'wpseo_twitter_image_size', 'full' ); } /** * Retrieves an image url by its id. * * @param int $image_id The image id. * * @return string The image url. Empty string if the attachment is not valid. */ public function get_by_id( $image_id ) { if ( ! $this->image->is_valid_attachment( $image_id ) ) { return ''; } return $this->image->get_attachment_image_source( $image_id, $this->get_image_size() ); } } helpers/string-helper.php 0000644 00000002302 15025657560 0011512 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for string operations. */ class String_Helper { /** * Strips all HTML tags including script and style. * * @param string $text The text to strip the tags from. * * @return string The processed string. */ public function strip_all_tags( $text ) { return \wp_strip_all_tags( $text ); } /** * Standardize whitespace in a string. * * Replace line breaks, carriage returns, tabs with a space, then remove double spaces. * * @param string $text Text input to standardize. * * @return string */ public function standardize_whitespace( $text ) { return \trim( \str_replace( ' ', ' ', \str_replace( [ "\t", "\n", "\r", "\f" ], ' ', $text ) ) ); } /** * First strip out registered and enclosing shortcodes using native WordPress strip_shortcodes function. * Then strip out the shortcodes with a filthy regex, because people don't properly register their shortcodes. * * @param string $text Input string that might contain shortcodes. * * @return string String without shortcodes. */ public function strip_shortcode( $text ) { return \preg_replace( '`\[[^\]]+\]`s', '', \strip_shortcodes( $text ) ); } } helpers/short-link-helper.php 0000644 00000007027 15025657560 0012307 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * Helper to get shortlinks for Yoast SEO. */ class Short_Link_Helper { /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The product helper. * * @var Product_Helper */ protected $product_helper; /** * Short_Link_Helper constructor. * * @param Options_Helper $options_helper The options helper. * @param Product_Helper $product_helper The product helper. */ public function __construct( Options_Helper $options_helper, Product_Helper $product_helper ) { $this->options_helper = $options_helper; $this->product_helper = $product_helper; } /** * Builds a URL to use in the plugin as shortlink. * * @param string $url The URL to build upon. * * @return string The final URL. */ public function build( $url ) { return \add_query_arg( $this->collect_additional_shortlink_data(), $url ); } /** * Returns a version of the URL with a utm_content with the current version. * * @param string $url The URL to build upon. * * @return string The final URL. */ public function get( $url ) { return $this->build( $url ); } /** * Echoes a version of the URL with a utm_content with the current version. * * @param string $url The URL to build upon. * * @return void */ public function show( $url ) { echo \esc_url( $this->get( $url ) ); } /** * Gets the shortlink's query params. * * @return array The shortlink's query params. */ public function get_query_params() { return $this->collect_additional_shortlink_data(); } /** * Gets the current site's PHP version, without the extra info. * * @return string The PHP version. */ private function get_php_version() { $version = \explode( '.', \PHP_VERSION ); return (int) $version[0] . '.' . (int) $version[1]; } /** * Gets the current site's platform version. * * @return string The wp_version. */ protected function get_platform_version() { return $GLOBALS['wp_version']; } /** * Collects the additional data necessary for the shortlink. * * @return array The shortlink data. */ protected function collect_additional_shortlink_data() { $data = [ 'php_version' => $this->get_php_version(), 'platform' => 'wordpress', 'platform_version' => $this->get_platform_version(), 'software' => $this->get_software(), 'software_version' => \WPSEO_VERSION, 'days_active' => $this->get_days_active(), 'user_language' => \get_user_locale(), ]; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $admin_page = \sanitize_text_field( \wp_unslash( $_GET['page'] ) ); if ( ! empty( $admin_page ) ) { $data['screen'] = $admin_page; } } return $data; } /** * Get our software and whether it's active or not. * * @return string The software name. */ protected function get_software() { if ( $this->product_helper->is_premium() ) { return 'premium'; } return 'free'; } /** * Gets the number of days the plugin has been active. * * @return int The number of days the plugin is active. */ protected function get_days_active() { $date_activated = $this->options_helper->get( 'first_activated_on' ); $datediff = ( \time() - $date_activated ); return (int) \round( $datediff / \DAY_IN_SECONDS ); } } helpers/taxonomy-helper.php 0000644 00000011754 15025657560 0012075 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WP_Taxonomy; use WP_Term; use WPSEO_Taxonomy_Meta; /** * A helper object for terms. */ class Taxonomy_Helper { /** * The options helper. * * @var Options_Helper */ private $options; /** * The string helper. * * @var String_Helper */ private $string; /** * Taxonomy_Helper constructor. * * @codeCoverageIgnore It only sets dependencies. * * @param Options_Helper $options The options helper. * @param String_Helper $string_helper The string helper. */ public function __construct( Options_Helper $options, String_Helper $string_helper ) { $this->options = $options; $this->string = $string_helper; } /** * Checks if the requested term is indexable. * * @param string $taxonomy The taxonomy slug. * * @return bool True when taxonomy is set to index. */ public function is_indexable( $taxonomy ) { return ! $this->options->get( 'noindex-tax-' . $taxonomy, false ); } /** * Returns an array with the public taxonomies. * * @param string $output The output type to use. * * @return string[]|WP_Taxonomy[] Array with all the public taxonomies. * The type depends on the specified output variable. */ public function get_public_taxonomies( $output = 'names' ) { return \get_taxonomies( [ 'public' => true ], $output ); } /** * Retrieves the term description (without tags). * * @param int $term_id Term ID. * * @return string Term description (without tags). */ public function get_term_description( $term_id ) { return $this->string->strip_all_tags( \term_description( $term_id ) ); } /** * Retrieves the taxonomy term's meta values. * * @codeCoverageIgnore We have to write test when this method contains own code. * * @param WP_Term $term Term to get the meta value for. * * @return array|bool Array of all the meta data for the term. * False if the term does not exist or the $meta provided is invalid. */ public function get_term_meta( $term ) { return WPSEO_Taxonomy_Meta::get_term_meta( $term, $term->taxonomy, null ); } /** * Gets the passed taxonomy's slug. * * @param string $taxonomy The name of the taxonomy. * * @return string The slug for the taxonomy. Returns the taxonomy's name if no slug could be found. */ public function get_taxonomy_slug( $taxonomy ) { $taxonomy_object = \get_taxonomy( $taxonomy ); if ( $taxonomy_object && \property_exists( $taxonomy_object, 'rewrite' ) && \is_array( $taxonomy_object->rewrite ) && isset( $taxonomy_object->rewrite['slug'] ) ) { return $taxonomy_object->rewrite['slug']; } return \strtolower( $taxonomy_object->name ); } /** * Returns an array with the custom taxonomies. * * @param string $output The output type to use. * * @return string[]|WP_Taxonomy[] Array with all the custom taxonomies. * The type depends on the specified output variable. */ public function get_custom_taxonomies( $output = 'names' ) { return \get_taxonomies( [ '_builtin' => false ], $output ); } /** * Returns an array of taxonomies that are excluded from being indexed for the * indexables. * * @return array The excluded taxonomies. */ public function get_excluded_taxonomies_for_indexables() { /** * Filter: 'wpseo_indexable_excluded_taxonomies' - Allow developers to prevent a certain taxonomy * from being saved to the indexable table. * * @param array $excluded_taxonomies The currently excluded taxonomies. */ $excluded_taxonomies = \apply_filters( 'wpseo_indexable_excluded_taxonomies', [] ); // Failsafe, to always make sure that `excluded_taxonomies` is an array. if ( ! \is_array( $excluded_taxonomies ) ) { return []; } return $excluded_taxonomies; } /** * Checks if the taxonomy is excluded. * * @param string $taxonomy The taxonomy to check. * * @return bool If the taxonomy is excluded. */ public function is_excluded( $taxonomy ) { return \in_array( $taxonomy, $this->get_excluded_taxonomies_for_indexables(), true ); } /** * This builds a list of indexable taxonomies. * * @return array The indexable taxonomies. */ public function get_indexable_taxonomies() { $public_taxonomies = $this->get_public_taxonomies(); $excluded_taxonomies = $this->get_excluded_taxonomies_for_indexables(); // `array_values`, to make sure that the keys are reset. return \array_values( \array_diff( $public_taxonomies, $excluded_taxonomies ) ); } /** * Returns an array of complete taxonomy objects for all indexable taxonomies. * * @return array List of indexable indexables objects. */ public function get_indexable_taxonomy_objects() { $taxonomy_objects = []; $indexable_taxonomies = $this->get_indexable_taxonomies(); foreach ( $indexable_taxonomies as $taxonomy ) { $taxonomy_object = \get_taxonomy( $taxonomy ); if ( ! empty( $taxonomy_object ) ) { $taxonomy_objects[ $taxonomy ] = $taxonomy_object; } } return $taxonomy_objects; } } helpers/wpdb-helper.php 0000644 00000001651 15025657560 0011146 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use wpdb; /** * A helper object for the wpdb. */ class Wpdb_Helper { /** * The WordPress database instance. * * @var wpdb */ private $wpdb; /** * Constructs a Wpdb_Helper instance. * * @param wpdb $wpdb The WordPress database instance. */ public function __construct( wpdb $wpdb ) { $this->wpdb = $wpdb; } /** * Check if table exists. * * @param string $table The table to be checked. * * @return bool Whether the table exists. */ public function table_exists( $table ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. $table_exists = $this->wpdb->get_var( "SHOW TABLES LIKE '{$table}'" ); if ( \is_wp_error( $table_exists ) || $table_exists === null ) { return false; } return true; } } helpers/indexable-helper.php 0000644 00000021764 15025657560 0012154 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * A helper object for indexables. */ class Indexable_Helper { /** * Represents the indexable repository. * * @var Indexable_Repository */ protected $repository; /** * Represents the options helper. * * @var Options_Helper */ protected $options_helper; /** * Represents the environment helper. * * @var Environment_Helper */ protected $environment_helper; /** * Represents the indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * Default values of certain columns. * * @var array */ protected $default_values = [ 'title' => [ 'default_value' => null, ], 'description' => [ 'default_value' => null, ], 'open_graph_title' => [ 'default_value' => null, ], 'open_graph_description' => [ 'default_value' => null, ], 'twitter_title' => [ 'default_value' => null, ], 'twitter_description' => [ 'default_value' => null, ], 'canonical' => [ 'default_value' => null, ], 'primary_focus_keyword' => [ 'default_value' => null, ], 'is_robots_noindex' => [ 'default_value' => null, ], 'is_robots_nofollow' => [ 'default_value' => false, ], 'is_robots_noarchive' => [ 'default_value' => null, ], 'is_robots_noimageindex' => [ 'default_value' => null, ], 'is_robots_nosnippet' => [ 'default_value' => null, ], ]; /** * Indexable_Helper constructor. * * @param Options_Helper $options_helper The options helper. * @param Environment_Helper $environment_helper The environment helper. * @param Indexing_Helper $indexing_helper The indexing helper. */ public function __construct( Options_Helper $options_helper, Environment_Helper $environment_helper, Indexing_Helper $indexing_helper ) { $this->options_helper = $options_helper; $this->environment_helper = $environment_helper; $this->indexing_helper = $indexing_helper; } /** * Sets the indexable repository. Done to avoid circular dependencies. * * @required * * @param Indexable_Repository $repository The indexable repository. * * @return void */ public function set_indexable_repository( Indexable_Repository $repository ) { $this->repository = $repository; } /** * Returns the page type of an indexable. * * @param Indexable $indexable The indexable. * * @return string|false The page type. False if it could not be determined. */ public function get_page_type_for_indexable( $indexable ) { switch ( $indexable->object_type ) { case 'post': $front_page_id = (int) \get_option( 'page_on_front' ); if ( $indexable->object_id === $front_page_id ) { return 'Static_Home_Page'; } $posts_page_id = (int) \get_option( 'page_for_posts' ); if ( $indexable->object_id === $posts_page_id ) { return 'Static_Posts_Page'; } return 'Post_Type'; case 'term': return 'Term_Archive'; case 'user': return 'Author_Archive'; case 'home-page': return 'Home_Page'; case 'post-type-archive': return 'Post_Type_Archive'; case 'date-archive': return 'Date_Archive'; case 'system-page': if ( $indexable->object_sub_type === 'search-result' ) { return 'Search_Result_Page'; } if ( $indexable->object_sub_type === '404' ) { return 'Error_Page'; } } return false; } /** * Resets the permalinks of the indexables. * * @param string|null $type The type of the indexable. * @param string|null $subtype The subtype. Can be null. * @param string $reason The reason that the permalink has been changed. * * @return void */ public function reset_permalink_indexables( $type = null, $subtype = null, $reason = Indexing_Reasons::REASON_PERMALINK_SETTINGS ) { $result = $this->repository->reset_permalink( $type, $subtype ); $this->indexing_helper->set_reason( $reason ); if ( $result !== false && $result > 0 ) { \delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( Indexable_Post_Type_Archive_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( Indexable_Term_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); } } /** * Determines whether indexing the specific indexable is appropriate at this time. * * @param Indexable $indexable The indexable. * * @return bool Whether indexing the specific indexable is appropriate at this time. */ public function should_index_indexable( $indexable ) { $intend_to_save = $this->should_index_indexables(); /** * Filter: 'wpseo_should_save_indexable' - Allow developers to enable / disable * saving the indexable when the indexable is updated. Warning: overriding * the intended action may cause problems when moving from a staging to a * production environment because indexable permalinks may get set incorrectly. * * @param bool $intend_to_save True if YoastSEO intends to save the indexable. * @param Indexable $indexable The indexable to be saved. */ return \apply_filters( 'wpseo_should_save_indexable', $intend_to_save, $indexable ); } /** * Determines whether indexing indexables is appropriate at this time. * * @return bool Whether the indexables should be indexed. */ public function should_index_indexables() { // Currently, the only reason to index is when we're on a production website. $should_index = $this->environment_helper->is_production_mode(); /** * Filter: 'Yoast\WP\SEO\should_index_indexables' - Allow developers to enable / disable * creating indexables. Warning: overriding * the intended action may cause problems when moving from a staging to a * production environment because indexable permalinks may get set incorrectly. * * @since 18.2 * * @param bool $should_index Whether the site's indexables should be created. */ return (bool) \apply_filters( 'Yoast\WP\SEO\should_index_indexables', $should_index ); } /** * Returns whether or not dynamic permalinks should be used. * * @return bool Whether or not the dynamic permalinks should be used. */ public function dynamic_permalinks_enabled() { /** * Filters the value of the `dynamic_permalinks` option. * * @param bool $value The value of the `dynamic_permalinks` option. */ return (bool) \apply_filters( 'wpseo_dynamic_permalinks_enabled', $this->options_helper->get( 'dynamic_permalinks', false ) ); } /** * Sets a boolean to indicate that the indexing of the indexables has completed. * * @return void */ public function finish_indexing() { $this->options_helper->set( 'indexables_indexing_completed', true ); } /** * Checks whether the indexable has default values in given fields. * * @param Indexable $indexable The Yoast indexable that we're checking. * @param array $fields The Yoast indexable fields that we're checking against. * * @return bool Whether the indexable has default values. */ public function check_if_default_indexable( $indexable, $fields ) { foreach ( $fields as $field ) { $is_default = $this->check_if_default_field( $indexable, $field ); if ( ! $is_default ) { break; } } return $is_default; } /** * Checks if an indexable field contains the default value. * * @param Indexable $indexable The Yoast indexable that we're checking. * @param string $field The field that we're checking. * * @return bool True if default value. */ public function check_if_default_field( $indexable, $field ) { $defaults = $this->default_values; if ( ! isset( $defaults[ $field ] ) ) { return false; } if ( $indexable->$field === $defaults[ $field ]['default_value'] ) { return true; } return false; } /** * Saves and returns an indexable (on production environments only). * * Moved from Yoast\WP\SEO\Builders\Indexable_Builder. * * @param Indexable $indexable The indexable. * @param Indexable|null $indexable_before The indexable before possible changes. * * @return bool True if default value. */ public function save_indexable( $indexable, $indexable_before = null ) { if ( ! $this->should_index_indexable( $indexable ) ) { return $indexable; } // Save the indexable before running the WordPress hook. $indexable->save(); if ( $indexable_before ) { /** * Action: 'wpseo_save_indexable' - Allow developers to perform an action * when the indexable is updated. * * @param Indexable $indexable The saved indexable. * @param Indexable $indexable_before The indexable before saving. */ \do_action( 'wpseo_save_indexable', $indexable, $indexable_before ); } return $indexable; } } helpers/social-profiles-helper.php 0000644 00000025356 15025657560 0013315 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * Class Social_Profiles_Helper. */ class Social_Profiles_Helper { /** * The fields for the person social profiles payload. * * @var array */ private $person_social_profile_fields = [ 'facebook' => 'get_non_valid_url', 'instagram' => 'get_non_valid_url', 'linkedin' => 'get_non_valid_url', 'myspace' => 'get_non_valid_url', 'pinterest' => 'get_non_valid_url', 'soundcloud' => 'get_non_valid_url', 'tumblr' => 'get_non_valid_url', 'twitter' => 'get_non_valid_twitter', 'youtube' => 'get_non_valid_url', 'wikipedia' => 'get_non_valid_url', ]; /** * The fields for the organization social profiles payload. * * @var array */ private $organization_social_profile_fields = [ 'facebook_site' => 'get_non_valid_url', 'twitter_site' => 'get_non_valid_twitter', 'other_social_urls' => 'get_non_valid_url_array', ]; /** * The Options_Helper instance. * * @var Options_Helper */ protected $options_helper; /** * Social_Profiles_Helper constructor. * * @param Options_Helper $options_helper The WPSEO options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Gets the person social profile fields supported by us. * * @return array The social profile fields. */ public function get_person_social_profile_fields() { /** * Filter: Allow changes to the social profiles fields available for a person. * * @param array $person_social_profile_fields The social profile fields. */ $person_social_profile_fields = \apply_filters( 'wpseo_person_social_profile_fields', $this->person_social_profile_fields ); return (array) $person_social_profile_fields; } /** * Gets the organization social profile fields supported by us. * * @return array The organization profile fields. */ public function get_organization_social_profile_fields() { /** * Filter: Allow changes to the social profiles fields available for an organization. * * @param array $organization_social_profile_fields The social profile fields. */ $organization_social_profile_fields = \apply_filters( 'wpseo_organization_social_profile_fields', $this->organization_social_profile_fields ); return (array) $organization_social_profile_fields; } /** * Gets the person social profiles stored in the database. * * @param int $person_id The id of the person. * * @return array The person's social profiles. */ public function get_person_social_profiles( $person_id ) { $social_profile_fields = \array_keys( $this->get_person_social_profile_fields() ); $person_social_profiles = \array_combine( $social_profile_fields, \array_fill( 0, \count( $social_profile_fields ), '' ) ); // If no person has been selected, $person_id is set to false. if ( \is_numeric( $person_id ) ) { foreach ( \array_keys( $person_social_profiles ) as $field_name ) { $value = \get_user_meta( $person_id, $field_name, true ); // If $person_id is an integer but does not represent a valid user, get_user_meta returns false. if ( ! \is_bool( $value ) ) { $person_social_profiles[ $field_name ] = $value; } } } return $person_social_profiles; } /** * Gets the organization social profiles stored in the database. * * @return array<string, string> The social profiles for the organization. */ public function get_organization_social_profiles() { $organization_social_profiles_fields = \array_keys( $this->get_organization_social_profile_fields() ); $organization_social_profiles = []; foreach ( $organization_social_profiles_fields as $field_name ) { $default_value = ''; if ( $field_name === 'other_social_urls' ) { $default_value = []; } $social_profile_value = $this->options_helper->get( $field_name, $default_value ); if ( $field_name === 'other_social_urls' ) { $other_social_profiles = \array_map( '\urldecode', \array_filter( $social_profile_value ) ); $organization_social_profiles['other_social_urls'] = $other_social_profiles; continue; } if ( $field_name === 'twitter_site' && $social_profile_value !== '' ) { $organization_social_profiles[ $field_name ] = 'https://x.com/' . $social_profile_value; continue; } $organization_social_profiles[ $field_name ] = \urldecode( $social_profile_value ); } return $organization_social_profiles; } /** * Stores the values for the person's social profiles. * * @param int $person_id The id of the person to edit. * @param array $social_profiles The array of the person's social profiles to be set. * * @return string[] An array of field names which failed to be saved in the db. */ public function set_person_social_profiles( $person_id, $social_profiles ) { $failures = []; $person_social_profile_fields = $this->get_person_social_profile_fields(); // First validate all social profiles, before even attempting to save them. foreach ( $person_social_profile_fields as $field_name => $validation_method ) { if ( ! isset( $social_profiles[ $field_name ] ) ) { // Just skip social profiles that were not passed. continue; } if ( $social_profiles[ $field_name ] === '' ) { continue; } $value_to_validate = $social_profiles[ $field_name ]; $failures = \array_merge( $failures, \call_user_func( [ $this, $validation_method ], $value_to_validate, $field_name ) ); } if ( ! empty( $failures ) ) { return $failures; } // All social profiles look good, now let's try to save them. foreach ( \array_keys( $person_social_profile_fields ) as $field_name ) { if ( ! isset( $social_profiles[ $field_name ] ) ) { // Just skip social profiles that were not passed. continue; } $social_profiles[ $field_name ] = $this->sanitize_social_field( $social_profiles[ $field_name ] ); \update_user_meta( $person_id, $field_name, $social_profiles[ $field_name ] ); } return $failures; } /** * Stores the values for the organization's social profiles. * * @param array $social_profiles An array with the social profiles values to be saved in the db. * * @return string[] An array of field names which failed to be saved in the db. */ public function set_organization_social_profiles( $social_profiles ) { $failures = []; $organization_social_profile_fields = $this->get_organization_social_profile_fields(); // First validate all social profiles, before even attempting to save them. foreach ( $organization_social_profile_fields as $field_name => $validation_method ) { if ( ! isset( $social_profiles[ $field_name ] ) ) { // Just skip social profiles that were not passed. continue; } $social_profiles[ $field_name ] = $this->sanitize_social_field( $social_profiles[ $field_name ] ); $value_to_validate = $social_profiles[ $field_name ]; $failures = \array_merge( $failures, \call_user_func( [ $this, $validation_method ], $value_to_validate, $field_name ) ); } if ( ! empty( $failures ) ) { return $failures; } // All social profiles look good, now let's try to save them. foreach ( \array_keys( $organization_social_profile_fields ) as $field_name ) { if ( ! isset( $social_profiles[ $field_name ] ) ) { // Just skip social profiles that were not passed. continue; } // Remove empty strings in Other Social URLs. if ( $field_name === 'other_social_urls' ) { $other_social_urls = \array_filter( $social_profiles[ $field_name ], static function ( $other_social_url ) { return $other_social_url !== ''; } ); $social_profiles[ $field_name ] = \array_values( $other_social_urls ); } $result = $this->options_helper->set( $field_name, $social_profiles[ $field_name ] ); if ( ! $result ) { /** * The value for Twitter might have been sanitised from URL to username. * If so, $result will be false. We should check if the option value is part of the received value. */ if ( $field_name === 'twitter_site' ) { $current_option = $this->options_helper->get( $field_name ); if ( ! \strpos( $social_profiles[ $field_name ], 'twitter.com/' . $current_option ) && ! \strpos( $social_profiles[ $field_name ], 'x.com/' . $current_option ) ) { $failures[] = $field_name; } } else { $failures[] = $field_name; } } } if ( ! empty( $failures ) ) { return $failures; } return []; } /** * Returns a sanitized social field. * * @param string|array $social_field The social field to sanitize. * * @return string|array The sanitized social field. */ protected function sanitize_social_field( $social_field ) { if ( \is_array( $social_field ) ) { foreach ( $social_field as $key => $value ) { $social_field[ $key ] = \sanitize_text_field( $value ); } return $social_field; } return \sanitize_text_field( $social_field ); } /** * Checks if url is not valid and returns the name of the setting if it's not. * * @param string $url The url to be validated. * @param string $url_setting The name of the setting to be updated with the url. * * @return array An array with the setting that the non-valid url is about to update. */ protected function get_non_valid_url( $url, $url_setting ) { if ( $this->options_helper->is_social_url_valid( $url ) ) { return []; } return [ $url_setting ]; } /** * Checks if urls in an array are not valid and return the name of the setting if one of them is not, including the non-valid url's index in the array * * @param array $urls The urls to be validated. * @param string $urls_setting The name of the setting to be updated with the urls. * * @return array An array with the settings that the non-valid urls are about to update, suffixed with a dash-separated index of the positions of those settings, eg. other_social_urls-2. */ protected function get_non_valid_url_array( $urls, $urls_setting ) { $non_valid_url_array = []; foreach ( $urls as $key => $url ) { if ( ! $this->options_helper->is_social_url_valid( $url ) ) { $non_valid_url_array[] = $urls_setting . '-' . $key; } } return $non_valid_url_array; } /** * Checks if the twitter value is not valid and returns the name of the setting if it's not. * * @param array $twitter_site The twitter value to be validated. * @param string $twitter_setting The name of the twitter setting to be updated with the value. * * @return array An array with the setting that the non-valid twitter value is about to update. */ protected function get_non_valid_twitter( $twitter_site, $twitter_setting ) { if ( $this->options_helper->is_twitter_id_valid( $twitter_site, false ) ) { return []; } return [ $twitter_setting ]; } } helpers/wordpress-helper.php 0000644 00000001000 15025657560 0012226 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; // phpcs:disable WordPress.WP.CapitalPDangit.MisspelledClassName -- It is spelled like `Wordpress_Helper` because of Yoast's naming conventions for classes, which would otherwise break dependency injection in some cases. /** * A helper object for WordPress matters. */ class Wordpress_Helper { /** * Returns the WordPress version. * * @return string The version. */ public function get_wordpress_version() { global $wp_version; return $wp_version; } } helpers/redirect-helper.php 0000644 00000003344 15025657560 0012014 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for redirects. */ class Redirect_Helper { /** * Wraps wp_redirect to allow testing for redirects. * * @codeCoverageIgnore It only wraps a WordPress function. * * @param string $location The path to redirect to. * @param int $status The status code to use. * @param string $reason The reason for the redirect. * * @return void */ public function do_unsafe_redirect( $location, $status = 302, $reason = 'Yoast SEO' ) { // phpcs:ignore WordPress.Security.SafeRedirect -- intentional, function has been renamed to make unsafe more clear. \wp_redirect( $location, $status, $reason ); exit; } /** * Wraps wp_safe_redirect to allow testing for safe redirects. * * @codeCoverageIgnore It only wraps a WordPress function. * * @param string $location The path to redirect to. * @param int $status The status code to use. * @param string $reason The reason for the redirect. * * @return void */ public function do_safe_redirect( $location, $status = 302, $reason = 'Yoast SEO' ) { \wp_safe_redirect( $location, $status, $reason ); exit; } /** * Sets a header. * This is a tiny helper function to enable better testing. * * @codeCoverageIgnore It only wraps a WordPress function. * * @param string $header The header to set. * * @return void */ public function set_header( $header ) { \header( $header ); } /** * Removes a header. * This is a tiny helper function to enable better testing. * * @codeCoverageIgnore It only wraps a WordPress function. * * @param string $header The header to remove. * * @return void */ public function remove_header( $header ) { \header_remove( $header ); } } helpers/capability-helper.php 0000644 00000004104 15025657560 0012327 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for user capabilities. */ class Capability_Helper { /** * Checks if the user has at least one of the proper capabilities. * * @param string $capability Capability to check. * * @return bool True if the user has at least one of the proper rights. */ public function current_user_can( $capability ) { if ( $capability === 'wpseo_manage_options' ) { return \current_user_can( $capability ); } return $this->has_any( [ 'wpseo_manage_options', $capability ] ); } /** * Retrieves the users that have the specified capability. * * @param string $capability The name of the capability. * * @return array The users that have the capability. */ public function get_applicable_users( $capability ) { $applicable_roles = $this->get_applicable_roles( $capability ); if ( $applicable_roles === [] ) { return []; } return \get_users( [ 'role__in' => $applicable_roles ] ); } /** * Retrieves the roles that have the specified capability. * * @param string $capability The name of the capability. * * @return array The names of the roles that have the capability. */ public function get_applicable_roles( $capability ) { $roles = \wp_roles(); $role_names = $roles->get_names(); $applicable_roles = []; foreach ( \array_keys( $role_names ) as $role_name ) { $role = $roles->get_role( $role_name ); if ( ! $role ) { continue; } // Add role if it has the capability. if ( \array_key_exists( $capability, $role->capabilities ) && $role->capabilities[ $capability ] === true ) { $applicable_roles[] = $role_name; } } return $applicable_roles; } /** * Checks if the current user has at least one of the supplied capabilities. * * @param array $capabilities Capabilities to check against. * * @return bool True if the user has at least one capability. */ private function has_any( array $capabilities ) { foreach ( $capabilities as $capability ) { if ( \current_user_can( $capability ) ) { return true; } } return false; } } helpers/import-cursor-helper.php 0000644 00000002560 15025657560 0013037 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * The Import Cursor Helper. */ class Import_Cursor_Helper { /** * The Options_Helper. * * @var Options_Helper */ public $options; /** * Class constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns the stored cursor value. * * @param string $cursor_id The cursor id. * @param mixed $default_value The default value if no cursor has been set yet. * * @return int The stored cursor value. */ public function get_cursor( $cursor_id, $default_value = 0 ) { $import_cursors = $this->options->get( 'import_cursors', [] ); return ( isset( $import_cursors[ $cursor_id ] ) ) ? $import_cursors[ $cursor_id ] : $default_value; } /** * Stores the current cursor value. * * @param string $cursor_id The cursor id. * @param int $last_imported_id The id of the lastly imported entry. * * @return void */ public function set_cursor( $cursor_id, $last_imported_id ) { $current_cursors = $this->options->get( 'import_cursors', [] ); if ( ! isset( $current_cursors[ $cursor_id ] ) || $current_cursors[ $cursor_id ] < $last_imported_id ) { $current_cursors[ $cursor_id ] = $last_imported_id; $this->options->set( 'import_cursors', $current_cursors ); } } } helpers/score-icon-helper.php 0000644 00000005436 15025657560 0012260 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WPSEO_Rank; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Presenters\Score_Icon_Presenter; /** * A helper object for score icons. */ class Score_Icon_Helper { /** * Holds the Robots_Helper. * * @var Robots_Helper */ protected $robots_helper; /** * Constructs a Score_Helper. * * @param Robots_Helper $robots_helper The Robots_Helper. */ public function __construct( Robots_Helper $robots_helper ) { $this->robots_helper = $robots_helper; } /** * Creates a Score_Icon_Presenter for the readability analysis. * * @param int $score The readability analysis score. * @param string $extra_class Optional. Any extra class. * * @return Score_Icon_Presenter The Score_Icon_Presenter. */ public function for_readability( $score, $extra_class = '' ) { $rank = WPSEO_Rank::from_numeric_score( (int) $score ); $class = $rank->get_css_class(); if ( $extra_class ) { $class .= " $extra_class"; } return new Score_Icon_Presenter( $rank->get_label(), $class ); } /** * Creates a Score_Icon_Presenter for the inclusive language analysis. * * @param int $score The inclusive language analysis score. * @param string $extra_class Optional. Any extra class. * * @return Score_Icon_Presenter The Score_Icon_Presenter. */ public function for_inclusive_language( $score, $extra_class = '' ) { $rank = WPSEO_Rank::from_numeric_score( (int) $score ); $class = $rank->get_css_class(); if ( $extra_class ) { $class .= " $extra_class"; } return new Score_Icon_Presenter( $rank->get_inclusive_language_label(), $class ); } /** * Creates a Score_Icon_Presenter for the SEO analysis from an indexable. * * @param Indexable|false $indexable The Indexable. * @param string $extra_class Optional. Any extra class. * @param string $no_index_title Optional. Override the title when not indexable. * * @return Score_Icon_Presenter The Score_Icon_Presenter. */ public function for_seo( $indexable, $extra_class = '', $no_index_title = '' ) { $is_indexable = $indexable && $this->robots_helper->is_indexable( $indexable ); if ( ! $is_indexable ) { $rank = new WPSEO_Rank( WPSEO_Rank::NO_INDEX ); $title = empty( $no_index_title ) ? $rank->get_label() : $no_index_title; } elseif ( empty( $indexable && $indexable->primary_focus_keyword ) ) { $rank = new WPSEO_Rank( WPSEO_Rank::BAD ); $title = \__( 'Focus keyphrase not set', 'wordpress-seo' ); } else { $rank = WPSEO_Rank::from_numeric_score( ( $indexable ) ? $indexable->primary_focus_keyword_score : 0 ); $title = $rank->get_label(); } $class = $rank->get_css_class(); if ( $extra_class ) { $class .= " $extra_class"; } return new Score_Icon_Presenter( $title, $class ); } } helpers/image-helper.php 0000644 00000027304 15025657560 0011277 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WPSEO_Image_Utils; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; /** * A helper object for images. */ class Image_Helper { /** * Image types that are supported by Open Graph. * * @var array */ protected static $valid_image_types = [ 'image/jpeg', 'image/gif', 'image/png', 'image/webp' ]; /** * Image extensions that are supported by Open Graph. * * @var array */ protected static $valid_image_extensions = [ 'jpeg', 'jpg', 'gif', 'png', 'webp' ]; /** * Represents the indexables repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * Represents the SEO Links repository. * * @var SEO_Links_Repository */ protected $seo_links_repository; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The URL helper. * * @var Url_Helper */ private $url_helper; /** * Image_Helper constructor. * * @param Indexable_Repository $indexable_repository The indexable repository. * @param SEO_Links_Repository $seo_links_repository The SEO Links repository. * @param Options_Helper $options The options helper. * @param Url_Helper $url_helper The URL helper. */ public function __construct( Indexable_Repository $indexable_repository, SEO_Links_Repository $seo_links_repository, Options_Helper $options, Url_Helper $url_helper ) { $this->indexable_repository = $indexable_repository; $this->seo_links_repository = $seo_links_repository; $this->options_helper = $options; $this->url_helper = $url_helper; } /** * Determines whether or not the wanted attachment is considered valid. * * @param int $attachment_id The attachment ID to get the attachment by. * * @return bool Whether or not the attachment is valid. */ public function is_valid_attachment( $attachment_id ) { if ( ! \wp_attachment_is_image( $attachment_id ) ) { return false; } $post_mime_type = \get_post_mime_type( $attachment_id ); if ( $post_mime_type === false ) { return false; } return $this->is_valid_image_type( $post_mime_type ); } /** * Checks if the given extension is a valid extension * * @param string $image_extension The image extension. * * @return bool True when valid. */ public function is_extension_valid( $image_extension ) { return \in_array( $image_extension, static::$valid_image_extensions, true ); } /** * Determines whether the passed mime type is a valid image type. * * @param string $mime_type The detected mime type. * * @return bool Whether or not the attachment is a valid image type. */ public function is_valid_image_type( $mime_type ) { return \in_array( $mime_type, static::$valid_image_types, true ); } /** * Retrieves the image source for an attachment. * * @param int $attachment_id The attachment. * @param string $image_size The image size to retrieve. * * @return string The image url or an empty string when not found. */ public function get_attachment_image_source( $attachment_id, $image_size = 'full' ) { $attachment = \wp_get_attachment_image_src( $attachment_id, $image_size ); if ( ! $attachment ) { return ''; } return $attachment[0]; } /** * Retrieves the ID of the featured image. * * @param int $post_id The post id to get featured image id for. * * @return int|bool ID when found, false when not. */ public function get_featured_image_id( $post_id ) { if ( ! \has_post_thumbnail( $post_id ) ) { return false; } return \get_post_thumbnail_id( $post_id ); } /** * Gets the image url from the content. * * @param int $post_id The post id to extract the images from. * * @return string The image url or an empty string when not found. */ public function get_post_content_image( $post_id ) { $image_url = $this->get_first_usable_content_image_for_post( $post_id ); if ( $image_url === null ) { return ''; } return $image_url; } /** * Gets the first image url of a gallery. * * @param int $post_id Post ID to use. * * @return string The image url or an empty string when not found. */ public function get_gallery_image( $post_id ) { $post = \get_post( $post_id ); if ( \strpos( $post->post_content, '[gallery' ) === false ) { return ''; } $images = \get_post_gallery_images( $post ); if ( empty( $images ) ) { return ''; } return \reset( $images ); } /** * Gets the image url from the term content. * * @param int $term_id The term id to extract the images from. * * @return string The image url or an empty string when not found. */ public function get_term_content_image( $term_id ) { $image_url = $this->get_first_content_image_for_term( $term_id ); if ( $image_url === null ) { return ''; } return $image_url; } /** * Retrieves the caption for an attachment. * * @param int $attachment_id Attachment ID. * * @return string The caption when found, empty string when no caption is found. */ public function get_caption( $attachment_id ) { $caption = \wp_get_attachment_caption( $attachment_id ); if ( ! empty( $caption ) ) { return $caption; } $caption = \get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); if ( ! empty( $caption ) ) { return $caption; } return ''; } /** * Retrieves the attachment metadata. * * @param int $attachment_id Attachment ID. * * @return array The metadata, empty array when no metadata is found. */ public function get_metadata( $attachment_id ) { $metadata = \wp_get_attachment_metadata( $attachment_id ); if ( ! $metadata || ! \is_array( $metadata ) ) { return []; } return $metadata; } /** * Retrieves the attachment image url. * * @param int $attachment_id Attachment ID. * @param string $size The size to get. * * @return string The url when found, empty string otherwise. */ public function get_attachment_image_url( $attachment_id, $size ) { $url = \wp_get_attachment_image_url( $attachment_id, $size ); if ( ! $url ) { return ''; } return $url; } /** * Find the right version of an image based on size. * * @codeCoverageIgnore - We have to write test when this method contains own code. * * @param int $attachment_id Attachment ID. * @param string $size Size name. * * @return array|false Returns an array with image data on success, false on failure. */ public function get_image( $attachment_id, $size ) { return WPSEO_Image_Utils::get_image( $attachment_id, $size ); } /** * Retrieves the best attachment variation for the given attachment. * * @codeCoverageIgnore - We have to write test when this method contains own code. * * @param int $attachment_id The attachment id. * * @return bool|string The attachment url or false when no variations found. */ public function get_best_attachment_variation( $attachment_id ) { $variations = WPSEO_Image_Utils::get_variations( $attachment_id ); $variations = WPSEO_Image_Utils::filter_usable_file_size( $variations ); // If we are left without variations, there is no valid variation for this attachment. if ( empty( $variations ) ) { return false; } // The variations are ordered so the first variations is by definition the best one. return \reset( $variations ); } /** * Find an attachment ID for a given URL. * * @param string $url The URL to find the attachment for. * @param bool $use_link_table Whether the SEO Links table will be used to retrieve the id. * * @return int The found attachment ID, or 0 if none was found. */ public function get_attachment_by_url( $url, $use_link_table = true ) { // Don't try to do this for external URLs. $parsed_url = \wp_parse_url( $url ); if ( $this->url_helper->get_link_type( $parsed_url ) === SEO_Links::TYPE_EXTERNAL ) { return 0; } /** The `wpseo_force_creating_and_using_attachment_indexables` filter is documented in indexable-link-builder.php */ if ( ! $this->options_helper->get( 'disable-attachment' ) || \apply_filters( 'wpseo_force_creating_and_using_attachment_indexables', false ) ) { // Strip out the size part of an image URL. $url = \preg_replace( '/(.*)-\d+x\d+\.(jpeg|jpg|png|gif)$/', '$1.$2', $url ); $indexable = $this->indexable_repository->find_by_permalink( $url ); if ( $indexable && $indexable->object_type === 'post' && $indexable->object_sub_type === 'attachment' ) { return $indexable->object_id; } $post_id = WPSEO_Image_Utils::get_attachment_by_url( $url ); if ( $post_id !== 0 ) { // Find the indexable, this triggers creating it so it can be found next time. $this->indexable_repository->find_by_id_and_type( $post_id, 'post' ); } return $post_id; } if ( ! $use_link_table ) { return WPSEO_Image_Utils::get_attachment_by_url( $url ); } $cache_key = 'attachment_seo_link_object_' . \md5( $url ); $found = false; $link = \wp_cache_get( $cache_key, 'yoast-seo-attachment-link', false, $found ); if ( $found === false ) { $link = $this->seo_links_repository->find_one_by_url( $url ); \wp_cache_set( $cache_key, $link, 'yoast-seo-attachment-link', \MINUTE_IN_SECONDS ); } if ( ! \is_a( $link, SEO_Links::class ) ) { return WPSEO_Image_Utils::get_attachment_by_url( $url ); } return $link->target_post_id; } /** * Retrieves an attachment ID for an image uploaded in the settings. * * Due to self::get_attachment_by_url returning 0 instead of false. * 0 is also a possibility when no ID is available. * * @codeCoverageIgnore - We have to write test when this method contains own code. * * @param string $setting The setting the image is stored in. * * @return int|bool The attachment id, or false or 0 if no ID is available. */ public function get_attachment_id_from_settings( $setting ) { return WPSEO_Image_Utils::get_attachment_id_from_settings( $setting ); } /** * Based on and image ID return array with the best variation of that image. If it's not saved to the DB, save it * to an option. * * @param string $setting The setting name. Should be company or person. * * @return array|bool Array with image details when the image is found, boolean when it's not found. */ public function get_attachment_meta_from_settings( $setting ) { $image_meta = $this->options_helper->get( $setting . '_meta', false ); if ( ! $image_meta ) { $image_id = $this->options_helper->get( $setting . '_id', false ); if ( $image_id ) { // There is not an option to put a URL in an image field in the settings anymore, only to upload it through the media manager. // This means an attachment always exists, so doing this is only needed once. $image_meta = $this->get_best_attachment_variation( $image_id ); if ( $image_meta ) { $this->options_helper->set( $setting . '_meta', $image_meta ); } } } return $image_meta; } /** * Retrieves the first usable content image for a post. * * @codeCoverageIgnore - We have to write test when this method contains own code. * * @param int $post_id The post id to extract the images from. * * @return string|null */ protected function get_first_usable_content_image_for_post( $post_id ) { return WPSEO_Image_Utils::get_first_usable_content_image_for_post( $post_id ); } /** * Gets the term's first usable content image. Null if none is available. * * @codeCoverageIgnore - We have to write test when this method contains own code. * * @param int $term_id The term id. * * @return string|null The image URL. */ protected function get_first_content_image_for_term( $term_id ) { return WPSEO_Image_Utils::get_first_content_image_for_term( $term_id ); } } helpers/curl-helper.php 0000644 00000001217 15025657560 0011155 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * Helper class for getting information about the installed cURL version. */ class Curl_Helper { /** * Checks is cURL is installed. * * @return bool Returns true if cURL is installed. */ public function is_installed() { return \function_exists( 'curl_version' ); } /** * Returns the currently installed cURL version. * * @return string|null Returns a string containing the cURL version, or null if cURL is not installed. */ public function get_version() { $version = \curl_version(); if ( ! isset( $version['version'] ) ) { return null; } return $version['version']; } } helpers/wincher-helper.php 0000644 00000005011 15025657560 0011643 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WPSEO_Shortlinker; use Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional; use Yoast\WP\SEO\Config\Wincher_Client; use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception; /** * A helper object for Wincher matters. */ class Wincher_Helper { /** * Holds the Options Page helper instance. * * @var Options_Helper */ protected $options; /** * Options_Helper constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Checks if the integration should be active for the current user. * * @return bool Whether the integration is active. */ public function is_active() { $conditional = new Non_Multisite_Conditional(); if ( ! $conditional->is_met() ) { return false; } if ( ! \current_user_can( 'publish_posts' ) && ! \current_user_can( 'publish_pages' ) ) { return false; } return (bool) $this->options->get( 'wincher_integration_active', true ); } /** * Checks if the user is logged in to Wincher. * * @return bool The Wincher login status. */ public function login_status() { try { $wincher = \YoastSEO()->classes->get( Wincher_Client::class ); } catch ( Empty_Property_Exception $e ) { // Return false if token is malformed (empty property). return false; } // Get token (and refresh it if it's expired). try { $wincher->get_tokens(); } catch ( Authentication_Failed_Exception $e ) { return false; } catch ( Empty_Token_Exception $e ) { return false; } return $wincher->has_valid_tokens(); } /** * Returns the Wincher links that can be used to localize the global admin * script. Mainly exists to avoid duplicating these links in multiple places * around the code base. * * @return string[] */ public function get_admin_global_links() { return [ 'links.wincher.login' => 'https://app.wincher.com/login?utm_medium=plugin&utm_source=yoast&referer=yoast&partner=yoast', 'links.wincher.about' => WPSEO_Shortlinker::get( 'https://yoa.st/dashboard-about-wincher' ), 'links.wincher.pricing' => WPSEO_Shortlinker::get( 'https://yoa.st/wincher-popup-pricing' ), 'links.wincher.website' => WPSEO_Shortlinker::get( 'https://yoa.st/wincher-popup' ), 'links.wincher.upgrade' => WPSEO_Shortlinker::get( 'https://yoa.st/wincher-upgrade' ), ]; } } helpers/meta-helper.php 0000644 00000005646 15025657560 0011150 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use WPSEO_Meta; use WPSEO_Taxonomy_Meta; /** * A helper object for meta. */ class Meta_Helper { /** * Get a custom post meta value. * * Returns the default value if the meta value has not been set. * * {@internal Unfortunately there isn't a filter available to hook into before returning * the results for get_post_meta(), get_post_custom() and the likes. That * would have been the preferred solution.}} * * @codeCoverageIgnore We have to write test when this method contains own code. * * @param string $key Internal key of the value to get (without prefix). * @param int $postid Post ID of the post to get the value for. * * @return string All 'normal' values returned from get_post_meta() are strings. * Objects and arrays are possible, but not used by this plugin * and therefore discarted (except when the special 'serialized' field def * value is set to true - only used by add-on plugins for now). * Will return the default value if no value was found. * Will return empty string if no default was found (not one of our keys) or * if the post does not exist. */ public function get_value( $key, $postid = 0 ) { return WPSEO_Meta::get_value( $key, $postid ); } /** * Retrieve a taxonomy term's meta value(s). * * @param mixed $term Term to get the meta value for * either (string) term name, (int) term id or (object) term. * @param string $taxonomy Name of the taxonomy to which the term is attached. * @param string|null $meta Optional. Meta value to get (without prefix). * * @return mixed|bool Value for the $meta if one is given, might be the default. * If no meta is given, an array of all the meta data for the term. * False if the term does not exist or the $meta provided is invalid. */ public function get_term_value( $term, $taxonomy, $meta = null ) { return WPSEO_Taxonomy_Meta::get_term_meta( $term, $taxonomy, $meta ); } /** * Set a custom post meta value. * * @param string $key Internal key of the value to set (without prefix). * @param mixed $meta_value The value to set the meta value to. * @param int $post_id Post ID of the post to set the value for. * * @return bool Whether the value was changed. */ public function set_value( $key, $meta_value, $post_id ) { return WPSEO_Meta::set_value( $key, $meta_value, $post_id ); } /** * Deletes a meta value for a post. * * @param string $key The internal key of the meta value to change (without prefix). * @param int $post_id The ID of the post to delete the meta for. * * @return bool Whether the delete was successful or not. */ public function delete( $key, $post_id ) { return WPSEO_Meta::delete( $key, $post_id ); } } helpers/site-helper.php 0000644 00000001066 15025657560 0011156 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for site options. */ class Site_Helper { /** * Retrieves the site name. * * @return string */ public function get_site_name() { return \wp_strip_all_tags( \get_bloginfo( 'name' ), true ); } /** * Checks if the current installation is a multisite and there has been a switch * between the set multisites. * * @return bool True when there was a switch between the multisites. */ public function is_multisite_and_switched() { return \is_multisite() && \ms_is_switched(); } } helpers/primary-term-helper.php 0000644 00000002566 15025657560 0012650 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use stdClass; /** * A helper object for primary terms. */ class Primary_Term_Helper { /** * Generate the primary term taxonomies. * * @param int $post_id ID of the post. * * @return array The taxonomies. */ public function get_primary_term_taxonomies( $post_id ) { $post_type = \get_post_type( $post_id ); $all_taxonomies = \get_object_taxonomies( $post_type, 'objects' ); $all_taxonomies = \array_filter( $all_taxonomies, [ $this, 'filter_hierarchical_taxonomies' ] ); /** * Filters which taxonomies for which the user can choose the primary term. * * @param array $taxonomies An array of taxonomy objects that are primary_term enabled. * @param string $post_type The post type for which to filter the taxonomies. * @param array $all_taxonomies All taxonomies for this post types, even ones that don't have primary term * enabled. */ $taxonomies = (array) \apply_filters( 'wpseo_primary_term_taxonomies', $all_taxonomies, $post_type, $all_taxonomies ); return $taxonomies; } /** * Returns whether or not a taxonomy is hierarchical. * * @param stdClass $taxonomy Taxonomy object. * * @return bool True for hierarchical taxonomy. */ protected function filter_hierarchical_taxonomies( $taxonomy ) { return (bool) $taxonomy->hierarchical; } } helpers/home-url-helper.php 0000644 00000001402 15025657560 0011734 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; /** * A helper object for the home URL. */ class Home_Url_Helper { /** * The home url. * * @var string */ protected static $home_url; /** * The parsed home url. * * @var array */ protected static $parsed_home_url; /** * Retrieves the home url. * * @return string The home url. */ public function get() { if ( static::$home_url === null ) { static::$home_url = \home_url(); } return static::$home_url; } /** * Retrieves the home url that has been parsed. * * @return array The parsed url. */ public function get_parsed() { if ( static::$parsed_home_url === null ) { static::$parsed_home_url = \wp_parse_url( $this->get() ); } return static::$parsed_home_url; } } helpers/open-graph/values-helper.php 0000644 00000005134 15025657560 0013551 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Open_Graph; /** * A helper object for the filtering of values. */ class Values_Helper { /** * Filters the Open Graph title. * * @param string $title The default title. * @param string $object_type The object type. * @param string $object_subtype The object subtype. * * @return string The open graph title. */ public function get_open_graph_title( $title, $object_type, $object_subtype ) { /** * Allow changing the Open Graph title. * * @param string $title The default title. * @param string $object_subtype The object subtype. */ return \apply_filters( 'Yoast\WP\SEO\open_graph_title_' . $object_type, $title, $object_subtype ); } /** * Filters the Open Graph description. * * @param string $description The default description. * @param string $object_type The object type. * @param string $object_subtype The object subtype. * * @return string The open graph description. */ public function get_open_graph_description( $description, $object_type, $object_subtype ) { /** * Allow changing the Open Graph description. * * @param string $description The default description. * @param string $object_subtype The object subtype. */ return \apply_filters( 'Yoast\WP\SEO\open_graph_description_' . $object_type, $description, $object_subtype ); } /** * Filters the Open Graph image ID. * * @param int $image_id The default image ID. * @param string $object_type The object type. * @param string $object_subtype The object subtype. * * @return string The open graph image ID. */ public function get_open_graph_image_id( $image_id, $object_type, $object_subtype ) { /** * Allow changing the Open Graph image ID. * * @param int $image_id The default image ID. * @param string $object_subtype The object subtype. */ return \apply_filters( 'Yoast\WP\SEO\open_graph_image_id_' . $object_type, $image_id, $object_subtype ); } /** * Filters the Open Graph image URL. * * @param string $image The default image URL. * @param string $object_type The object type. * @param string $object_subtype The object subtype. * * @return string The open graph image URL. */ public function get_open_graph_image( $image, $object_type, $object_subtype ) { /** * Allow changing the Open Graph image URL. * * @param string $image The default image URL. * @param string $object_subtype The object subtype. */ return \apply_filters( 'Yoast\WP\SEO\open_graph_image_' . $object_type, $image, $object_subtype ); } } helpers/open-graph/image-helper.php 0000644 00000006011 15025657560 0013327 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers\Open_Graph; use Yoast\WP\SEO\Helpers\Image_Helper as Base_Image_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; /** * A helper object for Open Graph images. */ class Image_Helper { /** * The URL helper. * * @var Url_Helper */ private $url; /** * The base image helper. * * @var Base_Image_Helper */ private $image; /** * Image_Helper constructor. * * @codeCoverageIgnore * * @param Url_Helper $url The url helper. * @param Base_Image_Helper $image The image helper. */ public function __construct( Url_Helper $url, Base_Image_Helper $image ) { $this->url = $url; $this->image = $image; } /** * Determines whether the passed URL is considered valid. * * @deprecated 22.4 * @codeCoverageIgnore * * @param array<array<string, string|int>> $image The image array. * * @return bool Whether or not the URL is a valid image. */ public function is_image_url_valid( array $image ) { \_deprecated_function( __METHOD__, 'Yoast SEO 22.4' ); if ( empty( $image['url'] ) || ! \is_string( $image['url'] ) ) { return false; } $image_extension = $this->url->get_extension_from_url( $image['url'] ); $is_valid = $this->image->is_extension_valid( $image_extension ); /** * Filter: 'wpseo_opengraph_is_valid_image_url' - Allows extra validation for an image url. * * @param bool $is_valid Current validation result. * @param string $url The image url to validate. */ return (bool) \apply_filters( 'wpseo_opengraph_is_valid_image_url', $is_valid, $image['url'] ); } /** * Retrieves the overridden image size value. * * @return string|null The image size when overriden by filter or null when not. */ public function get_override_image_size() { /** * Filter: 'wpseo_opengraph_image_size' - Allow overriding the image size used * for Open Graph sharing. If this filter is used, the defined size will always be * used for the og:image. The image will still be rejected if it is too small. * * Only use this filter if you manually want to determine the best image size * for the `og:image` tag. * * Use the `wpseo_image_sizes` filter if you want to use our logic. That filter * can be used to add an image size that needs to be taken into consideration * within our own logic. * * @param string|false $size Size string. */ return \apply_filters( 'wpseo_opengraph_image_size', null ); } /** * Retrieves the image data by a given attachment id. * * @param int $attachment_id The attachment id. * * @return array<string, string|int>|false The image data when found, `false` when not. */ public function get_image_by_id( $attachment_id ) { if ( ! $this->image->is_valid_attachment( $attachment_id ) ) { return false; } $override_image_size = $this->get_override_image_size(); if ( $override_image_size ) { return $this->image->get_image( $attachment_id, $override_image_size ); } return $this->image->get_best_attachment_variation( $attachment_id ); } } helpers/indexing-helper.php 0000644 00000031277 15025657560 0012026 0 ustar 00 <?php namespace Yoast\WP\SEO\Helpers; use Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface; use Yoast\WP\SEO\Actions\Indexing\Limited_Indexing_Action_Interface; use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action; use Yoast\WP\SEO\Actions\Indexing\Term_Link_Indexing_Action; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Integrations\Admin\Indexing_Notification_Integration; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast_Notification_Center; /** * A helper object for indexing. */ class Indexing_Helper { /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The date helper. * * @var Date_Helper */ protected $date_helper; /** * The notification center. * * @var Yoast_Notification_Center */ protected $notification_center; /** * The indexation actions. * * @var Indexation_Action_Interface[]|Limited_Indexing_Action_Interface[] */ protected $indexing_actions; /** * The indexation actions that can be done in the background. * * @var Indexation_Action_Interface[]|Limited_Indexing_Action_Interface[] */ protected $background_indexing_actions; /** * The indexable repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * Indexing_Helper constructor. * * @param Options_Helper $options_helper The options helper. * @param Date_Helper $date_helper The date helper. * @param Yoast_Notification_Center $notification_center The notification center. */ public function __construct( Options_Helper $options_helper, Date_Helper $date_helper, Yoast_Notification_Center $notification_center ) { $this->options_helper = $options_helper; $this->date_helper = $date_helper; $this->notification_center = $notification_center; } /** * Sets the actions. * * @required * * @param Indexable_Post_Indexation_Action $post_indexation The post indexing action. * @param Indexable_Term_Indexation_Action $term_indexation The term indexing action. * @param Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation The posttype indexing action. * @param Indexable_General_Indexation_Action $general_indexation The general indexing (homepage etc) action. * @param Post_Link_Indexing_Action $post_link_indexing_action The post crosslink indexing action. * @param Term_Link_Indexing_Action $term_link_indexing_action The term crossling indexing action. * * @return void */ public function set_indexing_actions( Indexable_Post_Indexation_Action $post_indexation, Indexable_Term_Indexation_Action $term_indexation, Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation, Indexable_General_Indexation_Action $general_indexation, Post_Link_Indexing_Action $post_link_indexing_action, Term_Link_Indexing_Action $term_link_indexing_action ) { $this->indexing_actions = [ $post_indexation, $term_indexation, $post_type_archive_indexation, $general_indexation, $post_link_indexing_action, $term_link_indexing_action, ]; // Coincidentally, the background indexing actions are the same with the Free indexing actions for now. $this->background_indexing_actions = $this->indexing_actions; } /** * Sets the indexable repository for the indexing helper class. * * @required * * @param Indexable_Repository $indexable_repository The indexable repository. * * @return void */ public function set_indexable_repository( Indexable_Repository $indexable_repository ) { $this->indexable_repository = $indexable_repository; } /** * Prepares the indexing process by setting several database options and removing the indexing notification. * * @return void */ public function prepare() { $this->set_first_time( false ); $this->set_started( $this->date_helper->current_time() ); $this->remove_indexing_notification(); // Do not set_reason here; if the process is cancelled, the reason to start indexing is still valid. } /** * Sets several database options when the indexing process is finished. * * @return void */ public function complete() { $this->set_reason( '' ); $this->set_started( null ); } /** * Sets appropriate flags when the indexing process fails. * * @return void */ public function indexing_failed() { $this->set_reason( Indexing_Reasons::REASON_INDEXING_FAILED ); $this->set_started( null ); } /** * Sets the indexing reason. * * @param string $reason The indexing reason. * * @return void */ public function set_reason( $reason ) { $this->options_helper->set( 'indexing_reason', $reason ); $this->remove_indexing_notification(); } /** * Removes any pre-existing notification, so that a new notification (with a possible new reason) can be added. * * @return void */ protected function remove_indexing_notification() { $this->notification_center->remove_notification_by_id( Indexing_Notification_Integration::NOTIFICATION_ID ); } /** * Determines whether an indexing reason has been set in the options. * * @return bool Whether an indexing reason has been set in the options. */ public function has_reason() { $reason = $this->get_reason(); return ! empty( $reason ); } /** * Returns the indexing reason. The reason why the site-wide indexing process should be run. * * @return string The indexing reason, defaults to the empty string if no reason has been set. */ public function get_reason() { return $this->options_helper->get( 'indexing_reason', '' ); } /** * Sets the start time when the indexing process has started but not completed. * * @param int|bool $timestamp The start time when the indexing process has started but not completed, false otherwise. * * @return void */ public function set_started( $timestamp ) { $this->options_helper->set( 'indexing_started', $timestamp ); } /** * Gets the start time when the indexing process has started but not completed. * * @return int|bool The start time when the indexing process has started but not completed, false otherwise. */ public function get_started() { return $this->options_helper->get( 'indexing_started' ); } /** * Sets a boolean that indicates whether or not a site still has to be indexed for the first time. * * @param bool $is_first_time_indexing Whether or not a site still has to be indexed for the first time. * * @return void */ public function set_first_time( $is_first_time_indexing ) { $this->options_helper->set( 'indexing_first_time', $is_first_time_indexing ); } /** * Gets a boolean that indicates whether or not the site still has to be indexed for the first time. * * @return bool Whether the site still has to be indexed for the first time. */ public function is_initial_indexing() { return $this->options_helper->get( 'indexing_first_time', true ); } /** * Gets a boolean that indicates whether or not the indexing of the indexables has completed. * * @return bool Whether the indexing of the indexables has completed. */ public function is_finished_indexables_indexing() { return $this->options_helper->get( 'indexables_indexing_completed', false ); } /** * Returns the total number of unindexed objects. * * @return int The total number of unindexed objects. */ public function get_unindexed_count() { $unindexed_count = 0; foreach ( $this->indexing_actions as $indexing_action ) { $unindexed_count += $indexing_action->get_total_unindexed(); } return $unindexed_count; } /** * Returns the amount of un-indexed posts expressed in percentage, which will be needed to set a threshold. * * @param int $unindexed_count The number of unindexed objects. * * @return int The amount of unindexed posts expressed in percentage. */ public function get_unindexed_percentage( $unindexed_count ) { // Gets the amount of indexed objects in the site. $indexed_count = $this->indexable_repository->get_total_number_of_indexables(); // The total amount of objects in the site. $total_objects_count = ( $indexed_count + $unindexed_count ); return ( ( $unindexed_count / $total_objects_count ) * 100 ); } /** * Returns whether the SEO optimization button should show. * * @return bool Whether the SEO optimization button should show. */ public function should_show_optimization_button() { // Gets the amount of unindexed objects in the site. $unindexed_count = $this->get_filtered_unindexed_count(); // If the amount of unidexed posts is <10 don't show configuration button. if ( $unindexed_count <= 10 ) { return false; } // If the amount of unidexed posts is >10, but the total amount of unidexed posts is ≤4% of the total amount of objects in the site, don't show configuration button. if ( $this->get_unindexed_percentage( $unindexed_count ) <= 4 ) { return false; } return true; } /** * Returns the total number of unindexed objects and applies a filter for third party integrations. * * @return int The total number of unindexed objects. */ public function get_filtered_unindexed_count() { $unindexed_count = $this->get_unindexed_count(); /** * Filter: 'wpseo_indexing_get_unindexed_count' - Allow changing the amount of unindexed objects. * * @param int $unindexed_count The amount of unindexed objects. */ return \apply_filters( 'wpseo_indexing_get_unindexed_count', $unindexed_count ); } /** * Returns a limited number of unindexed objects. * * @param int $limit Limit the number of unindexed objects that are counted. * @param Indexation_Action_Interface[]|Limited_Indexing_Action_Interface[] $actions The actions whose counts will be calculated. * * @return int The total number of unindexed objects. */ public function get_limited_unindexed_count( $limit, $actions = [] ) { $unindexed_count = 0; if ( empty( $actions ) ) { $actions = $this->indexing_actions; } foreach ( $actions as $action ) { $unindexed_count += $action->get_limited_unindexed_count( $limit - $unindexed_count + 1 ); if ( $unindexed_count > $limit ) { return $unindexed_count; } } return $unindexed_count; } /** * Returns the total number of unindexed objects and applies a filter for third party integrations. * * @param int $limit Limit the number of unindexed objects that are counted. * * @return int The total number of unindexed objects. */ public function get_limited_filtered_unindexed_count( $limit ) { $unindexed_count = $this->get_limited_unindexed_count( $limit, $this->indexing_actions ); if ( $unindexed_count > $limit ) { return $unindexed_count; } /** * Filter: 'wpseo_indexing_get_limited_unindexed_count' - Allow changing the amount of unindexed objects, * and allow for a maximum number of items counted to improve performance. * * @param int $unindexed_count The amount of unindexed objects. * @param int|false $limit Limit the number of unindexed objects that need to be counted. * False if it doesn't need to be limited. */ return \apply_filters( 'wpseo_indexing_get_limited_unindexed_count', $unindexed_count, $limit ); } /** * Returns the total number of unindexed objects that can be indexed in the background and applies a filter for third party integrations. * * @param int $limit Limit the number of unindexed objects that are counted. * * @return int The total number of unindexed objects that can be indexed in the background. */ public function get_limited_filtered_unindexed_count_background( $limit ) { $unindexed_count = $this->get_limited_unindexed_count( $limit, $this->background_indexing_actions ); if ( $unindexed_count > $limit ) { return $unindexed_count; } /** * Filter: 'wpseo_indexing_get_limited_unindexed_count_background' - Allow changing the amount of unindexed objects that can be indexed in the background, * and allow for a maximum number of items counted to improve performance. * * @param int $unindexed_count The amount of unindexed objects. * @param int|false $limit Limit the number of unindexed objects that need to be counted. * False if it doesn't need to be limited. */ return \apply_filters( 'wpseo_indexing_get_limited_unindexed_count_background', $unindexed_count, $limit ); } } wrappers/wp-rewrite-wrapper.php 0000644 00000000444 15025657560 0012720 0 ustar 00 <?php namespace Yoast\WP\SEO\Wrappers; use WP_Rewrite; /** * Wrapper for WP_Rewrite. */ class WP_Rewrite_Wrapper { /** * Returns the global WP_Rewrite_Wrapper object. * * @return WP_Rewrite The WP_Query object. */ public function get() { return $GLOBALS['wp_rewrite']; } } wrappers/wp-query-wrapper.php 0000644 00000001554 15025657560 0012407 0 ustar 00 <?php namespace Yoast\WP\SEO\Wrappers; use WP_Query; /** * Wrapper for WP_Query. */ class WP_Query_Wrapper { /** * Returns the global WP_Query object. * * @return WP_Query The WP_Query object. */ public function get_query() { return $GLOBALS['wp_query']; } /** * Returns the global main WP_Query object. * * @return WP_Query The WP_Query object. */ public function get_main_query() { return $GLOBALS['wp_the_query']; } /** * Sets the global WP_Query object. * * @param WP_Query $wp_query The WP Query. * * @return void */ public function set_query( WP_Query $wp_query ) { // phpcs:ignore WordPress.WP.GlobalVariablesOverride -- This is a deliberate action. $GLOBALS['wp_query'] = $wp_query; } /** * Resets the global WP_Query object. * * @return void */ public function reset_query() { \wp_reset_query(); } } wrappers/wp-remote-handler.php 0000644 00000003564 15025657560 0012475 0 ustar 00 <?php namespace Yoast\WP\SEO\Wrappers; use Exception; use YoastSEO_Vendor\GuzzleHttp\Promise\FulfilledPromise; use YoastSEO_Vendor\GuzzleHttp\Promise\PromiseInterface; use YoastSEO_Vendor\GuzzleHttp\Promise\RejectedPromise; use YoastSEO_Vendor\GuzzleHttp\Psr7\Response; use YoastSEO_Vendor\Psr\Http\Message\RequestInterface; /** * Wraps wp_remote_get in an interface compatible with Guzzle. */ class WP_Remote_Handler { /** * Calls the handler. * Cookies are currently not supported as they are not used by OAuth. * Writing responses to files is also not supported for the same reason. * * @param RequestInterface $request The request. * @param array $options The request options. * * @return PromiseInterface The promise interface. * * @throws Exception If the request fails. */ public function __invoke( RequestInterface $request, array $options ) { $headers = []; foreach ( $request->getHeaders() as $name => $values ) { $headers[ $name ] = \implode( ',', $values ); } $args = [ 'method' => $request->getMethod(), 'headers' => $headers, 'body' => (string) $request->getBody(), 'httpVersion' => $request->getProtocolVersion(), ]; if ( isset( $options['verify'] ) && $options['verify'] === false ) { $args['sslverify'] = false; } if ( isset( $options['timeout'] ) ) { $args['timeout'] = ( $options['timeout'] * 1000 ); } $raw_response = \wp_remote_request( (string) $request->getUri(), $args ); if ( \is_wp_error( $raw_response ) ) { $exception = new Exception( $raw_response->get_error_message() ); return new RejectedPromise( $exception ); } $response = new Response( $raw_response['response']['code'], $raw_response['headers']->getAll(), $raw_response['body'], $args['httpVersion'], $raw_response['response']['message'] ); return new FulfilledPromise( $response ); } } user-profiles-additions/user-interface/user-profiles-additions-ui.php 0000644 00000004323 15025657560 0022147 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Profiles_Additions\User_Interface; use WP_User; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\User_Profile_Conditional; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Adds a new hook in the user profiles edit screen to add content. */ class User_Profiles_Additions_Ui implements Integration_Interface { /** * Holds the Product_Helper. * * @var Product_Helper */ private $product_helper; /** * Holds the WPSEO_Admin_Asset_Manager. * * @var WPSEO_Admin_Asset_Manager */ private $asset_manager; /** * Constructs Academy_Integration. * * @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager. * @param Product_Helper $product_helper The Product_Helper. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Product_Helper $product_helper ) { $this->asset_manager = $asset_manager; $this->product_helper = $product_helper; } /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ User_Profile_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'show_user_profile', [ $this, 'add_hook_to_user_profile' ] ); \add_action( 'edit_user_profile', [ $this, 'add_hook_to_user_profile' ] ); } /** * Enqueues the assets needed for this integration. * * @return void */ public function enqueue_assets() { if ( $this->product_helper->is_premium() ) { $this->asset_manager->enqueue_style( 'introductions' ); } } /** * Add the inputs needed for SEO values to the User Profile page. * * @param WP_User $user User instance to output for. * * @return void */ public function add_hook_to_user_profile( $user ) { $this->enqueue_assets(); echo '<div class="yoast yoast-settings">'; /** * Fires in the user profile. * * @internal * * @param WP_User $user The current WP_User object. */ \do_action( 'wpseo_user_profile_additions', $user ); echo '</div>'; } } wordpress/wrapper.php 0000644 00000002702 15025657560 0011001 0 ustar 00 <?php namespace Yoast\WP\SEO\WordPress; use wpdb; use WPSEO_Addon_Manager; use WPSEO_Admin_Asset_Manager; use WPSEO_Replace_Vars; use WPSEO_Shortlinker; use WPSEO_Utils; /** * Wrapper class for WordPress globals. * * This consists of factory functions to inject WP globals into the dependency container. */ class Wrapper { /** * Wrapper method for returning the wpdb object for use in dependency injection. * * @return wpdb The wpdb global. */ public static function get_wpdb() { global $wpdb; return $wpdb; } /** * Factory function for replace vars helper. * * @return WPSEO_Replace_Vars The replace vars helper. */ public static function get_replace_vars() { return new WPSEO_Replace_Vars(); } /** * Factory function for the admin asset manager. * * @return WPSEO_Admin_Asset_Manager The admin asset manager. */ public static function get_admin_asset_manager() { return new WPSEO_Admin_Asset_Manager(); } /** * Factory function for the addon manager. * * @return WPSEO_Addon_Manager The addon manager. */ public static function get_addon_manager() { return new WPSEO_Addon_Manager(); } /** * Factory function for the shortlinker. * * @return WPSEO_Shortlinker */ public static function get_shortlinker() { return new WPSEO_Shortlinker(); } /** * Factory function for the utils class. * * @return WPSEO_Utils */ public static function get_utils() { return new WPSEO_Utils(); } } user-meta/domain/custom-meta-interface.php 0000644 00000002163 15025657560 0014637 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\Domain; /** * This interface describes a custom meta. */ interface Custom_Meta_Interface { /** * Returns the priority which the custom meta's form field should be rendered with. * * @return int. */ public function get_render_priority(): int; /** * Returns the db key of the custom meta. * * @return string */ public function get_key(): string; /** * Returns the id of the custom meta's form field. * * @return string */ public function get_field_id(): string; /** * Returns the meta value. * * @param int $user_id The user ID. * * @return string */ public function get_value( $user_id ): string; /** * Returns whether the respective global setting is enabled. * * @return bool */ public function is_setting_enabled(): bool; /** * Returns whether the custom meta is allowed to be empty. * * @return bool */ public function is_empty_allowed(): bool; /** * Renders the custom meta's field in the user form. * * @param int $user_id The user ID. * * @return void */ public function render_field( $user_id ): void; } user-meta/domain/additional-contactmethod-interface.php 0000644 00000000615 15025657560 0017343 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\Domain; /** * This interface describes an additional contactmethod. */ interface Additional_Contactmethod_Interface { /** * Returns the key of the contactmethod. * * @return string */ public function get_key(): string; /** * Returns the label of the contactmethod field. * * @return string */ public function get_label(): string; } user-meta/application/additional-contactmethods-collector.php 0000644 00000004532 15025657560 0020612 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\Application; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The collector to get additional contactmethods. * * @makePublic */ class Additional_Contactmethods_Collector { /** * All additional contactmethods. * * @var array<Additional_Contactmethod_Interface> */ private $additional_contactmethods; /** * The constructor. * * @param Additional_Contactmethod_Interface ...$additional_contactmethods All additional contactmethods. */ public function __construct( Additional_Contactmethod_Interface ...$additional_contactmethods ) { $this->additional_contactmethods = $additional_contactmethods; } /** * Returns all the additional contactmethods. * * @return array<Additional_Contactmethod_Interface> All the additional contactmethods. */ public function get_additional_contactmethods(): array { $additional_contactmethods = $this->additional_contactmethods; /** * Filter: Adds the possibility to add more additional contactmethods in the user profile. * * @param array<Additional_Contactmethod_Interface> $additional_contactmethods Array with additional contact method classes. */ return \apply_filters( 'wpseo_additional_contactmethods', $additional_contactmethods ); } /** * Returns the additional contactmethods key/value pairs. * * @return array<string, string> The additional contactmethods key/value pairs. */ public function get_additional_contactmethods_objects(): array { $additional_contactmethods_objects = []; $additional_contactmethods = $this->get_additional_contactmethods(); foreach ( $additional_contactmethods as $additional_contactmethod ) { $additional_contactmethods_objects[ $additional_contactmethod->get_key() ] = $additional_contactmethod->get_label(); } return $additional_contactmethods_objects; } /** * Returns the additional contactmethods keys. * * @return array<string> The additional contactmethods keys. */ public function get_additional_contactmethods_keys(): array { $additional_contactmethods_keys = []; $additional_contactmethods = $this->get_additional_contactmethods(); foreach ( $additional_contactmethods as $additional_contactmethod ) { $additional_contactmethods_keys[] = $additional_contactmethod->get_key(); } return $additional_contactmethods_keys; } } user-meta/application/cleanup-service.php 0000644 00000004266 15025657560 0014572 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\Application; use Yoast\WP\SEO\User_Meta\Infrastructure\Cleanup_Repository; /** * Service with all usermeta cleanup queries. */ class Cleanup_Service { /** * The additional contactmethods collector. * * @var Additional_Contactmethods_Collector */ private $additional_contactmethods_collector; /** * The custom meta collector. * * @var Custom_Meta_Collector */ private $custom_meta_collector; /** * The cleanup repository. * * @var Cleanup_Repository */ private $cleanup_repository; /** * The constructor. * * @param Additional_Contactmethods_Collector $additional_contactmethods_collector The additional contactmethods collector. * @param Custom_Meta_Collector $custom_meta_collector The custom meta collector. * @param Cleanup_Repository $cleanup_repository The cleanup repository. */ public function __construct( Additional_Contactmethods_Collector $additional_contactmethods_collector, Custom_Meta_Collector $custom_meta_collector, Cleanup_Repository $cleanup_repository ) { $this->additional_contactmethods_collector = $additional_contactmethods_collector; $this->custom_meta_collector = $custom_meta_collector; $this->cleanup_repository = $cleanup_repository; } /** * Deletes selected empty usermeta. * * @param int $limit The limit we'll apply to the cleanups. * * @return int|bool The number of rows that was deleted or false if the query failed. */ public function cleanup_selected_empty_usermeta( int $limit ) { $meta_to_check = $this->get_meta_to_check(); return $this->cleanup_repository->delete_empty_usermeta_query( $meta_to_check, $limit ); } /** * Gets which meta are going to be checked for emptiness. * * @return array<string> The meta to be checked for emptiness. */ private function get_meta_to_check() { $additional_contactmethods = $this->additional_contactmethods_collector->get_additional_contactmethods_keys(); $custom_meta = $this->custom_meta_collector->get_non_empty_custom_meta(); return \array_merge( $additional_contactmethods, $custom_meta ); } } user-meta/application/custom-meta-collector.php 0000644 00000003076 15025657560 0015725 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\Application; use Yoast\WP\SEO\User_Meta\Domain\Custom_Meta_Interface; /** * The collector to get custom user meta. * * @makePublic */ class Custom_Meta_Collector { /** * All custom meta. * * @var array<Custom_Meta_Interface> */ private $custom_meta; /** * The constructor. * * @param Custom_Meta_Interface ...$custom_meta All custom meta. */ public function __construct( Custom_Meta_Interface ...$custom_meta ) { $this->custom_meta = $custom_meta; } /** * Returns all the custom meta. * * @return array<Custom_Meta_Interface> All the custom meta. */ public function get_custom_meta(): array { return $this->custom_meta; } /** * Returns all the custom meta, sorted by rendering priority. * * @return array<Custom_Meta_Interface> All the custom meta, sorted by rendering priority. */ public function get_sorted_custom_meta(): array { $custom_meta = $this->get_custom_meta(); \usort( $custom_meta, static function ( Custom_Meta_Interface $a, Custom_Meta_Interface $b ) { return ( $a->get_render_priority() <=> $b->get_render_priority() ); } ); return $custom_meta; } /** * Returns the custom meta that can't be empty. * * @return array<string> The custom meta that can't be empty. */ public function get_non_empty_custom_meta(): array { $non_empty_custom_meta = []; foreach ( $this->custom_meta as $custom_meta ) { if ( ! $custom_meta->is_empty_allowed() ) { $non_empty_custom_meta[] = $custom_meta->get_key(); } } return $non_empty_custom_meta; } } user-meta/infrastructure/cleanup-repository.php 0000644 00000002464 15025657560 0016124 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\Infrastructure; /** * Repository going into the database to clean up. */ class Cleanup_Repository { /** * Deletes empty usermeta based on their meta_keys and returns the number of the deleted meta. * * @param array<string> $meta_keys The meta to be potentially deleted. * @param int $limit The number of maximum deletions. * * @return int|false The number of rows that was deleted or false if the query failed. */ public function delete_empty_usermeta_query( $meta_keys, $limit ) { global $wpdb; // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- Reason: we're passing an array instead. $delete_query = $wpdb->prepare( 'DELETE FROM %i WHERE meta_key IN ( ' . \implode( ', ', \array_fill( 0, \count( $meta_keys ), '%s' ) ) . ' ) AND meta_value = "" ORDER BY user_id LIMIT %d', \array_merge( [ $wpdb->usermeta ], $meta_keys, [ $limit ] ) ); // phpcs:enable // phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: Is it prepared already. return $wpdb->query( $delete_query ); // phpcs:enable } } user-meta/framework/custom-meta/keyword-analysis-disable.php 0000644 00000005657 15025657560 0020350 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Custom_Meta; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\User_Meta\Domain\Custom_Meta_Interface; /** * The Keyword_Analysis_Disable custom meta. */ class Keyword_Analysis_Disable implements Custom_Meta_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns the priority which the custom meta's form field should be rendered with. * * @return int The priority which the custom meta's form field should be rendered with. */ public function get_render_priority(): int { return 400; } /** * Returns the db key of the Keyword_Analysis_Disable custom meta. * * @return string The db key of the Keyword_Analysis_Disable custom meta. */ public function get_key(): string { return 'wpseo_keyword_analysis_disable'; } /** * Returns the id of the custom meta's form field. * * @return string The id of the custom meta's form field. */ public function get_field_id(): string { return 'wpseo_keyword_analysis_disable'; } /** * Returns the meta value. * * @param int $user_id The user ID. * * @return string The meta value. */ public function get_value( $user_id ): string { return \get_the_author_meta( $this->get_key(), $user_id ); } /** * Returns whether the respective global setting is enabled. * * @return bool Whether the respective global setting is enabled. */ public function is_setting_enabled(): bool { return ( $this->options_helper->get( 'keyword_analysis_active', false ) ); } /** * Returns whether the custom meta is allowed to be empty. * * @return bool Whether the custom meta is allowed to be empty. */ public function is_empty_allowed(): bool { return true; } /** * Renders the custom meta's field in the user form. * * @param int $user_id The user ID. * * @return void */ public function render_field( $user_id ): void { echo ' <input class="yoast-settings__checkbox double" type="checkbox" id="' . \esc_attr( $this->get_field_id() ) . '" name="' . \esc_attr( $this->get_field_id() ) . '" aria-describedby="' . \esc_attr( $this->get_field_id() ) . '_desc" value="on" ' . \checked( $this->get_value( $user_id ), 'on', false ) . '/>'; echo ' <label class="yoast-label-strong" for="' . \esc_attr( $this->get_field_id() ) . '">' . \esc_html__( 'Disable SEO analysis', 'wordpress-seo' ) . '</label><br>'; echo ' <p class="description" id="' . \esc_attr( $this->get_field_id() ) . '_desc">' . \esc_html__( 'Removes the focus keyphrase section from the metabox and disables all SEO-related suggestions.', 'wordpress-seo' ) . '</p>'; } } user-meta/framework/custom-meta/author-title.php 0000644 00000004765 15025657560 0016062 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Custom_Meta; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\User_Meta\Domain\Custom_Meta_Interface; /** * The Author_Title custom meta. */ class Author_Title implements Custom_Meta_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns the priority which the custom meta's form field should be rendered with. * * @return int The priority which the custom meta's form field should be rendered with. */ public function get_render_priority(): int { return 100; } /** * Returns the db key of the Author_Title custom meta. * * @return string The db key of the Author_Title custom meta. */ public function get_key(): string { return 'wpseo_title'; } /** * Returns the id of the custom meta's form field. * * @return string The id of the custom meta's form field. */ public function get_field_id(): string { return 'wpseo_author_title'; } /** * Returns the meta value. * * @param int $user_id The user ID. * * @return string The meta value. */ public function get_value( $user_id ): string { return \get_the_author_meta( $this->get_key(), $user_id ); } /** * Returns whether the respective global setting is enabled. * * @return bool Whether the respective global setting is enabled. */ public function is_setting_enabled(): bool { return ( ! $this->options_helper->get( 'disable-author' ) ); } /** * Returns whether the custom meta is allowed to be empty. * * @return bool Whether the custom meta is allowed to be empty. */ public function is_empty_allowed(): bool { return true; } /** * Renders the custom meta's field in the user form. * * @param int $user_id The user ID. * * @return void */ public function render_field( $user_id ): void { echo ' <label for="' . \esc_attr( $this->get_field_id() ) . '">' . \esc_html__( 'Title to use for Author page', 'wordpress-seo' ) . '</label>'; echo ' <input class="yoast-settings__text regular-text" type="text" id="' . \esc_attr( $this->get_field_id() ) . '" name="' . \esc_attr( $this->get_field_id() ) . '" value="' . \esc_attr( $this->get_value( $user_id ) ) . '"/><br>'; } } user-meta/framework/custom-meta/noindex-author.php 0000644 00000005155 15025657560 0016377 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Custom_Meta; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\User_Meta\Domain\Custom_Meta_Interface; /** * The Noindex_Author custom meta. */ class Noindex_Author implements Custom_Meta_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns the priority which the custom meta's form field should be rendered with. * * @return int The priority which the custom meta's form field should be rendered with. */ public function get_render_priority(): int { return 300; } /** * Returns the db key of the Noindex_Author custom meta. * * @return string The db key of the Noindex_Author custom meta. */ public function get_key(): string { return 'wpseo_noindex_author'; } /** * Returns the id of the custom meta's form field. * * @return string The id of the custom meta's form field. */ public function get_field_id(): string { return 'wpseo_noindex_author'; } /** * Returns the meta value. * * @param int $user_id The user ID. * * @return string The meta value. */ public function get_value( $user_id ): string { return \get_the_author_meta( $this->get_key(), $user_id ); } /** * Returns whether the respective global setting is enabled. * * @return bool Whether the respective global setting is enabled. */ public function is_setting_enabled(): bool { return ( ! $this->options_helper->get( 'disable-author' ) ); } /** * Returns whether the custom meta is allowed to be empty. * * @return bool Whether the custom meta is allowed to be empty. */ public function is_empty_allowed(): bool { return false; } /** * Renders the custom meta's field in the user form. * * @param int $user_id The user ID. * * @return void */ public function render_field( $user_id ): void { echo ' <input class="yoast-settings__checkbox double" type="checkbox" id="' . \esc_attr( $this->get_field_id() ) . '" name="' . \esc_attr( $this->get_field_id() ) . '" value="on" ' . \checked( $this->get_value( $user_id ), 'on', false ) . '/>'; echo ' <label class="yoast-label-strong" for="' . \esc_attr( $this->get_field_id() ) . '">' . \esc_html__( 'Do not allow search engines to show this author\'s archives in search results.', 'wordpress-seo' ) . '</label><br>'; } } user-meta/framework/custom-meta/content-analysis-disable.php 0000644 00000005704 15025657560 0020327 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Custom_Meta; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\User_Meta\Domain\Custom_Meta_Interface; /** * The Content_Analysis_Disable custom meta. */ class Content_Analysis_Disable implements Custom_Meta_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns the priority which the custom meta's form field should be rendered with. * * @return int The priority which the custom meta's form field should be rendered with. */ public function get_render_priority(): int { return 500; } /** * Returns the db key of the Content_Analysis_Disable custom meta. * * @return string The db key of the Content_Analysis_Disable custom meta. */ public function get_key(): string { return 'wpseo_content_analysis_disable'; } /** * Returns the id of the custom meta's form field. * * @return string The id of the custom meta's form field. */ public function get_field_id(): string { return 'wpseo_content_analysis_disable'; } /** * Returns the meta value. * * @param int $user_id The user ID. * * @return string The meta value. */ public function get_value( $user_id ): string { return \get_the_author_meta( $this->get_key(), $user_id ); } /** * Returns whether the respective global setting is enabled. * * @return bool Whether the respective global setting is enabled. */ public function is_setting_enabled(): bool { return ( $this->options_helper->get( 'content_analysis_active', false ) ); } /** * Returns whether the custom meta is allowed to be empty. * * @return bool Whether the custom meta is allowed to be empty. */ public function is_empty_allowed(): bool { return true; } /** * Renders the custom meta's field in the user form. * * @param int $user_id The user ID. * * @return void */ public function render_field( $user_id ): void { echo ' <input class="yoast-settings__checkbox double" type="checkbox" id="' . \esc_attr( $this->get_field_id() ) . '" name="' . \esc_attr( $this->get_field_id() ) . '" aria-describedby="' . \esc_attr( $this->get_field_id() ) . '_desc" value="on" ' . \checked( $this->get_value( $user_id ), 'on', false ) . '/>'; echo ' <label class="yoast-label-strong" for="' . \esc_attr( $this->get_field_id() ) . '">' . \esc_html__( 'Disable readability analysis', 'wordpress-seo' ) . '</label><br>'; echo ' <p class="description" id="' . \esc_attr( $this->get_field_id() ) . '_desc">' . \esc_html__( 'Removes the readability analysis section from the metabox and disables all readability-related suggestions.', 'wordpress-seo' ) . '</p>'; } } user-meta/framework/custom-meta/inclusive-language-analysis-disable.php 0000644 00000006046 15025657560 0022437 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Custom_Meta; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\User_Meta\Domain\Custom_Meta_Interface; /** * The Inclusive_Language_Analysis_Disable custom meta. */ class Inclusive_Language_Analysis_Disable implements Custom_Meta_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns the priority which the custom meta's form field should be rendered with. * * @return int The priority which the custom meta's form field should be rendered with. */ public function get_render_priority(): int { return 600; } /** * Returns the db key of the Inclusive_Language_Analysis_Disable custom meta. * * @return string The db key of the Inclusive_Language_Analysis_Disable custom meta. */ public function get_key(): string { return 'wpseo_inclusive_language_analysis_disable'; } /** * Returns the id of the custom meta's form field. * * @return string The id of the custom meta's form field. */ public function get_field_id(): string { return 'wpseo_inclusive_language_analysis_disable'; } /** * Returns the meta value. * * @param int $user_id The user ID. * * @return string The meta value. */ public function get_value( $user_id ): string { return \get_the_author_meta( $this->get_key(), $user_id ); } /** * Returns whether the respective global setting is enabled. * * @return bool Whether the respective global setting is enabled. */ public function is_setting_enabled(): bool { return ( $this->options_helper->get( 'inclusive_language_analysis_active', false ) ); } /** * Returns whether the custom meta is allowed to be empty. * * @return bool Whether the custom meta is allowed to be empty. */ public function is_empty_allowed(): bool { return true; } /** * Renders the custom meta's field in the user form. * * @param int $user_id The user ID. * * @return void */ public function render_field( $user_id ): void { echo ' <input class="yoast-settings__checkbox double" type="checkbox" id="' . \esc_attr( $this->get_field_id() ) . '" name="' . \esc_attr( $this->get_field_id() ) . '" aria-describedby="' . \esc_attr( $this->get_field_id() ) . '_desc" value="on" ' . \checked( $this->get_value( $user_id ), 'on', false ) . '/>'; echo ' <label class="yoast-label-strong" for="' . \esc_attr( $this->get_field_id() ) . '">' . \esc_html__( 'Disable inclusive language analysis', 'wordpress-seo' ) . '</label><br>'; echo ' <p class="description" id="' . \esc_attr( $this->get_field_id() ) . '_desc">' . \esc_html__( 'Removes the inclusive language analysis section from the metabox and disables all inclusive language-related suggestions.', 'wordpress-seo' ) . '</p>'; } } user-meta/framework/custom-meta/author-metadesc.php 0000644 00000005100 15025657560 0016506 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Custom_Meta; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\User_Meta\Domain\Custom_Meta_Interface; /** * The Author_Metadesc custom meta. */ class Author_Metadesc implements Custom_Meta_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns the priority which the custom meta's form field should be rendered with. * * @return int The priority which the custom meta's form field should be rendered with. */ public function get_render_priority(): int { return 200; } /** * Returns the db key of the Author_Metadesc custom meta. * * @return string The db key of the Author_Metadesc custom meta. */ public function get_key(): string { return 'wpseo_metadesc'; } /** * Returns the id of the custom meta's form field. * * @return string The id of the custom meta's form field. */ public function get_field_id(): string { return 'wpseo_author_metadesc'; } /** * Returns the meta value. * * @param int $user_id The user ID. * * @return string The meta value. */ public function get_value( $user_id ): string { return \get_the_author_meta( $this->get_key(), $user_id ); } /** * Returns whether the respective global setting is enabled. * * @return bool Whether the respective global setting is enabled. */ public function is_setting_enabled(): bool { return ( ! $this->options_helper->get( 'disable-author' ) ); } /** * Returns whether the custom meta is allowed to be empty. * * @return bool Whether the custom meta is allowed to be empty. */ public function is_empty_allowed(): bool { return true; } /** * Renders the custom meta's field in the user form. * * @param int $user_id The user ID. * * @return void */ public function render_field( $user_id ): void { echo ' <label for="' . \esc_attr( $this->get_field_id() ) . '">' . \esc_html__( 'Meta description to use for Author page', 'wordpress-seo' ) . '</label>'; echo ' <textarea rows="5" cols="30" id="' . \esc_attr( $this->get_field_id() ) . '" class="yoast-settings__textarea yoast-settings__textarea--medium" name="' . \esc_attr( $this->get_field_id() ) . '">' . \esc_textarea( $this->get_value( $user_id ) ) . '</textarea><br>'; } } user-meta/framework/additional-contactmethods/tumblr.php 0000644 00000001270 15025657560 0017621 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Tumblr contactmethod. */ class Tumblr implements Additional_Contactmethod_Interface { /** * Returns the key of the Tumblr contactmethod. * * @return string The key of the Tumblr contactmethod. */ public function get_key(): string { return 'tumblr'; } /** * Returns the label of the Tumblr field. * * @return string The label of the Tumblr field. */ public function get_label(): string { return \__( 'Tumblr profile URL', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/instagram.php 0000644 00000001320 15025657560 0020275 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Instagram contactmethod. */ class Instagram implements Additional_Contactmethod_Interface { /** * Returns the key of the Instagram contactmethod. * * @return string The key of the Instagram contactmethod. */ public function get_key(): string { return 'instagram'; } /** * Returns the label of the Instagram field. * * @return string The label of the Instagram field. */ public function get_label(): string { return \__( 'Instagram profile URL', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/linkedin.php 0000644 00000001310 15025657560 0020104 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Linkedin contactmethod. */ class Linkedin implements Additional_Contactmethod_Interface { /** * Returns the key of the Linkedin contactmethod. * * @return string The key of the Linkedin contactmethod. */ public function get_key(): string { return 'linkedin'; } /** * Returns the label of the Linkedin field. * * @return string The label of the Linkedin field. */ public function get_label(): string { return \__( 'LinkedIn profile URL', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/pinterest.php 0000644 00000001320 15025657560 0020325 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Pinterest contactmethod. */ class Pinterest implements Additional_Contactmethod_Interface { /** * Returns the key of the Pinterest contactmethod. * * @return string The key of the Pinterest contactmethod. */ public function get_key(): string { return 'pinterest'; } /** * Returns the label of the Pinterest field. * * @return string The label of the Pinterest field. */ public function get_label(): string { return \__( 'Pinterest profile URL', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/myspace.php 0000644 00000001300 15025657560 0017747 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Myspace contactmethod. */ class Myspace implements Additional_Contactmethod_Interface { /** * Returns the key of the MySpace contactmethod. * * @return string The key of the MySpace contactmethod. */ public function get_key(): string { return 'myspace'; } /** * Returns the label of the MySpace field. * * @return string The label of the MySpace field. */ public function get_label(): string { return \__( 'MySpace profile URL', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/youtube.php 0000644 00000001300 15025657560 0020002 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Youtube contactmethod. */ class Youtube implements Additional_Contactmethod_Interface { /** * Returns the key of the YouTube contactmethod. * * @return string The key of the YouTube contactmethod. */ public function get_key(): string { return 'youtube'; } /** * Returns the label of the YouTube field. * * @return string The label of the YouTube field. */ public function get_label(): string { return \__( 'YouTube profile URL', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/x.php 0000644 00000001237 15025657560 0016566 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The X contactmethod. */ class X implements Additional_Contactmethod_Interface { /** * Returns the key of the X contactmethod. * * @return string The key of the X contactmethod. */ public function get_key(): string { return 'twitter'; } /** * Returns the label of the X field. * * @return string The label of the X field. */ public function get_label(): string { return \__( 'X username (without @)', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/facebook.php 0000644 00000001310 15025657560 0020060 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Facebook contactmethod. */ class Facebook implements Additional_Contactmethod_Interface { /** * Returns the key of the Facebook contactmethod. * * @return string The key of the Facebook contactmethod. */ public function get_key(): string { return 'facebook'; } /** * Returns the label of the Facebook field. * * @return string The label of the Facebook field. */ public function get_label(): string { return \__( 'Facebook profile URL', 'wordpress-seo' ); } } user-meta/framework/additional-contactmethods/wikipedia.php 0000644 00000001435 15025657560 0020265 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Wikipedia contactmethod. */ class Wikipedia implements Additional_Contactmethod_Interface { /** * Returns the key of the Wikipedia contactmethod. * * @return string The key of the Wikipedia contactmethod. */ public function get_key(): string { return 'wikipedia'; } /** * Returns the label of the Wikipedia field. * * @return string The label of the Wikipedia field. */ public function get_label(): string { return \__( 'Wikipedia page about you', 'wordpress-seo' ) . '<br/><small>' . \__( '(if one exists)', 'wordpress-seo' ) . '</small>'; } } user-meta/framework/additional-contactmethods/soundcloud.php 0000644 00000001330 15025657560 0020470 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods; use Yoast\WP\SEO\User_Meta\Domain\Additional_Contactmethod_Interface; /** * The Soundcloud contactmethod. */ class Soundcloud implements Additional_Contactmethod_Interface { /** * Returns the key of the SoundCloud contactmethod. * * @return string The key of the SoundCloud contactmethod. */ public function get_key(): string { return 'soundcloud'; } /** * Returns the label of the SoundCloud field. * * @return string The label of the SoundCloud field. */ public function get_label(): string { return \__( 'SoundCloud profile URL', 'wordpress-seo' ); } } user-meta/user-interface/custom-meta-integration.php 0000644 00000006273 15025657560 0016675 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\User_Interface; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\User_Can_Edit_Users_Conditional; use Yoast\WP\SEO\Conditionals\User_Edit_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\User_Meta\Application\Custom_Meta_Collector; /** * Handles custom meta for users. */ class Custom_Meta_Integration implements Integration_Interface { /** * The custom meta collector. * * @var Custom_Meta_Collector */ private $custom_meta_collector; /** * The constructor. * * @param Custom_Meta_Collector $custom_meta_collector The custom meta collector. */ public function __construct( Custom_Meta_Collector $custom_meta_collector ) { $this->custom_meta_collector = $custom_meta_collector; } /** * Retrieves the conditionals for the integration. * * @return array<Yoast\WP\SEO\Conditionals> The conditionals. */ public static function get_conditionals() { return [ Admin_Conditional::class, User_Can_Edit_Users_Conditional::class, User_Edit_Conditional::class, ]; } /** * Registers action hook. * * @return void */ public function register_hooks(): void { \add_action( 'show_user_profile', [ $this, 'user_profile' ] ); \add_action( 'edit_user_profile', [ $this, 'user_profile' ] ); \add_action( 'personal_options_update', [ $this, 'process_user_option_update' ] ); \add_action( 'edit_user_profile_update', [ $this, 'process_user_option_update' ] ); } /** * Updates the user metas that (might) have been set on the user profile page. * * @param int $user_id User ID of the updated user. * * @return void */ public function process_user_option_update( $user_id ) { \update_user_meta( $user_id, '_yoast_wpseo_profile_updated', \time() ); if ( ! \check_admin_referer( 'wpseo_user_profile_update', 'wpseo_nonce' ) ) { return; } foreach ( $this->custom_meta_collector->get_custom_meta() as $meta ) { if ( ! $meta->is_setting_enabled() ) { continue; } $meta_field_id = $meta->get_field_id(); $user_input_to_store = isset( $_POST[ $meta_field_id ] ) ? \sanitize_text_field( \wp_unslash( $_POST[ $meta_field_id ] ) ) : ''; if ( $meta->is_empty_allowed() || $user_input_to_store !== '' ) { \update_user_meta( $user_id, $meta->get_key(), $user_input_to_store ); continue; } \delete_user_meta( $user_id, $meta->get_key() ); } } /** * Adds the inputs needed for SEO values to the User Profile page. * * @param WP_User $user User instance to output for. * * @return void */ public function user_profile( $user ) { \wp_nonce_field( 'wpseo_user_profile_update', 'wpseo_nonce' ); /* translators: %1$s expands to Yoast SEO */ $yoast_user_settings_header = \sprintf( \__( '%1$s settings', 'wordpress-seo' ), 'Yoast SEO' ); echo ' <div class="yoast yoast-settings"> <h2 id="wordpress-seo">' . \esc_html( $yoast_user_settings_header ) . '</h2>'; foreach ( $this->custom_meta_collector->get_sorted_custom_meta() as $meta ) { if ( ! $meta->is_setting_enabled() ) { continue; } $meta->render_field( $user->ID ); } \do_action( 'wpseo_render_user_profile', $user ); echo '</div>'; } } user-meta/user-interface/additional-contactmethods-integration.php 0000644 00000005244 15025657560 0021561 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\User_Interface; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\User_Meta\Application\Additional_Contactmethods_Collector; /** * Handles registering and saving additional contactmethods for users. */ class Additional_Contactmethods_Integration implements Integration_Interface { use No_Conditionals; /** * The additional contactmethods collector. * * @var Additional_Contactmethods_Collector */ private $additional_contactmethods_collector; /** * The constructor. * * @param Additional_Contactmethods_Collector $additional_contactmethods_collector The additional contactmethods collector. */ public function __construct( Additional_Contactmethods_Collector $additional_contactmethods_collector ) { $this->additional_contactmethods_collector = $additional_contactmethods_collector; } /** * Registers action hook. * * @return void */ public function register_hooks(): void { \add_filter( 'user_contactmethods', [ $this, 'update_contactmethods' ] ); \add_filter( 'update_user_metadata', [ $this, 'stop_storing_empty_metadata' ], 10, 4 ); } /** * Updates the contactmethods with an additional set of social profiles. * * These are used with the Facebook author, rel="author", Twitter cards implementation, but also in the `sameAs` schema attributes. * * @param array<string, string> $contactmethods Currently set contactmethods. * * @return array<string, string> Contactmethods with added contactmethods. */ public function update_contactmethods( $contactmethods ) { $additional_contactmethods = $this->additional_contactmethods_collector->get_additional_contactmethods_objects(); return \array_merge( ( $contactmethods ?? [] ), $additional_contactmethods ); } /** * Returns a check value, which will stop empty contactmethods from going into the database. * * @param bool|null $check Whether to allow updating metadata for the given type. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * * @return false|null False for when we are to filter out empty metadata, null for no filtering. */ public function stop_storing_empty_metadata( $check, $object_id, $meta_key, $meta_value ) { $additional_contactmethods = $this->additional_contactmethods_collector->get_additional_contactmethods_keys(); if ( \in_array( $meta_key, $additional_contactmethods, true ) && $meta_value === '' ) { \delete_user_meta( $object_id, $meta_key ); return false; } return $check; } } user-meta/user-interface/cleanup-integration.php 0000644 00000002450 15025657560 0016057 0 ustar 00 <?php namespace Yoast\WP\SEO\User_Meta\User_Interface; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\User_Meta\Application\Cleanup_Service; /** * Handles the cleanup for user meta. */ class Cleanup_Integration implements Integration_Interface { use No_Conditionals; /** * The cleanup service. * * @var Cleanup_Service */ private $cleanup_service; /** * The constructor. * * @param Cleanup_Service $cleanup_service The cleanup service. */ public function __construct( Cleanup_Service $cleanup_service ) { $this->cleanup_service = $cleanup_service; } /** * Registers action hook. * * @return void */ public function register_hooks(): void { \add_filter( 'wpseo_misc_cleanup_tasks', [ $this, 'add_user_meta_cleanup_tasks' ] ); } /** * Adds cleanup tasks for the cleanup integration. * * @param Closure[] $tasks Array of tasks to be added. * * @return Closure[] An associative array of tasks to be added to the cleanup integration. */ public function add_user_meta_cleanup_tasks( $tasks ) { return \array_merge( $tasks, [ 'clean_selected_empty_usermeta' => function ( $limit ) { return $this->cleanup_service->cleanup_selected_empty_usermeta( $limit ); }, ] ); } } editors/domain/integrations/integration-data-provider-interface.php 0000644 00000001560 15025657560 0021740 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Editors\Domain\Integrations; /** * Describes the interface for integration domain objects which can be enabled or not */ interface Integration_Data_Provider_Interface { /** * If the integration is activated. * * @return bool If the integration is activated. */ public function is_enabled(): bool; /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array; /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array; } editors/domain/analysis-features/analysis-features-list.php 0000644 00000002410 15025657560 0020254 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Editors\Domain\Analysis_Features; /** * This class describes a list of analysis features. */ class Analysis_Features_List { /** * The features. * * @var array<Analysis_Feature> */ private $features = []; /** * Adds an analysis feature to the list. * * @param Analysis_Feature $feature The analysis feature to add. * * @return void */ public function add_feature( Analysis_Feature $feature ): void { $this->features[] = $feature; } /** * Parses the feature list to a legacy ready array representation. * * @return array<string, bool> The list presented as a key value representation. */ public function parse_to_legacy_array(): array { $array = []; foreach ( $this->features as $feature ) { $array = \array_merge( $array, $feature->to_legacy_array() ); } return $array; } /** * Parses the feature list to an array representation. * * @return array<string, bool> The list presented as a key value representation. */ public function to_array(): array { $array = []; foreach ( $this->features as $feature ) { $array = \array_merge( $array, $feature->to_array() ); } return $array; } } editors/domain/analysis-features/analysis-feature.php 0000644 00000003455 15025657560 0017132 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Editors\Domain\Analysis_Features; /** * This class describes a single Analysis feature and if it is enabled. */ class Analysis_Feature { /** * If the feature is enabled. * * @var bool */ private $is_enabled; /** * What the identifier of the feature is. * * @var string */ private $name; /** * What the identifier is for the old script data array. * * @var string */ private $legacy_key; /** * The constructor. * * @param bool $is_enabled If the feature is enabled. * @param string $name What the identifier of the feature is. * @param string $legacy_key What the identifier is for the old script data array. */ public function __construct( bool $is_enabled, string $name, string $legacy_key ) { $this->is_enabled = $is_enabled; $this->name = $name; $this->legacy_key = $legacy_key; } /** * If the feature is enabled. * * @return bool If the feature is enabled. */ public function is_enabled(): bool { return $this->is_enabled; } /** * Gets the identifier. * * @return string The feature identifier. */ public function get_name(): string { return $this->name; } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ $this->name => $this->is_enabled ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ $this->legacy_key => $this->is_enabled ]; } } editors/domain/analysis-features/analysis-feature-interface.php 0000644 00000001221 15025657560 0021055 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Editors\Domain\Analysis_Features; /** * This interface describes an Analysis feature implementation. */ interface Analysis_Feature_Interface { /** * Returns If the analysis is enabled. * * @return bool */ public function is_enabled(): bool; /** * Returns the name of the object. * * @return string */ public function get_name(): string; /** * Returns the legacy key used in the front-end to determine if the feature is enabled. * * @return string */ public function get_legacy_key(): string; } editors/domain/seo/description.php 0000644 00000002432 15025657560 0013322 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Domain\Seo; /** * This class describes the description SEO data. */ class Description implements Seo_Plugin_Data_Interface { /** * The formatted description date. * * @var string */ private $description_date; /** * The description template. * * @var string */ private $description_template; /** * The constructor. * * @param string $description_date The description date. * @param string $description_template The description template. */ public function __construct( string $description_date, string $description_template ) { $this->description_date = $description_date; $this->description_template = $description_template; } /** * Returns the data as an array format. * * @return array<string> */ public function to_array(): array { return [ 'description_template' => $this->description_template, 'description_date' => $this->description_date, ]; } /** * Returns the data as an array format meant for legacy use. * * @return array<string> */ public function to_legacy_array(): array { return [ 'metadesc_template' => $this->description_template, 'metaDescriptionDate' => $this->description_date, ]; } } editors/domain/seo/keyphrase.php 0000644 00000002713 15025657560 0012774 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Domain\Seo; /** * This class describes the keyphrase SEO data. */ class Keyphrase implements Seo_Plugin_Data_Interface { /** * The keyphrase and the associated posts that use it. * * @var array<string> */ private $keyphrase_usage_count; /** * The post types for the given post IDs. * * @var array<string> */ private $keyphrase_usage_per_type; /** * The constructor. * * @param array<string> $keyphrase_usage_count The keyphrase and the associated posts that use it. * @param array<string> $keyphrase_usage_per_type The post types for the given post IDs. */ public function __construct( array $keyphrase_usage_count, array $keyphrase_usage_per_type ) { $this->keyphrase_usage_count = $keyphrase_usage_count; $this->keyphrase_usage_per_type = $keyphrase_usage_per_type; } /** * Returns the data as an array format. * * @return array<string> */ public function to_array(): array { return [ 'keyphrase_usage' => $this->keyphrase_usage_count, 'keyphrase_usage_per_type' => $this->keyphrase_usage_per_type, ]; } /** * Returns the data as an array format meant for legacy use. * * @return array<string> */ public function to_legacy_array(): array { return [ 'keyword_usage' => $this->keyphrase_usage_count, 'keyword_usage_post_types' => $this->keyphrase_usage_per_type, ]; } } editors/domain/seo/social.php 0000644 00000004422 15025657560 0012252 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Domain\Seo; /** * This class describes the social SEO data. */ class Social implements Seo_Plugin_Data_Interface { /** * The Social title template. * * @var string */ private $social_title_template; /** * The Social description template. * * @var string */ private $social_description_template; /** * The Social image template. * * @var string */ private $social_image_template; /** * The first content image for the social preview. * * @var string */ private $social_first_content_image; /** * The constructor. * * @param string $social_title_template The Social title template. * @param string $social_description_template The Social description template. * @param string $social_image_template The Social image template. * @param string $social_first_content_image The first content image for the social preview. */ public function __construct( string $social_title_template, string $social_description_template, string $social_image_template, string $social_first_content_image ) { $this->social_title_template = $social_title_template; $this->social_description_template = $social_description_template; $this->social_image_template = $social_image_template; $this->social_first_content_image = $social_first_content_image; } /** * Returns the data as an array format. * * @return array<string> */ public function to_array(): array { return [ 'social_title_template' => $this->social_title_template, 'social_description_template' => $this->social_description_template, 'social_image_template' => $this->social_image_template, 'first_content_image_social_preview' => $this->social_first_content_image, ]; } /** * Returns the data as an array format meant for legacy use. * * @return array<string> */ public function to_legacy_array(): array { return [ 'social_title_template' => $this->social_title_template, 'social_description_template' => $this->social_description_template, 'social_image_template' => $this->social_image_template, 'first_content_image' => $this->social_first_content_image, ]; } } editors/domain/seo/seo-plugin-data-interface.php 0000644 00000000731 15025657560 0015726 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Domain\Seo; /** * This class describes the SEO data interface. */ interface Seo_Plugin_Data_Interface { /** * Returns the data as an array format. * * @return array<string> */ public function to_array(): array; /** * Returns the data as an array format meant for legacy use. * * @return array<string> */ public function to_legacy_array(): array; } editors/domain/seo/title.php 0000644 00000002544 15025657560 0012124 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Domain\Seo; /** * This class describes the title SEO data. */ class Title implements Seo_Plugin_Data_Interface { /** * The title template. * * @var string */ private $title_template; /** * The title template without the fallback. * * @var string */ private $title_template_no_fallback; /** * The constructor. * * @param string $title_template The title template. * @param string $title_template_no_fallback The title template without the fallback. */ public function __construct( string $title_template, string $title_template_no_fallback ) { $this->title_template = $title_template; $this->title_template_no_fallback = $title_template_no_fallback; } /** * Returns the data as an array format. * * @return array<string> */ public function to_array(): array { return [ 'title_template' => $this->title_template, 'title_template_no_fallback' => $this->title_template_no_fallback, ]; } /** * Returns the data as an array format meant for legacy use. * * @return array<string> */ public function to_legacy_array(): array { return [ 'title_template' => $this->title_template, 'title_template_no_fallback' => $this->title_template_no_fallback, ]; } } editors/application/integrations/integration-information-repository.php 0000644 00000002027 15025657560 0023036 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Application\Integrations; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; /** * The repository to get all enabled integrations. * * @makePublic */ class Integration_Information_Repository { /** * All plugin integrations. * * @var Integration_Data_Provider_Interface[] */ private $plugin_integrations; /** * The constructor. * * @param Integration_Data_Provider_Interface ...$plugin_integrations All integrations. */ public function __construct( Integration_Data_Provider_Interface ...$plugin_integrations ) { $this->plugin_integrations = $plugin_integrations; } /** * Returns the analysis list. * * @return array<array<string, bool>> The parsed list. */ public function get_integration_information(): array { $array = []; foreach ( $this->plugin_integrations as $feature ) { $array = \array_merge( $array, $feature->to_legacy_array() ); } return $array; } } editors/application/site/website-information-repository.php 0000644 00000002714 15025657560 0020416 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Application\Site; use Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information; use Yoast\WP\SEO\Editors\Framework\Site\Term_Site_Information; /** * This class manages getting the two site information wrappers. * * @makePublic */ class Website_Information_Repository { /** * The post site information wrapper. * * @var Post_Site_Information */ private $post_site_information; /** * The term site information wrapper. * * @var Term_Site_Information */ private $term_site_information; /** * The constructor. * * @param Post_Site_Information $post_site_information The post specific wrapper. * @param Term_Site_Information $term_site_information The term specific wrapper. */ public function __construct( Post_Site_Information $post_site_information, Term_Site_Information $term_site_information ) { $this->post_site_information = $post_site_information; $this->term_site_information = $term_site_information; } /** * Returns the Post Site Information container. * * @return Post_Site_Information */ public function get_post_site_information(): Post_Site_Information { return $this->post_site_information; } /** * Returns the Term Site Information container. * * @return Term_Site_Information */ public function get_term_site_information(): Term_Site_Information { return $this->term_site_information; } } editors/application/analysis-features/enabled-analysis-features-repository.php 0000644 00000004441 15025657560 0024152 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Application\Analysis_Features; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature_Interface; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Features_List; /** * The repository to get all enabled features. * * @makePublic */ class Enabled_Analysis_Features_Repository { /** * All plugin features. * * @var Analysis_Feature_Interface[] */ private $plugin_features; /** * The list of analysis features. * * @var Analysis_Features_List */ private $enabled_analysis_features; /** * The constructor. * * @param Analysis_Feature_Interface ...$plugin_features All analysis objects. */ public function __construct( Analysis_Feature_Interface ...$plugin_features ) { $this->enabled_analysis_features = new Analysis_Features_List(); $this->plugin_features = $plugin_features; } /** * Returns the analysis list. * * @return Analysis_Features_List The analysis list. */ public function get_enabled_features(): Analysis_Features_List { if ( \count( $this->enabled_analysis_features->parse_to_legacy_array() ) === 0 ) { foreach ( $this->plugin_features as $plugin_feature ) { $analysis_feature = new Analysis_Feature( $plugin_feature->is_enabled(), $plugin_feature->get_name(), $plugin_feature->get_legacy_key() ); $this->enabled_analysis_features->add_feature( $analysis_feature ); } } return $this->enabled_analysis_features; } /** * Returns the analysis list for the given names. * * @param array<string> $feature_names The feature names to include. * * @return Analysis_Features_List The analysis list. */ public function get_features_by_keys( array $feature_names ): Analysis_Features_List { $enabled_analysis_features = new Analysis_Features_List(); foreach ( $this->plugin_features as $plugin_feature ) { if ( \in_array( $plugin_feature->get_name(), $feature_names, true ) ) { $analysis_feature = new Analysis_Feature( $plugin_feature->is_enabled(), $plugin_feature->get_name(), $plugin_feature->get_legacy_key() ); $enabled_analysis_features->add_feature( $analysis_feature ); } } return $enabled_analysis_features; } } editors/application/seo/term-seo-information-repository.php 0000644 00000002420 15025657560 0020323 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Application\Seo; use WP_Term; use Yoast\WP\SEO\Editors\Framework\Seo\Terms\Abstract_Term_Seo_Data_Provider; /** * The repository to get term related SEO data. * * @makePublic */ class Term_Seo_Information_Repository { /** * The term. * * @var WP_Term */ private $term; /** * The data providers. * * @var Abstract_Term_Seo_Data_Provider */ private $seo_data_providers; /** * The constructor. * * @param Abstract_Term_Seo_Data_Provider ...$seo_data_providers The providers. */ public function __construct( Abstract_Term_Seo_Data_Provider ...$seo_data_providers ) { $this->seo_data_providers = $seo_data_providers; } /** * The term. * * @param WP_Term $term The term. * * @return void */ public function set_term( WP_Term $term ): void { $this->term = $term; } /** * Method to return the compiled SEO data. * * @return array<string> The specific seo data. */ public function get_seo_data(): array { $array = []; foreach ( $this->seo_data_providers as $data_provider ) { $data_provider->set_term( $this->term ); $array = \array_merge( $array, $data_provider->get_data()->to_legacy_array() ); } return $array; } } editors/application/seo/post-seo-information-repository.php 0000644 00000002411 15025657560 0020341 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Application\Seo; use WP_Post; use Yoast\WP\SEO\Editors\Framework\Seo\Posts\Abstract_Post_Seo_Data_Provider; /** * The repository to get post related SEO data. * * @makePublic */ class Post_Seo_Information_Repository { /** * The post. * * @var WP_Post */ private $post; /** * The data providers. * * @var Abstract_Post_Seo_Data_Provider */ private $seo_data_providers; /** * The constructor. * * @param Abstract_Post_Seo_Data_Provider ...$seo_data_providers The providers. */ public function __construct( Abstract_Post_Seo_Data_Provider ...$seo_data_providers ) { $this->seo_data_providers = $seo_data_providers; } /** * The post. * * @param WP_Post $post The post. * * @return void */ public function set_post( WP_Post $post ) { $this->post = $post; } /** * Method to return the compiled SEO data. * * @return array<string> The specific seo data. */ public function get_seo_data(): array { $array = []; foreach ( $this->seo_data_providers as $data_provider ) { $data_provider->set_post( $this->post ); $array = \array_merge( $array, $data_provider->get_data()->to_legacy_array() ); } return $array; } } editors/framework/keyphrase-analysis.php 0000644 00000003151 15025657560 0014552 0 ustar 00 <?php namespace Yoast\WP\SEO\Editors\Framework; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature_Interface; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Describes how it is determined if the Keyphrase analysis is turned on. */ class Keyphrase_Analysis implements Analysis_Feature_Interface { public const NAME = 'keyphraseAnalysis'; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * If this analysis is enabled. * * @return bool If this analysis is enabled. */ public function is_enabled(): bool { return $this->is_globally_enabled() && $this->is_user_enabled(); } /** * If this analysis is enabled by the user. * * @return bool If this analysis is enabled by the user. */ public function is_user_enabled(): bool { return ! \get_user_meta( \get_current_user_id(), 'wpseo_keyword_analysis_disable', true ); } /** * If this analysis is enabled globally. * * @return bool If this analysis is enabled globally. */ public function is_globally_enabled(): bool { return (bool) $this->options_helper->get( 'keyword_analysis_active', true ); } /** * Gets the name. * * @return string The name. */ public function get_name(): string { return self::NAME; } /** * Gets the legacy key. * * @return string The legacy key. */ public function get_legacy_key(): string { return 'keywordAnalysisActive'; } } editors/framework/integrations/multilingual.php 0000644 00000005570 15025657560 0016161 0 ustar 00 <?php // @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. namespace Yoast\WP\SEO\Editors\Framework\Integrations; use Yoast\WP\SEO\Conditionals\Third_Party\Polylang_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\TranslatePress_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; /** * Describes if the Multilingual integration is enabled. */ class Multilingual implements Integration_Data_Provider_Interface { /** * The WPML conditional. * * @var WPML_Conditional */ private $wpml_conditional; /** * The Polylang conditional. * * @var Polylang_Conditional */ private $polylang_conditional; /** * The TranslatePress conditional. * * @var TranslatePress_Conditional */ private $translate_press_conditional; /** * The constructor. * * @param WPML_Conditional $wpml_conditional The wpml conditional. * @param Polylang_Conditional $polylang_conditional The polylang conditional. * @param TranslatePress_Conditional $translate_press_conditional The translate press conditional. */ public function __construct( WPML_Conditional $wpml_conditional, Polylang_Conditional $polylang_conditional, TranslatePress_Conditional $translate_press_conditional ) { $this->wpml_conditional = $wpml_conditional; $this->polylang_conditional = $polylang_conditional; $this->translate_press_conditional = $translate_press_conditional; } /** * If the integration is activated. * * @return bool If the integration is activated. */ public function is_enabled(): bool { return $this->multilingual_plugin_active(); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ 'isMultilingualActive' => $this->is_enabled() ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ 'multilingualPluginActive' => $this->is_enabled() ]; } /** * Checks whether a multilingual plugin is currently active. Currently, we only check the following plugins: * WPML, Polylang, and TranslatePress. * * @return bool Whether a multilingual plugin is currently active. */ private function multilingual_plugin_active() { $wpml_active = $this->wpml_conditional->is_met(); $polylang_active = $this->polylang_conditional->is_met(); $translatepress_active = $this->translate_press_conditional->is_met(); return ( $wpml_active || $polylang_active || $translatepress_active ); } } editors/framework/integrations/woocommerce-seo.php 0000644 00000003025 15025657560 0016547 0 ustar 00 <?php // @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. namespace Yoast\WP\SEO\Editors\Framework\Integrations; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; /** * Describes if the Woocommerce SEO addon is enabled. */ class WooCommerce_SEO implements Integration_Data_Provider_Interface { /** * The addon manager. * * @var WPSEO_Addon_Manager */ private $addon_manager; /** * The constructor. * * @param WPSEO_Addon_Manager $addon_manager The addon manager. */ public function __construct( WPSEO_Addon_Manager $addon_manager ) { $this->addon_manager = $addon_manager; } /** * If the plugin is activated. * * @return bool If the plugin is activated. */ public function is_enabled(): bool { return \is_plugin_active( $this->addon_manager->get_plugin_file( WPSEO_Addon_Manager::WOOCOMMERCE_SLUG ) ); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the addon is enabled. */ public function to_array(): array { return [ 'isWooCommerceSeoActive' => $this->is_enabled() ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ 'isWooCommerceSeoActive' => $this->is_enabled() ]; } } editors/framework/integrations/wincher.php 0000644 00000004451 15025657560 0015107 0 ustar 00 <?php // @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. namespace Yoast\WP\SEO\Editors\Framework\Integrations; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Wincher_Helper; /** * Describes if the Wincher integration is enabled. */ class Wincher implements Integration_Data_Provider_Interface { /** * The Wincher helper. * * @var Wincher_Helper */ private $wincher_helper; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Wincher_Helper $wincher_helper The Wincher helper. * @param Options_Helper $options_helper The options helper. */ public function __construct( Wincher_Helper $wincher_helper, Options_Helper $options_helper ) { $this->wincher_helper = $wincher_helper; $this->options_helper = $options_helper; } /** * If the integration is activated. * * @return bool If the integration is activated. */ public function is_enabled(): bool { return $this->wincher_helper->is_active(); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ 'active' => $this->is_enabled(), 'loginStatus' => $this->is_enabled() && $this->wincher_helper->login_status(), 'websiteId' => $this->options_helper->get( 'wincher_website_id', '' ), 'autoAddKeyphrases' => $this->options_helper->get( 'wincher_automatically_add_keyphrases', false ), ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ 'wincherIntegrationActive' => $this->is_enabled(), 'wincherLoginStatus' => $this->is_enabled() && $this->wincher_helper->login_status(), 'wincherWebsiteId' => $this->options_helper->get( 'wincher_website_id', '' ), 'wincherAutoAddKeyphrases' => $this->options_helper->get( 'wincher_automatically_add_keyphrases', false ), ]; } } editors/framework/integrations/jetpack-markdown.php 0000644 00000003665 15025657560 0016717 0 ustar 00 <?php // @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. namespace Yoast\WP\SEO\Editors\Framework\Integrations; use Jetpack; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; /** * Describes if the Jetpack markdown integration is enabled. */ class Jetpack_Markdown implements Integration_Data_Provider_Interface { /** * If the integration is activated. * * @return bool If the integration is activated. */ public function is_enabled(): bool { return $this->is_markdown_enabled(); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ 'markdownEnabled' => $this->is_enabled(), ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ 'markdownEnabled' => $this->is_enabled(), ]; } /** * Checks if Jetpack's markdown module is enabled. * Can be extended to work with other plugins that parse markdown in the content. * * @return bool */ private function is_markdown_enabled() { $is_markdown = false; if ( \class_exists( 'Jetpack' ) && \method_exists( 'Jetpack', 'get_active_modules' ) ) { $active_modules = Jetpack::get_active_modules(); // First at all, check if Jetpack's markdown module is active. $is_markdown = \in_array( 'markdown', $active_modules, true ); } /** * Filters whether markdown support is active in the readability- and seo-analysis. * * @since 11.3 * * @param array $is_markdown Is markdown support for Yoast SEO active. */ return \apply_filters( 'wpseo_is_markdown_enabled', $is_markdown ); } } editors/framework/integrations/news-seo.php 0000644 00000002765 15025657560 0015216 0 ustar 00 <?php // @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. namespace Yoast\WP\SEO\Editors\Framework\Integrations; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; /** * Describes if the News SEO plugin is enabled. */ class News_SEO implements Integration_Data_Provider_Interface { /** * The addon manager. * * @var WPSEO_Addon_Manager */ private $addon_manager; /** * The constructor. * * @param WPSEO_Addon_Manager $addon_manager The addon manager. */ public function __construct( WPSEO_Addon_Manager $addon_manager ) { $this->addon_manager = $addon_manager; } /** * If the plugin is activated. * * @return bool If the plugin is activated. */ public function is_enabled(): bool { return \is_plugin_active( $this->addon_manager->get_plugin_file( WPSEO_Addon_Manager::NEWS_SLUG ) ); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ 'isNewsSeoActive' => $this->is_enabled() ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ 'isNewsSeoActive' => $this->is_enabled() ]; } } editors/framework/integrations/woocommerce.php 0000644 00000003077 15025657560 0015772 0 ustar 00 <?php // @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. namespace Yoast\WP\SEO\Editors\Framework\Integrations; use Yoast\WP\SEO\Conditionals\WooCommerce_Conditional; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; /** * Describes if the Woocommerce plugin is enabled. */ class WooCommerce implements Integration_Data_Provider_Interface { /** * The WooCommerce conditional. * * @var WooCommerce_Conditional */ private $woocommerce_conditional; /** * The constructor. * * @param WooCommerce_Conditional $woocommerce_conditional The WooCommerce conditional. */ public function __construct( WooCommerce_Conditional $woocommerce_conditional ) { $this->woocommerce_conditional = $woocommerce_conditional; } /** * If the plugin is activated. * * @return bool If the plugin is activated. */ public function is_enabled(): bool { return $this->woocommerce_conditional->is_met(); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ 'isWooCommerceActive' => $this->is_enabled() ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ 'isWooCommerceActive' => $this->is_enabled() ]; } } editors/framework/integrations/semrush.php 0000644 00000005465 15025657560 0015144 0 ustar 00 <?php // @phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- This namespace should reflect the namespace of the original class. namespace Yoast\WP\SEO\Editors\Framework\Integrations; use Yoast\WP\SEO\Config\SEMrush_Client; use Yoast\WP\SEO\Editors\Domain\Integrations\Integration_Data_Provider_Interface; use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Describes if the Semrush integration is enabled. */ class Semrush implements Integration_Data_Provider_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * If the integration is activated. * * @return bool If the integration is activated. */ public function is_enabled(): bool { return (bool) $this->options_helper->get( 'semrush_integration_active', true ); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ 'active' => $this->is_enabled(), 'countryCode' => $this->options_helper->get( 'semrush_country_code', false ), 'loginStatus' => $this->options_helper->get( 'semrush_integration_active', true ) && $this->get_semrush_login_status(), ]; } /** * Returns this object represented by a key value structure that is compliant with the script data array. * * @return array<string, bool> Returns the legacy key and if the feature is enabled. */ public function to_legacy_array(): array { return [ 'semrushIntegrationActive' => $this->is_enabled(), 'countryCode' => $this->options_helper->get( 'semrush_country_code', false ), 'SEMrushLoginStatus' => $this->options_helper->get( 'semrush_integration_active', true ) && $this->get_semrush_login_status(), ]; } /** * Checks if the user is logged in to SEMrush. * * @return bool The SEMrush login status. */ private function get_semrush_login_status() { try { // Do this just in time to handle constructor exception. $semrush_client = \YoastSEO()->classes->get( SEMrush_Client::class ); } catch ( Empty_Property_Exception $e ) { // Return false if token is malformed (empty property). return false; } // Get token (and refresh it if it's expired). try { $semrush_client->get_tokens(); } catch ( Authentication_Failed_Exception | Empty_Token_Exception $e ) { return false; } return $semrush_client->has_valid_tokens(); } } editors/framework/previously-used-keyphrase.php 0000644 00000002016 15025657560 0016105 0 ustar 00 <?php namespace Yoast\WP\SEO\Editors\Framework; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature_Interface; /** * Describes if the previously used keyword feature should be enabled. */ class Previously_Used_Keyphrase implements Analysis_Feature_Interface { public const NAME = 'previouslyUsedKeyphrase'; /** * If this analysis is enabled. * * @return bool If this analysis is enabled. */ public function is_enabled(): bool { /** * Filter to determine If the PreviouslyUsedKeyphrase assessment should run. * * @param bool $previouslyUsedKeyphraseActive If the PreviouslyUsedKeyphrase assessment should run. */ return (bool) \apply_filters( 'wpseo_previously_used_keyword_active', true ); } /** * Returns the name of the object. * * @return string */ public function get_name(): string { return self::NAME; } /** * Gets the legacy key. * * @return string The legacy key. */ public function get_legacy_key(): string { return 'previouslyUsedKeywordActive'; } } editors/framework/inclusive-language-analysis.php 0000644 00000006277 15025657560 0016355 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature_Interface; use Yoast\WP\SEO\Helpers\Language_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; /** * Describes the inclusive language analysis feature. */ class Inclusive_Language_Analysis implements Analysis_Feature_Interface { public const NAME = 'inclusiveLanguageAnalysis'; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The language helper. * * @var Language_Helper */ private $language_helper; /** * The product helper. * * @var Product_Helper */ private $product_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. * @param Language_Helper $language_helper The language helper. * @param Product_Helper $product_helper The product helper. */ public function __construct( Options_Helper $options_helper, Language_Helper $language_helper, Product_Helper $product_helper ) { $this->options_helper = $options_helper; $this->language_helper = $language_helper; $this->product_helper = $product_helper; } /** * If this analysis is enabled. * * @return bool If this analysis is enabled. */ public function is_enabled(): bool { return $this->is_globally_enabled() && $this->is_user_enabled() && $this->is_current_version_supported() && $this->language_helper->has_inclusive_language_support( $this->language_helper->get_language() ); } /** * If this analysis is enabled by the user. * * @return bool If this analysis is enabled by the user. */ private function is_user_enabled(): bool { return ! \get_user_meta( \get_current_user_id(), 'wpseo_inclusive_language_analysis_disable', true ); } /** * If this analysis is enabled globally. * * @return bool If this analysis is enabled globally. */ private function is_globally_enabled(): bool { return (bool) $this->options_helper->get( 'inclusive_language_analysis_active', false ); } /** * If the inclusive language analysis should be loaded in Free. * * It should always be loaded when Premium is not active. If Premium is active, it depends on the version. Some * Premium versions also have inclusive language code (when it was still a Premium only feature) which would result * in rendering the analysis twice. In those cases, the analysis should be only loaded from the Premium side. * * @return bool If the inclusive language analysis should be loaded. */ private function is_current_version_supported(): bool { $is_premium = $this->product_helper->is_premium(); $premium_version = $this->product_helper->get_premium_version(); return ! $is_premium || \version_compare( $premium_version, '19.6-RC0', '>=' ) || \version_compare( $premium_version, '19.2', '==' ); } /** * Gets the name. * * @return string The name. */ public function get_name(): string { return self::NAME; } /** * Gets the legacy key. * * @return string The legacy key. */ public function get_legacy_key(): string { return 'inclusiveLanguageAnalysisActive'; } } editors/framework/site/base-site-information.php 0000644 00000013415 15025657560 0016105 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework\Site; use Exception; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; use Yoast\WP\SEO\Promotions\Application\Promotion_Manager; use Yoast\WP\SEO\Surfaces\Meta_Surface; /** * The Base_Site_Information class. */ abstract class Base_Site_Information { /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * The wistia embed permission repository. * * @var Wistia_Embed_Permission_Repository */ protected $wistia_embed_permission_repository; /** * The meta surface. * * @var Meta_Surface */ protected $meta; /** * The product helper. * * @var Product_Helper */ protected $product_helper; /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The promotion manager. * * @var Promotion_Manager */ protected $promotion_manager; /** * The constructor. * * @param Short_Link_Helper $short_link_helper The short link helper. * @param Wistia_Embed_Permission_Repository $wistia_embed_permission_repository The wistia embed permission * repository. * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. * @param Options_Helper $options_helper The options helper. * @param Promotion_Manager $promotion_manager The promotion manager. */ public function __construct( Short_Link_Helper $short_link_helper, Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, Product_Helper $product_helper, Options_Helper $options_helper, Promotion_Manager $promotion_manager ) { $this->short_link_helper = $short_link_helper; $this->wistia_embed_permission_repository = $wistia_embed_permission_repository; $this->meta = $meta; $this->product_helper = $product_helper; $this->options_helper = $options_helper; $this->promotion_manager = $promotion_manager; } /** * Returns site information that is the * * @return array<string, string|array<string, string>> * * @throws Exception If an invalid user ID is supplied to the wistia repository. */ public function get_site_information(): array { return [ 'adminUrl' => \admin_url( 'admin.php' ), 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( \get_current_user_id() ), 'site_name' => $this->meta->for_current_page()->site_name, 'contentLocale' => \get_locale(), 'userLocale' => \get_user_locale(), 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), 'showSocial' => [ 'facebook' => $this->options_helper->get( 'opengraph', false ), 'twitter' => $this->options_helper->get( 'twitter', false ), ], 'sitewideSocialImage' => $this->options_helper->get( 'og_default_image' ), // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. 'isPrivateBlog' => ( (string) \get_option( 'blog_public' ) ) === '0', 'currentPromotions' => $this->promotion_manager->get_current_promotions(), 'blackFridayBlockEditorUrl' => ( $this->promotion_manager->is( 'black-friday-2023-checklist' ) ) ? $this->short_link_helper->get( 'https://yoa.st/black-friday-checklist' ) : '', ]; } /** * Returns site information that is the * * @return array<string, string|array<string, string|array<string, string>>> * * @throws Exception If an invalid user ID is supplied to the wistia repository. */ public function get_legacy_site_information(): array { return [ 'adminUrl' => \admin_url( 'admin.php' ), 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( \get_current_user_id() ), 'sitewideSocialImage' => $this->options_helper->get( 'og_default_image' ), // phpcs:ignore Generic.ControlStructures.DisallowYodaConditions -- Bug: squizlabs/PHP_CodeSniffer#2962. 'isPrivateBlog' => ( (string) \get_option( 'blog_public' ) ) === '0', 'currentPromotions' => $this->promotion_manager->get_current_promotions(), 'blackFridayBlockEditorUrl' => ( $this->promotion_manager->is( 'black-friday-2023-checklist' ) ) ? $this->short_link_helper->get( 'https://yoa.st/black-friday-checklist' ) : '', 'metabox' => [ 'site_name' => $this->meta->for_current_page()->site_name, 'contentLocale' => \get_locale(), 'userLocale' => \get_user_locale(), 'isRtl' => \is_rtl(), 'isPremium' => $this->product_helper->is_premium(), 'siteIconUrl' => \get_site_icon_url(), 'showSocial' => [ 'facebook' => $this->options_helper->get( 'opengraph', false ), 'twitter' => $this->options_helper->get( 'twitter', false ), ], ], ]; } } editors/framework/site/post-site-information.php 0000644 00000011543 15025657560 0016160 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework\Site; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; use Yoast\WP\SEO\Promotions\Application\Promotion_Manager; use Yoast\WP\SEO\Surfaces\Meta_Surface; /** * The Post_Site_Information class. */ class Post_Site_Information extends Base_Site_Information { /** * The permalink. * * @var string */ private $permalink; /** * The alert dismissal action. * * @var Alert_Dismissal_Action */ private $alert_dismissal_action; /** * Constructs the class. * * @param Short_Link_Helper $short_link_helper The short link helper. * @param Wistia_Embed_Permission_Repository $wistia_embed_permission_repository The wistia embed permission * repository. * @param Meta_Surface $meta The meta surface. * @param Product_Helper $product_helper The product helper. * @param Alert_Dismissal_Action $alert_dismissal_action The alert dismissal action. * @param Options_Helper $options_helper The options helper. * @param Promotion_Manager $promotion_manager The promotion manager. * * @return void */ public function __construct( Short_Link_Helper $short_link_helper, Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, Meta_Surface $meta, Product_Helper $product_helper, Alert_Dismissal_Action $alert_dismissal_action, Options_Helper $options_helper, Promotion_Manager $promotion_manager ) { parent::__construct( $short_link_helper, $wistia_embed_permission_repository, $meta, $product_helper, $options_helper, $promotion_manager ); $this->alert_dismissal_action = $alert_dismissal_action; } /** * Sets the permalink. * * @param string $permalink The permalink. * * @return void */ public function set_permalink( string $permalink ): void { $this->permalink = $permalink; } /** * Returns post specific site information together with the generic site information. * * @return array<string, string|array<string, string>> */ public function get_legacy_site_information(): array { $dismissed_alerts = $this->alert_dismissal_action->all_dismissed(); $data = [ 'dismissedAlerts' => $dismissed_alerts, 'webinarIntroBlockEditorUrl' => $this->short_link_helper->get( 'https://yoa.st/webinar-intro-block-editor' ), 'metabox' => [ 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), 'base_url' => $this->base_url_for_js(), ], ]; return \array_merge_recursive( $data, parent::get_legacy_site_information() ); } /** * Returns post specific site information together with the generic site information. * * @return array<string, string|string[]> */ public function get_site_information(): array { $dismissed_alerts = $this->alert_dismissal_action->all_dismissed(); $data = [ 'dismissedAlerts' => $dismissed_alerts, 'webinarIntroBlockEditorUrl' => $this->short_link_helper->get( 'https://yoa.st/webinar-intro-block-editor' ), 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), 'base_url' => $this->base_url_for_js(), ]; return \array_merge( $data, parent::get_site_information() ); } /** * Returns the url to search for keyword for the post. * * @return string */ private function search_url(): string { return \admin_url( 'edit.php?seo_kw_filter={keyword}' ); } /** * Returns the url to edit the taxonomy. * * @return string */ private function edit_url(): string { return \admin_url( 'post.php?post={id}&action=edit' ); } /** * Returns a base URL for use in the JS, takes permalink structure into account. * * @return string */ private function base_url_for_js(): string { global $pagenow; // The default base is the home_url. $base_url = \home_url( '/', null ); if ( $pagenow === 'post-new.php' ) { return $base_url; } // If %postname% is the last tag, just strip it and use that as a base. if ( \preg_match( '#%postname%/?$#', $this->permalink ) === 1 ) { $base_url = \preg_replace( '#%postname%/?$#', '', $this->permalink ); } // If %pagename% is the last tag, just strip it and use that as a base. if ( \preg_match( '#%pagename%/?$#', $this->permalink ) === 1 ) { $base_url = \preg_replace( '#%pagename%/?$#', '', $this->permalink ); } return $base_url; } } editors/framework/site/term-site-information.php 0000644 00000004630 15025657560 0016141 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework\Site; use WP_Taxonomy; use WP_Term; /** * The Term_Site_Information class. */ class Term_Site_Information extends Base_Site_Information { /** * The taxonomy. * * @var WP_Taxonomy|false */ private $taxonomy; /** * The term. * * @var WP_Term|string|false */ private $term; /** * Sets the term for the information object and retrieves its taxonomy. * * @param WP_Term|string|false $term The term. * * @return void */ public function set_term( $term ) { $this->term = $term; $this->taxonomy = \get_taxonomy( $term->taxonomy ); } /** * Returns term specific site information together with the generic site information. * * @return array<string, string|string[]> */ public function get_site_information(): array { $data = [ 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), 'base_url' => $this->base_url_for_js(), ]; return \array_merge_recursive( $data, parent::get_site_information() ); } /** * Returns term specific site information together with the generic site information. * * @return array<string, array<string, string>> */ public function get_legacy_site_information(): array { $data = [ 'metabox' => [ 'search_url' => $this->search_url(), 'post_edit_url' => $this->edit_url(), 'base_url' => $this->base_url_for_js(), ], ]; return \array_merge_recursive( $data, parent::get_legacy_site_information() ); } /** * Returns the url to search for keyword for the taxonomy. * * @return string */ private function search_url(): string { return \admin_url( 'edit-tags.php?taxonomy=' . $this->term->taxonomy . '&seo_kw_filter={keyword}' ); } /** * Returns the url to edit the taxonomy. * * @return string */ private function edit_url(): string { return \admin_url( 'term.php?action=edit&taxonomy=' . $this->term->taxonomy . '&tag_ID={id}' ); } /** * Returns a base URL for use in the JS, takes permalink structure into account. * * @return string */ private function base_url_for_js(): string { $base_url = \home_url( '/', null ); if ( ! $this->options_helper->get( 'stripcategorybase', false ) ) { if ( $this->taxonomy->rewrite ) { $base_url = \trailingslashit( $base_url . $this->taxonomy->rewrite['slug'] ); } } return $base_url; } } editors/framework/cornerstone-content.php 0000644 00000002164 15025657560 0014752 0 ustar 00 <?php namespace Yoast\WP\SEO\Editors\Framework; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature_Interface; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Describes if the Cornerstone content features is enabled. */ class Cornerstone_Content implements Analysis_Feature_Interface { public const NAME = 'cornerstoneContent'; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * If cornerstone is enabled. * * @return bool If cornerstone is enabled. */ public function is_enabled(): bool { return (bool) $this->options_helper->get( 'enable_cornerstone_content', false ); } /** * Gets the name. * * @return string The name. */ public function get_name(): string { return self::NAME; } /** * Gets the legacy key. * * @return string The legacy key. */ public function get_legacy_key(): string { return 'cornerstoneActive'; } } editors/framework/word-form-recognition.php 0000644 00000002256 15025657560 0015175 0 ustar 00 <?php namespace Yoast\WP\SEO\Editors\Framework; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature_Interface; use Yoast\WP\SEO\Helpers\Language_Helper; /** * Describes if the word for recognition analysis is enabled */ class Word_Form_Recognition implements Analysis_Feature_Interface { public const NAME = 'wordFormRecognition'; /** * The language helper. * * @var Language_Helper */ private $language_helper; /** * The constructor. * * @param Language_Helper $language_helper The language helper. */ public function __construct( Language_Helper $language_helper ) { $this->language_helper = $language_helper; } /** * If this analysis is enabled. * * @return bool If this analysis is enabled. */ public function is_enabled(): bool { return $this->language_helper->is_word_form_recognition_active( $this->language_helper->get_language() ); } /** * Returns the name of the object. * * @return string */ public function get_name(): string { return self::NAME; } /** * Gets the legacy key. * * @return string The legacy key. */ public function get_legacy_key(): string { return 'wordFormRecognitionActive'; } } editors/framework/readability-analysis.php 0000644 00000003137 15025657560 0015054 0 ustar 00 <?php namespace Yoast\WP\SEO\Editors\Framework; use Yoast\WP\SEO\Editors\Domain\Analysis_Features\Analysis_Feature_Interface; use Yoast\WP\SEO\Helpers\Options_Helper; /** * This class describes the Readability analysis feature. */ class Readability_Analysis implements Analysis_Feature_Interface { public const NAME = 'readabilityAnalysis'; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * If this analysis is enabled. * * @return bool If this analysis is enabled. */ public function is_enabled(): bool { return $this->is_globally_enabled() && $this->is_user_enabled(); } /** * If this analysis is enabled by the user. * * @return bool If this analysis is enabled by the user. */ private function is_user_enabled(): bool { return ! \get_user_meta( \get_current_user_id(), 'wpseo_content_analysis_disable', true ); } /** * If this analysis is enabled globally. * * @return bool If this analysis is enabled globally. */ private function is_globally_enabled(): bool { return (bool) $this->options_helper->get( 'content_analysis_active', true ); } /** * Gets the name. * * @return string The name. */ public function get_name(): string { return self::NAME; } /** * Gets the legacy key. * * @return string The legacy key. */ public function get_legacy_key(): string { return 'contentAnalysisActive'; } } editors/framework/seo/description-data-provider-interface.php 0000644 00000000722 15025657560 0020545 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework\Seo; interface Description_Data_Provider_Interface { /** * Retrieves the description template. * * @return string The description template. */ public function get_description_template(): string; /** * Determines the date to be displayed in the snippet preview. * * @return string */ public function get_description_date(): string; } editors/framework/seo/keyphrase-interface.php 0000644 00000000604 15025657560 0015455 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework\Seo; interface Keyphrase_Interface { /** * Counts the number of given keyphrase used for other posts other than the given post_id. * * @return array<string> The keyphrase and the associated posts that use it. */ public function get_focus_keyphrase_usage(): array; } editors/framework/seo/terms/description-data-provider.php 0000644 00000002274 15025657560 0017745 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Terms; use Yoast\WP\SEO\Editors\Domain\Seo\Description; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Framework\Seo\Description_Data_Provider_Interface; /** * Describes if the description SEO data. */ class Description_Data_Provider extends Abstract_Term_Seo_Data_Provider implements Description_Data_Provider_Interface { /** * Retrieves the description template. * * @return string The description template. */ public function get_description_template(): string { return $this->get_template( 'metadesc' ); } /** * Determines the date to be displayed in the snippet preview. * * @return string */ public function get_description_date(): string { return ''; } /** * Method to return the Description domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { return new Description( $this->get_description_date(), $this->get_description_template() ); } } editors/framework/seo/terms/social-data-provider.php 0000644 00000007115 15025657560 0016673 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Terms; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Domain\Seo\Social; use Yoast\WP\SEO\Editors\Framework\Seo\Social_Data_Provider_Interface; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Describes if the social SEO data. */ class Social_Data_Provider extends Abstract_Term_Seo_Data_Provider implements Social_Data_Provider_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The image helper. * * @var Image_Helper */ private $image_helper; /** * Whether we must return social templates values. * * @var bool */ private $use_social_templates = false; /** * The constructor. * * @param Options_Helper $options_helper The options helper. * @param Image_Helper $image_helper The image helper. */ public function __construct( Options_Helper $options_helper, Image_Helper $image_helper ) { $this->options_helper = $options_helper; $this->image_helper = $image_helper; $this->use_social_templates = $this->use_social_templates(); } /** * Determines whether the social templates should be used. * * @return bool Whether the social templates should be used. */ public function use_social_templates(): bool { return $this->options_helper->get( 'opengraph', false ) === true; } /** * Gets the image url. * * @return string|null */ public function get_image_url(): ?string { return $this->image_helper->get_term_content_image( $this->term->term_id ); } /** * Retrieves the social title template. * * @return string The social title template. */ public function get_social_title_template(): string { if ( $this->use_social_templates ) { return $this->get_social_template( 'title' ); } return ''; } /** * Retrieves the social description template. * * @return string The social description template. */ public function get_social_description_template(): string { if ( $this->use_social_templates ) { return $this->get_social_template( 'description' ); } return ''; } /** * Retrieves the social image template. * * @return string The social description template. */ public function get_social_image_template(): string { if ( $this->use_social_templates ) { return $this->get_social_template( 'image-url' ); } return ''; } /** * Retrieves a social template. * * @param string $template_option_name The name of the option in which the template you want to get is saved. * * @return string */ private function get_social_template( $template_option_name ) { /** * Filters the social template value for a given taxonomy. * * @param string $template The social template value, defaults to empty string. * @param string $template_option_name The subname of the option in which the template you want to get is saved. * @param string $taxonomy The name of the taxonomy. */ return \apply_filters( 'wpseo_social_template_taxonomy', '', $template_option_name, $this->term->taxonomy ); } /** * Method to return the Social domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { return new Social( $this->get_social_title_template(), $this->get_social_description_template(), $this->get_social_image_template(), $this->get_image_url() ); } } editors/framework/seo/terms/abstract-term-seo-data-provider.php 0000644 00000002173 15025657560 0020754 0 ustar 00 <?php //phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Terms; use WP_Term; use WPSEO_Options; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; /** * Abstract class for all term data providers. */ abstract class Abstract_Term_Seo_Data_Provider { /** * The term the metabox formatter is for. * * @var WP_Term */ protected $term; /** * The term. * * @param WP_Term $term The term. * * @return void */ public function set_term( WP_Term $term ): void { $this->term = $term; } /** * Retrieves a template. * * @param string $template_option_name The name of the option in which the template you want to get is saved. * * @return string */ protected function get_template( string $template_option_name ): string { $needed_option = $template_option_name . '-tax-' . $this->term->taxonomy; return WPSEO_Options::get( $needed_option, '' ); } /** * Method to return the compiled SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ abstract public function get_data(): Seo_Plugin_Data_Interface; } editors/framework/seo/terms/title-data-provider.php 0000644 00000002462 15025657560 0016542 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Terms; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Domain\Seo\Title; use Yoast\WP\SEO\Editors\Framework\Seo\Title_Data_Provider_Interface; /** * Describes if the title SEO data. */ class Title_Data_Provider extends Abstract_Term_Seo_Data_Provider implements Title_Data_Provider_Interface { /** * Retrieves the title template. * * @param bool $fallback Whether to return the hardcoded fallback if the template value is empty. * * @return string The title template. */ public function get_title_template( bool $fallback = true ): string { $title = $this->get_template( 'title' ); if ( $title === '' && $fallback === true ) { /* translators: %s expands to the variable used for term title. */ $archives = \sprintf( \__( '%s Archives', 'wordpress-seo' ), '%%term_title%%' ); return $archives . ' %%page%% %%sep%% %%sitename%%'; } return $title; } /** * Method to return the Title domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { return new Title( $this->get_title_template(), $this->get_title_template( false ) ); } } editors/framework/seo/terms/keyphrase-data-provider.php 0000644 00000002315 15025657560 0017411 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Terms; use WPSEO_Taxonomy_Meta; use Yoast\WP\SEO\Editors\Domain\Seo\Keyphrase; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Framework\Seo\Keyphrase_Interface; /** * Describes if the keyphrase SEO data. */ class Keyphrase_Data_Provider extends Abstract_Term_Seo_Data_Provider implements Keyphrase_Interface { /** * Counting the number of given keyphrase used for other term than given term_id. * * @return array<string> */ public function get_focus_keyphrase_usage(): array { $focuskp = WPSEO_Taxonomy_Meta::get_term_meta( $this->term, $this->term->taxonomy, 'focuskw' ); return WPSEO_Taxonomy_Meta::get_keyword_usage( $focuskp, $this->term->term_id, $this->term->taxonomy ); } /** * Method to return the keyphrase domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { $keyphrase_usage = $this->get_focus_keyphrase_usage(); return new Keyphrase( $keyphrase_usage, [] ); } } editors/framework/seo/social-data-provider-interface.php 0000644 00000001473 15025657560 0017500 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework\Seo; interface Social_Data_Provider_Interface { /** * Gets the image URL for the post's social preview. * * @return string|null The image URL for the social preview. */ public function get_image_url(): ?string; /** * Retrieves the social title template. * * @return string The social title template. */ public function get_social_title_template(): string; /** * Retrieves the social description template. * * @return string The social description template. */ public function get_social_description_template(): string; /** * Retrieves the social image template. * * @return string The social description template. */ public function get_social_image_template(): string; } editors/framework/seo/title-data-provider-interface.php 0000644 00000000663 15025657560 0017347 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Editors\Framework\Seo; interface Title_Data_Provider_Interface { /** * Retrieves the title template. * * @param bool $fallback Whether to return the hardcoded fallback if the template value is empty. Default true. * * @return string The title template. */ public function get_title_template( bool $fallback = true ): string; } editors/framework/seo/posts/description-data-provider.php 0000644 00000004433 15025657560 0017762 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Posts; use Yoast\WP\SEO\Editors\Domain\Seo\Description; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Framework\Seo\Description_Data_Provider_Interface; use Yoast\WP\SEO\Helpers\Date_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Describes if the description SEO data. */ class Description_Data_Provider extends Abstract_Post_Seo_Data_Provider implements Description_Data_Provider_Interface { /** * The date helper. * * @var Date_Helper */ private $date_helper; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Date_Helper $date_helper The date helper. * @param Options_Helper $options_helper The options helper. */ public function __construct( Date_Helper $date_helper, Options_Helper $options_helper ) { $this->date_helper = $date_helper; $this->options_helper = $options_helper; } /** * Retrieves the description template. * * @return string The description template. */ public function get_description_template(): string { return $this->get_template( 'metadesc' ); } /** * Determines the date to be displayed in the snippet preview. * * @return string */ public function get_description_date(): string { return $this->date_helper->format_translated( $this->post->post_date, 'M j, Y' ); } /** * Retrieves a template. * * @param string $template_option_name The name of the option in which the template you want to get is saved. * * @return string */ private function get_template( string $template_option_name ): string { $needed_option = $template_option_name . '-' . $this->post->post_type; if ( $this->options_helper->get( $needed_option, '' ) !== '' ) { return $this->options_helper->get( $needed_option ); } return ''; } /** * Method to return the Description domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { return new Description( $this->get_description_date(), $this->get_description_template() ); } } editors/framework/seo/posts/abstract-post-seo-data-provider.php 0000644 00000001340 15025657560 0021003 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Posts; use WP_Post; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; /** * Abstract class for all post data providers. */ abstract class Abstract_Post_Seo_Data_Provider { /** * Holds the WordPress Post. * * @var WP_Post */ protected $post; /** * The post. * * @param WP_Post $post The post. * * @return void */ public function set_post( WP_Post $post ): void { $this->post = $post; } /** * Method to return the compiled SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ abstract public function get_data(): Seo_Plugin_Data_Interface; } editors/framework/seo/posts/social-data-provider.php 0000644 00000007115 15025657560 0016711 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Posts; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Domain\Seo\Social; use Yoast\WP\SEO\Editors\Framework\Seo\Social_Data_Provider_Interface; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Describes if the social SEO data. */ class Social_Data_Provider extends Abstract_Post_Seo_Data_Provider implements Social_Data_Provider_Interface { /** * The image helper. * * @var Image_Helper */ private $image_helper; /** * Whether we must return social templates values. * * @var bool */ private $use_social_templates = false; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. * @param Image_Helper $image_helper The image helper. */ public function __construct( Options_Helper $options_helper, Image_Helper $image_helper ) { $this->options_helper = $options_helper; $this->use_social_templates = $this->use_social_templates(); $this->image_helper = $image_helper; } /** * Determines whether the social templates should be used. * * @return bool Whether the social templates should be used. */ private function use_social_templates(): bool { return $this->options_helper->get( 'opengraph', false ) === true; } /** * Gets the image url. * * @return string|null */ public function get_image_url(): ?string { return $this->image_helper->get_post_content_image( $this->post->ID ); } /** * Retrieves the social title template. * * @return string The social title template. */ public function get_social_title_template(): string { if ( $this->use_social_templates ) { return $this->get_social_template( 'title' ); } return ''; } /** * Retrieves the social description template. * * @return string The social description template. */ public function get_social_description_template(): string { if ( $this->use_social_templates ) { return $this->get_social_template( 'description' ); } return ''; } /** * Retrieves the social image template. * * @return string The social description template. */ public function get_social_image_template(): string { if ( $this->use_social_templates ) { return $this->get_social_template( 'image-url' ); } return ''; } /** * Retrieves a social template. * * @param string $template_option_name The name of the option in which the template you want to get is saved. * * @return string */ private function get_social_template( $template_option_name ) { /** * Filters the social template value for a given post type. * * @param string $template The social template value, defaults to empty string. * @param string $template_option_name The subname of the option in which the template you want to get is saved. * @param string $post_type The name of the post type. */ return \apply_filters( 'wpseo_social_template_post_type', '', $template_option_name, $this->post->post_type ); } /** * Method to return the Social domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { return new Social( $this->get_social_title_template(), $this->get_social_description_template(), $this->get_social_image_template(), $this->get_image_url() ); } } editors/framework/seo/posts/title-data-provider.php 0000644 00000003770 15025657560 0016563 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Posts; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Domain\Seo\Title; use Yoast\WP\SEO\Editors\Framework\Seo\Title_Data_Provider_Interface; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Describes if the title SEO data. */ class Title_Data_Provider extends Abstract_Post_Seo_Data_Provider implements Title_Data_Provider_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Retrieves the title template. * * @param bool $fallback Whether to return the hardcoded fallback if the template value is empty. * * @return string The title template. */ public function get_title_template( bool $fallback = true ): string { $title = $this->get_template( 'title' ); if ( $title === '' && $fallback === true ) { return '%%title%% %%page%% %%sep%% %%sitename%%'; } return $title; } /** * Retrieves a template. * * @param string $template_option_name The name of the option in which the template you want to get is saved. * * @return string */ private function get_template( string $template_option_name ): string { $needed_option = $template_option_name . '-' . $this->post->post_type; if ( $this->options_helper->get( $needed_option, '' ) !== '' ) { return $this->options_helper->get( $needed_option ); } return ''; } /** * Method to return the Title domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { return new Title( $this->get_title_template(), $this->get_title_template( false ) ); } } editors/framework/seo/posts/keyphrase-data-provider.php 0000644 00000006003 15025657560 0017425 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Editors\Framework\Seo\Posts; use WPSEO_Meta; use Yoast\WP\SEO\Editors\Domain\Seo\Keyphrase; use Yoast\WP\SEO\Editors\Domain\Seo\Seo_Plugin_Data_Interface; use Yoast\WP\SEO\Editors\Framework\Seo\Keyphrase_Interface; use Yoast\WP\SEO\Helpers\Meta_Helper; /** * Describes if the keyphrase SEO data. */ class Keyphrase_Data_Provider extends Abstract_Post_Seo_Data_Provider implements Keyphrase_Interface { /** * The meta helper. * * @var Meta_Helper */ private $meta_helper; /** * The constructor. * * @param Meta_Helper $meta_helper The meta helper. */ public function __construct( Meta_Helper $meta_helper ) { $this->meta_helper = $meta_helper; } /** * Counts the number of given Keyphrase used for other posts other than the given post_id. * * @return array<string> The keyphrase and the associated posts that use it. */ public function get_focus_keyphrase_usage(): array { $keyphrase = $this->meta_helper->get_value( 'focuskw', $this->post->ID ); $usage = [ $keyphrase => $this->get_keyphrase_usage_for_current_post( $keyphrase ) ]; /** * Allows enhancing the array of posts' that share their focus Keyphrase with the post's related Keyphrase. * * @param array<string> $usage The array of posts' ids that share their focus Keyphrase with the post. * @param int $post_id The id of the post we're finding the usage of related Keyphrase for. */ return \apply_filters( 'wpseo_posts_for_related_keywords', $usage, $this->post->ID ); } /** * Retrieves the post types for the given post IDs. * * @param array<string|array<string>> $post_ids_per_keyphrase An associative array with keyphrase as keys and an array of post ids where those keyphrases are used. * * @return array<string|array<string>> The post types for the given post IDs. */ public function get_post_types_for_all_ids( array $post_ids_per_keyphrase ): array { $post_type_per_keyphrase_result = []; foreach ( $post_ids_per_keyphrase as $keyphrase => $post_ids ) { $post_type_per_keyphrase_result[ $keyphrase ] = WPSEO_Meta::post_types_for_ids( $post_ids ); } return $post_type_per_keyphrase_result; } /** * Gets the keyphrase usage for the current post and the specified keyphrase. * * @param string $keyphrase The keyphrase to check the usage of. * * @return array<string> The post IDs which use the passed keyphrase. */ private function get_keyphrase_usage_for_current_post( string $keyphrase ): array { return WPSEO_Meta::keyword_usage( $keyphrase, $this->post->ID ); } /** * Method to return the keyphrase domain object with SEO data. * * @return Seo_Plugin_Data_Interface The specific seo data. */ public function get_data(): Seo_Plugin_Data_Interface { $keyphrase_usage = $this->get_focus_keyphrase_usage(); return new Keyphrase( $keyphrase_usage, $this->get_post_types_for_all_ids( $keyphrase_usage ) ); } } generated/assets/languages.php 0000644 00000006034 15025657560 0012501 0 ustar 00 <?php return array('default.js' => array('dependencies' => array('wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'dcd5887e134799f6ade3'), 'ar.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '0d834af7f322c207067e'), 'ca.js' => array('dependencies' => array('wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '469209f8725d57e2dd13'), 'cs.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '8e4aac0747a3c7dddf8a'), 'de.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '4c4279bd1175e077993d'), 'el.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'd72edcab364d544c2469'), 'en.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '53ed833111ad00b16c23'), 'es.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '05fad82b6228df03c7c3'), 'fa.js' => array('dependencies' => array('wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'fa755a3b45ed946e75bb'), 'fr.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'a23c2630b045abc90aec'), 'he.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '081c52437d052c9cad74'), 'hu.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'd173d9d977c5b068443b'), 'id.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'ca63950fdd7d6938e7f0'), 'it.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'fb28921dba0da6614c80'), 'ja.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '2c5009841fa296834a3f'), 'nb.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '04746196da02bb736987'), 'nl.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'c324d67321be4de5dba8'), 'pl.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '5908ab61bfa52f1abf82'), 'pt.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => '36a5b8e949ba05f62ab9'), 'ru.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'f57759f2e75aed95a722'), 'sk.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'be3d5afc018c7be19ca5'), 'sv.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'aa3c53ba0e8c760d56ec'), 'tr.js' => array('dependencies' => array('lodash', 'wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'bcd0ead95eae50bee792')); generated/assets/externals.php 0000644 00000007715 15025657560 0012547 0 ustar 00 <?php return array('reduxJsToolkit.js' => array('dependencies' => array('wp-polyfill', 'yoast-seo-redux-package'), 'version' => '425acbd30b98c737df6e'), 'analysisReport.js' => array('dependencies' => array('lodash', 'react', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => 'd0e5327879ffb91fe37b'), 'componentsNew.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-a11y', 'wp-i18n', 'wp-polyfill', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-select', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => 'ddc246fa4b9e1b19f400'), 'dashboardFrontend.js' => array('dependencies' => array('lodash', 'react', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-chart.js-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-ui-library-package'), 'version' => 'a9455d55b8d87f4dbd05'), 'featureFlag.js' => array('dependencies' => array('wp-polyfill'), 'version' => '91e54e3dd01f59a724ae'), 'helpers.js' => array('dependencies' => array('lodash', 'react', 'wp-i18n', 'wp-polyfill', 'yoast-seo-prop-types-package', 'yoast-seo-styled-components-package'), 'version' => 'e7a33d29c6f511c40828'), 'relatedKeyphraseSuggestions.js' => array('dependencies' => array('lodash', 'react', 'wp-i18n', 'wp-polyfill', 'yoast-seo-prop-types-package', 'yoast-seo-ui-library-package'), 'version' => '203919b63461f2fda2c4'), 'replacementVariableEditor.js' => array('dependencies' => array('lodash', 'react', 'wp-a11y', 'wp-components', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-draft-js-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => 'f51d740705a1e611b964'), 'searchMetadataPreviews.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-i18n', 'wp-polyfill', 'yoast-seo-analysis-package', 'yoast-seo-components-new-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => '601dfae7874832482907'), 'socialMetadataForms.js' => array('dependencies' => array('lodash', 'react', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-redux-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => '9da36977b02b7b110e01'), 'styleGuide.js' => array('dependencies' => array('wp-polyfill', 'yoast-seo-helpers-package', 'yoast-seo-styled-components-package'), 'version' => 'a65ddb8de826da5fea4d'), 'uiLibrary.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-polyfill', 'yoast-seo-prop-types-package', 'yoast-seo-redux-js-toolkit-package'), 'version' => '0850817acd64e67dfcf2'), 'chart.js.js' => array('dependencies' => array('wp-polyfill'), 'version' => '196fb6740f0ef8ce192a'), 'draftJs.js' => array('dependencies' => array('react', 'react-dom', 'wp-polyfill'), 'version' => 'f03ff0ae2ee1cf6bbc54'), 'jed.js' => array('dependencies' => array('wp-polyfill'), 'version' => '28697086e82ae1cd0e88'), 'propTypes.js' => array('dependencies' => array('wp-polyfill'), 'version' => '4c546a0c9e97b70d3fe0'), 'reactHelmet.js' => array('dependencies' => array('react', 'wp-polyfill', 'yoast-seo-prop-types-package'), 'version' => 'b7d9f84f1dc499388f58'), 'redux.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'e83451c8529be91b4af7'), 'styledComponents.js' => array('dependencies' => array('react', 'wp-polyfill'), 'version' => 'f030a78c47ee9be46c07'), 'analysis.js' => array('dependencies' => array('lodash', 'wp-i18n', 'wp-polyfill', 'yoast-seo-feature-flag-package'), 'version' => 'e8d423c8366007a5bec9')); generated/assets/plugin.php 0000644 00000032266 15025657560 0012037 0 ustar 00 <?php return array('addon-installation.js' => array('dependencies' => array('react', 'wp-components', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-prop-types-package', 'yoast-seo-styled-components-package'), 'version' => 'b9f59c3fa79d3c219f77'), 'admin-global.js' => array('dependencies' => array('jquery', 'wp-polyfill'), 'version' => 'ee3745f3fdc45e43fb51'), 'admin-modules.js' => array('dependencies' => array('react', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package'), 'version' => 'fdbe5d62f7cf6543e337'), 'analysis-worker.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'aa04978fbd423b404462'), 'api-client.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'f56d7de163fa219c67e2'), 'block-editor.js' => array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-annotations', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-edit-post', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-plugins', 'wp-polyfill', 'wp-rich-text', 'wp-sanitize', 'wp-url', 'yoast-seo-analysis-package', 'yoast-seo-chart.js-package', 'yoast-seo-components-new-package', 'yoast-seo-externals-components', 'yoast-seo-externals-contexts', 'yoast-seo-externals-redux', 'yoast-seo-feature-flag-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-related-keyphrase-suggestions-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-search-metadata-previews-package', 'yoast-seo-social-metadata-forms-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => '70d2eef042afe97a4d2e'), 'bulk-editor.js' => array('dependencies' => array('jquery', 'wp-polyfill'), 'version' => '308d4f19cc8fcb346d3d'), 'classic-editor.js' => array('dependencies' => array('jquery', 'lodash', 'moment', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-sanitize', 'wp-url', 'yoast-seo-analysis-package', 'yoast-seo-chart.js-package', 'yoast-seo-components-new-package', 'yoast-seo-externals-components', 'yoast-seo-externals-contexts', 'yoast-seo-externals-redux', 'yoast-seo-feature-flag-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-related-keyphrase-suggestions-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-search-metadata-previews-package', 'yoast-seo-social-metadata-forms-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => '9f33473d44ff1b70790d'), 'crawl-settings.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'd511931b46d0b74648b4'), 'dashboard-widget.js' => array('dependencies' => array('react', 'wp-element', 'wp-polyfill', 'yoast-seo-analysis-report-package', 'yoast-seo-components-new-package', 'yoast-seo-helpers-package', 'yoast-seo-style-guide-package'), 'version' => 'ad6147e367cc50376cc5'), 'wincher-dashboard-widget.js' => array('dependencies' => array('lodash', 'moment', 'react', 'wp-api-fetch', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => '2dd220d7b48b6315a5f3'), 'dynamic-blocks.js' => array('dependencies' => array('react', 'wp-block-editor', 'wp-blocks', 'wp-polyfill', 'wp-server-side-render'), 'version' => '739ebde8b3e418b9886a'), 'edit-page.js' => array('dependencies' => array('jquery', 'wp-polyfill'), 'version' => 'afab9d8fdff1d98c8ca9'), 'editor-modules.js' => array('dependencies' => array('lodash', 'moment', 'react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-sanitize', 'wp-url', 'yoast-seo-analysis-package', 'yoast-seo-analysis-report-package', 'yoast-seo-chart.js-package', 'yoast-seo-components-new-package', 'yoast-seo-externals-contexts', 'yoast-seo-externals-redux', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-related-keyphrase-suggestions-package', 'yoast-seo-social-metadata-forms-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => 'e68c99ac23fcabaf8113'), 'elementor.js' => array('dependencies' => array('lodash', 'moment', 'react', 'react-dom', 'wp-annotations', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-rich-text', 'wp-sanitize', 'wp-url', 'yoast-seo-analysis-package', 'yoast-seo-chart.js-package', 'yoast-seo-components-new-package', 'yoast-seo-externals-components', 'yoast-seo-externals-contexts', 'yoast-seo-externals-redux', 'yoast-seo-feature-flag-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-related-keyphrase-suggestions-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-search-metadata-previews-package', 'yoast-seo-social-metadata-forms-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => '6e4c0f2c921f354f6a1e'), 'externals-components.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-url', 'yoast-seo-analysis-package', 'yoast-seo-analysis-report-package', 'yoast-seo-components-new-package', 'yoast-seo-externals-contexts', 'yoast-seo-externals-redux', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-related-keyphrase-suggestions-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => '8f5ad622b295f880946f'), 'externals-contexts.js' => array('dependencies' => array('react', 'wp-element', 'wp-polyfill', 'yoast-seo-prop-types-package'), 'version' => '10ecaeb7fee15b420938'), 'externals-redux.js' => array('dependencies' => array('lodash', 'react', 'wp-api-fetch', 'wp-data', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-sanitize', 'wp-url', 'yoast-seo-helpers-package', 'yoast-seo-redux-js-toolkit-package'), 'version' => 'f41e10aa9d403f53d476'), 'filter-explanation.js' => array('dependencies' => array('wp-polyfill'), 'version' => '8b3042cee26c58eb9be7'), 'help-scout-beacon.js' => array('dependencies' => array('react', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-styled-components-package'), 'version' => '4cff4f48af6493046757'), 'import.js' => array('dependencies' => array('jquery', 'lodash', 'wp-i18n', 'wp-polyfill'), 'version' => 'cbe848d7253c616f3a75'), 'indexation.js' => array('dependencies' => array('jquery', 'react', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-style-guide-package', 'yoast-seo-styled-components-package'), 'version' => '874600c8f036af9d81b5'), 'installation-success.js' => array('dependencies' => array('lodash', 'react', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill'), 'version' => '413369901a13055f6d7f'), 'integrations-page.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-dashboard-frontend-package', 'yoast-seo-externals-contexts', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => 'f5e90817778ec01d1902'), 'introductions.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-polyfill', 'wp-url', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-ui-library-package'), 'version' => 'e6de833613a93647262e'), 'network-admin.js' => array('dependencies' => array('jquery', 'wp-polyfill'), 'version' => 'c28de4314d03147fca4a'), 'post-edit.js' => array('dependencies' => array('jquery', 'lodash', 'react', 'react-dom', 'wp-annotations', 'wp-api', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-rich-text', 'wp-url', 'yoast-seo-analysis-package', 'yoast-seo-externals-redux', 'yoast-seo-feature-flag-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => 'cbfdfa571c12a8b587a8'), 'quick-edit-handler.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'e7d3f8a9873afbfd1425'), 'reindex-links.js' => array('dependencies' => array('jquery', 'wp-polyfill'), 'version' => 'e4694eb7292052d53fc4'), 'redirect-old-features-tab.js' => array('dependencies' => array('wp-polyfill'), 'version' => 'a792fdd4c0d1c2ef737c'), 'settings.js' => array('dependencies' => array('jquery', 'lodash', 'react', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url', 'yoast-seo-externals-redux', 'yoast-seo-prop-types-package', 'yoast-seo-styled-components-package'), 'version' => '0b6302a0ba04dc4a33a0'), 'new-settings.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-polyfill', 'wp-url', 'yoast-seo-externals-redux', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-replacement-variable-editor-package', 'yoast-seo-styled-components-package', 'yoast-seo-ui-library-package'), 'version' => 'fb091245476ee316a12f'), 'academy.js' => array('dependencies' => array('lodash', 'react', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-ui-library-package'), 'version' => '609712bcb8180edc0519'), 'general-page.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url', 'yoast-seo-dashboard-frontend-package', 'yoast-seo-externals-redux', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-social-metadata-forms-package', 'yoast-seo-ui-library-package'), 'version' => 'bddadfb45fece937b727'), 'support.js' => array('dependencies' => array('lodash', 'react', 'react-dom', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-url', 'yoast-seo-externals-redux', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-ui-library-package'), 'version' => 'c26f921a99aa5e818b92'), 'how-to-block.js' => array('dependencies' => array('lodash', 'react', 'wp-a11y', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'yoast-seo-prop-types-package', 'yoast-seo-styled-components-package'), 'version' => '70091636e473d2d87583'), 'faq-block.js' => array('dependencies' => array('react', 'wp-a11y', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-element', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'yoast-seo-prop-types-package'), 'version' => '34ebc93abf046084fe1c'), 'term-edit.js' => array('dependencies' => array('jquery', 'lodash', 'react', 'react-dom', 'wp-annotations', 'wp-api', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-hooks', 'wp-i18n', 'wp-is-shallow-equal', 'wp-polyfill', 'wp-rich-text', 'wp-url', 'yoast-seo-analysis-package', 'yoast-seo-externals-redux', 'yoast-seo-feature-flag-package', 'yoast-seo-prop-types-package', 'yoast-seo-react-helmet-package', 'yoast-seo-redux-js-toolkit-package', 'yoast-seo-ui-library-package'), 'version' => '69a82e4731e46b22294e'), 'used-keywords-assessment.js' => array('dependencies' => array('wp-polyfill', 'yoast-seo-analysis-package'), 'version' => 'f2d934f4e70fdace40fc'), 'react-select.js' => array('dependencies' => array('react', 'react-dom', 'wp-polyfill', 'yoast-seo-prop-types-package'), 'version' => '016b19f563b56deb8ad5'), 'workouts.js' => array('dependencies' => array('lodash', 'react', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-data', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-polyfill', 'yoast-seo-components-new-package', 'yoast-seo-externals-contexts', 'yoast-seo-helpers-package', 'yoast-seo-prop-types-package', 'yoast-seo-styled-components-package'), 'version' => '5285008d6544c5f1c0d4'), 'frontend-inspector-resources.js' => array('dependencies' => array('lodash', 'react', 'wp-i18n', 'wp-polyfill', 'yoast-seo-analysis-package', 'yoast-seo-components-new-package', 'yoast-seo-prop-types-package', 'yoast-seo-style-guide-package'), 'version' => '9f0c47d9fdc1587c505a')); generated/container.php 0000644 00002261177 15025657560 0011230 0 ustar 00 <?php namespace Yoast\WP\SEO\Generated; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ContainerInterface; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Container; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Exception\LogicException; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\Exception\RuntimeException; use YoastSEO_Vendor\Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; /** * This class has been auto-generated * by the Symfony Dependency Injection Component. * * @final since Symfony 3.3 */ class Cached_Container extends Container { private $parameters = []; private $targetDirs = []; public function __construct() { $this->services = []; $this->normalizedIds = [ 'wpseo_addon_manager' => 'WPSEO_Addon_Manager', 'wpseo_admin_asset_manager' => 'WPSEO_Admin_Asset_Manager', 'wpseo_breadcrumbs' => 'WPSEO_Breadcrumbs', 'wpseo_frontend' => 'WPSEO_Frontend', 'wpseo_replace_vars' => 'WPSEO_Replace_Vars', 'wpseo_shortlinker' => 'WPSEO_Shortlinker', 'wpseo_utils' => 'WPSEO_Utils', 'yoast\\wp\\lib\\migrations\\adapter' => 'Yoast\\WP\\Lib\\Migrations\\Adapter', 'yoast\\wp\\seo\\actions\\addon_installation\\addon_activate_action' => 'Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Activate_Action', 'yoast\\wp\\seo\\actions\\addon_installation\\addon_install_action' => 'Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Install_Action', 'yoast\\wp\\seo\\actions\\alert_dismissal_action' => 'Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action', 'yoast\\wp\\seo\\actions\\configuration\\first_time_configuration_action' => 'Yoast\\WP\\SEO\\Actions\\Configuration\\First_Time_Configuration_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_cleanup_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Cleanup_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_custom_archive_settings_importing_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_default_archive_settings_importing_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_general_settings_importing_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_posts_importing_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_posttype_defaults_settings_importing_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_taxonomy_settings_importing_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action', 'yoast\\wp\\seo\\actions\\importing\\aioseo\\aioseo_validate_data_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Validate_Data_Action', 'yoast\\wp\\seo\\actions\\importing\\deactivate_conflicting_plugins_action' => 'Yoast\\WP\\SEO\\Actions\\Importing\\Deactivate_Conflicting_Plugins_Action', 'yoast\\wp\\seo\\actions\\indexables\\indexable_head_action' => 'Yoast\\WP\\SEO\\Actions\\Indexables\\Indexable_Head_Action', 'yoast\\wp\\seo\\actions\\indexing\\indexable_general_indexation_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action', 'yoast\\wp\\seo\\actions\\indexing\\indexable_indexing_complete_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action', 'yoast\\wp\\seo\\actions\\indexing\\indexable_post_indexation_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action', 'yoast\\wp\\seo\\actions\\indexing\\indexable_post_type_archive_indexation_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action', 'yoast\\wp\\seo\\actions\\indexing\\indexable_term_indexation_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action', 'yoast\\wp\\seo\\actions\\indexing\\indexing_complete_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Complete_Action', 'yoast\\wp\\seo\\actions\\indexing\\indexing_prepare_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Prepare_Action', 'yoast\\wp\\seo\\actions\\indexing\\post_link_indexing_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action', 'yoast\\wp\\seo\\actions\\indexing\\term_link_indexing_action' => 'Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action', 'yoast\\wp\\seo\\actions\\integrations_action' => 'Yoast\\WP\\SEO\\Actions\\Integrations_Action', 'yoast\\wp\\seo\\actions\\semrush\\semrush_login_action' => 'Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Login_Action', 'yoast\\wp\\seo\\actions\\semrush\\semrush_options_action' => 'Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Options_Action', 'yoast\\wp\\seo\\actions\\semrush\\semrush_phrases_action' => 'Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Phrases_Action', 'yoast\\wp\\seo\\actions\\wincher\\wincher_account_action' => 'Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Account_Action', 'yoast\\wp\\seo\\actions\\wincher\\wincher_keyphrases_action' => 'Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Keyphrases_Action', 'yoast\\wp\\seo\\actions\\wincher\\wincher_login_action' => 'Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Login_Action', 'yoast\\wp\\seo\\analytics\\application\\missing_indexables_collector' => 'Yoast\\WP\\SEO\\Analytics\\Application\\Missing_Indexables_Collector', 'yoast\\wp\\seo\\analytics\\application\\to_be_cleaned_indexables_collector' => 'Yoast\\WP\\SEO\\Analytics\\Application\\To_Be_Cleaned_Indexables_Collector', 'yoast\\wp\\seo\\analytics\\user_interface\\last_completed_indexation_integration' => 'Yoast\\WP\\SEO\\Analytics\\User_Interface\\Last_Completed_Indexation_Integration', 'yoast\\wp\\seo\\builders\\indexable_author_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder', 'yoast\\wp\\seo\\builders\\indexable_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Builder', 'yoast\\wp\\seo\\builders\\indexable_date_archive_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Date_Archive_Builder', 'yoast\\wp\\seo\\builders\\indexable_hierarchy_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder', 'yoast\\wp\\seo\\builders\\indexable_home_page_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Home_Page_Builder', 'yoast\\wp\\seo\\builders\\indexable_link_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder', 'yoast\\wp\\seo\\builders\\indexable_post_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder', 'yoast\\wp\\seo\\builders\\indexable_post_type_archive_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Type_Archive_Builder', 'yoast\\wp\\seo\\builders\\indexable_system_page_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_System_Page_Builder', 'yoast\\wp\\seo\\builders\\indexable_term_builder' => 'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder', 'yoast\\wp\\seo\\builders\\primary_term_builder' => 'Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder', 'yoast\\wp\\seo\\commands\\cleanup_command' => 'Yoast\\WP\\SEO\\Commands\\Cleanup_Command', 'yoast\\wp\\seo\\commands\\index_command' => 'Yoast\\WP\\SEO\\Commands\\Index_Command', 'yoast\\wp\\seo\\conditionals\\addon_installation_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional', 'yoast\\wp\\seo\\conditionals\\admin\\doing_post_quick_edit_save_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Doing_Post_Quick_Edit_Save_Conditional', 'yoast\\wp\\seo\\conditionals\\admin\\estimated_reading_time_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Estimated_Reading_Time_Conditional', 'yoast\\wp\\seo\\conditionals\\admin\\licenses_page_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Licenses_Page_Conditional', 'yoast\\wp\\seo\\conditionals\\admin\\non_network_admin_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Non_Network_Admin_Conditional', 'yoast\\wp\\seo\\conditionals\\admin\\post_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Post_Conditional', 'yoast\\wp\\seo\\conditionals\\admin\\posts_overview_or_ajax_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Posts_Overview_Or_Ajax_Conditional', 'yoast\\wp\\seo\\conditionals\\admin\\yoast_admin_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Yoast_Admin_Conditional', 'yoast\\wp\\seo\\conditionals\\admin_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Admin_Conditional', 'yoast\\wp\\seo\\conditionals\\attachment_redirections_enabled_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Attachment_Redirections_Enabled_Conditional', 'yoast\\wp\\seo\\conditionals\\check_required_version_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Check_Required_Version_Conditional', 'yoast\\wp\\seo\\conditionals\\deactivating_yoast_seo_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Deactivating_Yoast_Seo_Conditional', 'yoast\\wp\\seo\\conditionals\\development_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Development_Conditional', 'yoast\\wp\\seo\\conditionals\\front_end_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Front_End_Conditional', 'yoast\\wp\\seo\\conditionals\\get_request_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Get_Request_Conditional', 'yoast\\wp\\seo\\conditionals\\google_site_kit_feature_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Google_Site_Kit_Feature_Conditional', 'yoast\\wp\\seo\\conditionals\\headless_rest_endpoints_enabled_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Headless_Rest_Endpoints_Enabled_Conditional', 'yoast\\wp\\seo\\conditionals\\import_tool_selected_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Import_Tool_Selected_Conditional', 'yoast\\wp\\seo\\conditionals\\jetpack_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Jetpack_Conditional', 'yoast\\wp\\seo\\conditionals\\migrations_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Migrations_Conditional', 'yoast\\wp\\seo\\conditionals\\new_settings_ui_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\New_Settings_Ui_Conditional', 'yoast\\wp\\seo\\conditionals\\news_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\News_Conditional', 'yoast\\wp\\seo\\conditionals\\no_tool_selected_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\No_Tool_Selected_Conditional', 'yoast\\wp\\seo\\conditionals\\non_multisite_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Non_Multisite_Conditional', 'yoast\\wp\\seo\\conditionals\\not_admin_ajax_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Not_Admin_Ajax_Conditional', 'yoast\\wp\\seo\\conditionals\\open_graph_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Open_Graph_Conditional', 'yoast\\wp\\seo\\conditionals\\premium_active_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Premium_Active_Conditional', 'yoast\\wp\\seo\\conditionals\\premium_inactive_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Premium_Inactive_Conditional', 'yoast\\wp\\seo\\conditionals\\primary_category_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Primary_Category_Conditional', 'yoast\\wp\\seo\\conditionals\\robots_txt_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Robots_Txt_Conditional', 'yoast\\wp\\seo\\conditionals\\semrush_enabled_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\SEMrush_Enabled_Conditional', 'yoast\\wp\\seo\\conditionals\\settings_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Settings_Conditional', 'yoast\\wp\\seo\\conditionals\\should_index_links_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Should_Index_Links_Conditional', 'yoast\\wp\\seo\\conditionals\\text_formality_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Text_Formality_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\elementor_activated_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Activated_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\elementor_edit_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Edit_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\polylang_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Polylang_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\site_kit_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Site_Kit_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\translatepress_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\TranslatePress_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\w3_total_cache_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\W3_Total_Cache_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\wordproof_integration_active_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Integration_Active_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\wordproof_plugin_inactive_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Plugin_Inactive_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\wpml_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_Conditional', 'yoast\\wp\\seo\\conditionals\\third_party\\wpml_wpseo_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_WPSEO_Conditional', 'yoast\\wp\\seo\\conditionals\\updated_importer_framework_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Updated_Importer_Framework_Conditional', 'yoast\\wp\\seo\\conditionals\\user_can_edit_users_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\User_Can_Edit_Users_Conditional', 'yoast\\wp\\seo\\conditionals\\user_can_manage_wpseo_options_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\User_Can_Manage_Wpseo_Options_Conditional', 'yoast\\wp\\seo\\conditionals\\user_can_publish_posts_and_pages_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\User_Can_Publish_Posts_And_Pages_Conditional', 'yoast\\wp\\seo\\conditionals\\user_edit_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\User_Edit_Conditional', 'yoast\\wp\\seo\\conditionals\\user_profile_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\User_Profile_Conditional', 'yoast\\wp\\seo\\conditionals\\web_stories_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Web_Stories_Conditional', 'yoast\\wp\\seo\\conditionals\\wincher_automatically_track_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Automatically_Track_Conditional', 'yoast\\wp\\seo\\conditionals\\wincher_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Conditional', 'yoast\\wp\\seo\\conditionals\\wincher_enabled_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Enabled_Conditional', 'yoast\\wp\\seo\\conditionals\\wincher_token_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Token_Conditional', 'yoast\\wp\\seo\\conditionals\\woocommerce_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional', 'yoast\\wp\\seo\\conditionals\\wp_cron_enabled_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\WP_CRON_Enabled_Conditional', 'yoast\\wp\\seo\\conditionals\\wp_robots_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\WP_Robots_Conditional', 'yoast\\wp\\seo\\conditionals\\xmlrpc_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\XMLRPC_Conditional', 'yoast\\wp\\seo\\conditionals\\yoast_admin_and_dashboard_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Yoast_Admin_And_Dashboard_Conditional', 'yoast\\wp\\seo\\conditionals\\yoast_tools_page_conditional' => 'Yoast\\WP\\SEO\\Conditionals\\Yoast_Tools_Page_Conditional', 'yoast\\wp\\seo\\config\\badge_group_names' => 'Yoast\\WP\\SEO\\Config\\Badge_Group_Names', 'yoast\\wp\\seo\\config\\conflicting_plugins' => 'Yoast\\WP\\SEO\\Config\\Conflicting_Plugins', 'yoast\\wp\\seo\\config\\indexing_reasons' => 'Yoast\\WP\\SEO\\Config\\Indexing_Reasons', 'yoast\\wp\\seo\\config\\migration_status' => 'Yoast\\WP\\SEO\\Config\\Migration_Status', 'yoast\\wp\\seo\\config\\migrations\\addcollationtotables' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddCollationToTables', 'yoast\\wp\\seo\\config\\migrations\\addcolumnstoindexables' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddColumnsToIndexables', 'yoast\\wp\\seo\\config\\migrations\\addestimatedreadingtime' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddEstimatedReadingTime', 'yoast\\wp\\seo\\config\\migrations\\addhasancestorscolumn' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddHasAncestorsColumn', 'yoast\\wp\\seo\\config\\migrations\\addinclusivelanguagescore' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddInclusiveLanguageScore', 'yoast\\wp\\seo\\config\\migrations\\addindexableobjectidandtypeindex' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexableObjectIdAndTypeIndex', 'yoast\\wp\\seo\\config\\migrations\\addindexesforprominentwordsonindexables' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexesForProminentWordsOnIndexables', 'yoast\\wp\\seo\\config\\migrations\\addobjecttimestamps' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddObjectTimestamps', 'yoast\\wp\\seo\\config\\migrations\\addversioncolumntoindexables' => 'Yoast\\WP\\SEO\\Config\\Migrations\\AddVersionColumnToIndexables', 'yoast\\wp\\seo\\config\\migrations\\breadcrumbtitleandhierarchyreset' => 'Yoast\\WP\\SEO\\Config\\Migrations\\BreadcrumbTitleAndHierarchyReset', 'yoast\\wp\\seo\\config\\migrations\\clearindexabletables' => 'Yoast\\WP\\SEO\\Config\\Migrations\\ClearIndexableTables', 'yoast\\wp\\seo\\config\\migrations\\createindexablesubpagesindex' => 'Yoast\\WP\\SEO\\Config\\Migrations\\CreateIndexableSubpagesIndex', 'yoast\\wp\\seo\\config\\migrations\\createseolinkstable' => 'Yoast\\WP\\SEO\\Config\\Migrations\\CreateSEOLinksTable', 'yoast\\wp\\seo\\config\\migrations\\deleteduplicateindexables' => 'Yoast\\WP\\SEO\\Config\\Migrations\\DeleteDuplicateIndexables', 'yoast\\wp\\seo\\config\\migrations\\expandindexablecolumnlengths' => 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableColumnLengths', 'yoast\\wp\\seo\\config\\migrations\\expandindexableidcolumnlengths' => 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableIDColumnLengths', 'yoast\\wp\\seo\\config\\migrations\\expandprimarytermidcolumnlengths' => 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandPrimaryTermIDColumnLengths', 'yoast\\wp\\seo\\config\\migrations\\replacepermalinkhashindex' => 'Yoast\\WP\\SEO\\Config\\Migrations\\ReplacePermalinkHashIndex', 'yoast\\wp\\seo\\config\\migrations\\resetindexablehierarchytable' => 'Yoast\\WP\\SEO\\Config\\Migrations\\ResetIndexableHierarchyTable', 'yoast\\wp\\seo\\config\\migrations\\truncateindexabletables' => 'Yoast\\WP\\SEO\\Config\\Migrations\\TruncateIndexableTables', 'yoast\\wp\\seo\\config\\migrations\\wpyoastdropindexablemetatableifexists' => 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastDropIndexableMetaTableIfExists', 'yoast\\wp\\seo\\config\\migrations\\wpyoastindexable' => 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexable', 'yoast\\wp\\seo\\config\\migrations\\wpyoastindexablehierarchy' => 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexableHierarchy', 'yoast\\wp\\seo\\config\\migrations\\wpyoastprimaryterm' => 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastPrimaryTerm', 'yoast\\wp\\seo\\config\\researcher_languages' => 'Yoast\\WP\\SEO\\Config\\Researcher_Languages', 'yoast\\wp\\seo\\config\\schema_ids' => 'Yoast\\WP\\SEO\\Config\\Schema_IDs', 'yoast\\wp\\seo\\config\\schema_types' => 'Yoast\\WP\\SEO\\Config\\Schema_Types', 'yoast\\wp\\seo\\config\\semrush_client' => 'Yoast\\WP\\SEO\\Config\\SEMrush_Client', 'yoast\\wp\\seo\\config\\wincher_client' => 'Yoast\\WP\\SEO\\Config\\Wincher_Client', 'yoast\\wp\\seo\\config\\wordproof_app_config' => 'Yoast\\WP\\SEO\\Config\\Wordproof_App_Config', 'yoast\\wp\\seo\\config\\wordproof_translations' => 'Yoast\\WP\\SEO\\Config\\Wordproof_Translations', 'yoast\\wp\\seo\\content_type_visibility\\application\\content_type_visibility_dismiss_notifications' => 'Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications', 'yoast\\wp\\seo\\content_type_visibility\\application\\content_type_visibility_watcher_actions' => 'Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Watcher_Actions', 'yoast\\wp\\seo\\content_type_visibility\\user_interface\\content_type_visibility_dismiss_new_route' => 'Yoast\\WP\\SEO\\Content_Type_Visibility\\User_Interface\\Content_Type_Visibility_Dismiss_New_Route', 'yoast\\wp\\seo\\context\\meta_tags_context' => 'Yoast\\WP\\SEO\\Context\\Meta_Tags_Context', 'yoast\\wp\\seo\\dashboard\\application\\score_results\\current_scores_repository' => 'Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository', 'yoast\\wp\\seo\\dashboard\\application\\taxonomies\\taxonomies_repository' => 'Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository', 'yoast\\wp\\seo\\dashboard\\domain\\score_groups\\seo_score_groups\\bad_seo_score_group' => 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group', 'yoast\\wp\\seo\\dashboard\\domain\\score_groups\\seo_score_groups\\good_seo_score_group' => 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group', 'yoast\\wp\\seo\\dashboard\\domain\\score_groups\\seo_score_groups\\no_seo_score_group' => 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group', 'yoast\\wp\\seo\\dashboard\\domain\\score_groups\\seo_score_groups\\ok_seo_score_group' => 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group', 'yoast\\wp\\seo\\dashboard\\infrastructure\\configuration\\permanently_dismissed_site_kit_configuration_repository' => 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository', 'yoast\\wp\\seo\\dashboard\\infrastructure\\configuration\\site_kit_consent_repository' => 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository', 'yoast\\wp\\seo\\dashboard\\infrastructure\\content_types\\content_types_collector' => 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector', 'yoast\\wp\\seo\\dashboard\\infrastructure\\endpoints\\site_kit_consent_management_endpoint' => 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint', 'yoast\\wp\\seo\\dashboard\\infrastructure\\integrations\\site_kit' => 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit', 'yoast\\wp\\seo\\dashboard\\infrastructure\\tracking\\setup_steps_tracking_repository' => 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository', 'yoast\\wp\\seo\\dashboard\\user_interface\\configuration\\site_kit_capabilities_integration' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Capabilities_Integration', 'yoast\\wp\\seo\\dashboard\\user_interface\\configuration\\site_kit_configuration_dismissal_route' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Configuration_Dismissal_Route', 'yoast\\wp\\seo\\dashboard\\user_interface\\configuration\\site_kit_consent_management_route' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Consent_Management_Route', 'yoast\\wp\\seo\\dashboard\\user_interface\\scores\\readability_scores_route' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\Readability_Scores_Route', 'yoast\\wp\\seo\\dashboard\\user_interface\\scores\\seo_scores_route' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\SEO_Scores_Route', 'yoast\\wp\\seo\\dashboard\\user_interface\\setup\\setup_flow_interceptor' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Flow_Interceptor', 'yoast\\wp\\seo\\dashboard\\user_interface\\setup\\setup_url_interceptor' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Url_Interceptor', 'yoast\\wp\\seo\\dashboard\\user_interface\\time_based_seo_metrics\\time_based_seo_metrics_route' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Time_Based_SEO_Metrics\\Time_Based_SEO_Metrics_Route', 'yoast\\wp\\seo\\dashboard\\user_interface\\tracking\\setup_steps_tracking_route' => 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Tracking\\Setup_Steps_Tracking_Route', 'yoast\\wp\\seo\\editors\\application\\analysis_features\\enabled_analysis_features_repository' => 'Yoast\\WP\\SEO\\Editors\\Application\\Analysis_Features\\Enabled_Analysis_Features_Repository', 'yoast\\wp\\seo\\editors\\application\\integrations\\integration_information_repository' => 'Yoast\\WP\\SEO\\Editors\\Application\\Integrations\\Integration_Information_Repository', 'yoast\\wp\\seo\\editors\\application\\seo\\post_seo_information_repository' => 'Yoast\\WP\\SEO\\Editors\\Application\\Seo\\Post_Seo_Information_Repository', 'yoast\\wp\\seo\\editors\\application\\seo\\term_seo_information_repository' => 'Yoast\\WP\\SEO\\Editors\\Application\\Seo\\Term_Seo_Information_Repository', 'yoast\\wp\\seo\\editors\\application\\site\\website_information_repository' => 'Yoast\\WP\\SEO\\Editors\\Application\\Site\\Website_Information_Repository', 'yoast\\wp\\seo\\general\\user_interface\\general_page_integration' => 'Yoast\\WP\\SEO\\General\\User_Interface\\General_Page_Integration', 'yoast\\wp\\seo\\generators\\breadcrumbs_generator' => 'Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator', 'yoast\\wp\\seo\\generators\\open_graph_image_generator' => 'Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator', 'yoast\\wp\\seo\\generators\\open_graph_locale_generator' => 'Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator', 'yoast\\wp\\seo\\generators\\schema\\article' => 'Yoast\\WP\\SEO\\Generators\\Schema\\Article', 'yoast\\wp\\seo\\generators\\schema\\author' => 'Yoast\\WP\\SEO\\Generators\\Schema\\Author', 'yoast\\wp\\seo\\generators\\schema\\breadcrumb' => 'Yoast\\WP\\SEO\\Generators\\Schema\\Breadcrumb', 'yoast\\wp\\seo\\generators\\schema\\faq' => 'Yoast\\WP\\SEO\\Generators\\Schema\\FAQ', 'yoast\\wp\\seo\\generators\\schema\\howto' => 'Yoast\\WP\\SEO\\Generators\\Schema\\HowTo', 'yoast\\wp\\seo\\generators\\schema\\main_image' => 'Yoast\\WP\\SEO\\Generators\\Schema\\Main_Image', 'yoast\\wp\\seo\\generators\\schema\\organization' => 'Yoast\\WP\\SEO\\Generators\\Schema\\Organization', 'yoast\\wp\\seo\\generators\\schema\\person' => 'Yoast\\WP\\SEO\\Generators\\Schema\\Person', 'yoast\\wp\\seo\\generators\\schema\\webpage' => 'Yoast\\WP\\SEO\\Generators\\Schema\\WebPage', 'yoast\\wp\\seo\\generators\\schema\\website' => 'Yoast\\WP\\SEO\\Generators\\Schema\\Website', 'yoast\\wp\\seo\\generators\\schema_generator' => 'Yoast\\WP\\SEO\\Generators\\Schema_Generator', 'yoast\\wp\\seo\\generators\\twitter_image_generator' => 'Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator', 'yoast\\wp\\seo\\helpers\\aioseo_helper' => 'Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper', 'yoast\\wp\\seo\\helpers\\asset_helper' => 'Yoast\\WP\\SEO\\Helpers\\Asset_Helper', 'yoast\\wp\\seo\\helpers\\attachment_cleanup_helper' => 'Yoast\\WP\\SEO\\Helpers\\Attachment_Cleanup_Helper', 'yoast\\wp\\seo\\helpers\\author_archive_helper' => 'Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper', 'yoast\\wp\\seo\\helpers\\blocks_helper' => 'Yoast\\WP\\SEO\\Helpers\\Blocks_Helper', 'yoast\\wp\\seo\\helpers\\capability_helper' => 'Yoast\\WP\\SEO\\Helpers\\Capability_Helper', 'yoast\\wp\\seo\\helpers\\crawl_cleanup_helper' => 'Yoast\\WP\\SEO\\Helpers\\Crawl_Cleanup_Helper', 'yoast\\wp\\seo\\helpers\\curl_helper' => 'Yoast\\WP\\SEO\\Helpers\\Curl_Helper', 'yoast\\wp\\seo\\helpers\\current_page_helper' => 'Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper', 'yoast\\wp\\seo\\helpers\\date_helper' => 'Yoast\\WP\\SEO\\Helpers\\Date_Helper', 'yoast\\wp\\seo\\helpers\\environment_helper' => 'Yoast\\WP\\SEO\\Helpers\\Environment_Helper', 'yoast\\wp\\seo\\helpers\\first_time_configuration_notice_helper' => 'Yoast\\WP\\SEO\\Helpers\\First_Time_Configuration_Notice_Helper', 'yoast\\wp\\seo\\helpers\\home_url_helper' => 'Yoast\\WP\\SEO\\Helpers\\Home_Url_Helper', 'yoast\\wp\\seo\\helpers\\image_helper' => 'Yoast\\WP\\SEO\\Helpers\\Image_Helper', 'yoast\\wp\\seo\\helpers\\import_cursor_helper' => 'Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper', 'yoast\\wp\\seo\\helpers\\import_helper' => 'Yoast\\WP\\SEO\\Helpers\\Import_Helper', 'yoast\\wp\\seo\\helpers\\indexable_helper' => 'Yoast\\WP\\SEO\\Helpers\\Indexable_Helper', 'yoast\\wp\\seo\\helpers\\indexable_to_postmeta_helper' => 'Yoast\\WP\\SEO\\Helpers\\Indexable_To_Postmeta_Helper', 'yoast\\wp\\seo\\helpers\\indexing_helper' => 'Yoast\\WP\\SEO\\Helpers\\Indexing_Helper', 'yoast\\wp\\seo\\helpers\\language_helper' => 'Yoast\\WP\\SEO\\Helpers\\Language_Helper', 'yoast\\wp\\seo\\helpers\\meta_helper' => 'Yoast\\WP\\SEO\\Helpers\\Meta_Helper', 'yoast\\wp\\seo\\helpers\\notification_helper' => 'Yoast\\WP\\SEO\\Helpers\\Notification_Helper', 'yoast\\wp\\seo\\helpers\\open_graph\\image_helper' => 'Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper', 'yoast\\wp\\seo\\helpers\\open_graph\\values_helper' => 'Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper', 'yoast\\wp\\seo\\helpers\\options_helper' => 'Yoast\\WP\\SEO\\Helpers\\Options_Helper', 'yoast\\wp\\seo\\helpers\\pagination_helper' => 'Yoast\\WP\\SEO\\Helpers\\Pagination_Helper', 'yoast\\wp\\seo\\helpers\\permalink_helper' => 'Yoast\\WP\\SEO\\Helpers\\Permalink_Helper', 'yoast\\wp\\seo\\helpers\\post_helper' => 'Yoast\\WP\\SEO\\Helpers\\Post_Helper', 'yoast\\wp\\seo\\helpers\\post_type_helper' => 'Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper', 'yoast\\wp\\seo\\helpers\\primary_term_helper' => 'Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper', 'yoast\\wp\\seo\\helpers\\product_helper' => 'Yoast\\WP\\SEO\\Helpers\\Product_Helper', 'yoast\\wp\\seo\\helpers\\redirect_helper' => 'Yoast\\WP\\SEO\\Helpers\\Redirect_Helper', 'yoast\\wp\\seo\\helpers\\request_helper' => 'Yoast\\WP\\SEO\\Helpers\\Request_Helper', 'yoast\\wp\\seo\\helpers\\require_file_helper' => 'Yoast\\WP\\SEO\\Helpers\\Require_File_Helper', 'yoast\\wp\\seo\\helpers\\robots_helper' => 'Yoast\\WP\\SEO\\Helpers\\Robots_Helper', 'yoast\\wp\\seo\\helpers\\robots_txt_helper' => 'Yoast\\WP\\SEO\\Helpers\\Robots_Txt_Helper', 'yoast\\wp\\seo\\helpers\\sanitization_helper' => 'Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper', 'yoast\\wp\\seo\\helpers\\schema\\article_helper' => 'Yoast\\WP\\SEO\\Helpers\\Schema\\Article_Helper', 'yoast\\wp\\seo\\helpers\\schema\\html_helper' => 'Yoast\\WP\\SEO\\Helpers\\Schema\\HTML_Helper', 'yoast\\wp\\seo\\helpers\\schema\\id_helper' => 'Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper', 'yoast\\wp\\seo\\helpers\\schema\\image_helper' => 'Yoast\\WP\\SEO\\Helpers\\Schema\\Image_Helper', 'yoast\\wp\\seo\\helpers\\schema\\language_helper' => 'Yoast\\WP\\SEO\\Helpers\\Schema\\Language_Helper', 'yoast\\wp\\seo\\helpers\\schema\\replace_vars_helper' => 'Yoast\\WP\\SEO\\Helpers\\Schema\\Replace_Vars_Helper', 'yoast\\wp\\seo\\helpers\\score_icon_helper' => 'Yoast\\WP\\SEO\\Helpers\\Score_Icon_Helper', 'yoast\\wp\\seo\\helpers\\short_link_helper' => 'Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper', 'yoast\\wp\\seo\\helpers\\site_helper' => 'Yoast\\WP\\SEO\\Helpers\\Site_Helper', 'yoast\\wp\\seo\\helpers\\social_profiles_helper' => 'Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper', 'yoast\\wp\\seo\\helpers\\string_helper' => 'Yoast\\WP\\SEO\\Helpers\\String_Helper', 'yoast\\wp\\seo\\helpers\\taxonomy_helper' => 'Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper', 'yoast\\wp\\seo\\helpers\\twitter\\image_helper' => 'Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper', 'yoast\\wp\\seo\\helpers\\url_helper' => 'Yoast\\WP\\SEO\\Helpers\\Url_Helper', 'yoast\\wp\\seo\\helpers\\user_helper' => 'Yoast\\WP\\SEO\\Helpers\\User_Helper', 'yoast\\wp\\seo\\helpers\\wincher_helper' => 'Yoast\\WP\\SEO\\Helpers\\Wincher_Helper', 'yoast\\wp\\seo\\helpers\\woocommerce_helper' => 'Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper', 'yoast\\wp\\seo\\helpers\\wordpress_helper' => 'Yoast\\WP\\SEO\\Helpers\\Wordpress_Helper', 'yoast\\wp\\seo\\helpers\\wordproof_helper' => 'Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper', 'yoast\\wp\\seo\\helpers\\wpdb_helper' => 'Yoast\\WP\\SEO\\Helpers\\Wpdb_Helper', 'yoast\\wp\\seo\\initializers\\crawl_cleanup_permalinks' => 'Yoast\\WP\\SEO\\Initializers\\Crawl_Cleanup_Permalinks', 'yoast\\wp\\seo\\initializers\\disable_core_sitemaps' => 'Yoast\\WP\\SEO\\Initializers\\Disable_Core_Sitemaps', 'yoast\\wp\\seo\\initializers\\migration_runner' => 'Yoast\\WP\\SEO\\Initializers\\Migration_Runner', 'yoast\\wp\\seo\\initializers\\plugin_headers' => 'Yoast\\WP\\SEO\\Initializers\\Plugin_Headers', 'yoast\\wp\\seo\\initializers\\woocommerce' => 'Yoast\\WP\\SEO\\Initializers\\Woocommerce', 'yoast\\wp\\seo\\integrations\\academy_integration' => 'Yoast\\WP\\SEO\\Integrations\\Academy_Integration', 'yoast\\wp\\seo\\integrations\\admin\\activation_cleanup_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Activation_Cleanup_Integration', 'yoast\\wp\\seo\\integrations\\admin\\addon_installation\\dialog_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Dialog_Integration', 'yoast\\wp\\seo\\integrations\\admin\\addon_installation\\installation_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Installation_Integration', 'yoast\\wp\\seo\\integrations\\admin\\admin_columns_cache_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Admin_Columns_Cache_Integration', 'yoast\\wp\\seo\\integrations\\admin\\background_indexing_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Background_Indexing_Integration', 'yoast\\wp\\seo\\integrations\\admin\\check_required_version' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Check_Required_Version', 'yoast\\wp\\seo\\integrations\\admin\\crawl_settings_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Crawl_Settings_Integration', 'yoast\\wp\\seo\\integrations\\admin\\cron_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Cron_Integration', 'yoast\\wp\\seo\\integrations\\admin\\deactivated_premium_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Deactivated_Premium_Integration', 'yoast\\wp\\seo\\integrations\\admin\\disable_concatenate_scripts_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Disable_Concatenate_Scripts_Integration', 'yoast\\wp\\seo\\integrations\\admin\\first_time_configuration_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Integration', 'yoast\\wp\\seo\\integrations\\admin\\first_time_configuration_notice_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Notice_Integration', 'yoast\\wp\\seo\\integrations\\admin\\fix_news_dependencies_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Fix_News_Dependencies_Integration', 'yoast\\wp\\seo\\integrations\\admin\\health_check_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Health_Check_Integration', 'yoast\\wp\\seo\\integrations\\admin\\helpscout_beacon' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\HelpScout_Beacon', 'yoast\\wp\\seo\\integrations\\admin\\import_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Import_Integration', 'yoast\\wp\\seo\\integrations\\admin\\indexables_exclude_taxonomy_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexables_Exclude_Taxonomy_Integration', 'yoast\\wp\\seo\\integrations\\admin\\indexing_notification_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Notification_Integration', 'yoast\\wp\\seo\\integrations\\admin\\indexing_tool_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Tool_Integration', 'yoast\\wp\\seo\\integrations\\admin\\installation_success_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Installation_Success_Integration', 'yoast\\wp\\seo\\integrations\\admin\\integrations_page' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Integrations_Page', 'yoast\\wp\\seo\\integrations\\admin\\link_count_columns_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Link_Count_Columns_Integration', 'yoast\\wp\\seo\\integrations\\admin\\menu_badge_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Menu_Badge_Integration', 'yoast\\wp\\seo\\integrations\\admin\\migration_error_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Migration_Error_Integration', 'yoast\\wp\\seo\\integrations\\admin\\old_configuration_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Configuration_Integration', 'yoast\\wp\\seo\\integrations\\admin\\old_premium_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Premium_Integration', 'yoast\\wp\\seo\\integrations\\admin\\redirect_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Redirect_Integration', 'yoast\\wp\\seo\\integrations\\admin\\redirects_page_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Redirects_Page_Integration', 'yoast\\wp\\seo\\integrations\\admin\\unsupported_php_version_notice' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Unsupported_PHP_Version_Notice', 'yoast\\wp\\seo\\integrations\\admin\\workouts_integration' => 'Yoast\\WP\\SEO\\Integrations\\Admin\\Workouts_Integration', 'yoast\\wp\\seo\\integrations\\alerts\\black_friday_product_editor_checklist_notification' => 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Product_Editor_Checklist_Notification', 'yoast\\wp\\seo\\integrations\\alerts\\black_friday_promotion_notification' => 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Promotion_Notification', 'yoast\\wp\\seo\\integrations\\alerts\\black_friday_sidebar_checklist_notification' => 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Sidebar_Checklist_Notification', 'yoast\\wp\\seo\\integrations\\alerts\\trustpilot_review_notification' => 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Trustpilot_Review_Notification', 'yoast\\wp\\seo\\integrations\\alerts\\webinar_promo_notification' => 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Webinar_Promo_Notification', 'yoast\\wp\\seo\\integrations\\blocks\\block_editor_integration' => 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Block_Editor_Integration', 'yoast\\wp\\seo\\integrations\\blocks\\breadcrumbs_block' => 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Breadcrumbs_Block', 'yoast\\wp\\seo\\integrations\\blocks\\internal_linking_category' => 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Internal_Linking_Category', 'yoast\\wp\\seo\\integrations\\blocks\\structured_data_blocks' => 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Structured_Data_Blocks', 'yoast\\wp\\seo\\integrations\\breadcrumbs_integration' => 'Yoast\\WP\\SEO\\Integrations\\Breadcrumbs_Integration', 'yoast\\wp\\seo\\integrations\\cleanup_integration' => 'Yoast\\WP\\SEO\\Integrations\\Cleanup_Integration', 'yoast\\wp\\seo\\integrations\\duplicate_post_integration' => 'Yoast\\WP\\SEO\\Integrations\\Duplicate_Post_Integration', 'yoast\\wp\\seo\\integrations\\estimated_reading_time' => 'Yoast\\WP\\SEO\\Integrations\\Estimated_Reading_Time', 'yoast\\wp\\seo\\integrations\\exclude_attachment_post_type' => 'Yoast\\WP\\SEO\\Integrations\\Exclude_Attachment_Post_Type', 'yoast\\wp\\seo\\integrations\\exclude_oembed_cache_post_type' => 'Yoast\\WP\\SEO\\Integrations\\Exclude_Oembed_Cache_Post_Type', 'yoast\\wp\\seo\\integrations\\feature_flag_integration' => 'Yoast\\WP\\SEO\\Integrations\\Feature_Flag_Integration', 'yoast\\wp\\seo\\integrations\\front_end\\backwards_compatibility' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Backwards_Compatibility', 'yoast\\wp\\seo\\integrations\\front_end\\category_term_description' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Category_Term_Description', 'yoast\\wp\\seo\\integrations\\front_end\\comment_link_fixer' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Comment_Link_Fixer', 'yoast\\wp\\seo\\integrations\\front_end\\crawl_cleanup_basic' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Basic', 'yoast\\wp\\seo\\integrations\\front_end\\crawl_cleanup_rss' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Rss', 'yoast\\wp\\seo\\integrations\\front_end\\crawl_cleanup_searches' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Searches', 'yoast\\wp\\seo\\integrations\\front_end\\feed_improvements' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Feed_Improvements', 'yoast\\wp\\seo\\integrations\\front_end\\force_rewrite_title' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Force_Rewrite_Title', 'yoast\\wp\\seo\\integrations\\front_end\\handle_404' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Handle_404', 'yoast\\wp\\seo\\integrations\\front_end\\indexing_controls' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Indexing_Controls', 'yoast\\wp\\seo\\integrations\\front_end\\open_graph_oembed' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Open_Graph_OEmbed', 'yoast\\wp\\seo\\integrations\\front_end\\redirects' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Redirects', 'yoast\\wp\\seo\\integrations\\front_end\\robots_txt_integration' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Robots_Txt_Integration', 'yoast\\wp\\seo\\integrations\\front_end\\rss_footer_embed' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\RSS_Footer_Embed', 'yoast\\wp\\seo\\integrations\\front_end\\schema_accessibility_feature' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Schema_Accessibility_Feature', 'yoast\\wp\\seo\\integrations\\front_end\\wp_robots_integration' => 'Yoast\\WP\\SEO\\Integrations\\Front_End\\WP_Robots_Integration', 'yoast\\wp\\seo\\integrations\\front_end_integration' => 'Yoast\\WP\\SEO\\Integrations\\Front_End_Integration', 'yoast\\wp\\seo\\integrations\\primary_category' => 'Yoast\\WP\\SEO\\Integrations\\Primary_Category', 'yoast\\wp\\seo\\integrations\\settings_integration' => 'Yoast\\WP\\SEO\\Integrations\\Settings_Integration', 'yoast\\wp\\seo\\integrations\\support_integration' => 'Yoast\\WP\\SEO\\Integrations\\Support_Integration', 'yoast\\wp\\seo\\integrations\\third_party\\amp' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\AMP', 'yoast\\wp\\seo\\integrations\\third_party\\bbpress' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\BbPress', 'yoast\\wp\\seo\\integrations\\third_party\\elementor' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Elementor', 'yoast\\wp\\seo\\integrations\\third_party\\exclude_elementor_post_types' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_Elementor_Post_Types', 'yoast\\wp\\seo\\integrations\\third_party\\exclude_woocommerce_post_types' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_WooCommerce_Post_Types', 'yoast\\wp\\seo\\integrations\\third_party\\jetpack' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Jetpack', 'yoast\\wp\\seo\\integrations\\third_party\\w3_total_cache' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\W3_Total_Cache', 'yoast\\wp\\seo\\integrations\\third_party\\web_stories' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories', 'yoast\\wp\\seo\\integrations\\third_party\\web_stories_post_edit' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories_Post_Edit', 'yoast\\wp\\seo\\integrations\\third_party\\wincher' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher', 'yoast\\wp\\seo\\integrations\\third_party\\wincher_publish' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher_Publish', 'yoast\\wp\\seo\\integrations\\third_party\\woocommerce' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce', 'yoast\\wp\\seo\\integrations\\third_party\\woocommerce_permalinks' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Woocommerce_Permalinks', 'yoast\\wp\\seo\\integrations\\third_party\\woocommerce_post_edit' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce_Post_Edit', 'yoast\\wp\\seo\\integrations\\third_party\\wordproof' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof', 'yoast\\wp\\seo\\integrations\\third_party\\wordproof_integration_toggle' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof_Integration_Toggle', 'yoast\\wp\\seo\\integrations\\third_party\\wpml' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML', 'yoast\\wp\\seo\\integrations\\third_party\\wpml_wpseo_notification' => 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML_WPSEO_Notification', 'yoast\\wp\\seo\\integrations\\uninstall_integration' => 'Yoast\\WP\\SEO\\Integrations\\Uninstall_Integration', 'yoast\\wp\\seo\\integrations\\watchers\\addon_update_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Addon_Update_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\auto_update_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Auto_Update_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_ancestor_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Ancestor_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_attachment_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Attachment_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_author_archive_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Archive_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_author_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_category_permalink_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Category_Permalink_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_date_archive_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Date_Archive_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_home_page_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Home_Page_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_homeurl_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_HomeUrl_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_permalink_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Permalink_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_post_meta_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Meta_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_post_type_archive_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Archive_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_post_type_change_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Change_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_post_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_static_home_page_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Static_Home_Page_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_system_page_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_System_Page_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_taxonomy_change_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Taxonomy_Change_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\indexable_term_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Term_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\option_titles_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Titles_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\option_wpseo_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Wpseo_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\primary_category_quick_edit_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Category_Quick_Edit_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\primary_term_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Term_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\search_engines_discouraged_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Search_Engines_Discouraged_Watcher', 'yoast\\wp\\seo\\integrations\\watchers\\woocommerce_beta_editor_watcher' => 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Woocommerce_Beta_Editor_Watcher', 'yoast\\wp\\seo\\integrations\\xmlrpc' => 'Yoast\\WP\\SEO\\Integrations\\XMLRPC', 'yoast\\wp\\seo\\introductions\\application\\ai_generate_titles_and_descriptions_introduction_upsell' => 'Yoast\\WP\\SEO\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell', 'yoast\\wp\\seo\\introductions\\application\\introductions_collector' => 'Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector', 'yoast\\wp\\seo\\introductions\\infrastructure\\wistia_embed_permission_repository' => 'Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository', 'yoast\\wp\\seo\\introductions\\user_interface\\introductions_integration' => 'Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Integration', 'yoast\\wp\\seo\\introductions\\user_interface\\introductions_seen_route' => 'Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Seen_Route', 'yoast\\wp\\seo\\introductions\\user_interface\\wistia_embed_permission_route' => 'Yoast\\WP\\SEO\\Introductions\\User_Interface\\Wistia_Embed_Permission_Route', 'yoast\\wp\\seo\\llms_txt\\application\\file\\commands\\populate_file_command_handler' => 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler', 'yoast\\wp\\seo\\llms_txt\\application\\file\\commands\\remove_file_command_handler' => 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler', 'yoast\\wp\\seo\\llms_txt\\application\\file\\llms_txt_cron_scheduler' => 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler', 'yoast\\wp\\seo\\llms_txt\\infrastructure\\file\\wordpress_file_system_adapter' => 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter', 'yoast\\wp\\seo\\llms_txt\\infrastructure\\file\\wordpress_llms_txt_permission_gate' => 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate', 'yoast\\wp\\seo\\llms_txt\\user_interface\\cleanup_llms_txt_on_deactivation' => 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Cleanup_Llms_Txt_On_Deactivation', 'yoast\\wp\\seo\\llms_txt\\user_interface\\enable_llms_txt_option_watcher' => 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Enable_Llms_Txt_Option_Watcher', 'yoast\\wp\\seo\\llms_txt\\user_interface\\llms_txt_cron_callback_integration' => 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Llms_Txt_Cron_Callback_Integration', 'yoast\\wp\\seo\\llms_txt\\user_interface\\schedule_population_on_activation_integration' => 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Schedule_Population_On_Activation_Integration', 'yoast\\wp\\seo\\loader' => 'Yoast\\WP\\SEO\\Loader', 'yoast\\wp\\seo\\loggers\\logger' => 'Yoast\\WP\\SEO\\Loggers\\Logger', 'yoast\\wp\\seo\\memoizers\\meta_tags_context_memoizer' => 'Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer', 'yoast\\wp\\seo\\memoizers\\presentation_memoizer' => 'Yoast\\WP\\SEO\\Memoizers\\Presentation_Memoizer', 'yoast\\wp\\seo\\presentations\\abstract_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Abstract_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_author_archive_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Author_Archive_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_date_archive_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Date_Archive_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_error_page_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Error_Page_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_home_page_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Home_Page_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_post_type_archive_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Post_Type_Archive_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_post_type_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Post_Type_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_search_result_page_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Search_Result_Page_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_static_home_page_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Static_Home_Page_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_static_posts_page_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Static_Posts_Page_Presentation', 'yoast\\wp\\seo\\presentations\\indexable_term_archive_presentation' => 'Yoast\\WP\\SEO\\Presentations\\Indexable_Term_Archive_Presentation', 'yoast\\wp\\seo\\promotions\\application\\promotion_manager' => 'Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager', 'yoast\\wp\\seo\\repositories\\indexable_cleanup_repository' => 'Yoast\\WP\\SEO\\Repositories\\Indexable_Cleanup_Repository', 'yoast\\wp\\seo\\repositories\\indexable_hierarchy_repository' => 'Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository', 'yoast\\wp\\seo\\repositories\\indexable_repository' => 'Yoast\\WP\\SEO\\Repositories\\Indexable_Repository', 'yoast\\wp\\seo\\repositories\\primary_term_repository' => 'Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository', 'yoast\\wp\\seo\\repositories\\seo_links_repository' => 'Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository', 'yoast\\wp\\seo\\routes\\alert_dismissal_route' => 'Yoast\\WP\\SEO\\Routes\\Alert_Dismissal_Route', 'yoast\\wp\\seo\\routes\\first_time_configuration_route' => 'Yoast\\WP\\SEO\\Routes\\First_Time_Configuration_Route', 'yoast\\wp\\seo\\routes\\importing_route' => 'Yoast\\WP\\SEO\\Routes\\Importing_Route', 'yoast\\wp\\seo\\routes\\indexables_head_route' => 'Yoast\\WP\\SEO\\Routes\\Indexables_Head_Route', 'yoast\\wp\\seo\\routes\\indexing_route' => 'Yoast\\WP\\SEO\\Routes\\Indexing_Route', 'yoast\\wp\\seo\\routes\\integrations_route' => 'Yoast\\WP\\SEO\\Routes\\Integrations_Route', 'yoast\\wp\\seo\\routes\\meta_search_route' => 'Yoast\\WP\\SEO\\Routes\\Meta_Search_Route', 'yoast\\wp\\seo\\routes\\semrush_route' => 'Yoast\\WP\\SEO\\Routes\\SEMrush_Route', 'yoast\\wp\\seo\\routes\\supported_features_route' => 'Yoast\\WP\\SEO\\Routes\\Supported_Features_Route', 'yoast\\wp\\seo\\routes\\wincher_route' => 'Yoast\\WP\\SEO\\Routes\\Wincher_Route', 'yoast\\wp\\seo\\routes\\workouts_route' => 'Yoast\\WP\\SEO\\Routes\\Workouts_Route', 'yoast\\wp\\seo\\routes\\yoast_head_rest_field' => 'Yoast\\WP\\SEO\\Routes\\Yoast_Head_REST_Field', 'yoast\\wp\\seo\\services\\health_check\\default_tagline_check' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Check', 'yoast\\wp\\seo\\services\\health_check\\default_tagline_reports' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Reports', 'yoast\\wp\\seo\\services\\health_check\\default_tagline_runner' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner', 'yoast\\wp\\seo\\services\\health_check\\links_table_check' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Check', 'yoast\\wp\\seo\\services\\health_check\\links_table_reports' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Reports', 'yoast\\wp\\seo\\services\\health_check\\links_table_runner' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Runner', 'yoast\\wp\\seo\\services\\health_check\\myyoast_api_request_factory' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\MyYoast_Api_Request_Factory', 'yoast\\wp\\seo\\services\\health_check\\page_comments_check' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Check', 'yoast\\wp\\seo\\services\\health_check\\page_comments_reports' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Reports', 'yoast\\wp\\seo\\services\\health_check\\page_comments_runner' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Runner', 'yoast\\wp\\seo\\services\\health_check\\postname_permalink_check' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Check', 'yoast\\wp\\seo\\services\\health_check\\postname_permalink_reports' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Reports', 'yoast\\wp\\seo\\services\\health_check\\postname_permalink_runner' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Runner', 'yoast\\wp\\seo\\services\\health_check\\report_builder' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder', 'yoast\\wp\\seo\\services\\health_check\\report_builder_factory' => 'Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory', 'yoast\\wp\\seo\\services\\importing\\aioseo\\aioseo_replacevar_service' => 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service', 'yoast\\wp\\seo\\services\\importing\\aioseo\\aioseo_robots_provider_service' => 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service', 'yoast\\wp\\seo\\services\\importing\\aioseo\\aioseo_robots_transformer_service' => 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service', 'yoast\\wp\\seo\\services\\importing\\aioseo\\aioseo_social_images_provider_service' => 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Social_Images_Provider_Service', 'yoast\\wp\\seo\\services\\importing\\conflicting_plugins_service' => 'Yoast\\WP\\SEO\\Services\\Importing\\Conflicting_Plugins_Service', 'yoast\\wp\\seo\\services\\importing\\importable_detector_service' => 'Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service', 'yoast\\wp\\seo\\services\\indexables\\indexable_version_manager' => 'Yoast\\WP\\SEO\\Services\\Indexables\\Indexable_Version_Manager', 'yoast\\wp\\seo\\surfaces\\classes_surface' => 'Yoast\\WP\\SEO\\Surfaces\\Classes_Surface', 'yoast\\wp\\seo\\surfaces\\helpers_surface' => 'Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface', 'yoast\\wp\\seo\\surfaces\\meta_surface' => 'Yoast\\WP\\SEO\\Surfaces\\Meta_Surface', 'yoast\\wp\\seo\\surfaces\\open_graph_helpers_surface' => 'Yoast\\WP\\SEO\\Surfaces\\Open_Graph_Helpers_Surface', 'yoast\\wp\\seo\\surfaces\\schema_helpers_surface' => 'Yoast\\WP\\SEO\\Surfaces\\Schema_Helpers_Surface', 'yoast\\wp\\seo\\surfaces\\twitter_helpers_surface' => 'Yoast\\WP\\SEO\\Surfaces\\Twitter_Helpers_Surface', 'yoast\\wp\\seo\\user_meta\\application\\additional_contactmethods_collector' => 'Yoast\\WP\\SEO\\User_Meta\\Application\\Additional_Contactmethods_Collector', 'yoast\\wp\\seo\\user_meta\\application\\custom_meta_collector' => 'Yoast\\WP\\SEO\\User_Meta\\Application\\Custom_Meta_Collector', 'yoast\\wp\\seo\\user_meta\\user_interface\\additional_contactmethods_integration' => 'Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Additional_Contactmethods_Integration', 'yoast\\wp\\seo\\user_meta\\user_interface\\cleanup_integration' => 'Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Cleanup_Integration', 'yoast\\wp\\seo\\user_meta\\user_interface\\custom_meta_integration' => 'Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Custom_Meta_Integration', 'yoast\\wp\\seo\\user_profiles_additions\\user_interface\\user_profiles_additions_ui' => 'Yoast\\WP\\SEO\\User_Profiles_Additions\\User_Interface\\User_Profiles_Additions_Ui', 'yoast\\wp\\seo\\values\\images' => 'Yoast\\WP\\SEO\\Values\\Images', 'yoast\\wp\\seo\\values\\indexables\\indexable_builder_versions' => 'Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions', 'yoast\\wp\\seo\\values\\open_graph\\images' => 'Yoast\\WP\\SEO\\Values\\Open_Graph\\Images', 'yoast\\wp\\seo\\values\\twitter\\images' => 'Yoast\\WP\\SEO\\Values\\Twitter\\Images', 'yoast\\wp\\seo\\wrappers\\wp_query_wrapper' => 'Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper', 'yoast\\wp\\seo\\wrappers\\wp_remote_handler' => 'Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler', 'yoast\\wp\\seo\\wrappers\\wp_rewrite_wrapper' => 'Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper', 'yoast_notification_center' => 'Yoast_Notification_Center', 'yoastseo_vendor\\symfony\\component\\dependencyinjection\\containerinterface' => 'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface', ]; $this->methodMap = [ 'WPSEO_Addon_Manager' => 'getWPSEOAddonManagerService', 'WPSEO_Admin_Asset_Manager' => 'getWPSEOAdminAssetManagerService', 'WPSEO_Breadcrumbs' => 'getWPSEOBreadcrumbsService', 'WPSEO_Frontend' => 'getWPSEOFrontendService', 'WPSEO_Replace_Vars' => 'getWPSEOReplaceVarsService', 'WPSEO_Shortlinker' => 'getWPSEOShortlinkerService', 'WPSEO_Utils' => 'getWPSEOUtilsService', 'Yoast\\WP\\Lib\\Migrations\\Adapter' => 'getAdapterService', 'Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Activate_Action' => 'getAddonActivateActionService', 'Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Install_Action' => 'getAddonInstallActionService', 'Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action' => 'getAlertDismissalActionService', 'Yoast\\WP\\SEO\\Actions\\Configuration\\First_Time_Configuration_Action' => 'getFirstTimeConfigurationActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Cleanup_Action' => 'getAioseoCleanupActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action' => 'getAioseoCustomArchiveSettingsImportingActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action' => 'getAioseoDefaultArchiveSettingsImportingActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action' => 'getAioseoGeneralSettingsImportingActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action' => 'getAioseoPostsImportingActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action' => 'getAioseoPosttypeDefaultsSettingsImportingActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action' => 'getAioseoTaxonomySettingsImportingActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Validate_Data_Action' => 'getAioseoValidateDataActionService', 'Yoast\\WP\\SEO\\Actions\\Importing\\Deactivate_Conflicting_Plugins_Action' => 'getDeactivateConflictingPluginsActionService', 'Yoast\\WP\\SEO\\Actions\\Indexables\\Indexable_Head_Action' => 'getIndexableHeadActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action' => 'getIndexableGeneralIndexationActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action' => 'getIndexableIndexingCompleteActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action' => 'getIndexablePostIndexationActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action' => 'getIndexablePostTypeArchiveIndexationActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action' => 'getIndexableTermIndexationActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Complete_Action' => 'getIndexingCompleteActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Prepare_Action' => 'getIndexingPrepareActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action' => 'getPostLinkIndexingActionService', 'Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action' => 'getTermLinkIndexingActionService', 'Yoast\\WP\\SEO\\Actions\\Integrations_Action' => 'getIntegrationsActionService', 'Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Login_Action' => 'getSEMrushLoginActionService', 'Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Options_Action' => 'getSEMrushOptionsActionService', 'Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Phrases_Action' => 'getSEMrushPhrasesActionService', 'Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Account_Action' => 'getWincherAccountActionService', 'Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Keyphrases_Action' => 'getWincherKeyphrasesActionService', 'Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Login_Action' => 'getWincherLoginActionService', 'Yoast\\WP\\SEO\\Analytics\\Application\\Missing_Indexables_Collector' => 'getMissingIndexablesCollectorService', 'Yoast\\WP\\SEO\\Analytics\\Application\\To_Be_Cleaned_Indexables_Collector' => 'getToBeCleanedIndexablesCollectorService', 'Yoast\\WP\\SEO\\Analytics\\User_Interface\\Last_Completed_Indexation_Integration' => 'getLastCompletedIndexationIntegrationService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder' => 'getIndexableAuthorBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Builder' => 'getIndexableBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Date_Archive_Builder' => 'getIndexableDateArchiveBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder' => 'getIndexableHierarchyBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Home_Page_Builder' => 'getIndexableHomePageBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder' => 'getIndexableLinkBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder' => 'getIndexablePostBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Post_Type_Archive_Builder' => 'getIndexablePostTypeArchiveBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_System_Page_Builder' => 'getIndexableSystemPageBuilderService', 'Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder' => 'getIndexableTermBuilderService', 'Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder' => 'getPrimaryTermBuilderService', 'Yoast\\WP\\SEO\\Commands\\Cleanup_Command' => 'getCleanupCommandService', 'Yoast\\WP\\SEO\\Commands\\Index_Command' => 'getIndexCommandService', 'Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional' => 'getAddonInstallationConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Doing_Post_Quick_Edit_Save_Conditional' => 'getDoingPostQuickEditSaveConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Estimated_Reading_Time_Conditional' => 'getEstimatedReadingTimeConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Licenses_Page_Conditional' => 'getLicensesPageConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Non_Network_Admin_Conditional' => 'getNonNetworkAdminConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Post_Conditional' => 'getPostConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Posts_Overview_Or_Ajax_Conditional' => 'getPostsOverviewOrAjaxConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin\\Yoast_Admin_Conditional' => 'getYoastAdminConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Admin_Conditional' => 'getAdminConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Attachment_Redirections_Enabled_Conditional' => 'getAttachmentRedirectionsEnabledConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Check_Required_Version_Conditional' => 'getCheckRequiredVersionConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Deactivating_Yoast_Seo_Conditional' => 'getDeactivatingYoastSeoConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Development_Conditional' => 'getDevelopmentConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Front_End_Conditional' => 'getFrontEndConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Get_Request_Conditional' => 'getGetRequestConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Google_Site_Kit_Feature_Conditional' => 'getGoogleSiteKitFeatureConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Headless_Rest_Endpoints_Enabled_Conditional' => 'getHeadlessRestEndpointsEnabledConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Import_Tool_Selected_Conditional' => 'getImportToolSelectedConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Jetpack_Conditional' => 'getJetpackConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Migrations_Conditional' => 'getMigrationsConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\New_Settings_Ui_Conditional' => 'getNewSettingsUiConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\News_Conditional' => 'getNewsConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\No_Tool_Selected_Conditional' => 'getNoToolSelectedConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Non_Multisite_Conditional' => 'getNonMultisiteConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Not_Admin_Ajax_Conditional' => 'getNotAdminAjaxConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Open_Graph_Conditional' => 'getOpenGraphConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Premium_Active_Conditional' => 'getPremiumActiveConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Premium_Inactive_Conditional' => 'getPremiumInactiveConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Primary_Category_Conditional' => 'getPrimaryCategoryConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Robots_Txt_Conditional' => 'getRobotsTxtConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\SEMrush_Enabled_Conditional' => 'getSEMrushEnabledConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Settings_Conditional' => 'getSettingsConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Should_Index_Links_Conditional' => 'getShouldIndexLinksConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Text_Formality_Conditional' => 'getTextFormalityConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Activated_Conditional' => 'getElementorActivatedConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Edit_Conditional' => 'getElementorEditConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Polylang_Conditional' => 'getPolylangConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Site_Kit_Conditional' => 'getSiteKitConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\TranslatePress_Conditional' => 'getTranslatePressConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\W3_Total_Cache_Conditional' => 'getW3TotalCacheConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_Conditional' => 'getWPMLConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_WPSEO_Conditional' => 'getWPMLWPSEOConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Integration_Active_Conditional' => 'getWordproofIntegrationActiveConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Plugin_Inactive_Conditional' => 'getWordproofPluginInactiveConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Updated_Importer_Framework_Conditional' => 'getUpdatedImporterFrameworkConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\User_Can_Edit_Users_Conditional' => 'getUserCanEditUsersConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\User_Can_Manage_Wpseo_Options_Conditional' => 'getUserCanManageWpseoOptionsConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\User_Can_Publish_Posts_And_Pages_Conditional' => 'getUserCanPublishPostsAndPagesConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\User_Edit_Conditional' => 'getUserEditConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\User_Profile_Conditional' => 'getUserProfileConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\WP_CRON_Enabled_Conditional' => 'getWPCRONEnabledConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\WP_Robots_Conditional' => 'getWPRobotsConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Web_Stories_Conditional' => 'getWebStoriesConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Automatically_Track_Conditional' => 'getWincherAutomaticallyTrackConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Conditional' => 'getWincherConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Enabled_Conditional' => 'getWincherEnabledConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Wincher_Token_Conditional' => 'getWincherTokenConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional' => 'getWooCommerceConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\XMLRPC_Conditional' => 'getXMLRPCConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Yoast_Admin_And_Dashboard_Conditional' => 'getYoastAdminAndDashboardConditionalService', 'Yoast\\WP\\SEO\\Conditionals\\Yoast_Tools_Page_Conditional' => 'getYoastToolsPageConditionalService', 'Yoast\\WP\\SEO\\Config\\Badge_Group_Names' => 'getBadgeGroupNamesService', 'Yoast\\WP\\SEO\\Config\\Conflicting_Plugins' => 'getConflictingPluginsService', 'Yoast\\WP\\SEO\\Config\\Indexing_Reasons' => 'getIndexingReasonsService', 'Yoast\\WP\\SEO\\Config\\Migration_Status' => 'getMigrationStatusService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddCollationToTables' => 'getAddCollationToTablesService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddColumnsToIndexables' => 'getAddColumnsToIndexablesService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddEstimatedReadingTime' => 'getAddEstimatedReadingTimeService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddHasAncestorsColumn' => 'getAddHasAncestorsColumnService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddInclusiveLanguageScore' => 'getAddInclusiveLanguageScoreService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexableObjectIdAndTypeIndex' => 'getAddIndexableObjectIdAndTypeIndexService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexesForProminentWordsOnIndexables' => 'getAddIndexesForProminentWordsOnIndexablesService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddObjectTimestamps' => 'getAddObjectTimestampsService', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddVersionColumnToIndexables' => 'getAddVersionColumnToIndexablesService', 'Yoast\\WP\\SEO\\Config\\Migrations\\BreadcrumbTitleAndHierarchyReset' => 'getBreadcrumbTitleAndHierarchyResetService', 'Yoast\\WP\\SEO\\Config\\Migrations\\ClearIndexableTables' => 'getClearIndexableTablesService', 'Yoast\\WP\\SEO\\Config\\Migrations\\CreateIndexableSubpagesIndex' => 'getCreateIndexableSubpagesIndexService', 'Yoast\\WP\\SEO\\Config\\Migrations\\CreateSEOLinksTable' => 'getCreateSEOLinksTableService', 'Yoast\\WP\\SEO\\Config\\Migrations\\DeleteDuplicateIndexables' => 'getDeleteDuplicateIndexablesService', 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableColumnLengths' => 'getExpandIndexableColumnLengthsService', 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableIDColumnLengths' => 'getExpandIndexableIDColumnLengthsService', 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandPrimaryTermIDColumnLengths' => 'getExpandPrimaryTermIDColumnLengthsService', 'Yoast\\WP\\SEO\\Config\\Migrations\\ReplacePermalinkHashIndex' => 'getReplacePermalinkHashIndexService', 'Yoast\\WP\\SEO\\Config\\Migrations\\ResetIndexableHierarchyTable' => 'getResetIndexableHierarchyTableService', 'Yoast\\WP\\SEO\\Config\\Migrations\\TruncateIndexableTables' => 'getTruncateIndexableTablesService', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastDropIndexableMetaTableIfExists' => 'getWpYoastDropIndexableMetaTableIfExistsService', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexable' => 'getWpYoastIndexableService', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexableHierarchy' => 'getWpYoastIndexableHierarchyService', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastPrimaryTerm' => 'getWpYoastPrimaryTermService', 'Yoast\\WP\\SEO\\Config\\Researcher_Languages' => 'getResearcherLanguagesService', 'Yoast\\WP\\SEO\\Config\\SEMrush_Client' => 'getSEMrushClientService', 'Yoast\\WP\\SEO\\Config\\Schema_IDs' => 'getSchemaIDsService', 'Yoast\\WP\\SEO\\Config\\Schema_Types' => 'getSchemaTypesService', 'Yoast\\WP\\SEO\\Config\\Wincher_Client' => 'getWincherClientService', 'Yoast\\WP\\SEO\\Config\\Wordproof_App_Config' => 'getWordproofAppConfigService', 'Yoast\\WP\\SEO\\Config\\Wordproof_Translations' => 'getWordproofTranslationsService', 'Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications' => 'getContentTypeVisibilityDismissNotificationsService', 'Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Watcher_Actions' => 'getContentTypeVisibilityWatcherActionsService', 'Yoast\\WP\\SEO\\Content_Type_Visibility\\User_Interface\\Content_Type_Visibility_Dismiss_New_Route' => 'getContentTypeVisibilityDismissNewRouteService', 'Yoast\\WP\\SEO\\Context\\Meta_Tags_Context' => 'getMetaTagsContextService', 'Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository' => 'getCurrentScoresRepositoryService', 'Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository' => 'getTaxonomiesRepositoryService', 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group' => 'getBadSEOScoreGroupService', 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group' => 'getGoodSEOScoreGroupService', 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group' => 'getNoSEOScoreGroupService', 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group' => 'getOkSEOScoreGroupService', 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository' => 'getPermanentlyDismissedSiteKitConfigurationRepositoryService', 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository' => 'getSiteKitConsentRepositoryService', 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector' => 'getContentTypesCollectorService', 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint' => 'getSiteKitConsentManagementEndpointService', 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit' => 'getSiteKitService', 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository' => 'getSetupStepsTrackingRepositoryService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Capabilities_Integration' => 'getSiteKitCapabilitiesIntegrationService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Configuration_Dismissal_Route' => 'getSiteKitConfigurationDismissalRouteService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Consent_Management_Route' => 'getSiteKitConsentManagementRouteService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\Readability_Scores_Route' => 'getReadabilityScoresRouteService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\SEO_Scores_Route' => 'getSEOScoresRouteService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Flow_Interceptor' => 'getSetupFlowInterceptorService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Url_Interceptor' => 'getSetupUrlInterceptorService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Time_Based_SEO_Metrics\\Time_Based_SEO_Metrics_Route' => 'getTimeBasedSEOMetricsRouteService', 'Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Tracking\\Setup_Steps_Tracking_Route' => 'getSetupStepsTrackingRouteService', 'Yoast\\WP\\SEO\\Editors\\Application\\Analysis_Features\\Enabled_Analysis_Features_Repository' => 'getEnabledAnalysisFeaturesRepositoryService', 'Yoast\\WP\\SEO\\Editors\\Application\\Integrations\\Integration_Information_Repository' => 'getIntegrationInformationRepositoryService', 'Yoast\\WP\\SEO\\Editors\\Application\\Seo\\Post_Seo_Information_Repository' => 'getPostSeoInformationRepositoryService', 'Yoast\\WP\\SEO\\Editors\\Application\\Seo\\Term_Seo_Information_Repository' => 'getTermSeoInformationRepositoryService', 'Yoast\\WP\\SEO\\Editors\\Application\\Site\\Website_Information_Repository' => 'getWebsiteInformationRepositoryService', 'Yoast\\WP\\SEO\\General\\User_Interface\\General_Page_Integration' => 'getGeneralPageIntegrationService', 'Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator' => 'getBreadcrumbsGeneratorService', 'Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator' => 'getOpenGraphImageGeneratorService', 'Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator' => 'getOpenGraphLocaleGeneratorService', 'Yoast\\WP\\SEO\\Generators\\Schema\\Article' => 'getArticleService', 'Yoast\\WP\\SEO\\Generators\\Schema\\Author' => 'getAuthorService', 'Yoast\\WP\\SEO\\Generators\\Schema\\Breadcrumb' => 'getBreadcrumbService', 'Yoast\\WP\\SEO\\Generators\\Schema\\FAQ' => 'getFAQService', 'Yoast\\WP\\SEO\\Generators\\Schema\\HowTo' => 'getHowToService', 'Yoast\\WP\\SEO\\Generators\\Schema\\Main_Image' => 'getMainImageService', 'Yoast\\WP\\SEO\\Generators\\Schema\\Organization' => 'getOrganizationService', 'Yoast\\WP\\SEO\\Generators\\Schema\\Person' => 'getPersonService', 'Yoast\\WP\\SEO\\Generators\\Schema\\WebPage' => 'getWebPageService', 'Yoast\\WP\\SEO\\Generators\\Schema\\Website' => 'getWebsiteService', 'Yoast\\WP\\SEO\\Generators\\Schema_Generator' => 'getSchemaGeneratorService', 'Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator' => 'getTwitterImageGeneratorService', 'Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper' => 'getAioseoHelperService', 'Yoast\\WP\\SEO\\Helpers\\Asset_Helper' => 'getAssetHelperService', 'Yoast\\WP\\SEO\\Helpers\\Attachment_Cleanup_Helper' => 'getAttachmentCleanupHelperService', 'Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper' => 'getAuthorArchiveHelperService', 'Yoast\\WP\\SEO\\Helpers\\Blocks_Helper' => 'getBlocksHelperService', 'Yoast\\WP\\SEO\\Helpers\\Capability_Helper' => 'getCapabilityHelperService', 'Yoast\\WP\\SEO\\Helpers\\Crawl_Cleanup_Helper' => 'getCrawlCleanupHelperService', 'Yoast\\WP\\SEO\\Helpers\\Curl_Helper' => 'getCurlHelperService', 'Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper' => 'getCurrentPageHelperService', 'Yoast\\WP\\SEO\\Helpers\\Date_Helper' => 'getDateHelperService', 'Yoast\\WP\\SEO\\Helpers\\Environment_Helper' => 'getEnvironmentHelperService', 'Yoast\\WP\\SEO\\Helpers\\First_Time_Configuration_Notice_Helper' => 'getFirstTimeConfigurationNoticeHelperService', 'Yoast\\WP\\SEO\\Helpers\\Home_Url_Helper' => 'getHomeUrlHelperService', 'Yoast\\WP\\SEO\\Helpers\\Image_Helper' => 'getImageHelperService', 'Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper' => 'getImportCursorHelperService', 'Yoast\\WP\\SEO\\Helpers\\Import_Helper' => 'getImportHelperService', 'Yoast\\WP\\SEO\\Helpers\\Indexable_Helper' => 'getIndexableHelperService', 'Yoast\\WP\\SEO\\Helpers\\Indexable_To_Postmeta_Helper' => 'getIndexableToPostmetaHelperService', 'Yoast\\WP\\SEO\\Helpers\\Indexing_Helper' => 'getIndexingHelperService', 'Yoast\\WP\\SEO\\Helpers\\Language_Helper' => 'getLanguageHelperService', 'Yoast\\WP\\SEO\\Helpers\\Meta_Helper' => 'getMetaHelperService', 'Yoast\\WP\\SEO\\Helpers\\Notification_Helper' => 'getNotificationHelperService', 'Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper' => 'getImageHelper2Service', 'Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper' => 'getValuesHelperService', 'Yoast\\WP\\SEO\\Helpers\\Options_Helper' => 'getOptionsHelperService', 'Yoast\\WP\\SEO\\Helpers\\Pagination_Helper' => 'getPaginationHelperService', 'Yoast\\WP\\SEO\\Helpers\\Permalink_Helper' => 'getPermalinkHelperService', 'Yoast\\WP\\SEO\\Helpers\\Post_Helper' => 'getPostHelperService', 'Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper' => 'getPostTypeHelperService', 'Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper' => 'getPrimaryTermHelperService', 'Yoast\\WP\\SEO\\Helpers\\Product_Helper' => 'getProductHelperService', 'Yoast\\WP\\SEO\\Helpers\\Redirect_Helper' => 'getRedirectHelperService', 'Yoast\\WP\\SEO\\Helpers\\Request_Helper' => 'getRequestHelperService', 'Yoast\\WP\\SEO\\Helpers\\Require_File_Helper' => 'getRequireFileHelperService', 'Yoast\\WP\\SEO\\Helpers\\Robots_Helper' => 'getRobotsHelperService', 'Yoast\\WP\\SEO\\Helpers\\Robots_Txt_Helper' => 'getRobotsTxtHelperService', 'Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper' => 'getSanitizationHelperService', 'Yoast\\WP\\SEO\\Helpers\\Schema\\Article_Helper' => 'getArticleHelperService', 'Yoast\\WP\\SEO\\Helpers\\Schema\\HTML_Helper' => 'getHTMLHelperService', 'Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper' => 'getIDHelperService', 'Yoast\\WP\\SEO\\Helpers\\Schema\\Image_Helper' => 'getImageHelper3Service', 'Yoast\\WP\\SEO\\Helpers\\Schema\\Language_Helper' => 'getLanguageHelper2Service', 'Yoast\\WP\\SEO\\Helpers\\Schema\\Replace_Vars_Helper' => 'getReplaceVarsHelperService', 'Yoast\\WP\\SEO\\Helpers\\Score_Icon_Helper' => 'getScoreIconHelperService', 'Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper' => 'getShortLinkHelperService', 'Yoast\\WP\\SEO\\Helpers\\Site_Helper' => 'getSiteHelperService', 'Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper' => 'getSocialProfilesHelperService', 'Yoast\\WP\\SEO\\Helpers\\String_Helper' => 'getStringHelperService', 'Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper' => 'getTaxonomyHelperService', 'Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper' => 'getImageHelper4Service', 'Yoast\\WP\\SEO\\Helpers\\Url_Helper' => 'getUrlHelperService', 'Yoast\\WP\\SEO\\Helpers\\User_Helper' => 'getUserHelperService', 'Yoast\\WP\\SEO\\Helpers\\Wincher_Helper' => 'getWincherHelperService', 'Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper' => 'getWoocommerceHelperService', 'Yoast\\WP\\SEO\\Helpers\\Wordpress_Helper' => 'getWordpressHelperService', 'Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper' => 'getWordproofHelperService', 'Yoast\\WP\\SEO\\Helpers\\Wpdb_Helper' => 'getWpdbHelperService', 'Yoast\\WP\\SEO\\Initializers\\Crawl_Cleanup_Permalinks' => 'getCrawlCleanupPermalinksService', 'Yoast\\WP\\SEO\\Initializers\\Disable_Core_Sitemaps' => 'getDisableCoreSitemapsService', 'Yoast\\WP\\SEO\\Initializers\\Migration_Runner' => 'getMigrationRunnerService', 'Yoast\\WP\\SEO\\Initializers\\Plugin_Headers' => 'getPluginHeadersService', 'Yoast\\WP\\SEO\\Initializers\\Woocommerce' => 'getWoocommerceService', 'Yoast\\WP\\SEO\\Integrations\\Academy_Integration' => 'getAcademyIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Activation_Cleanup_Integration' => 'getActivationCleanupIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Dialog_Integration' => 'getDialogIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Installation_Integration' => 'getInstallationIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Admin_Columns_Cache_Integration' => 'getAdminColumnsCacheIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Background_Indexing_Integration' => 'getBackgroundIndexingIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Check_Required_Version' => 'getCheckRequiredVersionService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Crawl_Settings_Integration' => 'getCrawlSettingsIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Cron_Integration' => 'getCronIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Deactivated_Premium_Integration' => 'getDeactivatedPremiumIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Disable_Concatenate_Scripts_Integration' => 'getDisableConcatenateScriptsIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Integration' => 'getFirstTimeConfigurationIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Notice_Integration' => 'getFirstTimeConfigurationNoticeIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Fix_News_Dependencies_Integration' => 'getFixNewsDependenciesIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Health_Check_Integration' => 'getHealthCheckIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\HelpScout_Beacon' => 'getHelpScoutBeaconService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Import_Integration' => 'getImportIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexables_Exclude_Taxonomy_Integration' => 'getIndexablesExcludeTaxonomyIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Notification_Integration' => 'getIndexingNotificationIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Tool_Integration' => 'getIndexingToolIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Installation_Success_Integration' => 'getInstallationSuccessIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Integrations_Page' => 'getIntegrationsPageService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Link_Count_Columns_Integration' => 'getLinkCountColumnsIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Menu_Badge_Integration' => 'getMenuBadgeIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Migration_Error_Integration' => 'getMigrationErrorIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Configuration_Integration' => 'getOldConfigurationIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Premium_Integration' => 'getOldPremiumIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Redirect_Integration' => 'getRedirectIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Redirects_Page_Integration' => 'getRedirectsPageIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Unsupported_PHP_Version_Notice' => 'getUnsupportedPHPVersionNoticeService', 'Yoast\\WP\\SEO\\Integrations\\Admin\\Workouts_Integration' => 'getWorkoutsIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Product_Editor_Checklist_Notification' => 'getBlackFridayProductEditorChecklistNotificationService', 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Promotion_Notification' => 'getBlackFridayPromotionNotificationService', 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Sidebar_Checklist_Notification' => 'getBlackFridaySidebarChecklistNotificationService', 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Trustpilot_Review_Notification' => 'getTrustpilotReviewNotificationService', 'Yoast\\WP\\SEO\\Integrations\\Alerts\\Webinar_Promo_Notification' => 'getWebinarPromoNotificationService', 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Block_Editor_Integration' => 'getBlockEditorIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Breadcrumbs_Block' => 'getBreadcrumbsBlockService', 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Internal_Linking_Category' => 'getInternalLinkingCategoryService', 'Yoast\\WP\\SEO\\Integrations\\Blocks\\Structured_Data_Blocks' => 'getStructuredDataBlocksService', 'Yoast\\WP\\SEO\\Integrations\\Breadcrumbs_Integration' => 'getBreadcrumbsIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Cleanup_Integration' => 'getCleanupIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Duplicate_Post_Integration' => 'getDuplicatePostIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Estimated_Reading_Time' => 'getEstimatedReadingTimeService', 'Yoast\\WP\\SEO\\Integrations\\Exclude_Attachment_Post_Type' => 'getExcludeAttachmentPostTypeService', 'Yoast\\WP\\SEO\\Integrations\\Exclude_Oembed_Cache_Post_Type' => 'getExcludeOembedCachePostTypeService', 'Yoast\\WP\\SEO\\Integrations\\Feature_Flag_Integration' => 'getFeatureFlagIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Backwards_Compatibility' => 'getBackwardsCompatibilityService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Category_Term_Description' => 'getCategoryTermDescriptionService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Comment_Link_Fixer' => 'getCommentLinkFixerService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Basic' => 'getCrawlCleanupBasicService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Rss' => 'getCrawlCleanupRssService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Searches' => 'getCrawlCleanupSearchesService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Feed_Improvements' => 'getFeedImprovementsService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Force_Rewrite_Title' => 'getForceRewriteTitleService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Handle_404' => 'getHandle404Service', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Indexing_Controls' => 'getIndexingControlsService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Open_Graph_OEmbed' => 'getOpenGraphOEmbedService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\RSS_Footer_Embed' => 'getRSSFooterEmbedService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Redirects' => 'getRedirectsService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Robots_Txt_Integration' => 'getRobotsTxtIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\Schema_Accessibility_Feature' => 'getSchemaAccessibilityFeatureService', 'Yoast\\WP\\SEO\\Integrations\\Front_End\\WP_Robots_Integration' => 'getWPRobotsIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Front_End_Integration' => 'getFrontEndIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Primary_Category' => 'getPrimaryCategoryService', 'Yoast\\WP\\SEO\\Integrations\\Settings_Integration' => 'getSettingsIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Support_Integration' => 'getSupportIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\AMP' => 'getAMPService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\BbPress' => 'getBbPressService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Elementor' => 'getElementorService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_Elementor_Post_Types' => 'getExcludeElementorPostTypesService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_WooCommerce_Post_Types' => 'getExcludeWooCommercePostTypesService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Jetpack' => 'getJetpackService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\W3_Total_Cache' => 'getW3TotalCacheService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML' => 'getWPMLService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML_WPSEO_Notification' => 'getWPMLWPSEONotificationService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories' => 'getWebStoriesService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories_Post_Edit' => 'getWebStoriesPostEditService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher' => 'getWincherService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher_Publish' => 'getWincherPublishService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce' => 'getWooCommerce2Service', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce_Post_Edit' => 'getWooCommercePostEditService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Woocommerce_Permalinks' => 'getWoocommercePermalinksService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof' => 'getWordproofService', 'Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof_Integration_Toggle' => 'getWordproofIntegrationToggleService', 'Yoast\\WP\\SEO\\Integrations\\Uninstall_Integration' => 'getUninstallIntegrationService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Addon_Update_Watcher' => 'getAddonUpdateWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Auto_Update_Watcher' => 'getAutoUpdateWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Ancestor_Watcher' => 'getIndexableAncestorWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Attachment_Watcher' => 'getIndexableAttachmentWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Archive_Watcher' => 'getIndexableAuthorArchiveWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Watcher' => 'getIndexableAuthorWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Category_Permalink_Watcher' => 'getIndexableCategoryPermalinkWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Date_Archive_Watcher' => 'getIndexableDateArchiveWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_HomeUrl_Watcher' => 'getIndexableHomeUrlWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Home_Page_Watcher' => 'getIndexableHomePageWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Permalink_Watcher' => 'getIndexablePermalinkWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Meta_Watcher' => 'getIndexablePostMetaWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Archive_Watcher' => 'getIndexablePostTypeArchiveWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Change_Watcher' => 'getIndexablePostTypeChangeWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher' => 'getIndexablePostWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Static_Home_Page_Watcher' => 'getIndexableStaticHomePageWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_System_Page_Watcher' => 'getIndexableSystemPageWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Taxonomy_Change_Watcher' => 'getIndexableTaxonomyChangeWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Term_Watcher' => 'getIndexableTermWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Titles_Watcher' => 'getOptionTitlesWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Wpseo_Watcher' => 'getOptionWpseoWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Category_Quick_Edit_Watcher' => 'getPrimaryCategoryQuickEditWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Term_Watcher' => 'getPrimaryTermWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Search_Engines_Discouraged_Watcher' => 'getSearchEnginesDiscouragedWatcherService', 'Yoast\\WP\\SEO\\Integrations\\Watchers\\Woocommerce_Beta_Editor_Watcher' => 'getWoocommerceBetaEditorWatcherService', 'Yoast\\WP\\SEO\\Integrations\\XMLRPC' => 'getXMLRPCService', 'Yoast\\WP\\SEO\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell' => 'getAiGenerateTitlesAndDescriptionsIntroductionUpsellService', 'Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector' => 'getIntroductionsCollectorService', 'Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository' => 'getWistiaEmbedPermissionRepositoryService', 'Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Integration' => 'getIntroductionsIntegrationService', 'Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Seen_Route' => 'getIntroductionsSeenRouteService', 'Yoast\\WP\\SEO\\Introductions\\User_Interface\\Wistia_Embed_Permission_Route' => 'getWistiaEmbedPermissionRouteService', 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler' => 'getPopulateFileCommandHandlerService', 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler' => 'getRemoveFileCommandHandlerService', 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler' => 'getLlmsTxtCronSchedulerService', 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter' => 'getWordPressFileSystemAdapterService', 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate' => 'getWordPressLlmsTxtPermissionGateService', 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Cleanup_Llms_Txt_On_Deactivation' => 'getCleanupLlmsTxtOnDeactivationService', 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Enable_Llms_Txt_Option_Watcher' => 'getEnableLlmsTxtOptionWatcherService', 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Llms_Txt_Cron_Callback_Integration' => 'getLlmsTxtCronCallbackIntegrationService', 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Schedule_Population_On_Activation_Integration' => 'getSchedulePopulationOnActivationIntegrationService', 'Yoast\\WP\\SEO\\Loader' => 'getLoaderService', 'Yoast\\WP\\SEO\\Loggers\\Logger' => 'getLoggerService', 'Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer' => 'getMetaTagsContextMemoizerService', 'Yoast\\WP\\SEO\\Memoizers\\Presentation_Memoizer' => 'getPresentationMemoizerService', 'Yoast\\WP\\SEO\\Presentations\\Abstract_Presentation' => 'getAbstractPresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Author_Archive_Presentation' => 'getIndexableAuthorArchivePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Date_Archive_Presentation' => 'getIndexableDateArchivePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Error_Page_Presentation' => 'getIndexableErrorPagePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Home_Page_Presentation' => 'getIndexableHomePagePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Post_Type_Archive_Presentation' => 'getIndexablePostTypeArchivePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Post_Type_Presentation' => 'getIndexablePostTypePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Presentation' => 'getIndexablePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Search_Result_Page_Presentation' => 'getIndexableSearchResultPagePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Static_Home_Page_Presentation' => 'getIndexableStaticHomePagePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Static_Posts_Page_Presentation' => 'getIndexableStaticPostsPagePresentationService', 'Yoast\\WP\\SEO\\Presentations\\Indexable_Term_Archive_Presentation' => 'getIndexableTermArchivePresentationService', 'Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager' => 'getPromotionManagerService', 'Yoast\\WP\\SEO\\Repositories\\Indexable_Cleanup_Repository' => 'getIndexableCleanupRepositoryService', 'Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository' => 'getIndexableHierarchyRepositoryService', 'Yoast\\WP\\SEO\\Repositories\\Indexable_Repository' => 'getIndexableRepositoryService', 'Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository' => 'getPrimaryTermRepositoryService', 'Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository' => 'getSEOLinksRepositoryService', 'Yoast\\WP\\SEO\\Routes\\Alert_Dismissal_Route' => 'getAlertDismissalRouteService', 'Yoast\\WP\\SEO\\Routes\\First_Time_Configuration_Route' => 'getFirstTimeConfigurationRouteService', 'Yoast\\WP\\SEO\\Routes\\Importing_Route' => 'getImportingRouteService', 'Yoast\\WP\\SEO\\Routes\\Indexables_Head_Route' => 'getIndexablesHeadRouteService', 'Yoast\\WP\\SEO\\Routes\\Indexing_Route' => 'getIndexingRouteService', 'Yoast\\WP\\SEO\\Routes\\Integrations_Route' => 'getIntegrationsRouteService', 'Yoast\\WP\\SEO\\Routes\\Meta_Search_Route' => 'getMetaSearchRouteService', 'Yoast\\WP\\SEO\\Routes\\SEMrush_Route' => 'getSEMrushRouteService', 'Yoast\\WP\\SEO\\Routes\\Supported_Features_Route' => 'getSupportedFeaturesRouteService', 'Yoast\\WP\\SEO\\Routes\\Wincher_Route' => 'getWincherRouteService', 'Yoast\\WP\\SEO\\Routes\\Workouts_Route' => 'getWorkoutsRouteService', 'Yoast\\WP\\SEO\\Routes\\Yoast_Head_REST_Field' => 'getYoastHeadRESTFieldService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Check' => 'getDefaultTaglineCheckService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Reports' => 'getDefaultTaglineReportsService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner' => 'getDefaultTaglineRunnerService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Check' => 'getLinksTableCheckService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Reports' => 'getLinksTableReportsService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Runner' => 'getLinksTableRunnerService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\MyYoast_Api_Request_Factory' => 'getMyYoastApiRequestFactoryService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Check' => 'getPageCommentsCheckService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Reports' => 'getPageCommentsReportsService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Runner' => 'getPageCommentsRunnerService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Check' => 'getPostnamePermalinkCheckService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Reports' => 'getPostnamePermalinkReportsService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Runner' => 'getPostnamePermalinkRunnerService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder' => 'getReportBuilderService', 'Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory' => 'getReportBuilderFactoryService', 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service' => 'getAioseoReplacevarServiceService', 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service' => 'getAioseoRobotsProviderServiceService', 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service' => 'getAioseoRobotsTransformerServiceService', 'Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Social_Images_Provider_Service' => 'getAioseoSocialImagesProviderServiceService', 'Yoast\\WP\\SEO\\Services\\Importing\\Conflicting_Plugins_Service' => 'getConflictingPluginsServiceService', 'Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service' => 'getImportableDetectorServiceService', 'Yoast\\WP\\SEO\\Services\\Indexables\\Indexable_Version_Manager' => 'getIndexableVersionManagerService', 'Yoast\\WP\\SEO\\Surfaces\\Classes_Surface' => 'getClassesSurfaceService', 'Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface' => 'getHelpersSurfaceService', 'Yoast\\WP\\SEO\\Surfaces\\Meta_Surface' => 'getMetaSurfaceService', 'Yoast\\WP\\SEO\\Surfaces\\Open_Graph_Helpers_Surface' => 'getOpenGraphHelpersSurfaceService', 'Yoast\\WP\\SEO\\Surfaces\\Schema_Helpers_Surface' => 'getSchemaHelpersSurfaceService', 'Yoast\\WP\\SEO\\Surfaces\\Twitter_Helpers_Surface' => 'getTwitterHelpersSurfaceService', 'Yoast\\WP\\SEO\\User_Meta\\Application\\Additional_Contactmethods_Collector' => 'getAdditionalContactmethodsCollectorService', 'Yoast\\WP\\SEO\\User_Meta\\Application\\Custom_Meta_Collector' => 'getCustomMetaCollectorService', 'Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Additional_Contactmethods_Integration' => 'getAdditionalContactmethodsIntegrationService', 'Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Cleanup_Integration' => 'getCleanupIntegration2Service', 'Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Custom_Meta_Integration' => 'getCustomMetaIntegrationService', 'Yoast\\WP\\SEO\\User_Profiles_Additions\\User_Interface\\User_Profiles_Additions_Ui' => 'getUserProfilesAdditionsUiService', 'Yoast\\WP\\SEO\\Values\\Images' => 'getImagesService', 'Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions' => 'getIndexableBuilderVersionsService', 'Yoast\\WP\\SEO\\Values\\Open_Graph\\Images' => 'getImages2Service', 'Yoast\\WP\\SEO\\Values\\Twitter\\Images' => 'getImages3Service', 'Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper' => 'getWPQueryWrapperService', 'Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler' => 'getWPRemoteHandlerService', 'Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper' => 'getWPRewriteWrapperService', 'Yoast_Notification_Center' => 'getYoastNotificationCenterService', 'wpdb' => 'getWpdbService', ]; $this->privates = [ 'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository' => true, 'Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate' => true, ]; $this->aliases = [ 'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => 'service_container', ]; } public function getRemovedIds() { return [ 'Psr\\Container\\ContainerInterface' => true, 'YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'YoastSEO_Vendor\\YoastSEO_Vendor\\Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'Yoast\\WP\\SEO\\Analytics\\Domain\\Missing_Indexable_Bucket' => true, 'Yoast\\WP\\SEO\\Analytics\\Domain\\Missing_Indexable_Count' => true, 'Yoast\\WP\\SEO\\Analytics\\Domain\\To_Be_Cleaned_Indexable_Bucket' => true, 'Yoast\\WP\\SEO\\Analytics\\Domain\\To_Be_Cleaned_Indexable_Count' => true, 'Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Configuration\\Dashboard_Configuration' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Content_Types\\Content_Types_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Endpoints\\Endpoints_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Filter_Pairs\\Filter_Pairs_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Groups\\SEO_Score_Groups\\SEO_Score_Groups_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Readability_Score_Results\\Readability_Score_Results_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\SEO_Score_Results\\SEO_Score_Results_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Search_Rankings\\Search_Ranking_Compare_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Search_Rankings\\Top_Page_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Search_Rankings\\Top_Query_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Tracking\\Setup_Steps_Tracking' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Traffic\\Organic_Sessions_Compare_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Application\\Traffic\\Organic_Sessions_Daily_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Analytics_4\\Failed_Request_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Analytics_4\\Invalid_Request_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Analytics_4\\Unexpected_Response_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Content_Types\\Content_Type' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Content_Types\\Content_Types_List' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Data_Provider\\Data_Container' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Endpoint\\Endpoint_List' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Filter_Pairs\\Filter_Pairs_Interface' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Filter_Pairs\\Product_Category_Filter_Pair' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\Readability_Score_Groups\\Bad_Readability_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\Readability_Score_Groups\\Good_Readability_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\Readability_Score_Groups\\No_Readability_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\Readability_Score_Groups\\Ok_Readability_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Results\\Current_Score' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Results\\Current_Scores_List' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Results\\Score_Result' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Results\\Score_Results_Not_Found_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Search_Console\\Failed_Request_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Search_Console\\Unexpected_Response_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Search_Rankings\\Comparison_Search_Ranking_Data' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Search_Rankings\\Search_Ranking_Data' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Search_Rankings\\Top_Page_Data' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Taxonomies\\Taxonomy' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Time_Based_SEO_Metrics\\Repository_Not_Found_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Time_Based_Seo_Metrics\\Data_Source_Not_Available_Exception' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Traffic\\Comparison_Traffic_Data' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Traffic\\Daily_Traffic_Data' => true, 'Yoast\\WP\\SEO\\Dashboard\\Domain\\Traffic\\Traffic_Data' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Analytics_4\\Analytics_4_Parameters' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Analytics_4\\Site_Kit_Analytics_4_Adapter' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Analytics_4\\Site_Kit_Analytics_4_Api_Call' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Browser_Cache\\Browser_Cache_Configuration' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository_Interface' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Connection\\Site_Kit_Is_Connected_Call' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Readability_Scores_Endpoint' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\SEO_Scores_Endpoint' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Setup_Steps_Tracking_Endpoint' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Configuration_Dismissal_Endpoint' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Time_Based_SEO_Metrics_Endpoint' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Indexables\\Top_Page_Indexable_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Nonces\\Nonce_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Score_Groups\\Score_Group_Link_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Score_Results\\Readability_Score_Results\\Cached_Readability_Score_Results_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Score_Results\\Readability_Score_Results\\Readability_Score_Results_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Score_Results\\SEO_Score_Results\\Cached_SEO_Score_Results_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Score_Results\\SEO_Score_Results\\SEO_Score_Results_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Search_Console\\Search_Console_Parameters' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Search_Console\\Site_Kit_Search_Console_Adapter' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Search_Console\\Site_Kit_Search_Console_Api_Call' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Taxonomies\\Taxonomies_Collector' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Taxonomies\\Taxonomy_Validator' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository' => true, 'Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository_Interface' => true, 'Yoast\\WP\\SEO\\Editors\\Domain\\Analysis_Features\\Analysis_Feature' => true, 'Yoast\\WP\\SEO\\Editors\\Domain\\Analysis_Features\\Analysis_Features_List' => true, 'Yoast\\WP\\SEO\\Editors\\Domain\\Seo\\Description' => true, 'Yoast\\WP\\SEO\\Editors\\Domain\\Seo\\Keyphrase' => true, 'Yoast\\WP\\SEO\\Editors\\Domain\\Seo\\Social' => true, 'Yoast\\WP\\SEO\\Editors\\Domain\\Seo\\Title' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Cornerstone_Content' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Inclusive_Language_Analysis' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Integrations\\Jetpack_Markdown' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Integrations\\Multilingual' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Integrations\\News_SEO' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Integrations\\Semrush' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Integrations\\Wincher' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Integrations\\WooCommerce' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Integrations\\WooCommerce_SEO' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Keyphrase_Analysis' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Previously_Used_Keyphrase' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Readability_Analysis' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Posts\\Description_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Posts\\Keyphrase_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Posts\\Social_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Posts\\Title_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Terms\\Description_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Terms\\Keyphrase_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Terms\\Social_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Seo\\Terms\\Title_Data_Provider' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Site\\Post_Site_Information' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Site\\Term_Site_Information' => true, 'Yoast\\WP\\SEO\\Editors\\Framework\\Word_Form_Recognition' => true, 'Yoast\\WP\\SEO\\Elementor\\Infrastructure\\Request_Post' => true, 'Yoast\\WP\\SEO\\Images\\Application\\Image_Content_Extractor' => true, 'Yoast\\WP\\SEO\\Introductions\\Application\\Ai_Fix_Assessments_Upsell' => true, 'Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector' => true, 'Yoast\\WP\\SEO\\Introductions\\Domain\\Introduction_Interface' => true, 'Yoast\\WP\\SEO\\Introductions\\Domain\\Introduction_Item' => true, 'Yoast\\WP\\SEO\\Introductions\\Domain\\Introductions_Bucket' => true, 'Yoast\\WP\\SEO\\Introductions\\Domain\\Invalid_User_Id_Exception' => true, 'Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Introductions_Seen_Repository' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Health_Check\\File_Check' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Health_Check\\File_Runner' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Markdown_Builders\\Description_Builder' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Markdown_Builders\\Intro_Builder' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Markdown_Builders\\Link_Lists_Builder' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Markdown_Builders\\Markdown_Builder' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Markdown_Builders\\Title_Builder' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Application\\Markdown_Escaper' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\File\\Llms_File_System_Interface' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\File\\Llms_Txt_Permission_Gate_Interface' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\Markdown\\Items\\Link' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\Markdown\\Llms_Txt_Renderer' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\Markdown\\Sections\\Description' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\Markdown\\Sections\\Intro' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\Markdown\\Sections\\Link_List' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Domain\\Markdown\\Sections\\Title' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\Markdown_Services\\Content_Types_Collector' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\Markdown_Services\\Description_Adapter' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\Markdown_Services\\Sitemap_Link_Collector' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\Markdown_Services\\Terms_Collector' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\Markdown_Services\\Title_Adapter' => true, 'Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Health_Check\\File_Reports' => true, 'Yoast\\WP\\SEO\\Presenters\\Robots_Txt_Presenter' => true, 'Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager_Interface' => true, 'Yoast\\WP\\SEO\\Promotions\\Domain\\Black_Friday_Checklist_Promotion' => true, 'Yoast\\WP\\SEO\\Promotions\\Domain\\Black_Friday_Promotion' => true, 'Yoast\\WP\\SEO\\Promotions\\Domain\\Time_Interval' => true, 'Yoast\\WP\\SEO\\User_Meta\\Application\\Cleanup_Service' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Facebook' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Instagram' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Linkedin' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Myspace' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Pinterest' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Soundcloud' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Tumblr' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Wikipedia' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\X' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Additional_Contactmethods\\Youtube' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Custom_Meta\\Author_Metadesc' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Custom_Meta\\Author_Title' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Custom_Meta\\Content_Analysis_Disable' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Custom_Meta\\Inclusive_Language_Analysis_Disable' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Custom_Meta\\Keyword_Analysis_Disable' => true, 'Yoast\\WP\\SEO\\User_Meta\\Framework\\Custom_Meta\\Noindex_Author' => true, 'Yoast\\WP\\SEO\\User_Meta\\Infrastructure\\Cleanup_Repository' => true, ]; } public function compile() { throw new LogicException('You cannot compile a dumped container that was already compiled.'); } public function isCompiled() { return true; } public function isFrozen() { @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED); return true; } /** * Gets the public 'WPSEO_Addon_Manager' shared service. * * @return \WPSEO_Addon_Manager */ protected function getWPSEOAddonManagerService() { return $this->services['WPSEO_Addon_Manager'] = \Yoast\WP\SEO\WordPress\Wrapper::get_addon_manager(); } /** * Gets the public 'WPSEO_Admin_Asset_Manager' shared service. * * @return \WPSEO_Admin_Asset_Manager */ protected function getWPSEOAdminAssetManagerService() { return $this->services['WPSEO_Admin_Asset_Manager'] = \Yoast\WP\SEO\WordPress\Wrapper::get_admin_asset_manager(); } /** * Gets the public 'WPSEO_Breadcrumbs' shared autowired service. * * @return \WPSEO_Breadcrumbs */ protected function getWPSEOBreadcrumbsService() { return $this->services['WPSEO_Breadcrumbs'] = new \WPSEO_Breadcrumbs(); } /** * Gets the public 'WPSEO_Frontend' shared autowired service. * * @return \WPSEO_Frontend */ protected function getWPSEOFrontendService() { return $this->services['WPSEO_Frontend'] = new \WPSEO_Frontend(); } /** * Gets the public 'WPSEO_Replace_Vars' shared service. * * @return \WPSEO_Replace_Vars */ protected function getWPSEOReplaceVarsService() { return $this->services['WPSEO_Replace_Vars'] = \Yoast\WP\SEO\WordPress\Wrapper::get_replace_vars(); } /** * Gets the public 'WPSEO_Shortlinker' shared service. * * @return \WPSEO_Shortlinker */ protected function getWPSEOShortlinkerService() { return $this->services['WPSEO_Shortlinker'] = \Yoast\WP\SEO\WordPress\Wrapper::get_shortlinker(); } /** * Gets the public 'WPSEO_Utils' shared service. * * @return \WPSEO_Utils */ protected function getWPSEOUtilsService() { return $this->services['WPSEO_Utils'] = \Yoast\WP\SEO\WordPress\Wrapper::get_utils(); } /** * Gets the public 'Yoast\WP\Lib\Migrations\Adapter' shared autowired service. * * @return \Yoast\WP\Lib\Migrations\Adapter */ protected function getAdapterService() { return $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter(); } /** * Gets the public 'Yoast\WP\SEO\Actions\Addon_Installation\Addon_Activate_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Addon_Installation\Addon_Activate_Action */ protected function getAddonActivateActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Activate_Action'] = new \Yoast\WP\SEO\Actions\Addon_Installation\Addon_Activate_Action(${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Require_File_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Require_File_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Require_File_Helper'] = new \Yoast\WP\SEO\Helpers\Require_File_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Addon_Installation\Addon_Install_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Addon_Installation\Addon_Install_Action */ protected function getAddonInstallActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Install_Action'] = new \Yoast\WP\SEO\Actions\Addon_Installation\Addon_Install_Action(${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Require_File_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Require_File_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Require_File_Helper'] = new \Yoast\WP\SEO\Helpers\Require_File_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Alert_Dismissal_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Alert_Dismissal_Action */ protected function getAlertDismissalActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action'] = new \Yoast\WP\SEO\Actions\Alert_Dismissal_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Configuration\First_Time_Configuration_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Configuration\First_Time_Configuration_Action */ protected function getFirstTimeConfigurationActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Configuration\\First_Time_Configuration_Action'] = new \Yoast\WP\SEO\Actions\Configuration\First_Time_Configuration_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper'] : $this->getSocialProfilesHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Cleanup_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Cleanup_Action */ protected function getAioseoCleanupActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Cleanup_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Cleanup_Action(${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Custom_Archive_Settings_Importing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Custom_Archive_Settings_Importing_Action */ protected function getAioseoCustomArchiveSettingsImportingActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Custom_Archive_Settings_Importing_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] : $this->getImportCursorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] : $this->getAioseoRobotsTransformerServiceService()) && false ?: '_'}); $instance->set_import_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] = new \Yoast\WP\SEO\Helpers\Import_Helper())) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Default_Archive_Settings_Importing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Default_Archive_Settings_Importing_Action */ protected function getAioseoDefaultArchiveSettingsImportingActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Default_Archive_Settings_Importing_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] : $this->getImportCursorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] : $this->getAioseoRobotsTransformerServiceService()) && false ?: '_'}); $instance->set_import_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] = new \Yoast\WP\SEO\Helpers\Import_Helper())) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_General_Settings_Importing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_General_Settings_Importing_Action */ protected function getAioseoGeneralSettingsImportingActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_General_Settings_Importing_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] : $this->getImportCursorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] : $this->getAioseoRobotsTransformerServiceService()) && false ?: '_'}); $instance->set_import_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] = new \Yoast\WP\SEO\Helpers\Import_Helper())) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Posts_Importing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Posts_Importing_Action */ protected function getAioseoPostsImportingActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Posts_Importing_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] : $this->getImportCursorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_To_Postmeta_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_To_Postmeta_Helper'] : $this->getIndexableToPostmetaHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] : $this->getAioseoRobotsTransformerServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Social_Images_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Social_Images_Provider_Service'] : $this->getAioseoSocialImagesProviderServiceService()) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Posttype_Defaults_Settings_Importing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Posttype_Defaults_Settings_Importing_Action */ protected function getAioseoPosttypeDefaultsSettingsImportingActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Posttype_Defaults_Settings_Importing_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] : $this->getImportCursorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] : $this->getAioseoRobotsTransformerServiceService()) && false ?: '_'}); $instance->set_import_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] = new \Yoast\WP\SEO\Helpers\Import_Helper())) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Taxonomy_Settings_Importing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Taxonomy_Settings_Importing_Action */ protected function getAioseoTaxonomySettingsImportingActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Taxonomy_Settings_Importing_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] : $this->getImportCursorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] : $this->getAioseoRobotsTransformerServiceService()) && false ?: '_'}); $instance->set_import_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] = new \Yoast\WP\SEO\Helpers\Import_Helper())) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Validate_Data_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Validate_Data_Action */ protected function getAioseoValidateDataActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Validate_Data_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Aioseo\Aioseo_Validate_Data_Action(${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action'] : $this->getAioseoCustomArchiveSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action'] : $this->getAioseoDefaultArchiveSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action'] : $this->getAioseoGeneralSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action'] : $this->getAioseoPosttypeDefaultsSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action'] : $this->getAioseoTaxonomySettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action'] : $this->getAioseoPostsImportingActionService()) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Importing\Deactivate_Conflicting_Plugins_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Importing\Deactivate_Conflicting_Plugins_Action */ protected function getDeactivateConflictingPluginsActionService() { $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Deactivate_Conflicting_Plugins_Action'] = $instance = new \Yoast\WP\SEO\Actions\Importing\Deactivate_Conflicting_Plugins_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] : $this->getImportCursorHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] : $this->getAioseoRobotsTransformerServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Conflicting_Plugins_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Conflicting_Plugins_Service'] : ($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Conflicting_Plugins_Service'] = new \Yoast\WP\SEO\Services\Importing\Conflicting_Plugins_Service())) && false ?: '_'}); $instance->set_aioseo_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexables\Indexable_Head_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexables\Indexable_Head_Action */ protected function getIndexableHeadActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexables\\Indexable_Head_Action'] = new \Yoast\WP\SEO\Actions\Indexables\Indexable_Head_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface'] : $this->getMetaSurfaceService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action */ protected function getIndexableGeneralIndexationActionService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action']; } return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action'] = new \Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action($a); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action */ protected function getIndexableIndexingCompleteActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action'] = new \Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action */ protected function getIndexablePostIndexationActionService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action']; } return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'] = new \Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, $a, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}, $b); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action */ protected function getIndexablePostTypeArchiveIndexationActionService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action']; } return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'] = new \Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action($a, $b, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action */ protected function getIndexableTermIndexationActionService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action']; } return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action'] = new \Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}, $a, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Indexing_Complete_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Indexing_Complete_Action */ protected function getIndexingCompleteActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Complete_Action'] = new \Yoast\WP\SEO\Actions\Indexing\Indexing_Complete_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Indexing_Prepare_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Indexing_Prepare_Action */ protected function getIndexingPrepareActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Prepare_Action'] = new \Yoast\WP\SEO\Actions\Indexing\Indexing_Prepare_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action */ protected function getPostLinkIndexingActionService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'] : $this->getIndexableLinkBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']; } $c = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']; } $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action($a, $b, $c, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}); $instance->set_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Indexing\Term_Link_Indexing_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Indexing\Term_Link_Indexing_Action */ protected function getTermLinkIndexingActionService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'] : $this->getIndexableLinkBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']; } $c = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'])) { return $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']; } $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'] = $instance = new \Yoast\WP\SEO\Actions\Indexing\Term_Link_Indexing_Action($a, $b, $c, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}); $instance->set_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Actions\Integrations_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Integrations_Action */ protected function getIntegrationsActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Integrations_Action'] = new \Yoast\WP\SEO\Actions\Integrations_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\SEMrush\SEMrush_Login_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\SEMrush\SEMrush_Login_Action */ protected function getSEMrushLoginActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Login_Action'] = new \Yoast\WP\SEO\Actions\SEMrush\SEMrush_Login_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\SEMrush_Client']) ? $this->services['Yoast\\WP\\SEO\\Config\\SEMrush_Client'] : $this->getSEMrushClientService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\SEMrush\SEMrush_Options_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\SEMrush\SEMrush_Options_Action */ protected function getSEMrushOptionsActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Options_Action'] = new \Yoast\WP\SEO\Actions\SEMrush\SEMrush_Options_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\SEMrush\SEMrush_Phrases_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\SEMrush\SEMrush_Phrases_Action */ protected function getSEMrushPhrasesActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Phrases_Action'] = new \Yoast\WP\SEO\Actions\SEMrush\SEMrush_Phrases_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\SEMrush_Client']) ? $this->services['Yoast\\WP\\SEO\\Config\\SEMrush_Client'] : $this->getSEMrushClientService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Wincher\Wincher_Account_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Wincher\Wincher_Account_Action */ protected function getWincherAccountActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Account_Action'] = new \Yoast\WP\SEO\Actions\Wincher\Wincher_Account_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client']) ? $this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client'] : $this->getWincherClientService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Wincher\Wincher_Keyphrases_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Wincher\Wincher_Keyphrases_Action */ protected function getWincherKeyphrasesActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Keyphrases_Action'] = new \Yoast\WP\SEO\Actions\Wincher\Wincher_Keyphrases_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client']) ? $this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client'] : $this->getWincherClientService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Actions\Wincher\Wincher_Login_Action' shared autowired service. * * @return \Yoast\WP\SEO\Actions\Wincher\Wincher_Login_Action */ protected function getWincherLoginActionService() { return $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Login_Action'] = new \Yoast\WP\SEO\Actions\Wincher\Wincher_Login_Action(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client']) ? $this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client'] : $this->getWincherClientService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Analytics\Application\Missing_Indexables_Collector' shared autowired service. * * @return \Yoast\WP\SEO\Analytics\Application\Missing_Indexables_Collector */ protected function getMissingIndexablesCollectorService() { return $this->services['Yoast\\WP\\SEO\\Analytics\\Application\\Missing_Indexables_Collector'] = new \Yoast\WP\SEO\Analytics\Application\Missing_Indexables_Collector(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action'] : $this->getIndexableGeneralIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'] : $this->getIndexablePostIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'] : $this->getIndexablePostTypeArchiveIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action'] : $this->getIndexableTermIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'] : $this->getPostLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'] : $this->getTermLinkIndexingActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Analytics\Application\To_Be_Cleaned_Indexables_Collector' shared autowired service. * * @return \Yoast\WP\SEO\Analytics\Application\To_Be_Cleaned_Indexables_Collector */ protected function getToBeCleanedIndexablesCollectorService() { return $this->services['Yoast\\WP\\SEO\\Analytics\\Application\\To_Be_Cleaned_Indexables_Collector'] = new \Yoast\WP\SEO\Analytics\Application\To_Be_Cleaned_Indexables_Collector(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Cleanup_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Cleanup_Repository'] : $this->getIndexableCleanupRepositoryService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Analytics\User_Interface\Last_Completed_Indexation_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Analytics\User_Interface\Last_Completed_Indexation_Integration */ protected function getLastCompletedIndexationIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Analytics\\User_Interface\\Last_Completed_Indexation_Integration'] = new \Yoast\WP\SEO\Analytics\User_Interface\Last_Completed_Indexation_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Author_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Author_Builder */ protected function getIndexableAuthorBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder']; } $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Author_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper'] : $this->getAuthorArchiveHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, $a); $instance->set_social_image_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] : $this->getImageHelper2Service()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] : $this->getImageHelper4Service()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Builder */ protected function getIndexableBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Author_Builder'] : $this->getIndexableAuthorBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder'] : $this->getIndexablePostBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $c = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] : $this->getIndexableTermBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $d = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Home_Page_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Home_Page_Builder'] : $this->getIndexableHomePageBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $e = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Type_Archive_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Type_Archive_Builder'] : $this->getIndexablePostTypeArchiveBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $f = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'] : $this->getIndexableHierarchyBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $g = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder'] : $this->getPrimaryTermBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $h = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $i = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'] : $this->getIndexableLinkBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']; } $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Builder($a, $b, $c, $d, $e, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Date_Archive_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Date_Archive_Builder'] : $this->getIndexableDateArchiveBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_System_Page_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_System_Page_Builder'] : $this->getIndexableSystemPageBuilderService()) && false ?: '_'}, $f, $g, $h, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Indexables\\Indexable_Version_Manager']) ? $this->services['Yoast\\WP\\SEO\\Services\\Indexables\\Indexable_Version_Manager'] : $this->getIndexableVersionManagerService()) && false ?: '_'}, $i); $instance->set_indexable_repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Date_Archive_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Date_Archive_Builder */ protected function getIndexableDateArchiveBuilderService() { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Date_Archive_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Date_Archive_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder */ protected function getIndexableHierarchyBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository'] : $this->getIndexableHierarchyRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder']; } $c = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder']; } $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder($a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] : ($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] = new \Yoast\WP\SEO\Repositories\Primary_Term_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, $b, $c); $instance->set_indexable_repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Home_Page_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Home_Page_Builder */ protected function getIndexableHomePageBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Home_Page_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Home_Page_Builder']; } $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Home_Page_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Home_Page_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}, $a); $instance->set_social_image_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] : $this->getImageHelper2Service()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] : $this->getImageHelper4Service()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Link_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Link_Builder */ protected function getIndexableLinkBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder']; } $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Link_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository'] : ($this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository'] = new \Yoast\WP\SEO\Repositories\SEO_Links_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, $a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, $b, new \Yoast\WP\SEO\Images\Application\Image_Content_Extractor()); $instance->set_dependencies(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Post_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Post_Builder */ protected function getIndexablePostBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder']; } $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Post_Builder($a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] = new \Yoast\WP\SEO\Helpers\Meta_Helper())) && false ?: '_'}); $instance->set_indexable_repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); $instance->set_social_image_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] : $this->getImageHelper2Service()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] : $this->getImageHelper4Service()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Post_Type_Archive_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Post_Type_Archive_Builder */ protected function getIndexablePostTypeArchiveBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Type_Archive_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Type_Archive_Builder']; } return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Post_Type_Archive_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_Post_Type_Archive_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}, $a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_System_Page_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_System_Page_Builder */ protected function getIndexableSystemPageBuilderService() { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_System_Page_Builder'] = new \Yoast\WP\SEO\Builders\Indexable_System_Page_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Builders\Indexable_Term_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Indexable_Term_Builder */ protected function getIndexableTermBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder']; } $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Term_Builder'] = $instance = new \Yoast\WP\SEO\Builders\Indexable_Term_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}, $a); $instance->set_social_image_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] : $this->getImageHelper2Service()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] : $this->getImageHelper4Service()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Builders\Primary_Term_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Builders\Primary_Term_Builder */ protected function getPrimaryTermBuilderService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder'])) { return $this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder']; } return $this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder'] = new \Yoast\WP\SEO\Builders\Primary_Term_Builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] : ($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] = new \Yoast\WP\SEO\Repositories\Primary_Term_Repository())) && false ?: '_'}, $a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper'] = new \Yoast\WP\SEO\Helpers\Primary_Term_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] = new \Yoast\WP\SEO\Helpers\Meta_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Commands\Cleanup_Command' shared autowired service. * * @return \Yoast\WP\SEO\Commands\Cleanup_Command */ protected function getCleanupCommandService() { return $this->services['Yoast\\WP\\SEO\\Commands\\Cleanup_Command'] = new \Yoast\WP\SEO\Commands\Cleanup_Command(${($_ = isset($this->services['Yoast\\WP\\SEO\\Integrations\\Cleanup_Integration']) ? $this->services['Yoast\\WP\\SEO\\Integrations\\Cleanup_Integration'] : $this->getCleanupIntegrationService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Commands\Index_Command' shared autowired service. * * @return \Yoast\WP\SEO\Commands\Index_Command */ protected function getIndexCommandService() { return $this->services['Yoast\\WP\\SEO\\Commands\\Index_Command'] = new \Yoast\WP\SEO\Commands\Index_Command(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'] : $this->getIndexablePostIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action'] : $this->getIndexableTermIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'] : $this->getIndexablePostTypeArchiveIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action'] : $this->getIndexableGeneralIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action'] : $this->getIndexableIndexingCompleteActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Prepare_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Prepare_Action'] : $this->getIndexingPrepareActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'] : $this->getPostLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'] : $this->getTermLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional */ protected function getAddonInstallationConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional'] = new \Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin\Doing_Post_Quick_Edit_Save_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin\Doing_Post_Quick_Edit_Save_Conditional */ protected function getDoingPostQuickEditSaveConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Doing_Post_Quick_Edit_Save_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Doing_Post_Quick_Edit_Save_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin\Estimated_Reading_Time_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin\Estimated_Reading_Time_Conditional */ protected function getEstimatedReadingTimeConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Estimated_Reading_Time_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Estimated_Reading_Time_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Post_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Post_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Post_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Post_Conditional())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin\Licenses_Page_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin\Licenses_Page_Conditional */ protected function getLicensesPageConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Licenses_Page_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Licenses_Page_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin\Non_Network_Admin_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin\Non_Network_Admin_Conditional */ protected function getNonNetworkAdminConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Non_Network_Admin_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Non_Network_Admin_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin\Post_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin\Post_Conditional */ protected function getPostConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Post_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Post_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional */ protected function getPostsOverviewOrAjaxConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Posts_Overview_Or_Ajax_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Posts_Overview_Or_Ajax_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin\Yoast_Admin_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin\Yoast_Admin_Conditional */ protected function getYoastAdminConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin\\Yoast_Admin_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin\Yoast_Admin_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Admin_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Admin_Conditional */ protected function getAdminConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Admin_Conditional'] = new \Yoast\WP\SEO\Conditionals\Admin_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Attachment_Redirections_Enabled_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Attachment_Redirections_Enabled_Conditional */ protected function getAttachmentRedirectionsEnabledConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Attachment_Redirections_Enabled_Conditional'] = new \Yoast\WP\SEO\Conditionals\Attachment_Redirections_Enabled_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Check_Required_Version_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Check_Required_Version_Conditional */ protected function getCheckRequiredVersionConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Check_Required_Version_Conditional'] = new \Yoast\WP\SEO\Conditionals\Check_Required_Version_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Deactivating_Yoast_Seo_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Deactivating_Yoast_Seo_Conditional */ protected function getDeactivatingYoastSeoConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Deactivating_Yoast_Seo_Conditional'] = new \Yoast\WP\SEO\Conditionals\Deactivating_Yoast_Seo_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Development_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Development_Conditional */ protected function getDevelopmentConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Development_Conditional'] = new \Yoast\WP\SEO\Conditionals\Development_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Front_End_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Front_End_Conditional */ protected function getFrontEndConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Front_End_Conditional'] = new \Yoast\WP\SEO\Conditionals\Front_End_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Get_Request_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Get_Request_Conditional */ protected function getGetRequestConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Get_Request_Conditional'] = new \Yoast\WP\SEO\Conditionals\Get_Request_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional */ protected function getGoogleSiteKitFeatureConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Google_Site_Kit_Feature_Conditional'] = new \Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Headless_Rest_Endpoints_Enabled_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Headless_Rest_Endpoints_Enabled_Conditional */ protected function getHeadlessRestEndpointsEnabledConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Headless_Rest_Endpoints_Enabled_Conditional'] = new \Yoast\WP\SEO\Conditionals\Headless_Rest_Endpoints_Enabled_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Import_Tool_Selected_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Import_Tool_Selected_Conditional */ protected function getImportToolSelectedConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Import_Tool_Selected_Conditional'] = new \Yoast\WP\SEO\Conditionals\Import_Tool_Selected_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Jetpack_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Jetpack_Conditional */ protected function getJetpackConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Jetpack_Conditional'] = new \Yoast\WP\SEO\Conditionals\Jetpack_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Migrations_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Migrations_Conditional */ protected function getMigrationsConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Migrations_Conditional'] = new \Yoast\WP\SEO\Conditionals\Migrations_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status']) ? $this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] : ($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] = new \Yoast\WP\SEO\Config\Migration_Status())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\New_Settings_Ui_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\New_Settings_Ui_Conditional */ protected function getNewSettingsUiConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\New_Settings_Ui_Conditional'] = new \Yoast\WP\SEO\Conditionals\New_Settings_Ui_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\News_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\News_Conditional */ protected function getNewsConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\News_Conditional'] = new \Yoast\WP\SEO\Conditionals\News_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\No_Tool_Selected_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\No_Tool_Selected_Conditional */ protected function getNoToolSelectedConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\No_Tool_Selected_Conditional'] = new \Yoast\WP\SEO\Conditionals\No_Tool_Selected_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional */ protected function getNonMultisiteConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Non_Multisite_Conditional'] = new \Yoast\WP\SEO\Conditionals\Non_Multisite_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional */ protected function getNotAdminAjaxConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Not_Admin_Ajax_Conditional'] = new \Yoast\WP\SEO\Conditionals\Not_Admin_Ajax_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Open_Graph_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Open_Graph_Conditional */ protected function getOpenGraphConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Open_Graph_Conditional'] = new \Yoast\WP\SEO\Conditionals\Open_Graph_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Premium_Active_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Premium_Active_Conditional */ protected function getPremiumActiveConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Premium_Active_Conditional'] = new \Yoast\WP\SEO\Conditionals\Premium_Active_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Premium_Inactive_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Premium_Inactive_Conditional */ protected function getPremiumInactiveConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Premium_Inactive_Conditional'] = new \Yoast\WP\SEO\Conditionals\Premium_Inactive_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Primary_Category_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Primary_Category_Conditional */ protected function getPrimaryCategoryConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Primary_Category_Conditional'] = new \Yoast\WP\SEO\Conditionals\Primary_Category_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Robots_Txt_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Robots_Txt_Conditional */ protected function getRobotsTxtConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Robots_Txt_Conditional'] = new \Yoast\WP\SEO\Conditionals\Robots_Txt_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Front_End_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Front_End_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Front_End_Conditional'] = new \Yoast\WP\SEO\Conditionals\Front_End_Conditional())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\SEMrush_Enabled_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\SEMrush_Enabled_Conditional */ protected function getSEMrushEnabledConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\SEMrush_Enabled_Conditional'] = new \Yoast\WP\SEO\Conditionals\SEMrush_Enabled_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Settings_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Settings_Conditional */ protected function getSettingsConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Settings_Conditional'] = new \Yoast\WP\SEO\Conditionals\Settings_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\User_Can_Manage_Wpseo_Options_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\User_Can_Manage_Wpseo_Options_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\User_Can_Manage_Wpseo_Options_Conditional'] = new \Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Should_Index_Links_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Should_Index_Links_Conditional */ protected function getShouldIndexLinksConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Should_Index_Links_Conditional'] = new \Yoast\WP\SEO\Conditionals\Should_Index_Links_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Text_Formality_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Text_Formality_Conditional */ protected function getTextFormalityConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Text_Formality_Conditional'] = new \Yoast\WP\SEO\Conditionals\Text_Formality_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional */ protected function getElementorActivatedConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Activated_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional */ protected function getElementorEditConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Edit_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Edit_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\Polylang_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\Polylang_Conditional */ protected function getPolylangConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Polylang_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Polylang_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional */ protected function getSiteKitConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Site_Kit_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit'] : $this->getSiteKitService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\TranslatePress_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\TranslatePress_Conditional */ protected function getTranslatePressConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\TranslatePress_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\TranslatePress_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\W3_Total_Cache_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\W3_Total_Cache_Conditional */ protected function getW3TotalCacheConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\W3_Total_Cache_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\W3_Total_Cache_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional */ protected function getWPMLConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\WPML_WPSEO_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\WPML_WPSEO_Conditional */ protected function getWPMLWPSEOConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_WPSEO_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\WPML_WPSEO_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Integration_Active_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Integration_Active_Conditional * * @deprecated Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Integration_Active_Conditional is deprecated since version 22.10! */ protected function getWordproofIntegrationActiveConditionalService() { @trigger_error('Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Integration_Active_Conditional is deprecated since version 22.10!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Integration_Active_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Integration_Active_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper'] : $this->getWordproofHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Plugin_Inactive_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Plugin_Inactive_Conditional * * @deprecated Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Plugin_Inactive_Conditional is deprecated since version 22.10! */ protected function getWordproofPluginInactiveConditionalService() { @trigger_error('Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Plugin_Inactive_Conditional is deprecated since version 22.10!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Wordproof_Plugin_Inactive_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Wordproof_Plugin_Inactive_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Updated_Importer_Framework_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Updated_Importer_Framework_Conditional */ protected function getUpdatedImporterFrameworkConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Updated_Importer_Framework_Conditional'] = new \Yoast\WP\SEO\Conditionals\Updated_Importer_Framework_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\User_Can_Edit_Users_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\User_Can_Edit_Users_Conditional */ protected function getUserCanEditUsersConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\User_Can_Edit_Users_Conditional'] = new \Yoast\WP\SEO\Conditionals\User_Can_Edit_Users_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional */ protected function getUserCanManageWpseoOptionsConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\User_Can_Manage_Wpseo_Options_Conditional'] = new \Yoast\WP\SEO\Conditionals\User_Can_Manage_Wpseo_Options_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\User_Can_Publish_Posts_And_Pages_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\User_Can_Publish_Posts_And_Pages_Conditional */ protected function getUserCanPublishPostsAndPagesConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\User_Can_Publish_Posts_And_Pages_Conditional'] = new \Yoast\WP\SEO\Conditionals\User_Can_Publish_Posts_And_Pages_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\User_Edit_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\User_Edit_Conditional */ protected function getUserEditConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\User_Edit_Conditional'] = new \Yoast\WP\SEO\Conditionals\User_Edit_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\User_Profile_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\User_Profile_Conditional */ protected function getUserProfileConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\User_Profile_Conditional'] = new \Yoast\WP\SEO\Conditionals\User_Profile_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\WP_CRON_Enabled_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\WP_CRON_Enabled_Conditional */ protected function getWPCRONEnabledConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\WP_CRON_Enabled_Conditional'] = new \Yoast\WP\SEO\Conditionals\WP_CRON_Enabled_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\WP_Robots_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\WP_Robots_Conditional */ protected function getWPRobotsConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\WP_Robots_Conditional'] = new \Yoast\WP\SEO\Conditionals\WP_Robots_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Web_Stories_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Web_Stories_Conditional */ protected function getWebStoriesConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Web_Stories_Conditional'] = new \Yoast\WP\SEO\Conditionals\Web_Stories_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Wincher_Automatically_Track_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Wincher_Automatically_Track_Conditional */ protected function getWincherAutomaticallyTrackConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Wincher_Automatically_Track_Conditional'] = new \Yoast\WP\SEO\Conditionals\Wincher_Automatically_Track_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Wincher_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Wincher_Conditional */ protected function getWincherConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Wincher_Conditional'] = new \Yoast\WP\SEO\Conditionals\Wincher_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional */ protected function getWincherEnabledConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Wincher_Enabled_Conditional'] = new \Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Wincher_Token_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Wincher_Token_Conditional */ protected function getWincherTokenConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Wincher_Token_Conditional'] = new \Yoast\WP\SEO\Conditionals\Wincher_Token_Conditional(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client']) ? $this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client'] : $this->getWincherClientService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\WooCommerce_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\WooCommerce_Conditional */ protected function getWooCommerceConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional'] = new \Yoast\WP\SEO\Conditionals\WooCommerce_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\XMLRPC_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\XMLRPC_Conditional */ protected function getXMLRPCConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\XMLRPC_Conditional'] = new \Yoast\WP\SEO\Conditionals\XMLRPC_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional */ protected function getYoastAdminAndDashboardConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Yoast_Admin_And_Dashboard_Conditional'] = new \Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Conditionals\Yoast_Tools_Page_Conditional' shared autowired service. * * @return \Yoast\WP\SEO\Conditionals\Yoast_Tools_Page_Conditional */ protected function getYoastToolsPageConditionalService() { return $this->services['Yoast\\WP\\SEO\\Conditionals\\Yoast_Tools_Page_Conditional'] = new \Yoast\WP\SEO\Conditionals\Yoast_Tools_Page_Conditional(); } /** * Gets the public 'Yoast\WP\SEO\Config\Badge_Group_Names' shared autowired service. * * @return \Yoast\WP\SEO\Config\Badge_Group_Names */ protected function getBadgeGroupNamesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Badge_Group_Names'] = new \Yoast\WP\SEO\Config\Badge_Group_Names(); } /** * Gets the public 'Yoast\WP\SEO\Config\Conflicting_Plugins' shared autowired service. * * @return \Yoast\WP\SEO\Config\Conflicting_Plugins */ protected function getConflictingPluginsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Conflicting_Plugins'] = new \Yoast\WP\SEO\Config\Conflicting_Plugins(); } /** * Gets the public 'Yoast\WP\SEO\Config\Indexing_Reasons' shared autowired service. * * @return \Yoast\WP\SEO\Config\Indexing_Reasons */ protected function getIndexingReasonsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Indexing_Reasons'] = new \Yoast\WP\SEO\Config\Indexing_Reasons(); } /** * Gets the public 'Yoast\WP\SEO\Config\Migration_Status' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migration_Status */ protected function getMigrationStatusService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] = new \Yoast\WP\SEO\Config\Migration_Status(); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddCollationToTables' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddCollationToTables */ protected function getAddCollationToTablesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddCollationToTables'] = new \Yoast\WP\SEO\Config\Migrations\AddCollationToTables(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddColumnsToIndexables' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddColumnsToIndexables */ protected function getAddColumnsToIndexablesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddColumnsToIndexables'] = new \Yoast\WP\SEO\Config\Migrations\AddColumnsToIndexables(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddEstimatedReadingTime' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddEstimatedReadingTime */ protected function getAddEstimatedReadingTimeService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddEstimatedReadingTime'] = new \Yoast\WP\SEO\Config\Migrations\AddEstimatedReadingTime(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddHasAncestorsColumn' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddHasAncestorsColumn */ protected function getAddHasAncestorsColumnService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddHasAncestorsColumn'] = new \Yoast\WP\SEO\Config\Migrations\AddHasAncestorsColumn(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddInclusiveLanguageScore' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddInclusiveLanguageScore */ protected function getAddInclusiveLanguageScoreService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddInclusiveLanguageScore'] = new \Yoast\WP\SEO\Config\Migrations\AddInclusiveLanguageScore(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddIndexableObjectIdAndTypeIndex' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddIndexableObjectIdAndTypeIndex */ protected function getAddIndexableObjectIdAndTypeIndexService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexableObjectIdAndTypeIndex'] = new \Yoast\WP\SEO\Config\Migrations\AddIndexableObjectIdAndTypeIndex(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddIndexesForProminentWordsOnIndexables' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddIndexesForProminentWordsOnIndexables */ protected function getAddIndexesForProminentWordsOnIndexablesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexesForProminentWordsOnIndexables'] = new \Yoast\WP\SEO\Config\Migrations\AddIndexesForProminentWordsOnIndexables(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddObjectTimestamps' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddObjectTimestamps */ protected function getAddObjectTimestampsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddObjectTimestamps'] = new \Yoast\WP\SEO\Config\Migrations\AddObjectTimestamps(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\AddVersionColumnToIndexables' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\AddVersionColumnToIndexables */ protected function getAddVersionColumnToIndexablesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\AddVersionColumnToIndexables'] = new \Yoast\WP\SEO\Config\Migrations\AddVersionColumnToIndexables(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\BreadcrumbTitleAndHierarchyReset' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\BreadcrumbTitleAndHierarchyReset */ protected function getBreadcrumbTitleAndHierarchyResetService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\BreadcrumbTitleAndHierarchyReset'] = new \Yoast\WP\SEO\Config\Migrations\BreadcrumbTitleAndHierarchyReset(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\ClearIndexableTables' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\ClearIndexableTables */ protected function getClearIndexableTablesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\ClearIndexableTables'] = new \Yoast\WP\SEO\Config\Migrations\ClearIndexableTables(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\CreateIndexableSubpagesIndex' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\CreateIndexableSubpagesIndex */ protected function getCreateIndexableSubpagesIndexService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\CreateIndexableSubpagesIndex'] = new \Yoast\WP\SEO\Config\Migrations\CreateIndexableSubpagesIndex(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\CreateSEOLinksTable' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\CreateSEOLinksTable */ protected function getCreateSEOLinksTableService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\CreateSEOLinksTable'] = new \Yoast\WP\SEO\Config\Migrations\CreateSEOLinksTable(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\DeleteDuplicateIndexables' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\DeleteDuplicateIndexables */ protected function getDeleteDuplicateIndexablesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\DeleteDuplicateIndexables'] = new \Yoast\WP\SEO\Config\Migrations\DeleteDuplicateIndexables(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\ExpandIndexableColumnLengths' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\ExpandIndexableColumnLengths */ protected function getExpandIndexableColumnLengthsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableColumnLengths'] = new \Yoast\WP\SEO\Config\Migrations\ExpandIndexableColumnLengths(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\ExpandIndexableIDColumnLengths' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\ExpandIndexableIDColumnLengths */ protected function getExpandIndexableIDColumnLengthsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableIDColumnLengths'] = new \Yoast\WP\SEO\Config\Migrations\ExpandIndexableIDColumnLengths(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\ExpandPrimaryTermIDColumnLengths' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\ExpandPrimaryTermIDColumnLengths */ protected function getExpandPrimaryTermIDColumnLengthsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\ExpandPrimaryTermIDColumnLengths'] = new \Yoast\WP\SEO\Config\Migrations\ExpandPrimaryTermIDColumnLengths(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\ReplacePermalinkHashIndex' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\ReplacePermalinkHashIndex */ protected function getReplacePermalinkHashIndexService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\ReplacePermalinkHashIndex'] = new \Yoast\WP\SEO\Config\Migrations\ReplacePermalinkHashIndex(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\ResetIndexableHierarchyTable' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\ResetIndexableHierarchyTable */ protected function getResetIndexableHierarchyTableService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\ResetIndexableHierarchyTable'] = new \Yoast\WP\SEO\Config\Migrations\ResetIndexableHierarchyTable(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\TruncateIndexableTables' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\TruncateIndexableTables */ protected function getTruncateIndexableTablesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\TruncateIndexableTables'] = new \Yoast\WP\SEO\Config\Migrations\TruncateIndexableTables(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\WpYoastDropIndexableMetaTableIfExists' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\WpYoastDropIndexableMetaTableIfExists */ protected function getWpYoastDropIndexableMetaTableIfExistsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastDropIndexableMetaTableIfExists'] = new \Yoast\WP\SEO\Config\Migrations\WpYoastDropIndexableMetaTableIfExists(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\WpYoastIndexable' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\WpYoastIndexable */ protected function getWpYoastIndexableService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexable'] = new \Yoast\WP\SEO\Config\Migrations\WpYoastIndexable(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\WpYoastIndexableHierarchy' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\WpYoastIndexableHierarchy */ protected function getWpYoastIndexableHierarchyService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexableHierarchy'] = new \Yoast\WP\SEO\Config\Migrations\WpYoastIndexableHierarchy(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Migrations\WpYoastPrimaryTerm' shared autowired service. * * @return \Yoast\WP\SEO\Config\Migrations\WpYoastPrimaryTerm */ protected function getWpYoastPrimaryTermService() { return $this->services['Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastPrimaryTerm'] = new \Yoast\WP\SEO\Config\Migrations\WpYoastPrimaryTerm(${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Researcher_Languages' shared autowired service. * * @return \Yoast\WP\SEO\Config\Researcher_Languages */ protected function getResearcherLanguagesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Researcher_Languages'] = new \Yoast\WP\SEO\Config\Researcher_Languages(); } /** * Gets the public 'Yoast\WP\SEO\Config\SEMrush_Client' shared autowired service. * * @return \Yoast\WP\SEO\Config\SEMrush_Client */ protected function getSEMrushClientService() { return $this->services['Yoast\\WP\\SEO\\Config\\SEMrush_Client'] = new \Yoast\WP\SEO\Config\SEMrush_Client(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler'] = new \Yoast\WP\SEO\Wrappers\WP_Remote_Handler())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Schema_IDs' shared autowired service. * * @return \Yoast\WP\SEO\Config\Schema_IDs */ protected function getSchemaIDsService() { return $this->services['Yoast\\WP\\SEO\\Config\\Schema_IDs'] = new \Yoast\WP\SEO\Config\Schema_IDs(); } /** * Gets the public 'Yoast\WP\SEO\Config\Schema_Types' shared autowired service. * * @return \Yoast\WP\SEO\Config\Schema_Types */ protected function getSchemaTypesService() { return $this->services['Yoast\\WP\\SEO\\Config\\Schema_Types'] = new \Yoast\WP\SEO\Config\Schema_Types(); } /** * Gets the public 'Yoast\WP\SEO\Config\Wincher_Client' shared autowired service. * * @return \Yoast\WP\SEO\Config\Wincher_Client */ protected function getWincherClientService() { return $this->services['Yoast\\WP\\SEO\\Config\\Wincher_Client'] = new \Yoast\WP\SEO\Config\Wincher_Client(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler'] = new \Yoast\WP\SEO\Wrappers\WP_Remote_Handler())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Config\Wordproof_App_Config' shared autowired service. * * @return \Yoast\WP\SEO\Config\Wordproof_App_Config * * @deprecated Yoast\WP\SEO\Config\Wordproof_App_Config is deprecated since version 22.10! */ protected function getWordproofAppConfigService() { @trigger_error('Yoast\\WP\\SEO\\Config\\Wordproof_App_Config is deprecated since version 22.10!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Config\\Wordproof_App_Config'] = new \Yoast\WP\SEO\Config\Wordproof_App_Config(); } /** * Gets the public 'Yoast\WP\SEO\Config\Wordproof_Translations' shared autowired service. * * @return \Yoast\WP\SEO\Config\Wordproof_Translations * * @deprecated Yoast\WP\SEO\Config\Wordproof_Translations is deprecated since version 22.10! */ protected function getWordproofTranslationsService() { @trigger_error('Yoast\\WP\\SEO\\Config\\Wordproof_Translations is deprecated since version 22.10!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Config\\Wordproof_Translations'] = new \Yoast\WP\SEO\Config\Wordproof_Translations(); } /** * Gets the public 'Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Watcher_Actions' shared autowired service. * * @return \Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Watcher_Actions */ protected function getContentTypeVisibilityWatcherActionsService() { return $this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Watcher_Actions'] = new \Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Watcher_Actions(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications']) ? $this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications'] : $this->getContentTypeVisibilityDismissNotificationsService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Content_Type_Visibility\User_Interface\Content_Type_Visibility_Dismiss_New_Route' shared autowired service. * * @return \Yoast\WP\SEO\Content_Type_Visibility\User_Interface\Content_Type_Visibility_Dismiss_New_Route */ protected function getContentTypeVisibilityDismissNewRouteService() { return $this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\User_Interface\\Content_Type_Visibility_Dismiss_New_Route'] = new \Yoast\WP\SEO\Content_Type_Visibility\User_Interface\Content_Type_Visibility_Dismiss_New_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications']) ? $this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications'] : $this->getContentTypeVisibilityDismissNotificationsService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Context\Meta_Tags_Context' shared autowired service. * * @return \Yoast\WP\SEO\Context\Meta_Tags_Context */ protected function getMetaTagsContextService() { return $this->services['Yoast\\WP\\SEO\\Context\\Meta_Tags_Context'] = new \Yoast\WP\SEO\Context\Meta_Tags_Context(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\ID_Helper())) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Replace_Vars']) ? $this->services['WPSEO_Replace_Vars'] : $this->getWPSEOReplaceVarsService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper'] = new \Yoast\WP\SEO\Helpers\Site_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Capabilities_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Capabilities_Integration */ protected function getSiteKitCapabilitiesIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Capabilities_Integration'] = new \Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Capabilities_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Configuration_Dismissal_Route' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Configuration_Dismissal_Route */ protected function getSiteKitConfigurationDismissalRouteService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Configuration_Dismissal_Route'] = new \Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Configuration_Dismissal_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository'] : $this->getPermanentlyDismissedSiteKitConfigurationRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Consent_Management_Route' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Consent_Management_Route */ protected function getSiteKitConsentManagementRouteService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Consent_Management_Route'] = new \Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Consent_Management_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository'] : $this->getSiteKitConsentRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Scores\Readability_Scores_Route' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Scores\Readability_Scores_Route */ protected function getReadabilityScoresRouteService() { $a = new \Yoast\WP\SEO\Dashboard\Application\Score_Results\Readability_Score_Results\Readability_Score_Results_Repository(new \Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results\Cached_Readability_Score_Results_Collector(new \Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results\Readability_Score_Results_Collector()), new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Bad_Readability_Score_Group(), new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Good_Readability_Score_Group(), new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\No_Readability_Score_Group(), new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Ok_Readability_Score_Group()); $a->set_repositories(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository'] : $this->getCurrentScoresRepositoryService()) && false ?: '_'}); $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\Readability_Scores_Route'] = $instance = new \Yoast\WP\SEO\Dashboard\User_Interface\Scores\Readability_Scores_Route($a); $instance->set_collectors(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector'] : $this->getContentTypesCollectorService()) && false ?: '_'}); $instance->set_repositories(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository'] : $this->getTaxonomiesRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Scores\SEO_Scores_Route' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Scores\SEO_Scores_Route */ protected function getSEOScoresRouteService() { $a = new \Yoast\WP\SEO\Dashboard\Application\Score_Results\SEO_Score_Results\SEO_Score_Results_Repository(new \Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results\Cached_SEO_Score_Results_Collector(new \Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results\SEO_Score_Results_Collector()), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Bad_SEO_Score_Group())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Good_SEO_Score_Group())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Ok_SEO_Score_Group())) && false ?: '_'}); $a->set_repositories(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository'] : $this->getCurrentScoresRepositoryService()) && false ?: '_'}); $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\SEO_Scores_Route'] = $instance = new \Yoast\WP\SEO\Dashboard\User_Interface\Scores\SEO_Scores_Route($a); $instance->set_collectors(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector'] : $this->getContentTypesCollectorService()) && false ?: '_'}); $instance->set_repositories(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository'] : $this->getTaxonomiesRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Flow_Interceptor' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Flow_Interceptor */ protected function getSetupFlowInterceptorService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Flow_Interceptor'] = new \Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Flow_Interceptor(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit'] : $this->getSiteKitService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Url_Interceptor' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Url_Interceptor */ protected function getSetupUrlInterceptorService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Url_Interceptor'] = new \Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Url_Interceptor(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit'] : $this->getSiteKitService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Time_Based_SEO_Metrics\Time_Based_SEO_Metrics_Route' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Time_Based_SEO_Metrics\Time_Based_SEO_Metrics_Route */ protected function getTimeBasedSEOMetricsRouteService() { $a = new \Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter(new \Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Api_Call()); $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit'] : $this->getSiteKitService()) && false ?: '_'}; $c = new \Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Site_Kit_Analytics_4_Adapter(new \Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Site_Kit_Analytics_4_Api_Call()); return $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Time_Based_SEO_Metrics\\Time_Based_SEO_Metrics_Route'] = new \Yoast\WP\SEO\Dashboard\User_Interface\Time_Based_SEO_Metrics\Time_Based_SEO_Metrics_Route(new \Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Repository($a, new \Yoast\WP\SEO\Dashboard\Infrastructure\Indexables\Top_Page_Indexable_Collector(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, new \Yoast\WP\SEO\Dashboard\Application\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Bad_SEO_Score_Group())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Good_SEO_Score_Group())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Ok_SEO_Score_Group())) && false ?: '_'})), $b), new \Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Query_Repository($a, $b), new \Yoast\WP\SEO\Dashboard\Application\Traffic\Organic_Sessions_Compare_Repository($c, $b), new \Yoast\WP\SEO\Dashboard\Application\Traffic\Organic_Sessions_Daily_Repository($c, $b), new \Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Search_Ranking_Compare_Repository($a, $b), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Dashboard\User_Interface\Tracking\Setup_Steps_Tracking_Route' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\User_Interface\Tracking\Setup_Steps_Tracking_Route */ protected function getSetupStepsTrackingRouteService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Tracking\\Setup_Steps_Tracking_Route'] = new \Yoast\WP\SEO\Dashboard\User_Interface\Tracking\Setup_Steps_Tracking_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository'] : $this->getSetupStepsTrackingRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository */ protected function getEnabledAnalysisFeaturesRepositoryService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}; $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Language_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Language_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Language_Helper'] = new \Yoast\WP\SEO\Helpers\Language_Helper())) && false ?: '_'}; return $this->services['Yoast\\WP\\SEO\\Editors\\Application\\Analysis_Features\\Enabled_Analysis_Features_Repository'] = new \Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository(new \Yoast\WP\SEO\Editors\Framework\Cornerstone_Content($a), new \Yoast\WP\SEO\Editors\Framework\Inclusive_Language_Analysis($a, $b, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}), new \Yoast\WP\SEO\Editors\Framework\Keyphrase_Analysis($a), new \Yoast\WP\SEO\Editors\Framework\Previously_Used_Keyphrase(), new \Yoast\WP\SEO\Editors\Framework\Readability_Analysis($a), new \Yoast\WP\SEO\Editors\Framework\Word_Form_Recognition($b)); } /** * Gets the public 'Yoast\WP\SEO\Editors\Application\Integrations\Integration_Information_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Editors\Application\Integrations\Integration_Information_Repository */ protected function getIntegrationInformationRepositoryService() { $a = ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}; $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}; return $this->services['Yoast\\WP\\SEO\\Editors\\Application\\Integrations\\Integration_Information_Repository'] = new \Yoast\WP\SEO\Editors\Application\Integrations\Integration_Information_Repository(new \Yoast\WP\SEO\Editors\Framework\Integrations\Jetpack_Markdown(), new \Yoast\WP\SEO\Editors\Framework\Integrations\Multilingual(${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\WPML_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Polylang_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Polylang_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Polylang_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Polylang_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\TranslatePress_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\TranslatePress_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\TranslatePress_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\TranslatePress_Conditional())) && false ?: '_'}), new \Yoast\WP\SEO\Editors\Framework\Integrations\News_SEO($a), new \Yoast\WP\SEO\Editors\Framework\Integrations\Semrush($b), new \Yoast\WP\SEO\Editors\Framework\Integrations\Wincher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Wincher_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Wincher_Helper'] : $this->getWincherHelperService()) && false ?: '_'}, $b), new \Yoast\WP\SEO\Editors\Framework\Integrations\WooCommerce_SEO($a), new \Yoast\WP\SEO\Editors\Framework\Integrations\WooCommerce(${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional'] = new \Yoast\WP\SEO\Conditionals\WooCommerce_Conditional())) && false ?: '_'})); } /** * Gets the public 'Yoast\WP\SEO\Editors\Application\Seo\Post_Seo_Information_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Editors\Application\Seo\Post_Seo_Information_Repository */ protected function getPostSeoInformationRepositoryService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}; return $this->services['Yoast\\WP\\SEO\\Editors\\Application\\Seo\\Post_Seo_Information_Repository'] = new \Yoast\WP\SEO\Editors\Application\Seo\Post_Seo_Information_Repository(new \Yoast\WP\SEO\Editors\Framework\Seo\Posts\Description_Data_Provider(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper())) && false ?: '_'}, $a), new \Yoast\WP\SEO\Editors\Framework\Seo\Posts\Keyphrase_Data_Provider(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] = new \Yoast\WP\SEO\Helpers\Meta_Helper())) && false ?: '_'}), new \Yoast\WP\SEO\Editors\Framework\Seo\Posts\Social_Data_Provider($a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}), new \Yoast\WP\SEO\Editors\Framework\Seo\Posts\Title_Data_Provider($a)); } /** * Gets the public 'Yoast\WP\SEO\Editors\Application\Seo\Term_Seo_Information_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Editors\Application\Seo\Term_Seo_Information_Repository */ protected function getTermSeoInformationRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Editors\\Application\\Seo\\Term_Seo_Information_Repository'] = new \Yoast\WP\SEO\Editors\Application\Seo\Term_Seo_Information_Repository(new \Yoast\WP\SEO\Editors\Framework\Seo\Terms\Description_Data_Provider(), new \Yoast\WP\SEO\Editors\Framework\Seo\Terms\Keyphrase_Data_Provider(), new \Yoast\WP\SEO\Editors\Framework\Seo\Terms\Social_Data_Provider(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}), new \Yoast\WP\SEO\Editors\Framework\Seo\Terms\Title_Data_Provider()); } /** * Gets the public 'Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository */ protected function getWebsiteInformationRepositoryService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}; $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository']) ? $this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository'] : $this->getWistiaEmbedPermissionRepositoryService()) && false ?: '_'}; $c = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface'] : $this->getMetaSurfaceService()) && false ?: '_'}; $d = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}; $e = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}; $f = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager']) ? $this->services['Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager'] : $this->getPromotionManagerService()) && false ?: '_'}; return $this->services['Yoast\\WP\\SEO\\Editors\\Application\\Site\\Website_Information_Repository'] = new \Yoast\WP\SEO\Editors\Application\Site\Website_Information_Repository(new \Yoast\WP\SEO\Editors\Framework\Site\Post_Site_Information($a, $b, $c, $d, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action'] : $this->getAlertDismissalActionService()) && false ?: '_'}, $e, $f), new \Yoast\WP\SEO\Editors\Framework\Site\Term_Site_Information($a, $b, $c, $d, $e, $f)); } /** * Gets the public 'Yoast\WP\SEO\General\User_Interface\General_Page_Integration' shared autowired service. * * @return \Yoast\WP\SEO\General\User_Interface\General_Page_Integration */ protected function getGeneralPageIntegrationService() { return $this->services['Yoast\\WP\\SEO\\General\\User_Interface\\General_Page_Integration'] = new \Yoast\WP\SEO\General\User_Interface\General_Page_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] = new \Yoast\WP\SEO\Helpers\Notification_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action'] : $this->getAlertDismissalActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager']) ? $this->services['Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager'] : $this->getPromotionManagerService()) && false ?: '_'}, new \Yoast\WP\SEO\Dashboard\Application\Configuration\Dashboard_Configuration(new \Yoast\WP\SEO\Dashboard\Application\Content_Types\Content_Types_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector'] : $this->getContentTypesCollectorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository'] : $this->getTaxonomiesRepositoryService()) && false ?: '_'}), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Editors\\Application\\Analysis_Features\\Enabled_Analysis_Features_Repository']) ? $this->services['Yoast\\WP\\SEO\\Editors\\Application\\Analysis_Features\\Enabled_Analysis_Features_Repository'] : $this->getEnabledAnalysisFeaturesRepositoryService()) && false ?: '_'}, new \Yoast\WP\SEO\Dashboard\Application\Endpoints\Endpoints_Repository(new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Readability_Scores_Endpoint(), new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\SEO_Scores_Endpoint(), new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Setup_Steps_Tracking_Endpoint(), new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Configuration_Dismissal_Endpoint(), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Consent_Management_Endpoint())) && false ?: '_'}, new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Time_Based_SEO_Metrics_Endpoint()), new \Yoast\WP\SEO\Dashboard\Infrastructure\Nonces\Nonce_Repository(), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit'] : $this->getSiteKitService()) && false ?: '_'}, new \Yoast\WP\SEO\Dashboard\Application\Tracking\Setup_Steps_Tracking(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository'] : $this->getSetupStepsTrackingRepositoryService()) && false ?: '_'}), new \Yoast\WP\SEO\Dashboard\Infrastructure\Browser_Cache\Browser_Cache_Configuration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Google_Site_Kit_Feature_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Google_Site_Kit_Feature_Conditional'] : $this->getGoogleSiteKitFeatureConditionalService()) && false ?: '_'}))); } /** * Gets the public 'Yoast\WP\SEO\Generators\Breadcrumbs_Generator' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Breadcrumbs_Generator */ protected function getBreadcrumbsGeneratorService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] = new \Yoast\WP\SEO\Generators\Breadcrumbs_Generator(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Generators\Open_Graph_Image_Generator' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Open_Graph_Image_Generator */ protected function getOpenGraphImageGeneratorService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Image_Generator(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] : $this->getImageHelper2Service()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator */ protected function getOpenGraphLocaleGeneratorService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\Article' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\Article */ protected function getArticleService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\Article'] = new \Yoast\WP\SEO\Generators\Schema\Article(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\Author' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\Author */ protected function getAuthorService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\Author'] = new \Yoast\WP\SEO\Generators\Schema\Author(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\Breadcrumb' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\Breadcrumb */ protected function getBreadcrumbService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\Breadcrumb'] = new \Yoast\WP\SEO\Generators\Schema\Breadcrumb(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\FAQ' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\FAQ */ protected function getFAQService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\FAQ'] = new \Yoast\WP\SEO\Generators\Schema\FAQ(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\HowTo' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\HowTo */ protected function getHowToService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\HowTo'] = new \Yoast\WP\SEO\Generators\Schema\HowTo(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\Main_Image' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\Main_Image */ protected function getMainImageService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\Main_Image'] = new \Yoast\WP\SEO\Generators\Schema\Main_Image(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\Organization' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\Organization */ protected function getOrganizationService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\Organization'] = new \Yoast\WP\SEO\Generators\Schema\Organization(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\Person' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\Person */ protected function getPersonService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\Person'] = new \Yoast\WP\SEO\Generators\Schema\Person(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\WebPage' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\WebPage */ protected function getWebPageService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\WebPage'] = new \Yoast\WP\SEO\Generators\Schema\WebPage(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema\Website' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema\Website */ protected function getWebsiteService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema\\Website'] = new \Yoast\WP\SEO\Generators\Schema\Website(); } /** * Gets the public 'Yoast\WP\SEO\Generators\Schema_Generator' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Schema_Generator */ protected function getSchemaGeneratorService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] = new \Yoast\WP\SEO\Generators\Schema_Generator(${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface'] : $this->getHelpersSurfaceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Replace_Vars_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Replace_Vars_Helper'] : $this->getReplaceVarsHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Generators\Twitter_Image_Generator' shared autowired service. * * @return \Yoast\WP\SEO\Generators\Twitter_Image_Generator */ protected function getTwitterImageGeneratorService() { return $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] = new \Yoast\WP\SEO\Generators\Twitter_Image_Generator(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] : $this->getImageHelper4Service()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Aioseo_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Aioseo_Helper */ protected function getAioseoHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] = new \Yoast\WP\SEO\Helpers\Aioseo_Helper(${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Wpdb_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Wpdb_Helper'] : $this->getWpdbHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Asset_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Asset_Helper */ protected function getAssetHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Asset_Helper'] = new \Yoast\WP\SEO\Helpers\Asset_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Attachment_Cleanup_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Attachment_Cleanup_Helper */ protected function getAttachmentCleanupHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Attachment_Cleanup_Helper'] = new \Yoast\WP\SEO\Helpers\Attachment_Cleanup_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Author_Archive_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Author_Archive_Helper */ protected function getAuthorArchiveHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper'] = new \Yoast\WP\SEO\Helpers\Author_Archive_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Blocks_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Blocks_Helper */ protected function getBlocksHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Blocks_Helper'] = new \Yoast\WP\SEO\Helpers\Blocks_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Capability_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Capability_Helper */ protected function getCapabilityHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Crawl_Cleanup_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Crawl_Cleanup_Helper */ protected function getCrawlCleanupHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Crawl_Cleanup_Helper'] = new \Yoast\WP\SEO\Helpers\Crawl_Cleanup_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Curl_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Curl_Helper */ protected function getCurlHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Curl_Helper'] = new \Yoast\WP\SEO\Helpers\Curl_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Current_Page_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Current_Page_Helper */ protected function getCurrentPageHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] = new \Yoast\WP\SEO\Helpers\Current_Page_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Query_Wrapper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Date_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Date_Helper */ protected function getDateHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Environment_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Environment_Helper */ protected function getEnvironmentHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Environment_Helper'] = new \Yoast\WP\SEO\Helpers\Environment_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\First_Time_Configuration_Notice_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\First_Time_Configuration_Notice_Helper */ protected function getFirstTimeConfigurationNoticeHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\First_Time_Configuration_Notice_Helper'] = new \Yoast\WP\SEO\Helpers\First_Time_Configuration_Notice_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Home_Url_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Home_Url_Helper */ protected function getHomeUrlHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Home_Url_Helper'] = new \Yoast\WP\SEO\Helpers\Home_Url_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Image_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Image_Helper */ protected function getImageHelperService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'])) { return $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']; } return $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] = new \Yoast\WP\SEO\Helpers\Image_Helper($a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository'] : ($this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository'] = new \Yoast\WP\SEO\Repositories\SEO_Links_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Import_Cursor_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Import_Cursor_Helper */ protected function getImportCursorHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Cursor_Helper'] = new \Yoast\WP\SEO\Helpers\Import_Cursor_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Import_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Import_Helper */ protected function getImportHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Import_Helper'] = new \Yoast\WP\SEO\Helpers\Import_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Indexable_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Indexable_Helper */ protected function getIndexableHelperService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'])) { return $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']; } $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] = $instance = new \Yoast\WP\SEO\Helpers\Indexable_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Environment_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Environment_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Environment_Helper'] = new \Yoast\WP\SEO\Helpers\Environment_Helper())) && false ?: '_'}, $a); $instance->set_indexable_repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Helpers\Indexable_To_Postmeta_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Indexable_To_Postmeta_Helper */ protected function getIndexableToPostmetaHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_To_Postmeta_Helper'] = new \Yoast\WP\SEO\Helpers\Indexable_To_Postmeta_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] = new \Yoast\WP\SEO\Helpers\Meta_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Indexing_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Indexing_Helper */ protected function getIndexingHelperService() { $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] = $instance = new \Yoast\WP\SEO\Helpers\Indexing_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}); $instance->set_indexing_actions(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'] : $this->getIndexablePostIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action'] : $this->getIndexableTermIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'] : $this->getIndexablePostTypeArchiveIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action'] : $this->getIndexableGeneralIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'] : $this->getPostLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'] : $this->getTermLinkIndexingActionService()) && false ?: '_'}); $instance->set_indexable_repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Helpers\Language_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Language_Helper */ protected function getLanguageHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Language_Helper'] = new \Yoast\WP\SEO\Helpers\Language_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Meta_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Meta_Helper */ protected function getMetaHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] = new \Yoast\WP\SEO\Helpers\Meta_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Notification_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Notification_Helper */ protected function getNotificationHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] = new \Yoast\WP\SEO\Helpers\Notification_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Open_Graph\Image_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Open_Graph\Image_Helper */ protected function getImageHelper2Service() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'])) { return $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']; } return $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Image_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, $a); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper */ protected function getValuesHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Options_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Options_Helper */ protected function getOptionsHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Pagination_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Pagination_Helper */ protected function getPaginationHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] = new \Yoast\WP\SEO\Helpers\Pagination_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Query_Wrapper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Permalink_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Permalink_Helper */ protected function getPermalinkHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Post_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Post_Helper */ protected function getPostHelperService() { $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] = $instance = new \Yoast\WP\SEO\Helpers\Post_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\String_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\String_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\String_Helper'] = new \Yoast\WP\SEO\Helpers\String_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}); $instance->set_indexable_repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Helpers\Post_Type_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Post_Type_Helper */ protected function getPostTypeHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] = new \Yoast\WP\SEO\Helpers\Post_Type_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Primary_Term_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Primary_Term_Helper */ protected function getPrimaryTermHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper'] = new \Yoast\WP\SEO\Helpers\Primary_Term_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Product_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Product_Helper */ protected function getProductHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Redirect_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Redirect_Helper */ protected function getRedirectHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Request_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Request_Helper * * @deprecated Yoast\WP\SEO\Helpers\Request_Helper is deprecated since version 23.6! */ protected function getRequestHelperService() { @trigger_error('Yoast\\WP\\SEO\\Helpers\\Request_Helper is deprecated since version 23.6!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Helpers\\Request_Helper'] = new \Yoast\WP\SEO\Helpers\Request_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Require_File_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Require_File_Helper */ protected function getRequireFileHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Require_File_Helper'] = new \Yoast\WP\SEO\Helpers\Require_File_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Robots_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Robots_Helper */ protected function getRobotsHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Helper'] = new \Yoast\WP\SEO\Helpers\Robots_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Robots_Txt_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Robots_Txt_Helper */ protected function getRobotsTxtHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Txt_Helper'] = new \Yoast\WP\SEO\Helpers\Robots_Txt_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Sanitization_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Sanitization_Helper */ protected function getSanitizationHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Sanitization_Helper'] = new \Yoast\WP\SEO\Helpers\Sanitization_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Schema\Article_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Schema\Article_Helper */ protected function getArticleHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Article_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\Article_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Schema\HTML_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Schema\HTML_Helper */ protected function getHTMLHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\HTML_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\HTML_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Schema\ID_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Schema\ID_Helper */ protected function getIDHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\ID_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Schema\Image_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Schema\Image_Helper */ protected function getImageHelper3Service() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Image_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\Image_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\HTML_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\HTML_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\HTML_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\HTML_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Language_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Language_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Language_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\Language_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Schema\Language_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Schema\Language_Helper */ protected function getLanguageHelper2Service() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Language_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\Language_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Schema\Replace_Vars_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Schema\Replace_Vars_Helper */ protected function getReplaceVarsHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Replace_Vars_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\Replace_Vars_Helper(${($_ = isset($this->services['WPSEO_Replace_Vars']) ? $this->services['WPSEO_Replace_Vars'] : $this->getWPSEOReplaceVarsService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\ID_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\ID_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Score_Icon_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Score_Icon_Helper */ protected function getScoreIconHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Score_Icon_Helper'] = new \Yoast\WP\SEO\Helpers\Score_Icon_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Helper'] : $this->getRobotsHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Short_Link_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Short_Link_Helper */ protected function getShortLinkHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] = new \Yoast\WP\SEO\Helpers\Short_Link_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Site_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Site_Helper */ protected function getSiteHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper'] = new \Yoast\WP\SEO\Helpers\Site_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Social_Profiles_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Social_Profiles_Helper */ protected function getSocialProfilesHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper'] = new \Yoast\WP\SEO\Helpers\Social_Profiles_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\String_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\String_Helper */ protected function getStringHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\String_Helper'] = new \Yoast\WP\SEO\Helpers\String_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Taxonomy_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Taxonomy_Helper */ protected function getTaxonomyHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] = new \Yoast\WP\SEO\Helpers\Taxonomy_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\String_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\String_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\String_Helper'] = new \Yoast\WP\SEO\Helpers\String_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Twitter\Image_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Twitter\Image_Helper */ protected function getImageHelper4Service() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'])) { return $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']; } return $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] = new \Yoast\WP\SEO\Helpers\Twitter\Image_Helper($a); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Url_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Url_Helper */ protected function getUrlHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\User_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\User_Helper */ protected function getUserHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Wincher_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Wincher_Helper */ protected function getWincherHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Wincher_Helper'] = new \Yoast\WP\SEO\Helpers\Wincher_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Woocommerce_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Woocommerce_Helper */ protected function getWoocommerceHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] = new \Yoast\WP\SEO\Helpers\Woocommerce_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Wordpress_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Wordpress_Helper */ protected function getWordpressHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Wordpress_Helper'] = new \Yoast\WP\SEO\Helpers\Wordpress_Helper(); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Wordproof_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Wordproof_Helper * * @deprecated Yoast\WP\SEO\Helpers\Wordproof_Helper is deprecated since version 22.10! */ protected function getWordproofHelperService() { @trigger_error('Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper is deprecated since version 22.10!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper'] = new \Yoast\WP\SEO\Helpers\Wordproof_Helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] = new \Yoast\WP\SEO\Helpers\Woocommerce_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Helpers\Wpdb_Helper' shared autowired service. * * @return \Yoast\WP\SEO\Helpers\Wpdb_Helper */ protected function getWpdbHelperService() { return $this->services['Yoast\\WP\\SEO\\Helpers\\Wpdb_Helper'] = new \Yoast\WP\SEO\Helpers\Wpdb_Helper(${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks' shared autowired service. * * @return \Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks */ protected function getCrawlCleanupPermalinksService() { return $this->services['Yoast\\WP\\SEO\\Initializers\\Crawl_Cleanup_Permalinks'] = new \Yoast\WP\SEO\Initializers\Crawl_Cleanup_Permalinks(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Crawl_Cleanup_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Crawl_Cleanup_Helper'] : $this->getCrawlCleanupHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Initializers\Disable_Core_Sitemaps' shared autowired service. * * @return \Yoast\WP\SEO\Initializers\Disable_Core_Sitemaps */ protected function getDisableCoreSitemapsService() { return $this->services['Yoast\\WP\\SEO\\Initializers\\Disable_Core_Sitemaps'] = new \Yoast\WP\SEO\Initializers\Disable_Core_Sitemaps(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Initializers\Migration_Runner' shared autowired service. * * @return \Yoast\WP\SEO\Initializers\Migration_Runner */ protected function getMigrationRunnerService() { return $this->services['Yoast\\WP\\SEO\\Initializers\\Migration_Runner'] = new \Yoast\WP\SEO\Initializers\Migration_Runner(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status']) ? $this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] : ($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] = new \Yoast\WP\SEO\Config\Migration_Status())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Loader']) ? $this->services['Yoast\\WP\\SEO\\Loader'] : $this->getLoaderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter']) ? $this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] : ($this->services['Yoast\\WP\\Lib\\Migrations\\Adapter'] = new \Yoast\WP\Lib\Migrations\Adapter())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Initializers\Plugin_Headers' shared autowired service. * * @return \Yoast\WP\SEO\Initializers\Plugin_Headers */ protected function getPluginHeadersService() { return $this->services['Yoast\\WP\\SEO\\Initializers\\Plugin_Headers'] = new \Yoast\WP\SEO\Initializers\Plugin_Headers(); } /** * Gets the public 'Yoast\WP\SEO\Initializers\Woocommerce' shared autowired service. * * @return \Yoast\WP\SEO\Initializers\Woocommerce */ protected function getWoocommerceService() { return $this->services['Yoast\\WP\\SEO\\Initializers\\Woocommerce'] = new \Yoast\WP\SEO\Initializers\Woocommerce(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Academy_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Academy_Integration */ protected function getAcademyIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Academy_Integration'] = new \Yoast\WP\SEO\Integrations\Academy_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Activation_Cleanup_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Activation_Cleanup_Integration */ protected function getActivationCleanupIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Activation_Cleanup_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Activation_Cleanup_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Addon_Installation\Dialog_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Addon_Installation\Dialog_Integration */ protected function getDialogIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Dialog_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Addon_Installation\Dialog_Integration(${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Addon_Installation\Installation_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Addon_Installation\Installation_Integration */ protected function getInstallationIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Installation_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Addon_Installation\Installation_Integration(${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Activate_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Activate_Action'] : $this->getAddonActivateActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Install_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Addon_Installation\\Addon_Install_Action'] : $this->getAddonInstallActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Admin_Columns_Cache_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Admin_Columns_Cache_Integration */ protected function getAdminColumnsCacheIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Admin_Columns_Cache_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Admin_Columns_Cache_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Background_Indexing_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Background_Indexing_Integration */ protected function getBackgroundIndexingIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Background_Indexing_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Background_Indexing_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action'] : $this->getIndexableIndexingCompleteActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Yoast_Admin_And_Dashboard_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Yoast_Admin_And_Dashboard_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Yoast_Admin_And_Dashboard_Conditional'] = new \Yoast\WP\SEO\Conditionals\Yoast_Admin_And_Dashboard_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Get_Request_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Get_Request_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Get_Request_Conditional'] = new \Yoast\WP\SEO\Conditionals\Get_Request_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\WP_CRON_Enabled_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\WP_CRON_Enabled_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\WP_CRON_Enabled_Conditional'] = new \Yoast\WP\SEO\Conditionals\WP_CRON_Enabled_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action'] : $this->getIndexableGeneralIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'] : $this->getIndexablePostIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'] : $this->getIndexablePostTypeArchiveIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action'] : $this->getIndexableTermIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'] : $this->getPostLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'] : $this->getTermLinkIndexingActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Check_Required_Version' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Check_Required_Version */ protected function getCheckRequiredVersionService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Check_Required_Version'] = new \Yoast\WP\SEO\Integrations\Admin\Check_Required_Version(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration */ protected function getCrawlSettingsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Crawl_Settings_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Crawl_Settings_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Shortlinker']) ? $this->services['WPSEO_Shortlinker'] : $this->getWPSEOShortlinkerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Cron_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Cron_Integration */ protected function getCronIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Cron_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Cron_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Deactivated_Premium_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Deactivated_Premium_Integration */ protected function getDeactivatedPremiumIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Deactivated_Premium_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Deactivated_Premium_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Disable_Concatenate_Scripts_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Disable_Concatenate_Scripts_Integration * * @deprecated Yoast\WP\SEO\Integrations\Admin\Disable_Concatenate_Scripts_Integration is deprecated since version 23.2! */ protected function getDisableConcatenateScriptsIntegrationService() { @trigger_error('Yoast\\WP\\SEO\\Integrations\\Admin\\Disable_Concatenate_Scripts_Integration is deprecated since version 23.2!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Disable_Concatenate_Scripts_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Disable_Concatenate_Scripts_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\First_Time_Configuration_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\First_Time_Configuration_Integration */ protected function getFirstTimeConfigurationIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\First_Time_Configuration_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Shortlinker']) ? $this->services['WPSEO_Shortlinker'] : $this->getWPSEOShortlinkerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Social_Profiles_Helper'] : $this->getSocialProfilesHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Context\\Meta_Tags_Context']) ? $this->services['Yoast\\WP\\SEO\\Context\\Meta_Tags_Context'] : $this->getMetaTagsContextService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\First_Time_Configuration_Notice_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\First_Time_Configuration_Notice_Integration */ protected function getFirstTimeConfigurationNoticeIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Notice_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\First_Time_Configuration_Notice_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\First_Time_Configuration_Notice_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\First_Time_Configuration_Notice_Helper'] : $this->getFirstTimeConfigurationNoticeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Fix_News_Dependencies_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Fix_News_Dependencies_Integration */ protected function getFixNewsDependenciesIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Fix_News_Dependencies_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Fix_News_Dependencies_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Health_Check_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Health_Check_Integration */ protected function getHealthCheckIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Health_Check_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Health_Check_Integration(new \Yoast\WP\SEO\Llms_Txt\Application\Health_Check\File_Check(new \Yoast\WP\SEO\Llms_Txt\Application\Health_Check\File_Runner(), new \Yoast\WP\SEO\Llms_Txt\User_Interface\Health_Check\File_Reports(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] = new \Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory())) && false ?: '_'}), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Check']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Check'] : $this->getDefaultTaglineCheckService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Check']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Check'] : $this->getLinksTableCheckService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Check']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Check'] : $this->getPageCommentsCheckService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Check']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Check'] : $this->getPostnamePermalinkCheckService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\HelpScout_Beacon' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\HelpScout_Beacon */ protected function getHelpScoutBeaconService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\HelpScout_Beacon'] = new \Yoast\WP\SEO\Integrations\Admin\HelpScout_Beacon(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status']) ? $this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] : ($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] = new \Yoast\WP\SEO\Config\Migration_Status())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Import_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Import_Integration */ protected function getImportIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Import_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Import_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service'] : $this->getImportableDetectorServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Routes\\Importing_Route']) ? $this->services['Yoast\\WP\\SEO\\Routes\\Importing_Route'] : $this->getImportingRouteService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Indexables_Exclude_Taxonomy_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Indexables_Exclude_Taxonomy_Integration */ protected function getIndexablesExcludeTaxonomyIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Indexables_Exclude_Taxonomy_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Indexables_Exclude_Taxonomy_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Indexing_Notification_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Indexing_Notification_Integration */ protected function getIndexingNotificationIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Notification_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Indexing_Notification_Integration(${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] = new \Yoast\WP\SEO\Helpers\Notification_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Environment_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Environment_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Environment_Helper'] = new \Yoast\WP\SEO\Helpers\Environment_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Indexing_Tool_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Indexing_Tool_Integration */ protected function getIndexingToolIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Tool_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Indexing_Tool_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service'] : $this->getImportableDetectorServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Routes\\Importing_Route']) ? $this->services['Yoast\\WP\\SEO\\Routes\\Importing_Route'] : $this->getImportingRouteService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Installation_Success_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Installation_Success_Integration */ protected function getInstallationSuccessIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Installation_Success_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Installation_Success_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Integrations_Page' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Integrations_Page */ protected function getIntegrationsPageService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Integrations_Page'] = new \Yoast\WP\SEO\Integrations\Admin\Integrations_Page(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] = new \Yoast\WP\SEO\Helpers\Woocommerce_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Activated_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Activated_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\Elementor_Activated_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\Elementor_Activated_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Jetpack_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Jetpack_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Jetpack_Conditional'] = new \Yoast\WP\SEO\Conditionals\Jetpack_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit'] : $this->getSiteKitService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint'] : ($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Consent_Management_Endpoint())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Link_Count_Columns_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Link_Count_Columns_Integration */ protected function getLinkCountColumnsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Link_Count_Columns_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Link_Count_Columns_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'] : $this->getPostLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Admin_Columns_Cache_Integration']) ? $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Admin_Columns_Cache_Integration'] : $this->getAdminColumnsCacheIntegrationService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Menu_Badge_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Menu_Badge_Integration */ protected function getMenuBadgeIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Menu_Badge_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Menu_Badge_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Migration_Error_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Migration_Error_Integration */ protected function getMigrationErrorIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Migration_Error_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Migration_Error_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status']) ? $this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] : ($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] = new \Yoast\WP\SEO\Config\Migration_Status())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Old_Configuration_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Old_Configuration_Integration */ protected function getOldConfigurationIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Configuration_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Old_Configuration_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Old_Premium_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Old_Premium_Integration * * @deprecated Yoast\WP\SEO\Integrations\Admin\Old_Premium_Integration is deprecated since version 20.10! */ protected function getOldPremiumIntegrationService() { @trigger_error('Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Premium_Integration is deprecated since version 20.10!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Premium_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Old_Premium_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper())) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Redirect_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Redirect_Integration */ protected function getRedirectIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Redirect_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Redirect_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Redirects_Page_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Redirects_Page_Integration */ protected function getRedirectsPageIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Redirects_Page_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Redirects_Page_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Unsupported_PHP_Version_Notice' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Unsupported_PHP_Version_Notice * * @deprecated Yoast\WP\SEO\Integrations\Admin\Unsupported_PHP_Version_Notice is deprecated since version 25.0! */ protected function getUnsupportedPHPVersionNoticeService() { @trigger_error('Yoast\\WP\\SEO\\Integrations\\Admin\\Unsupported_PHP_Version_Notice is deprecated since version 25.0!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Unsupported_PHP_Version_Notice'] = new \Yoast\WP\SEO\Integrations\Admin\Unsupported_PHP_Version_Notice(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Admin\Workouts_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Admin\Workouts_Integration */ protected function getWorkoutsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Admin\\Workouts_Integration'] = new \Yoast\WP\SEO\Integrations\Admin\Workouts_Integration(${($_ = isset($this->services['WPSEO_Addon_Manager']) ? $this->services['WPSEO_Addon_Manager'] : $this->getWPSEOAddonManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Product_Editor_Checklist_Notification' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Product_Editor_Checklist_Notification */ protected function getBlackFridayProductEditorChecklistNotificationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Product_Editor_Checklist_Notification'] = new \Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Product_Editor_Checklist_Notification(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Promotion_Notification' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Promotion_Notification */ protected function getBlackFridayPromotionNotificationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Promotion_Notification'] = new \Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Promotion_Notification(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Sidebar_Checklist_Notification' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Sidebar_Checklist_Notification */ protected function getBlackFridaySidebarChecklistNotificationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Sidebar_Checklist_Notification'] = new \Yoast\WP\SEO\Integrations\Alerts\Black_Friday_Sidebar_Checklist_Notification(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Alerts\Trustpilot_Review_Notification' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Alerts\Trustpilot_Review_Notification */ protected function getTrustpilotReviewNotificationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Alerts\\Trustpilot_Review_Notification'] = new \Yoast\WP\SEO\Integrations\Alerts\Trustpilot_Review_Notification(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Alerts\Webinar_Promo_Notification' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Alerts\Webinar_Promo_Notification */ protected function getWebinarPromoNotificationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Alerts\\Webinar_Promo_Notification'] = new \Yoast\WP\SEO\Integrations\Alerts\Webinar_Promo_Notification(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Blocks\Block_Editor_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Blocks\Block_Editor_Integration */ protected function getBlockEditorIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Blocks\\Block_Editor_Integration'] = new \Yoast\WP\SEO\Integrations\Blocks\Block_Editor_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Blocks\Breadcrumbs_Block' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Blocks\Breadcrumbs_Block */ protected function getBreadcrumbsBlockService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Blocks\\Breadcrumbs_Block'] = new \Yoast\WP\SEO\Integrations\Blocks\Breadcrumbs_Block(${($_ = isset($this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer']) ? $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] : $this->getMetaTagsContextMemoizerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Replace_Vars']) ? $this->services['WPSEO_Replace_Vars'] : $this->getWPSEOReplaceVarsService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface'] : $this->getHelpersSurfaceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Blocks\Internal_Linking_Category' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Blocks\Internal_Linking_Category */ protected function getInternalLinkingCategoryService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Blocks\\Internal_Linking_Category'] = new \Yoast\WP\SEO\Integrations\Blocks\Internal_Linking_Category(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Blocks\Structured_Data_Blocks' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Blocks\Structured_Data_Blocks */ protected function getStructuredDataBlocksService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Blocks\\Structured_Data_Blocks'] = new \Yoast\WP\SEO\Integrations\Blocks\Structured_Data_Blocks(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Breadcrumbs_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Breadcrumbs_Integration */ protected function getBreadcrumbsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Breadcrumbs_Integration'] = new \Yoast\WP\SEO\Integrations\Breadcrumbs_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface'] : $this->getHelpersSurfaceService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Replace_Vars']) ? $this->services['WPSEO_Replace_Vars'] : $this->getWPSEOReplaceVarsService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer']) ? $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] : $this->getMetaTagsContextMemoizerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Cleanup_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Cleanup_Integration */ protected function getCleanupIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Cleanup_Integration'] = new \Yoast\WP\SEO\Integrations\Cleanup_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Cleanup_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Cleanup_Repository'] : $this->getIndexableCleanupRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Duplicate_Post_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Duplicate_Post_Integration * * @deprecated Yoast\WP\SEO\Integrations\Duplicate_Post_Integration is deprecated since version 23.4! */ protected function getDuplicatePostIntegrationService() { @trigger_error('Yoast\\WP\\SEO\\Integrations\\Duplicate_Post_Integration is deprecated since version 23.4!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Integrations\\Duplicate_Post_Integration'] = new \Yoast\WP\SEO\Integrations\Duplicate_Post_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Estimated_Reading_Time' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Estimated_Reading_Time */ protected function getEstimatedReadingTimeService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Estimated_Reading_Time'] = new \Yoast\WP\SEO\Integrations\Estimated_Reading_Time(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Exclude_Attachment_Post_Type' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Exclude_Attachment_Post_Type */ protected function getExcludeAttachmentPostTypeService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Exclude_Attachment_Post_Type'] = new \Yoast\WP\SEO\Integrations\Exclude_Attachment_Post_Type(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Exclude_Oembed_Cache_Post_Type' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Exclude_Oembed_Cache_Post_Type */ protected function getExcludeOembedCachePostTypeService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Exclude_Oembed_Cache_Post_Type'] = new \Yoast\WP\SEO\Integrations\Exclude_Oembed_Cache_Post_Type(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Feature_Flag_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Feature_Flag_Integration */ protected function getFeatureFlagIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Feature_Flag_Integration'] = new \Yoast\WP\SEO\Integrations\Feature_Flag_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Addon_Installation_Conditional'] = new \Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Check_Required_Version_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Check_Required_Version_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Check_Required_Version_Conditional'] = new \Yoast\WP\SEO\Conditionals\Check_Required_Version_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\New_Settings_Ui_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\New_Settings_Ui_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\New_Settings_Ui_Conditional'] = new \Yoast\WP\SEO\Conditionals\New_Settings_Ui_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Text_Formality_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Text_Formality_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Text_Formality_Conditional'] = new \Yoast\WP\SEO\Conditionals\Text_Formality_Conditional())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Updated_Importer_Framework_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Updated_Importer_Framework_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Updated_Importer_Framework_Conditional'] = new \Yoast\WP\SEO\Conditionals\Updated_Importer_Framework_Conditional())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Backwards_Compatibility' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Backwards_Compatibility */ protected function getBackwardsCompatibilityService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Backwards_Compatibility'] = new \Yoast\WP\SEO\Integrations\Front_End\Backwards_Compatibility(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Category_Term_Description' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Category_Term_Description */ protected function getCategoryTermDescriptionService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Category_Term_Description'] = new \Yoast\WP\SEO\Integrations\Front_End\Category_Term_Description(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Comment_Link_Fixer' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Comment_Link_Fixer */ protected function getCommentLinkFixerService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Comment_Link_Fixer'] = new \Yoast\WP\SEO\Integrations\Front_End\Comment_Link_Fixer(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Helper'] : $this->getRobotsHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic */ protected function getCrawlCleanupBasicService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Basic'] = new \Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Basic(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss */ protected function getCrawlCleanupRssService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Rss'] = new \Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Rss(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches */ protected function getCrawlCleanupSearchesService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Searches'] = new \Yoast\WP\SEO\Integrations\Front_End\Crawl_Cleanup_Searches(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Feed_Improvements' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Feed_Improvements */ protected function getFeedImprovementsService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Feed_Improvements'] = new \Yoast\WP\SEO\Integrations\Front_End\Feed_Improvements(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface'] : $this->getMetaSurfaceService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Force_Rewrite_Title' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Force_Rewrite_Title */ protected function getForceRewriteTitleService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Force_Rewrite_Title'] = new \Yoast\WP\SEO\Integrations\Front_End\Force_Rewrite_Title(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Query_Wrapper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Handle_404' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Handle_404 */ protected function getHandle404Service() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Handle_404'] = new \Yoast\WP\SEO\Integrations\Front_End\Handle_404(${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Query_Wrapper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Indexing_Controls' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Indexing_Controls */ protected function getIndexingControlsService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Indexing_Controls'] = new \Yoast\WP\SEO\Integrations\Front_End\Indexing_Controls(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Helper'] : $this->getRobotsHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Open_Graph_OEmbed' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Open_Graph_OEmbed */ protected function getOpenGraphOEmbedService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Open_Graph_OEmbed'] = new \Yoast\WP\SEO\Integrations\Front_End\Open_Graph_OEmbed(${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface'] : $this->getMetaSurfaceService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\RSS_Footer_Embed' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\RSS_Footer_Embed */ protected function getRSSFooterEmbedService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\RSS_Footer_Embed'] = new \Yoast\WP\SEO\Integrations\Front_End\RSS_Footer_Embed(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Redirects' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Redirects */ protected function getRedirectsService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Redirects'] = new \Yoast\WP\SEO\Integrations\Front_End\Redirects(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Meta_Helper'] = new \Yoast\WP\SEO\Helpers\Meta_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Redirect_Helper'] = new \Yoast\WP\SEO\Helpers\Redirect_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Robots_Txt_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Robots_Txt_Integration */ protected function getRobotsTxtIntegrationService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Txt_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Txt_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Robots_Txt_Helper'] = new \Yoast\WP\SEO\Helpers\Robots_Txt_Helper())) && false ?: '_'}; return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Robots_Txt_Integration'] = new \Yoast\WP\SEO\Integrations\Front_End\Robots_Txt_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, $a, new \Yoast\WP\SEO\Presenters\Robots_Txt_Presenter($a)); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\Schema_Accessibility_Feature' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\Schema_Accessibility_Feature */ protected function getSchemaAccessibilityFeatureService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\Schema_Accessibility_Feature'] = new \Yoast\WP\SEO\Integrations\Front_End\Schema_Accessibility_Feature(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End\WP_Robots_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End\WP_Robots_Integration */ protected function getWPRobotsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End\\WP_Robots_Integration'] = new \Yoast\WP\SEO\Integrations\Front_End\WP_Robots_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer']) ? $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] : $this->getMetaTagsContextMemoizerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Front_End_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Front_End_Integration */ protected function getFrontEndIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End_Integration'] = new \Yoast\WP\SEO\Integrations\Front_End_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer']) ? $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] : $this->getMetaTagsContextMemoizerService()) && false ?: '_'}, $this, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface'] : $this->getHelpersSurfaceService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Replace_Vars']) ? $this->services['WPSEO_Replace_Vars'] : $this->getWPSEOReplaceVarsService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Primary_Category' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Primary_Category */ protected function getPrimaryCategoryService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Primary_Category'] = new \Yoast\WP\SEO\Integrations\Primary_Category(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Settings_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Settings_Integration */ protected function getSettingsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Settings_Integration'] = new \Yoast\WP\SEO\Integrations\Settings_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Replace_Vars']) ? $this->services['WPSEO_Replace_Vars'] : $this->getWPSEOReplaceVarsService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Schema_Types']) ? $this->services['Yoast\\WP\\SEO\\Config\\Schema_Types'] : ($this->services['Yoast\\WP\\SEO\\Config\\Schema_Types'] = new \Yoast\WP\SEO\Config\Schema_Types())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Language_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Language_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Language_Helper'] = new \Yoast\WP\SEO\Helpers\Language_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] = new \Yoast\WP\SEO\Helpers\Woocommerce_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Article_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Article_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Schema\\Article_Helper'] = new \Yoast\WP\SEO\Helpers\Schema\Article_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications']) ? $this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications'] : $this->getContentTypeVisibilityDismissNotificationsService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Support_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Support_Integration */ protected function getSupportIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Support_Integration'] = new \Yoast\WP\SEO\Integrations\Support_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\AMP' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\AMP */ protected function getAMPService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\AMP'] = new \Yoast\WP\SEO\Integrations\Third_Party\AMP(${($_ = isset($this->services['Yoast\\WP\\SEO\\Integrations\\Front_End_Integration']) ? $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End_Integration'] : $this->getFrontEndIntegrationService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\BbPress' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\BbPress */ protected function getBbPressService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\BbPress'] = new \Yoast\WP\SEO\Integrations\Third_Party\BbPress(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Elementor' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Elementor */ protected function getElementorService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Elementor'] = new \Yoast\WP\SEO\Integrations\Third_Party\Elementor(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper())) && false ?: '_'}, new \Yoast\WP\SEO\Elementor\Infrastructure\Request_Post()); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Exclude_Elementor_Post_Types' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Exclude_Elementor_Post_Types */ protected function getExcludeElementorPostTypesService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_Elementor_Post_Types'] = new \Yoast\WP\SEO\Integrations\Third_Party\Exclude_Elementor_Post_Types(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Exclude_WooCommerce_Post_Types' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Exclude_WooCommerce_Post_Types */ protected function getExcludeWooCommercePostTypesService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_WooCommerce_Post_Types'] = new \Yoast\WP\SEO\Integrations\Third_Party\Exclude_WooCommerce_Post_Types(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Jetpack' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Jetpack */ protected function getJetpackService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Jetpack'] = new \Yoast\WP\SEO\Integrations\Third_Party\Jetpack(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\W3_Total_Cache' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\W3_Total_Cache */ protected function getW3TotalCacheService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\W3_Total_Cache'] = new \Yoast\WP\SEO\Integrations\Third_Party\W3_Total_Cache(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\WPML' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\WPML */ protected function getWPMLService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML'] = new \Yoast\WP\SEO\Integrations\Third_Party\WPML(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\WPML_WPSEO_Notification' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\WPML_WPSEO_Notification */ protected function getWPMLWPSEONotificationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML_WPSEO_Notification'] = new \Yoast\WP\SEO\Integrations\Third_Party\WPML_WPSEO_Notification(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_WPSEO_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_WPSEO_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\Third_Party\\WPML_WPSEO_Conditional'] = new \Yoast\WP\SEO\Conditionals\Third_Party\WPML_WPSEO_Conditional())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Web_Stories' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Web_Stories */ protected function getWebStoriesService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories'] = new \Yoast\WP\SEO\Integrations\Third_Party\Web_Stories(${($_ = isset($this->services['Yoast\\WP\\SEO\\Integrations\\Front_End_Integration']) ? $this->services['Yoast\\WP\\SEO\\Integrations\\Front_End_Integration'] : $this->getFrontEndIntegrationService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Web_Stories_Post_Edit' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Web_Stories_Post_Edit */ protected function getWebStoriesPostEditService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories_Post_Edit'] = new \Yoast\WP\SEO\Integrations\Third_Party\Web_Stories_Post_Edit(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Wincher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Wincher * * @deprecated Yoast\WP\SEO\Integrations\Third_Party\Wincher is deprecated since version 21.6! */ protected function getWincherService() { @trigger_error('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher is deprecated since version 21.6!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher'] = new \Yoast\WP\SEO\Integrations\Third_Party\Wincher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Wincher_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Wincher_Helper'] : $this->getWincherHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Wincher_Publish' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Wincher_Publish */ protected function getWincherPublishService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher_Publish'] = new \Yoast\WP\SEO\Integrations\Third_Party\Wincher_Publish(${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Wincher_Enabled_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Wincher_Enabled_Conditional'] : $this->getWincherEnabledConditionalService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Keyphrases_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Keyphrases_Action'] : $this->getWincherKeyphrasesActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Account_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Account_Action'] : $this->getWincherAccountActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\WooCommerce' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\WooCommerce */ protected function getWooCommerce2Service() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce'] = new \Yoast\WP\SEO\Integrations\Third_Party\WooCommerce(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Replace_Vars']) ? $this->services['WPSEO_Replace_Vars'] : $this->getWPSEOReplaceVarsService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer']) ? $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] : $this->getMetaTagsContextMemoizerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Woocommerce_Helper'] = new \Yoast\WP\SEO\Helpers\Woocommerce_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\WooCommerce_Post_Edit' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\WooCommerce_Post_Edit */ protected function getWooCommercePostEditService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce_Post_Edit'] = new \Yoast\WP\SEO\Integrations\Third_Party\WooCommerce_Post_Edit(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Woocommerce_Permalinks' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Woocommerce_Permalinks */ protected function getWoocommercePermalinksService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Woocommerce_Permalinks'] = new \Yoast\WP\SEO\Integrations\Third_Party\Woocommerce_Permalinks(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Wordproof' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Wordproof * * @deprecated Yoast\WP\SEO\Integrations\Third_Party\Wordproof is deprecated since version 22.10! */ protected function getWordproofService() { @trigger_error('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof is deprecated since version 22.10!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof'] = new \Yoast\WP\SEO\Integrations\Third_Party\Wordproof(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper'] : $this->getWordproofHelperService()) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Third_Party\Wordproof_Integration_Toggle' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Third_Party\Wordproof_Integration_Toggle * * @deprecated Yoast\WP\SEO\Integrations\Third_Party\Wordproof_Integration_Toggle is deprecated since version 21.6! */ protected function getWordproofIntegrationToggleService() { @trigger_error('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof_Integration_Toggle is deprecated since version 21.6!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wordproof_Integration_Toggle'] = new \Yoast\WP\SEO\Integrations\Third_Party\Wordproof_Integration_Toggle(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Wordproof_Helper'] : $this->getWordproofHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Uninstall_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Uninstall_Integration */ protected function getUninstallIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Uninstall_Integration'] = new \Yoast\WP\SEO\Integrations\Uninstall_Integration(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Addon_Update_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Addon_Update_Watcher */ protected function getAddonUpdateWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Addon_Update_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Addon_Update_Watcher(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Auto_Update_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Auto_Update_Watcher */ protected function getAutoUpdateWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Auto_Update_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Auto_Update_Watcher(${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Ancestor_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Ancestor_Watcher */ protected function getIndexableAncestorWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Ancestor_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Ancestor_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'] : $this->getIndexableHierarchyBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository'] : $this->getIndexableHierarchyRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Attachment_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Attachment_Watcher */ protected function getIndexableAttachmentWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Attachment_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Attachment_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Attachment_Cleanup_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Attachment_Cleanup_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Attachment_Cleanup_Helper'] = new \Yoast\WP\SEO\Helpers\Attachment_Cleanup_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Author_Archive_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Author_Archive_Watcher */ protected function getIndexableAuthorArchiveWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Archive_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Author_Archive_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Author_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Author_Watcher */ protected function getIndexableAuthorWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Author_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Category_Permalink_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Category_Permalink_Watcher */ protected function getIndexableCategoryPermalinkWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Category_Permalink_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Category_Permalink_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Date_Archive_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Date_Archive_Watcher */ protected function getIndexableDateArchiveWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Date_Archive_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Date_Archive_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_HomeUrl_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_HomeUrl_Watcher */ protected function getIndexableHomeUrlWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_HomeUrl_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_HomeUrl_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Home_Page_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Home_Page_Watcher */ protected function getIndexableHomePageWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Home_Page_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Home_Page_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Permalink_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Permalink_Watcher */ protected function getIndexablePermalinkWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Permalink_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Permalink_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Meta_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Meta_Watcher */ protected function getIndexablePostMetaWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Meta_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Meta_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher']) ? $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher'] : $this->getIndexablePostWatcherService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Type_Archive_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Type_Archive_Watcher */ protected function getIndexablePostTypeArchiveWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Archive_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Type_Archive_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Type_Change_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Type_Change_Watcher */ protected function getIndexablePostTypeChangeWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Change_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Type_Change_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Watcher */ protected function getIndexablePostWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Post_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository'] : $this->getIndexableHierarchyRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'] : $this->getIndexableLinkBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper'] : $this->getAuthorArchiveHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Loggers\\Logger']) ? $this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] : ($this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] = new \Yoast\WP\SEO\Loggers\Logger())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Static_Home_Page_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Static_Home_Page_Watcher */ protected function getIndexableStaticHomePageWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Static_Home_Page_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Static_Home_Page_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_System_Page_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_System_Page_Watcher */ protected function getIndexableSystemPageWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_System_Page_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_System_Page_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Taxonomy_Change_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Taxonomy_Change_Watcher */ protected function getIndexableTaxonomyChangeWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Taxonomy_Change_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Taxonomy_Change_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Indexable_Term_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Indexable_Term_Watcher */ protected function getIndexableTermWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Term_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Indexable_Term_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Link_Builder'] : $this->getIndexableLinkBuilderService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper'] = new \Yoast\WP\SEO\Helpers\Site_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Option_Titles_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Option_Titles_Watcher */ protected function getOptionTitlesWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Titles_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Option_Titles_Watcher(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Option_Wpseo_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Option_Wpseo_Watcher */ protected function getOptionWpseoWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Wpseo_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Option_Wpseo_Watcher(); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Primary_Category_Quick_Edit_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Primary_Category_Quick_Edit_Watcher */ protected function getPrimaryCategoryQuickEditWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Category_Quick_Edit_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Primary_Category_Quick_Edit_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] : ($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] = new \Yoast\WP\SEO\Repositories\Primary_Term_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'] : $this->getIndexableHierarchyBuilderService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Primary_Term_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Primary_Term_Watcher */ protected function getPrimaryTermWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Term_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Primary_Term_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] : ($this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] = new \Yoast\WP\SEO\Repositories\Primary_Term_Repository())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Site_Helper'] = new \Yoast\WP\SEO\Helpers\Site_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Primary_Term_Helper'] = new \Yoast\WP\SEO\Helpers\Primary_Term_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Primary_Term_Builder'] : $this->getPrimaryTermBuilderService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Search_Engines_Discouraged_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Search_Engines_Discouraged_Watcher */ protected function getSearchEnginesDiscouragedWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Search_Engines_Discouraged_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Search_Engines_Discouraged_Watcher(${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] = new \Yoast\WP\SEO\Helpers\Notification_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Capability_Helper'] = new \Yoast\WP\SEO\Helpers\Capability_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\Watchers\Woocommerce_Beta_Editor_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\Watchers\Woocommerce_Beta_Editor_Watcher */ protected function getWoocommerceBetaEditorWatcherService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\Watchers\\Woocommerce_Beta_Editor_Watcher'] = new \Yoast\WP\SEO\Integrations\Watchers\Woocommerce_Beta_Editor_Watcher(${($_ = isset($this->services['Yoast_Notification_Center']) ? $this->services['Yoast_Notification_Center'] : $this->getYoastNotificationCenterService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Notification_Helper'] = new \Yoast\WP\SEO\Helpers\Notification_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional'] : ($this->services['Yoast\\WP\\SEO\\Conditionals\\WooCommerce_Conditional'] = new \Yoast\WP\SEO\Conditionals\WooCommerce_Conditional())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Integrations\XMLRPC' shared autowired service. * * @return \Yoast\WP\SEO\Integrations\XMLRPC */ protected function getXMLRPCService() { return $this->services['Yoast\\WP\\SEO\\Integrations\\XMLRPC'] = new \Yoast\WP\SEO\Integrations\XMLRPC(); } /** * Gets the public 'Yoast\WP\SEO\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell' shared autowired service. * * @return \Yoast\WP\SEO\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell * * @deprecated Yoast\WP\SEO\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell is deprecated since version 23.2! */ protected function getAiGenerateTitlesAndDescriptionsIntroductionUpsellService() { @trigger_error('Yoast\\WP\\SEO\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell is deprecated since version 23.2!', E_USER_DEPRECATED); return $this->services['Yoast\\WP\\SEO\\Introductions\\Application\\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell'] = new \Yoast\WP\SEO\Introductions\Application\Ai_Generate_Titles_And_Descriptions_Introduction_Upsell(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository */ protected function getWistiaEmbedPermissionRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository'] = new \Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Introductions\User_Interface\Introductions_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Introductions\User_Interface\Introductions_Integration */ protected function getIntroductionsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Integration'] = new \Yoast\WP\SEO\Introductions\User_Interface\Introductions_Integration(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector']) ? $this->services['Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector'] : $this->getIntroductionsCollectorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Short_Link_Helper'] : $this->getShortLinkHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository']) ? $this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository'] : $this->getWistiaEmbedPermissionRepositoryService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Introductions\User_Interface\Introductions_Seen_Route' shared autowired service. * * @return \Yoast\WP\SEO\Introductions\User_Interface\Introductions_Seen_Route */ protected function getIntroductionsSeenRouteService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}; return $this->services['Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Seen_Route'] = new \Yoast\WP\SEO\Introductions\User_Interface\Introductions_Seen_Route(new \Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository($a), $a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector']) ? $this->services['Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector'] : $this->getIntroductionsCollectorService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Introductions\User_Interface\Wistia_Embed_Permission_Route' shared autowired service. * * @return \Yoast\WP\SEO\Introductions\User_Interface\Wistia_Embed_Permission_Route */ protected function getWistiaEmbedPermissionRouteService() { return $this->services['Yoast\\WP\\SEO\\Introductions\\User_Interface\\Wistia_Embed_Permission_Route'] = new \Yoast\WP\SEO\Introductions\User_Interface\Wistia_Embed_Permission_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository']) ? $this->services['Yoast\\WP\\SEO\\Introductions\\Infrastructure\\Wistia_Embed_Permission_Repository'] : $this->getWistiaEmbedPermissionRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Llms_Txt\User_Interface\Cleanup_Llms_Txt_On_Deactivation' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\User_Interface\Cleanup_Llms_Txt_On_Deactivation */ protected function getCleanupLlmsTxtOnDeactivationService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Cleanup_Llms_Txt_On_Deactivation'] = new \Yoast\WP\SEO\Llms_Txt\User_Interface\Cleanup_Llms_Txt_On_Deactivation(${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler'] : $this->getRemoveFileCommandHandlerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler'] : $this->getLlmsTxtCronSchedulerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Llms_Txt\User_Interface\Enable_Llms_Txt_Option_Watcher' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\User_Interface\Enable_Llms_Txt_Option_Watcher */ protected function getEnableLlmsTxtOptionWatcherService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Enable_Llms_Txt_Option_Watcher'] = new \Yoast\WP\SEO\Llms_Txt\User_Interface\Enable_Llms_Txt_Option_Watcher(${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler'] : $this->getLlmsTxtCronSchedulerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler'] : $this->getRemoveFileCommandHandlerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler'] : $this->getPopulateFileCommandHandlerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Llms_Txt\User_Interface\Llms_Txt_Cron_Callback_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\User_Interface\Llms_Txt_Cron_Callback_Integration */ protected function getLlmsTxtCronCallbackIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Llms_Txt_Cron_Callback_Integration'] = new \Yoast\WP\SEO\Llms_Txt\User_Interface\Llms_Txt_Cron_Callback_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler'] : $this->getLlmsTxtCronSchedulerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler'] : $this->getPopulateFileCommandHandlerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler'] : $this->getRemoveFileCommandHandlerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Llms_Txt\User_Interface\Schedule_Population_On_Activation_Integration' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\User_Interface\Schedule_Population_On_Activation_Integration */ protected function getSchedulePopulationOnActivationIntegrationService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Schedule_Population_On_Activation_Integration'] = new \Yoast\WP\SEO\Llms_Txt\User_Interface\Schedule_Population_On_Activation_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler'] : $this->getLlmsTxtCronSchedulerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Loader' shared autowired service. * * @return \Yoast\WP\SEO\Loader */ protected function getLoaderService() { $this->services['Yoast\\WP\\SEO\\Loader'] = $instance = new \Yoast\WP\SEO\Loader($this); $instance->register_integration('Yoast\\WP\\SEO\\Analytics\\User_Interface\\Last_Completed_Indexation_Integration'); $instance->register_command('Yoast\\WP\\SEO\\Commands\\Cleanup_Command'); $instance->register_command('Yoast\\WP\\SEO\\Commands\\Index_Command'); $instance->register_migration('free', '20171228151840', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexable'); $instance->register_migration('free', '20171228151841', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastPrimaryTerm'); $instance->register_migration('free', '20190529075038', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastDropIndexableMetaTableIfExists'); $instance->register_migration('free', '20191011111109', 'Yoast\\WP\\SEO\\Config\\Migrations\\WpYoastIndexableHierarchy'); $instance->register_migration('free', '20200408101900', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddCollationToTables'); $instance->register_migration('free', '20200420073606', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddColumnsToIndexables'); $instance->register_migration('free', '20200428123747', 'Yoast\\WP\\SEO\\Config\\Migrations\\BreadcrumbTitleAndHierarchyReset'); $instance->register_migration('free', '20200428194858', 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableColumnLengths'); $instance->register_migration('free', '20200429105310', 'Yoast\\WP\\SEO\\Config\\Migrations\\TruncateIndexableTables'); $instance->register_migration('free', '20200430075614', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexableObjectIdAndTypeIndex'); $instance->register_migration('free', '20200430150130', 'Yoast\\WP\\SEO\\Config\\Migrations\\ClearIndexableTables'); $instance->register_migration('free', '20200507054848', 'Yoast\\WP\\SEO\\Config\\Migrations\\DeleteDuplicateIndexables'); $instance->register_migration('free', '20200513133401', 'Yoast\\WP\\SEO\\Config\\Migrations\\ResetIndexableHierarchyTable'); $instance->register_migration('free', '20200609154515', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddHasAncestorsColumn'); $instance->register_migration('free', '20200616130143', 'Yoast\\WP\\SEO\\Config\\Migrations\\ReplacePermalinkHashIndex'); $instance->register_migration('free', '20200617122511', 'Yoast\\WP\\SEO\\Config\\Migrations\\CreateSEOLinksTable'); $instance->register_migration('free', '20200702141921', 'Yoast\\WP\\SEO\\Config\\Migrations\\CreateIndexableSubpagesIndex'); $instance->register_migration('free', '20200728095334', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddIndexesForProminentWordsOnIndexables'); $instance->register_migration('free', '20201202144329', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddEstimatedReadingTime'); $instance->register_migration('free', '20201216124002', 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandIndexableIDColumnLengths'); $instance->register_migration('free', '20201216141134', 'Yoast\\WP\\SEO\\Config\\Migrations\\ExpandPrimaryTermIDColumnLengths'); $instance->register_migration('free', '20210817092415', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddVersionColumnToIndexables'); $instance->register_migration('free', '20211020091404', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddObjectTimestamps'); $instance->register_migration('free', '20230417083836', 'Yoast\\WP\\SEO\\Config\\Migrations\\AddInclusiveLanguageScore'); $instance->register_integration('Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Watcher_Actions'); $instance->register_route('Yoast\\WP\\SEO\\Content_Type_Visibility\\User_Interface\\Content_Type_Visibility_Dismiss_New_Route'); $instance->register_integration('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Capabilities_Integration'); $instance->register_route('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Configuration_Dismissal_Route'); $instance->register_route('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Configuration\\Site_Kit_Consent_Management_Route'); $instance->register_route('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\Readability_Scores_Route'); $instance->register_route('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Scores\\SEO_Scores_Route'); $instance->register_integration('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Flow_Interceptor'); $instance->register_integration('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Setup\\Setup_Url_Interceptor'); $instance->register_route('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Time_Based_SEO_Metrics\\Time_Based_SEO_Metrics_Route'); $instance->register_route('Yoast\\WP\\SEO\\Dashboard\\User_Interface\\Tracking\\Setup_Steps_Tracking_Route'); $instance->register_integration('Yoast\\WP\\SEO\\General\\User_Interface\\General_Page_Integration'); $instance->register_initializer('Yoast\\WP\\SEO\\Initializers\\Crawl_Cleanup_Permalinks'); $instance->register_initializer('Yoast\\WP\\SEO\\Initializers\\Disable_Core_Sitemaps'); $instance->register_initializer('Yoast\\WP\\SEO\\Initializers\\Migration_Runner'); $instance->register_initializer('Yoast\\WP\\SEO\\Initializers\\Plugin_Headers'); $instance->register_initializer('Yoast\\WP\\SEO\\Initializers\\Woocommerce'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Academy_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Activation_Cleanup_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Dialog_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Addon_Installation\\Installation_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Admin_Columns_Cache_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Background_Indexing_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Check_Required_Version'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Crawl_Settings_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Cron_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Deactivated_Premium_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\First_Time_Configuration_Notice_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Fix_News_Dependencies_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Health_Check_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\HelpScout_Beacon'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Import_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Indexables_Exclude_Taxonomy_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Notification_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Indexing_Tool_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Installation_Success_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Integrations_Page'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Link_Count_Columns_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Menu_Badge_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Migration_Error_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Old_Configuration_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Redirect_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Redirects_Page_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Admin\\Workouts_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Product_Editor_Checklist_Notification'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Promotion_Notification'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Alerts\\Black_Friday_Sidebar_Checklist_Notification'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Alerts\\Trustpilot_Review_Notification'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Alerts\\Webinar_Promo_Notification'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Blocks\\Internal_Linking_Category'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Blocks\\Block_Editor_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Blocks\\Breadcrumbs_Block'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Blocks\\Structured_Data_Blocks'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Breadcrumbs_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Cleanup_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Estimated_Reading_Time'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Exclude_Attachment_Post_Type'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Exclude_Oembed_Cache_Post_Type'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Feature_Flag_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Backwards_Compatibility'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Category_Term_Description'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Comment_Link_Fixer'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Basic'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Rss'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Crawl_Cleanup_Searches'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Feed_Improvements'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Force_Rewrite_Title'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Handle_404'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Indexing_Controls'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Open_Graph_OEmbed'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Redirects'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Robots_Txt_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\RSS_Footer_Embed'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\Schema_Accessibility_Feature'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Front_End\\WP_Robots_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Primary_Category'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Settings_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Support_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\AMP'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\BbPress'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Elementor'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_Elementor_Post_Types'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Exclude_WooCommerce_Post_Types'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Jetpack'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\W3_Total_Cache'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories_Post_Edit'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Web_Stories'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Wincher_Publish'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\Woocommerce_Permalinks'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce_Post_Edit'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\WooCommerce'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML_WPSEO_Notification'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Third_Party\\WPML'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Uninstall_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Addon_Update_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Auto_Update_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Ancestor_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Attachment_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Archive_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Author_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Category_Permalink_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Date_Archive_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Home_Page_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_HomeUrl_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Permalink_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Meta_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Archive_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Type_Change_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Post_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Static_Home_Page_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_System_Page_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Taxonomy_Change_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Indexable_Term_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Titles_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Option_Wpseo_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Category_Quick_Edit_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Primary_Term_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Search_Engines_Discouraged_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\Watchers\\Woocommerce_Beta_Editor_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Integrations\\XMLRPC'); $instance->register_integration('Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Integration'); $instance->register_route('Yoast\\WP\\SEO\\Introductions\\User_Interface\\Introductions_Seen_Route'); $instance->register_route('Yoast\\WP\\SEO\\Introductions\\User_Interface\\Wistia_Embed_Permission_Route'); $instance->register_integration('Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Cleanup_Llms_Txt_On_Deactivation'); $instance->register_integration('Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Enable_Llms_Txt_Option_Watcher'); $instance->register_integration('Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Llms_Txt_Cron_Callback_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\Llms_Txt\\User_Interface\\Schedule_Population_On_Activation_Integration'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Alert_Dismissal_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\First_Time_Configuration_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Importing_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Indexables_Head_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Indexing_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Integrations_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Meta_Search_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\SEMrush_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Supported_Features_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Wincher_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Workouts_Route'); $instance->register_route('Yoast\\WP\\SEO\\Routes\\Yoast_Head_REST_Field'); $instance->register_integration('Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Additional_Contactmethods_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Cleanup_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Custom_Meta_Integration'); $instance->register_integration('Yoast\\WP\\SEO\\User_Profiles_Additions\\User_Interface\\User_Profiles_Additions_Ui'); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Loggers\Logger' shared autowired service. * * @return \Yoast\WP\SEO\Loggers\Logger */ protected function getLoggerService() { return $this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] = new \Yoast\WP\SEO\Loggers\Logger(); } /** * Gets the public 'Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer' shared autowired service. * * @return \Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer */ protected function getMetaTagsContextMemoizerService() { return $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] = new \Yoast\WP\SEO\Memoizers\Meta_Tags_Context_Memoizer(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Blocks_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Blocks_Helper'] : $this->getBlocksHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Context\\Meta_Tags_Context']) ? $this->services['Yoast\\WP\\SEO\\Context\\Meta_Tags_Context'] : $this->getMetaTagsContextService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Memoizers\\Presentation_Memoizer']) ? $this->services['Yoast\\WP\\SEO\\Memoizers\\Presentation_Memoizer'] : ($this->services['Yoast\\WP\\SEO\\Memoizers\\Presentation_Memoizer'] = new \Yoast\WP\SEO\Memoizers\Presentation_Memoizer($this))) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Memoizers\Presentation_Memoizer' shared autowired service. * * @return \Yoast\WP\SEO\Memoizers\Presentation_Memoizer */ protected function getPresentationMemoizerService() { return $this->services['Yoast\\WP\\SEO\\Memoizers\\Presentation_Memoizer'] = new \Yoast\WP\SEO\Memoizers\Presentation_Memoizer($this); } /** * Gets the public 'Yoast\WP\SEO\Presentations\Abstract_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Abstract_Presentation */ protected function getAbstractPresentationService() { return $this->services['Yoast\\WP\\SEO\\Presentations\\Abstract_Presentation'] = new \Yoast\WP\SEO\Presentations\Abstract_Presentation(); } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Author_Archive_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Author_Archive_Presentation */ protected function getIndexableAuthorArchivePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Author_Archive_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Author_Archive_Presentation(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper'] : $this->getAuthorArchiveHelperService()) && false ?: '_'}); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); $instance->set_archive_adjacent_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Date_Archive_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Date_Archive_Presentation */ protected function getIndexableDateArchivePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Date_Archive_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Date_Archive_Presentation(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Error_Page_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Error_Page_Presentation */ protected function getIndexableErrorPagePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Error_Page_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Error_Page_Presentation(); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Home_Page_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Home_Page_Presentation */ protected function getIndexableHomePagePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Home_Page_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Home_Page_Presentation(); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); $instance->set_archive_adjacent_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Post_Type_Archive_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Post_Type_Archive_Presentation */ protected function getIndexablePostTypeArchivePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Post_Type_Archive_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Post_Type_Archive_Presentation(); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); $instance->set_archive_adjacent_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Post_Type_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Post_Type_Presentation */ protected function getIndexablePostTypePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Post_Type_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Post_Type_Presentation(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Presentation */ protected function getIndexablePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Presentation(); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Search_Result_Page_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Search_Result_Page_Presentation */ protected function getIndexableSearchResultPagePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Search_Result_Page_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Search_Result_Page_Presentation(); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Static_Home_Page_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Static_Home_Page_Presentation */ protected function getIndexableStaticHomePagePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Static_Home_Page_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Static_Home_Page_Presentation(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Static_Posts_Page_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Static_Posts_Page_Presentation */ protected function getIndexableStaticPostsPagePresentationService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}; $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Static_Posts_Page_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Static_Posts_Page_Presentation(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Date_Helper'] = new \Yoast\WP\SEO\Helpers\Date_Helper())) && false ?: '_'}, $a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); $instance->set_archive_adjacent_helpers($a); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Presentations\Indexable_Term_Archive_Presentation' shared autowired service. * * @return \Yoast\WP\SEO\Presentations\Indexable_Term_Archive_Presentation */ protected function getIndexableTermArchivePresentationService() { $this->services['Yoast\\WP\\SEO\\Presentations\\Indexable_Term_Archive_Presentation'] = $instance = new \Yoast\WP\SEO\Presentations\Indexable_Term_Archive_Presentation(${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Query_Wrapper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}); $instance->set_generators(${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Schema_Generator'] : $this->getSchemaGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] : ($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Locale_Generator'] = new \Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Open_Graph_Image_Generator'] : $this->getOpenGraphImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Twitter_Image_Generator'] : $this->getTwitterImageGeneratorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator']) ? $this->services['Yoast\\WP\\SEO\\Generators\\Breadcrumbs_Generator'] : $this->getBreadcrumbsGeneratorService()) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Permalink_Helper'] = new \Yoast\WP\SEO\Helpers\Permalink_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Values_Helper'] = new \Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper())) && false ?: '_'}); $instance->set_archive_adjacent_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Pagination_Helper'] : $this->getPaginationHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Promotions\Application\Promotion_Manager' shared autowired service. * * @return \Yoast\WP\SEO\Promotions\Application\Promotion_Manager */ protected function getPromotionManagerService() { return $this->services['Yoast\\WP\\SEO\\Promotions\\Application\\Promotion_Manager'] = new \Yoast\WP\SEO\Promotions\Application\Promotion_Manager(new \Yoast\WP\SEO\Promotions\Domain\Black_Friday_Checklist_Promotion(), new \Yoast\WP\SEO\Promotions\Domain\Black_Friday_Promotion()); } /** * Gets the public 'Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository */ protected function getIndexableCleanupRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Cleanup_Repository'] = new \Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Author_Archive_Helper'] : $this->getAuthorArchiveHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository */ protected function getIndexableHierarchyRepositoryService() { $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository'] = $instance = new \Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository(); $instance->set_builder(${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Hierarchy_Builder'] : $this->getIndexableHierarchyBuilderService()) && false ?: '_'}); $instance->set_helper(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Repositories\Indexable_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Repositories\Indexable_Repository */ protected function getIndexableRepositoryService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder']) ? $this->services['Yoast\\WP\\SEO\\Builders\\Indexable_Builder'] : $this->getIndexableBuilderService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'])) { return $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']; } $b = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Hierarchy_Repository'] : $this->getIndexableHierarchyRepositoryService()) && false ?: '_'}; if (isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'])) { return $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']; } return $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] = new \Yoast\WP\SEO\Repositories\Indexable_Repository($a, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Current_Page_Helper'] : $this->getCurrentPageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Loggers\\Logger']) ? $this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] : ($this->services['Yoast\\WP\\SEO\\Loggers\\Logger'] = new \Yoast\WP\SEO\Loggers\Logger())) && false ?: '_'}, $b, ${($_ = isset($this->services['wpdb']) ? $this->services['wpdb'] : $this->getWpdbService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Indexables\\Indexable_Version_Manager']) ? $this->services['Yoast\\WP\\SEO\\Services\\Indexables\\Indexable_Version_Manager'] : $this->getIndexableVersionManagerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Repositories\Primary_Term_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Repositories\Primary_Term_Repository */ protected function getPrimaryTermRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Repositories\\Primary_Term_Repository'] = new \Yoast\WP\SEO\Repositories\Primary_Term_Repository(); } /** * Gets the public 'Yoast\WP\SEO\Repositories\SEO_Links_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Repositories\SEO_Links_Repository */ protected function getSEOLinksRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Repositories\\SEO_Links_Repository'] = new \Yoast\WP\SEO\Repositories\SEO_Links_Repository(); } /** * Gets the public 'Yoast\WP\SEO\Routes\Alert_Dismissal_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Alert_Dismissal_Route */ protected function getAlertDismissalRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Alert_Dismissal_Route'] = new \Yoast\WP\SEO\Routes\Alert_Dismissal_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Alert_Dismissal_Action'] : $this->getAlertDismissalActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\First_Time_Configuration_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\First_Time_Configuration_Route */ protected function getFirstTimeConfigurationRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\First_Time_Configuration_Route'] = new \Yoast\WP\SEO\Routes\First_Time_Configuration_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Configuration\\First_Time_Configuration_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Configuration\\First_Time_Configuration_Action'] : $this->getFirstTimeConfigurationActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Importing_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Importing_Route */ protected function getImportingRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Importing_Route'] = new \Yoast\WP\SEO\Routes\Importing_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service'] : $this->getImportableDetectorServiceService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Cleanup_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Cleanup_Action'] : $this->getAioseoCleanupActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action'] : $this->getAioseoCustomArchiveSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action'] : $this->getAioseoDefaultArchiveSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action'] : $this->getAioseoGeneralSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action'] : $this->getAioseoPostsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action'] : $this->getAioseoPosttypeDefaultsSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action'] : $this->getAioseoTaxonomySettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Validate_Data_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Validate_Data_Action'] : $this->getAioseoValidateDataActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Deactivate_Conflicting_Plugins_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Deactivate_Conflicting_Plugins_Action'] : $this->getDeactivateConflictingPluginsActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Indexables_Head_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Indexables_Head_Route */ protected function getIndexablesHeadRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Indexables_Head_Route'] = new \Yoast\WP\SEO\Routes\Indexables_Head_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexables\\Indexable_Head_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexables\\Indexable_Head_Action'] : $this->getIndexableHeadActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Indexing_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Indexing_Route */ protected function getIndexingRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Indexing_Route'] = new \Yoast\WP\SEO\Routes\Indexing_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Indexation_Action'] : $this->getIndexablePostIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Term_Indexation_Action'] : $this->getIndexableTermIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Post_Type_Archive_Indexation_Action'] : $this->getIndexablePostTypeArchiveIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_General_Indexation_Action'] : $this->getIndexableGeneralIndexationActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexable_Indexing_Complete_Action'] : $this->getIndexableIndexingCompleteActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Complete_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Complete_Action'] : $this->getIndexingCompleteActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Prepare_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Indexing_Prepare_Action'] : $this->getIndexingPrepareActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Post_Link_Indexing_Action'] : $this->getPostLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexing\\Term_Link_Indexing_Action'] : $this->getTermLinkIndexingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexing_Helper'] : $this->getIndexingHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Integrations_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Integrations_Route */ protected function getIntegrationsRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Integrations_Route'] = new \Yoast\WP\SEO\Routes\Integrations_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Integrations_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Integrations_Action'] : $this->getIntegrationsActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Meta_Search_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Meta_Search_Route */ protected function getMetaSearchRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Meta_Search_Route'] = new \Yoast\WP\SEO\Routes\Meta_Search_Route(); } /** * Gets the public 'Yoast\WP\SEO\Routes\SEMrush_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\SEMrush_Route */ protected function getSEMrushRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\SEMrush_Route'] = new \Yoast\WP\SEO\Routes\SEMrush_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Login_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Login_Action'] : $this->getSEMrushLoginActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Options_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Options_Action'] : $this->getSEMrushOptionsActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Phrases_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\SEMrush\\SEMrush_Phrases_Action'] : $this->getSEMrushPhrasesActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Supported_Features_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Supported_Features_Route */ protected function getSupportedFeaturesRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Supported_Features_Route'] = new \Yoast\WP\SEO\Routes\Supported_Features_Route(); } /** * Gets the public 'Yoast\WP\SEO\Routes\Wincher_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Wincher_Route */ protected function getWincherRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Wincher_Route'] = new \Yoast\WP\SEO\Routes\Wincher_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Login_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Login_Action'] : $this->getWincherLoginActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Account_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Account_Action'] : $this->getWincherAccountActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Keyphrases_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Wincher\\Wincher_Keyphrases_Action'] : $this->getWincherKeyphrasesActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Workouts_Route' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Workouts_Route */ protected function getWorkoutsRouteService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Workouts_Route'] = new \Yoast\WP\SEO\Routes\Workouts_Route(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Routes\Yoast_Head_REST_Field' shared autowired service. * * @return \Yoast\WP\SEO\Routes\Yoast_Head_REST_Field */ protected function getYoastHeadRESTFieldService() { return $this->services['Yoast\\WP\\SEO\\Routes\\Yoast_Head_REST_Field'] = new \Yoast\WP\SEO\Routes\Yoast_Head_REST_Field(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Helper'] : $this->getPostHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Indexables\\Indexable_Head_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Indexables\\Indexable_Head_Action'] : $this->getIndexableHeadActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Check' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Check */ protected function getDefaultTaglineCheckService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Check'] = new \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Check(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Runner())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Reports']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Reports'] : $this->getDefaultTaglineReportsService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Reports' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Reports */ protected function getDefaultTaglineReportsService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Reports'] = new \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Reports(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] = new \Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Runner' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Runner */ protected function getDefaultTaglineRunnerService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Runner(); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Links_Table_Check' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Links_Table_Check */ protected function getLinksTableCheckService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Check'] = new \Yoast\WP\SEO\Services\Health_Check\Links_Table_Check(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Runner']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Runner'] : $this->getLinksTableRunnerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Reports']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Reports'] : $this->getLinksTableReportsService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Should_Index_Links_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Should_Index_Links_Conditional'] : $this->getShouldIndexLinksConditionalService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Links_Table_Reports' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Links_Table_Reports */ protected function getLinksTableReportsService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Reports'] = new \Yoast\WP\SEO\Services\Health_Check\Links_Table_Reports(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] = new \Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory())) && false ?: '_'}, ${($_ = isset($this->services['WPSEO_Shortlinker']) ? $this->services['WPSEO_Shortlinker'] : $this->getWPSEOShortlinkerService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Links_Table_Runner' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Links_Table_Runner */ protected function getLinksTableRunnerService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Links_Table_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Links_Table_Runner(${($_ = isset($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status']) ? $this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] : ($this->services['Yoast\\WP\\SEO\\Config\\Migration_Status'] = new \Yoast\WP\SEO\Config\Migration_Status())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\MyYoast_Api_Request_Factory' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\MyYoast_Api_Request_Factory */ protected function getMyYoastApiRequestFactoryService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\MyYoast_Api_Request_Factory'] = new \Yoast\WP\SEO\Services\Health_Check\MyYoast_Api_Request_Factory(); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Page_Comments_Check' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Page_Comments_Check */ protected function getPageCommentsCheckService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Check'] = new \Yoast\WP\SEO\Services\Health_Check\Page_Comments_Check(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Runner']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Runner'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Page_Comments_Runner())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Reports']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Reports'] : $this->getPageCommentsReportsService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Page_Comments_Reports' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Page_Comments_Reports */ protected function getPageCommentsReportsService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Reports'] = new \Yoast\WP\SEO\Services\Health_Check\Page_Comments_Reports(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] = new \Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Page_Comments_Runner' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Page_Comments_Runner */ protected function getPageCommentsRunnerService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Page_Comments_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Page_Comments_Runner(); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Check' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Check */ protected function getPostnamePermalinkCheckService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Check'] = new \Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Check(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Runner']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Runner'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Runner())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Reports']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Reports'] : $this->getPostnamePermalinkReportsService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Reports' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Reports */ protected function getPostnamePermalinkReportsService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Reports'] = new \Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Reports(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] = new \Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Runner' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Runner */ protected function getPostnamePermalinkRunnerService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Postname_Permalink_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Postname_Permalink_Runner(); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Report_Builder' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Report_Builder */ protected function getReportBuilderService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder'] = new \Yoast\WP\SEO\Services\Health_Check\Report_Builder(); } /** * Gets the public 'Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory' shared autowired service. * * @return \Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory */ protected function getReportBuilderFactoryService() { return $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Report_Builder_Factory'] = new \Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory(); } /** * Gets the public 'Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service' shared autowired service. * * @return \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service */ protected function getAioseoReplacevarServiceService() { return $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Replacevar_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service(); } /** * Gets the public 'Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service' shared autowired service. * * @return \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service */ protected function getAioseoRobotsProviderServiceService() { return $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service' shared autowired service. * * @return \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service */ protected function getAioseoRobotsTransformerServiceService() { return $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Transformer_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service']) ? $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Robots_Provider_Service'] : $this->getAioseoRobotsProviderServiceService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Social_Images_Provider_Service' shared autowired service. * * @return \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Social_Images_Provider_Service */ protected function getAioseoSocialImagesProviderServiceService() { return $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Aioseo\\Aioseo_Social_Images_Provider_Service'] = new \Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Social_Images_Provider_Service(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Aioseo_Helper'] : $this->getAioseoHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Importing\Conflicting_Plugins_Service' shared autowired service. * * @return \Yoast\WP\SEO\Services\Importing\Conflicting_Plugins_Service */ protected function getConflictingPluginsServiceService() { return $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Conflicting_Plugins_Service'] = new \Yoast\WP\SEO\Services\Importing\Conflicting_Plugins_Service(); } /** * Gets the public 'Yoast\WP\SEO\Services\Importing\Importable_Detector_Service' shared autowired service. * * @return \Yoast\WP\SEO\Services\Importing\Importable_Detector_Service */ protected function getImportableDetectorServiceService() { return $this->services['Yoast\\WP\\SEO\\Services\\Importing\\Importable_Detector_Service'] = new \Yoast\WP\SEO\Services\Importing\Importable_Detector_Service(${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Cleanup_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Cleanup_Action'] : $this->getAioseoCleanupActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Custom_Archive_Settings_Importing_Action'] : $this->getAioseoCustomArchiveSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Default_Archive_Settings_Importing_Action'] : $this->getAioseoDefaultArchiveSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_General_Settings_Importing_Action'] : $this->getAioseoGeneralSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posts_Importing_Action'] : $this->getAioseoPostsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Posttype_Defaults_Settings_Importing_Action'] : $this->getAioseoPosttypeDefaultsSettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Taxonomy_Settings_Importing_Action'] : $this->getAioseoTaxonomySettingsImportingActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Validate_Data_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Aioseo\\Aioseo_Validate_Data_Action'] : $this->getAioseoValidateDataActionService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Deactivate_Conflicting_Plugins_Action']) ? $this->services['Yoast\\WP\\SEO\\Actions\\Importing\\Deactivate_Conflicting_Plugins_Action'] : $this->getDeactivateConflictingPluginsActionService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager' shared autowired service. * * @return \Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager */ protected function getIndexableVersionManagerService() { return $this->services['Yoast\\WP\\SEO\\Services\\Indexables\\Indexable_Version_Manager'] = new \Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager(${($_ = isset($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions']) ? $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] : ($this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Surfaces\Classes_Surface' shared autowired service. * * @return \Yoast\WP\SEO\Surfaces\Classes_Surface */ protected function getClassesSurfaceService() { return $this->services['Yoast\\WP\\SEO\\Surfaces\\Classes_Surface'] = new \Yoast\WP\SEO\Surfaces\Classes_Surface($this); } /** * Gets the public 'Yoast\WP\SEO\Surfaces\Helpers_Surface' shared autowired service. * * @return \Yoast\WP\SEO\Surfaces\Helpers_Surface */ protected function getHelpersSurfaceService() { return $this->services['Yoast\\WP\\SEO\\Surfaces\\Helpers_Surface'] = new \Yoast\WP\SEO\Surfaces\Helpers_Surface($this, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Open_Graph_Helpers_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Open_Graph_Helpers_Surface'] : ($this->services['Yoast\\WP\\SEO\\Surfaces\\Open_Graph_Helpers_Surface'] = new \Yoast\WP\SEO\Surfaces\Open_Graph_Helpers_Surface($this))) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Schema_Helpers_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Schema_Helpers_Surface'] : ($this->services['Yoast\\WP\\SEO\\Surfaces\\Schema_Helpers_Surface'] = new \Yoast\WP\SEO\Surfaces\Schema_Helpers_Surface($this))) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Twitter_Helpers_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Twitter_Helpers_Surface'] : ($this->services['Yoast\\WP\\SEO\\Surfaces\\Twitter_Helpers_Surface'] = new \Yoast\WP\SEO\Surfaces\Twitter_Helpers_Surface($this))) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Surfaces\Meta_Surface' shared autowired service. * * @return \Yoast\WP\SEO\Surfaces\Meta_Surface */ protected function getMetaSurfaceService() { return $this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface'] = new \Yoast\WP\SEO\Surfaces\Meta_Surface($this, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer']) ? $this->services['Yoast\\WP\\SEO\\Memoizers\\Meta_Tags_Context_Memoizer'] : $this->getMetaTagsContextMemoizerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository']) ? $this->services['Yoast\\WP\\SEO\\Repositories\\Indexable_Repository'] : $this->getIndexableRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper']) ? $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper'] : ($this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Indexable_Helper'] : $this->getIndexableHelperService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Surfaces\Open_Graph_Helpers_Surface' shared autowired service. * * @return \Yoast\WP\SEO\Surfaces\Open_Graph_Helpers_Surface */ protected function getOpenGraphHelpersSurfaceService() { return $this->services['Yoast\\WP\\SEO\\Surfaces\\Open_Graph_Helpers_Surface'] = new \Yoast\WP\SEO\Surfaces\Open_Graph_Helpers_Surface($this); } /** * Gets the public 'Yoast\WP\SEO\Surfaces\Schema_Helpers_Surface' shared autowired service. * * @return \Yoast\WP\SEO\Surfaces\Schema_Helpers_Surface */ protected function getSchemaHelpersSurfaceService() { return $this->services['Yoast\\WP\\SEO\\Surfaces\\Schema_Helpers_Surface'] = new \Yoast\WP\SEO\Surfaces\Schema_Helpers_Surface($this); } /** * Gets the public 'Yoast\WP\SEO\Surfaces\Twitter_Helpers_Surface' shared autowired service. * * @return \Yoast\WP\SEO\Surfaces\Twitter_Helpers_Surface */ protected function getTwitterHelpersSurfaceService() { return $this->services['Yoast\\WP\\SEO\\Surfaces\\Twitter_Helpers_Surface'] = new \Yoast\WP\SEO\Surfaces\Twitter_Helpers_Surface($this); } /** * Gets the public 'Yoast\WP\SEO\User_Meta\Application\Additional_Contactmethods_Collector' shared autowired service. * * @return \Yoast\WP\SEO\User_Meta\Application\Additional_Contactmethods_Collector */ protected function getAdditionalContactmethodsCollectorService() { return $this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Additional_Contactmethods_Collector'] = new \Yoast\WP\SEO\User_Meta\Application\Additional_Contactmethods_Collector(new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Facebook(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Instagram(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Linkedin(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Myspace(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Pinterest(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Soundcloud(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Tumblr(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Wikipedia(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\X(), new \Yoast\WP\SEO\User_Meta\Framework\Additional_Contactmethods\Youtube()); } /** * Gets the public 'Yoast\WP\SEO\User_Meta\Application\Custom_Meta_Collector' shared autowired service. * * @return \Yoast\WP\SEO\User_Meta\Application\Custom_Meta_Collector */ protected function getCustomMetaCollectorService() { $a = ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}; return $this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Custom_Meta_Collector'] = new \Yoast\WP\SEO\User_Meta\Application\Custom_Meta_Collector(new \Yoast\WP\SEO\User_Meta\Framework\Custom_Meta\Author_Metadesc($a), new \Yoast\WP\SEO\User_Meta\Framework\Custom_Meta\Author_Title($a), new \Yoast\WP\SEO\User_Meta\Framework\Custom_Meta\Content_Analysis_Disable($a), new \Yoast\WP\SEO\User_Meta\Framework\Custom_Meta\Inclusive_Language_Analysis_Disable($a), new \Yoast\WP\SEO\User_Meta\Framework\Custom_Meta\Keyword_Analysis_Disable($a), new \Yoast\WP\SEO\User_Meta\Framework\Custom_Meta\Noindex_Author($a)); } /** * Gets the public 'Yoast\WP\SEO\User_Meta\User_Interface\Additional_Contactmethods_Integration' shared autowired service. * * @return \Yoast\WP\SEO\User_Meta\User_Interface\Additional_Contactmethods_Integration */ protected function getAdditionalContactmethodsIntegrationService() { return $this->services['Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Additional_Contactmethods_Integration'] = new \Yoast\WP\SEO\User_Meta\User_Interface\Additional_Contactmethods_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Additional_Contactmethods_Collector']) ? $this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Additional_Contactmethods_Collector'] : $this->getAdditionalContactmethodsCollectorService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\User_Meta\User_Interface\Cleanup_Integration' shared autowired service. * * @return \Yoast\WP\SEO\User_Meta\User_Interface\Cleanup_Integration */ protected function getCleanupIntegration2Service() { return $this->services['Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Cleanup_Integration'] = new \Yoast\WP\SEO\User_Meta\User_Interface\Cleanup_Integration(new \Yoast\WP\SEO\User_Meta\Application\Cleanup_Service(${($_ = isset($this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Additional_Contactmethods_Collector']) ? $this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Additional_Contactmethods_Collector'] : $this->getAdditionalContactmethodsCollectorService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Custom_Meta_Collector']) ? $this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Custom_Meta_Collector'] : $this->getCustomMetaCollectorService()) && false ?: '_'}, new \Yoast\WP\SEO\User_Meta\Infrastructure\Cleanup_Repository())); } /** * Gets the public 'Yoast\WP\SEO\User_Meta\User_Interface\Custom_Meta_Integration' shared autowired service. * * @return \Yoast\WP\SEO\User_Meta\User_Interface\Custom_Meta_Integration */ protected function getCustomMetaIntegrationService() { return $this->services['Yoast\\WP\\SEO\\User_Meta\\User_Interface\\Custom_Meta_Integration'] = new \Yoast\WP\SEO\User_Meta\User_Interface\Custom_Meta_Integration(${($_ = isset($this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Custom_Meta_Collector']) ? $this->services['Yoast\\WP\\SEO\\User_Meta\\Application\\Custom_Meta_Collector'] : $this->getCustomMetaCollectorService()) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\User_Profiles_Additions\User_Interface\User_Profiles_Additions_Ui' shared autowired service. * * @return \Yoast\WP\SEO\User_Profiles_Additions\User_Interface\User_Profiles_Additions_Ui */ protected function getUserProfilesAdditionsUiService() { return $this->services['Yoast\\WP\\SEO\\User_Profiles_Additions\\User_Interface\\User_Profiles_Additions_Ui'] = new \Yoast\WP\SEO\User_Profiles_Additions\User_Interface\User_Profiles_Additions_Ui(${($_ = isset($this->services['WPSEO_Admin_Asset_Manager']) ? $this->services['WPSEO_Admin_Asset_Manager'] : $this->getWPSEOAdminAssetManagerService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Values\Images' shared autowired service. * * @return \Yoast\WP\SEO\Values\Images */ protected function getImagesService() { return $this->services['Yoast\\WP\\SEO\\Values\\Images'] = new \Yoast\WP\SEO\Values\Images(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}); } /** * Gets the public 'Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions' shared autowired service. * * @return \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions */ protected function getIndexableBuilderVersionsService() { return $this->services['Yoast\\WP\\SEO\\Values\\Indexables\\Indexable_Builder_Versions'] = new \Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions(); } /** * Gets the public 'Yoast\WP\SEO\Values\Open_Graph\Images' shared autowired service. * * @return \Yoast\WP\SEO\Values\Open_Graph\Images */ protected function getImages2Service() { $this->services['Yoast\\WP\\SEO\\Values\\Open_Graph\\Images'] = $instance = new \Yoast\WP\SEO\Values\Open_Graph\Images(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Open_Graph\\Image_Helper'] : $this->getImageHelper2Service()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Values\Twitter\Images' shared autowired service. * * @return \Yoast\WP\SEO\Values\Twitter\Images */ protected function getImages3Service() { $this->services['Yoast\\WP\\SEO\\Values\\Twitter\\Images'] = $instance = new \Yoast\WP\SEO\Values\Twitter\Images(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Image_Helper'] : $this->getImageHelperService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Url_Helper'] = new \Yoast\WP\SEO\Helpers\Url_Helper())) && false ?: '_'}); $instance->set_helpers(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Twitter\\Image_Helper'] : $this->getImageHelper4Service()) && false ?: '_'}); return $instance; } /** * Gets the public 'Yoast\WP\SEO\Wrappers\WP_Query_Wrapper' shared autowired service. * * @return \Yoast\WP\SEO\Wrappers\WP_Query_Wrapper */ protected function getWPQueryWrapperService() { return $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Query_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Query_Wrapper(); } /** * Gets the public 'Yoast\WP\SEO\Wrappers\WP_Remote_Handler' shared autowired service. * * @return \Yoast\WP\SEO\Wrappers\WP_Remote_Handler */ protected function getWPRemoteHandlerService() { return $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Remote_Handler'] = new \Yoast\WP\SEO\Wrappers\WP_Remote_Handler(); } /** * Gets the public 'Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper' shared autowired service. * * @return \Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper */ protected function getWPRewriteWrapperService() { return $this->services['Yoast\\WP\\SEO\\Wrappers\\WP_Rewrite_Wrapper'] = new \Yoast\WP\SEO\Wrappers\WP_Rewrite_Wrapper(); } /** * Gets the public 'Yoast_Notification_Center' shared service. * * @return \Yoast_Notification_Center */ protected function getYoastNotificationCenterService() { return $this->services['Yoast_Notification_Center'] = \Yoast_Notification_Center::get(); } /** * Gets the public 'wpdb' shared service. * * @return \wpdb */ protected function getWpdbService() { return $this->services['wpdb'] = \Yoast\WP\SEO\WordPress\Wrapper::get_wpdb(); } /** * Gets the private 'Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Dismiss_Notifications' shared autowired service. * * @return \Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Dismiss_Notifications */ protected function getContentTypeVisibilityDismissNotificationsService() { return $this->services['Yoast\\WP\\SEO\\Content_Type_Visibility\\Application\\Content_Type_Visibility_Dismiss_Notifications'] = new \Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Dismiss_Notifications(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Application\Score_Results\Current_Scores_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Application\Score_Results\Current_Scores_Repository */ protected function getCurrentScoresRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Score_Results\\Current_Scores_Repository'] = new \Yoast\WP\SEO\Dashboard\Application\Score_Results\Current_Scores_Repository(new \Yoast\WP\SEO\Dashboard\Infrastructure\Score_Groups\Score_Group_Link_Collector()); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository */ protected function getTaxonomiesRepositoryService() { $a = new \Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies\Taxonomies_Collector(new \Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies\Taxonomy_Validator()); return $this->services['Yoast\\WP\\SEO\\Dashboard\\Application\\Taxonomies\\Taxonomies_Repository'] = new \Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository($a, new \Yoast\WP\SEO\Dashboard\Application\Filter_Pairs\Filter_Pairs_Repository($a, new \Yoast\WP\SEO\Dashboard\Domain\Filter_Pairs\Product_Category_Filter_Pair())); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Bad_SEO_Score_Group' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Bad_SEO_Score_Group */ protected function getBadSEOScoreGroupService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Bad_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Bad_SEO_Score_Group(); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Good_SEO_Score_Group' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Good_SEO_Score_Group */ protected function getGoodSEOScoreGroupService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Good_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Good_SEO_Score_Group(); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group */ protected function getNoSEOScoreGroupService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\No_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group(); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Ok_SEO_Score_Group' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Ok_SEO_Score_Group */ protected function getOkSEOScoreGroupService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Domain\\Score_Groups\\SEO_Score_Groups\\Ok_SEO_Score_Group'] = new \Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\Ok_SEO_Score_Group(); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository */ protected function getPermanentlyDismissedSiteKitConfigurationRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository */ protected function getSiteKitConsentRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector */ protected function getContentTypesCollectorService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Content_Types\\Content_Types_Collector'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Consent_Management_Endpoint' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Consent_Management_Endpoint */ protected function getSiteKitConsentManagementEndpointService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Endpoints\\Site_Kit_Consent_Management_Endpoint'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints\Site_Kit_Consent_Management_Endpoint(); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit */ protected function getSiteKitService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Integrations\\Site_Kit'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit(${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Site_Kit_Consent_Repository'] : $this->getSiteKitConsentRepositoryService()) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository']) ? $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Configuration\\Permanently_Dismissed_Site_Kit_Configuration_Repository'] : $this->getPermanentlyDismissedSiteKitConfigurationRepositoryService()) && false ?: '_'}, new \Yoast\WP\SEO\Dashboard\Infrastructure\Connection\Site_Kit_Is_Connected_Call(), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Conditionals\\Google_Site_Kit_Feature_Conditional']) ? $this->services['Yoast\\WP\\SEO\\Conditionals\\Google_Site_Kit_Feature_Conditional'] : $this->getGoogleSiteKitFeatureConditionalService()) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Dashboard\Infrastructure\Tracking\Setup_Steps_Tracking_Repository' shared autowired service. * * @return \Yoast\WP\SEO\Dashboard\Infrastructure\Tracking\Setup_Steps_Tracking_Repository */ protected function getSetupStepsTrackingRepositoryService() { return $this->services['Yoast\\WP\\SEO\\Dashboard\\Infrastructure\\Tracking\\Setup_Steps_Tracking_Repository'] = new \Yoast\WP\SEO\Dashboard\Infrastructure\Tracking\Setup_Steps_Tracking_Repository(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Introductions\Application\Introductions_Collector' shared autowired service. * * @return \Yoast\WP\SEO\Introductions\Application\Introductions_Collector */ protected function getIntroductionsCollectorService() { return $this->services['Yoast\\WP\\SEO\\Introductions\\Application\\Introductions_Collector'] = new \Yoast\WP\SEO\Introductions\Application\Introductions_Collector(new \Yoast\WP\SEO\Introductions\Application\Ai_Fix_Assessments_Upsell(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\User_Helper'] = new \Yoast\WP\SEO\Helpers\User_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Product_Helper'] = new \Yoast\WP\SEO\Helpers\Product_Helper())) && false ?: '_'})); } /** * Gets the private 'Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler */ protected function getPopulateFileCommandHandlerService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Populate_File_Command_Handler'] = new \Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter'] : ($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter'] = new \Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter())) && false ?: '_'}, new \Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders\Markdown_Builder(new \Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Llms_Txt_Renderer(), new \Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders\Intro_Builder(new \Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Sitemap_Link_Collector()), new \Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders\Title_Builder(new \Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Title_Adapter(${($_ = isset($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner']) ? $this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner'] : ($this->services['Yoast\\WP\\SEO\\Services\\Health_Check\\Default_Tagline_Runner'] = new \Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Runner())) && false ?: '_'})), new \Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders\Description_Builder(new \Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Description_Adapter(${($_ = isset($this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface']) ? $this->services['Yoast\\WP\\SEO\\Surfaces\\Meta_Surface'] : $this->getMetaSurfaceService()) && false ?: '_'})), new \Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders\Link_Lists_Builder(new \Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Content_Types_Collector(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Post_Type_Helper'] : $this->getPostTypeHelperService()) && false ?: '_'}), new \Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Terms_Collector(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Taxonomy_Helper'] : $this->getTaxonomyHelperService()) && false ?: '_'})), new \Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper()), ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate'] : $this->getWordPressLlmsTxtPermissionGateService()) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler */ protected function getRemoveFileCommandHandlerService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Commands\\Remove_File_Command_Handler'] = new \Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter'] : ($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter'] = new \Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate'] : $this->getWordPressLlmsTxtPermissionGateService()) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler */ protected function getLlmsTxtCronSchedulerService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Application\\File\\Llms_Txt_Cron_Scheduler'] = new \Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler(${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } /** * Gets the private 'Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter */ protected function getWordPressFileSystemAdapterService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter'] = new \Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter(); } /** * Gets the private 'Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_Llms_Txt_Permission_Gate' shared autowired service. * * @return \Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_Llms_Txt_Permission_Gate */ protected function getWordPressLlmsTxtPermissionGateService() { return $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_Llms_Txt_Permission_Gate'] = new \Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_Llms_Txt_Permission_Gate(${($_ = isset($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter']) ? $this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter'] : ($this->services['Yoast\\WP\\SEO\\Llms_Txt\\Infrastructure\\File\\WordPress_File_System_Adapter'] = new \Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter())) && false ?: '_'}, ${($_ = isset($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper']) ? $this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] : ($this->services['Yoast\\WP\\SEO\\Helpers\\Options_Helper'] = new \Yoast\WP\SEO\Helpers\Options_Helper())) && false ?: '_'}); } } commands/cleanup-command.php 0000644 00000012425 15025657560 0012140 0 ustar 00 <?php namespace Yoast\WP\SEO\Commands; use WP_CLI; use WP_CLI\ExitException; use WP_CLI\Utils; use Yoast\WP\SEO\Integrations\Cleanup_Integration; use Yoast\WP\SEO\Main; /** * A WP CLI command that helps with cleaning up unwanted records from our custom tables. */ final class Cleanup_Command implements Command_Interface { /** * The integration that cleans up on cron. * * @var Cleanup_Integration */ private $cleanup_integration; /** * The constructor. * * @param Cleanup_Integration $cleanup_integration The integration that cleans up on cron. */ public function __construct( Cleanup_Integration $cleanup_integration ) { $this->cleanup_integration = $cleanup_integration; } /** * Returns the namespace of this command. * * @return string */ public static function get_namespace() { return Main::WP_CLI_NAMESPACE; } /** * Performs a cleanup of custom Yoast tables. * * This removes unused, unwanted or orphaned database records, which ensures the best performance. Including: * - Indexables * - Indexable hierarchy * - SEO links * * ## OPTIONS * * [--batch-size=<batch-size>] * : The number of database records to clean up in a single sql query. * --- * default: 1000 * --- * * [--interval=<interval>] * : The number of microseconds (millionths of a second) to wait between cleanup batches. * --- * default: 500000 * --- * * [--network] * : Performs the cleanup on all sites within the network. * * ## EXAMPLES * * wp yoast cleanup * * @when after_wp_load * * @param array|null $args The arguments. * @param array|null $assoc_args The associative arguments. * * @return void * * @throws ExitException When the input args are invalid. */ public function cleanup( $args = null, $assoc_args = null ) { if ( isset( $assoc_args['interval'] ) && (int) $assoc_args['interval'] < 0 ) { WP_CLI::error( \__( 'The value for \'interval\' must be a positive integer.', 'wordpress-seo' ) ); } if ( isset( $assoc_args['batch-size'] ) && (int) $assoc_args['batch-size'] < 1 ) { WP_CLI::error( \__( 'The value for \'batch-size\' must be a positive integer higher than equal to 1.', 'wordpress-seo' ) ); } if ( isset( $assoc_args['network'] ) && \is_multisite() ) { $total_removed = $this->cleanup_network( $assoc_args ); } else { $total_removed = $this->cleanup_current_site( $assoc_args ); } WP_CLI::success( \sprintf( /* translators: %1$d is the number of records that are removed. */ \_n( 'Cleaned up %1$d record.', 'Cleaned up %1$d records.', $total_removed, 'wordpress-seo' ), $total_removed ) ); } /** * Performs the cleanup for the entire network. * * @param array|null $assoc_args The associative arguments. * * @return int The number of cleaned up records. */ private function cleanup_network( $assoc_args ) { $criteria = [ 'fields' => 'ids', 'spam' => 0, 'deleted' => 0, 'archived' => 0, ]; $blog_ids = \get_sites( $criteria ); $total_removed = 0; foreach ( $blog_ids as $blog_id ) { \switch_to_blog( $blog_id ); $total_removed += $this->cleanup_current_site( $assoc_args ); \restore_current_blog(); } return $total_removed; } /** * Performs the cleanup for a single site. * * @param array|null $assoc_args The associative arguments. * * @return int The number of cleaned up records. */ private function cleanup_current_site( $assoc_args ) { $site_url = \site_url(); $total_removed = 0; if ( ! \is_plugin_active( \WPSEO_BASENAME ) ) { /* translators: %1$s is the site url of the site that is skipped. %2$s is Yoast SEO. */ WP_CLI::warning( \sprintf( \__( 'Skipping %1$s. %2$s is not active on this site.', 'wordpress-seo' ), $site_url, 'Yoast SEO' ) ); return $total_removed; } // Make sure the DB is up to date first. \do_action( '_yoast_run_migrations' ); $tasks = $this->cleanup_integration->get_cleanup_tasks(); $limit = (int) $assoc_args['batch-size']; $interval = (int) $assoc_args['interval']; /* translators: %1$s is the site url of the site that is cleaned up. %2$s is the name of the cleanup task that is currently running. */ $progress_bar_title_format = \__( 'Cleaning up %1$s [%2$s]', 'wordpress-seo' ); $progress = Utils\make_progress_bar( \sprintf( $progress_bar_title_format, $site_url, \key( $tasks ) ), \count( $tasks ) ); foreach ( $tasks as $task_name => $task ) { // Update the progressbar title with the current task name. $progress->tick( 0, \sprintf( $progress_bar_title_format, $site_url, $task_name ) ); do { $items_cleaned = $task( $limit ); if ( \is_int( $items_cleaned ) ) { $total_removed += $items_cleaned; } \usleep( $interval ); // Update the timer. $progress->tick( 0 ); } while ( $items_cleaned !== false && $items_cleaned > 0 ); $progress->tick(); } $progress->finish(); $this->cleanup_integration->reset_cleanup(); WP_CLI::log( \sprintf( /* translators: %1$d is the number of records that were removed. %2$s is the site url. */ \_n( 'Cleaned up %1$d record from %2$s.', 'Cleaned up %1$d records from %2$s.', $total_removed, 'wordpress-seo' ), $total_removed, $site_url ) ); return $total_removed; } } commands/command-interface.php 0000644 00000000464 15025657560 0012451 0 ustar 00 <?php namespace Yoast\WP\SEO\Commands; /** * Interface definition for WP CLI commands. * * An interface for registering integrations with WordPress. */ interface Command_Interface { /** * Returns the namespace of this command. * * @return string */ public static function get_namespace(); } commands/index-command.php 0000644 00000023662 15025657560 0011625 0 ustar 00 <?php namespace Yoast\WP\SEO\Commands; use WP_CLI; use WP_CLI\Utils; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface; use Yoast\WP\SEO\Actions\Indexing\Indexing_Prepare_Action; use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action; use Yoast\WP\SEO\Actions\Indexing\Term_Link_Indexing_Action; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Main; /** * Command to generate indexables for all posts and terms. */ class Index_Command implements Command_Interface { /** * The post indexation action. * * @var Indexable_Post_Indexation_Action */ private $post_indexation_action; /** * The term indexation action. * * @var Indexable_Term_Indexation_Action */ private $term_indexation_action; /** * The post type archive indexation action. * * @var Indexable_Post_Type_Archive_Indexation_Action */ private $post_type_archive_indexation_action; /** * The general indexation action. * * @var Indexable_General_Indexation_Action */ private $general_indexation_action; /** * The term link indexing action. * * @var Term_Link_Indexing_Action */ private $term_link_indexing_action; /** * The post link indexing action. * * @var Post_Link_Indexing_Action */ private $post_link_indexing_action; /** * The complete indexation action. * * @var Indexable_Indexing_Complete_Action */ private $complete_indexation_action; /** * The indexing prepare action. * * @var Indexing_Prepare_Action */ private $prepare_indexing_action; /** * Represents the indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Generate_Indexables_Command constructor. * * @param Indexable_Post_Indexation_Action $post_indexation_action The post indexation * action. * @param Indexable_Term_Indexation_Action $term_indexation_action The term indexation * action. * @param Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action The post type archive * indexation action. * @param Indexable_General_Indexation_Action $general_indexation_action The general indexation * action. * @param Indexable_Indexing_Complete_Action $complete_indexation_action The complete indexation * action. * @param Indexing_Prepare_Action $prepare_indexing_action The prepare indexing * action. * @param Post_Link_Indexing_Action $post_link_indexing_action The post link indexation * action. * @param Term_Link_Indexing_Action $term_link_indexing_action The term link indexation * action. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Indexable_Post_Indexation_Action $post_indexation_action, Indexable_Term_Indexation_Action $term_indexation_action, Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action, Indexable_General_Indexation_Action $general_indexation_action, Indexable_Indexing_Complete_Action $complete_indexation_action, Indexing_Prepare_Action $prepare_indexing_action, Post_Link_Indexing_Action $post_link_indexing_action, Term_Link_Indexing_Action $term_link_indexing_action, Indexable_Helper $indexable_helper ) { $this->post_indexation_action = $post_indexation_action; $this->term_indexation_action = $term_indexation_action; $this->post_type_archive_indexation_action = $post_type_archive_indexation_action; $this->general_indexation_action = $general_indexation_action; $this->complete_indexation_action = $complete_indexation_action; $this->prepare_indexing_action = $prepare_indexing_action; $this->post_link_indexing_action = $post_link_indexing_action; $this->term_link_indexing_action = $term_link_indexing_action; $this->indexable_helper = $indexable_helper; } /** * Gets the namespace. * * @return string */ public static function get_namespace() { return Main::WP_CLI_NAMESPACE; } /** * Indexes all your content to ensure the best performance. * * ## OPTIONS * * [--network] * : Performs the indexation on all sites within the network. * * [--reindex] * : Removes all existing indexables and then reindexes them. * * [--skip-confirmation] * : Skips the confirmations (for automated systems). * * [--interval=<interval>] * : The number of microseconds (millionths of a second) to wait between index actions. * --- * default: 500000 * --- * * ## EXAMPLES * * wp yoast index * * @when after_wp_load * * @param array|null $args The arguments. * @param array|null $assoc_args The associative arguments. * * @return void */ public function index( $args = null, $assoc_args = null ) { if ( ! $this->indexable_helper->should_index_indexables() ) { WP_CLI::log( \__( 'Your WordPress environment is running on a non-production site. Indexables can only be created on production environments. Please check your `WP_ENVIRONMENT_TYPE` settings.', 'wordpress-seo' ) ); return; } if ( ! isset( $assoc_args['network'] ) ) { $this->run_indexation_actions( $assoc_args ); return; } $criteria = [ 'fields' => 'ids', 'spam' => 0, 'deleted' => 0, 'archived' => 0, ]; $blog_ids = \get_sites( $criteria ); foreach ( $blog_ids as $blog_id ) { \switch_to_blog( $blog_id ); \do_action( '_yoast_run_migrations' ); $this->run_indexation_actions( $assoc_args ); \restore_current_blog(); } } /** * Runs all indexation actions. * * @param array $assoc_args The associative arguments. * * @return void */ protected function run_indexation_actions( $assoc_args ) { // See if we need to clear all indexables before repopulating. if ( isset( $assoc_args['reindex'] ) ) { // Argument --skip-confirmation to prevent confirmation (for automated systems). if ( ! isset( $assoc_args['skip-confirmation'] ) ) { WP_CLI::confirm( 'This will clear all previously indexed objects. Are you certain you wish to proceed?' ); } // Truncate the tables. $this->clear(); // Delete the transients to make sure re-indexing runs every time. \delete_transient( Indexable_Post_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( Indexable_Post_Type_Archive_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( Indexable_Term_Indexation_Action::UNINDEXED_COUNT_TRANSIENT ); } $indexation_actions = [ 'posts' => $this->post_indexation_action, 'terms' => $this->term_indexation_action, 'post type archives' => $this->post_type_archive_indexation_action, 'general objects' => $this->general_indexation_action, 'post links' => $this->post_link_indexing_action, 'term links' => $this->term_link_indexing_action, ]; $this->prepare_indexing_action->prepare(); $interval = (int) $assoc_args['interval']; foreach ( $indexation_actions as $name => $indexation_action ) { $this->run_indexation_action( $name, $indexation_action, $interval ); } $this->complete_indexation_action->complete(); } /** * Runs an indexation action. * * @param string $name The name of the object to be indexed. * @param Indexation_Action_Interface $indexation_action The indexation action. * @param int $interval Number of microseconds (millionths of a second) to wait between index actions. * * @return void */ protected function run_indexation_action( $name, Indexation_Action_Interface $indexation_action, $interval ) { $total = $indexation_action->get_total_unindexed(); if ( $total > 0 ) { $limit = $indexation_action->get_limit(); $progress = Utils\make_progress_bar( 'Indexing ' . $name, $total ); do { $indexables = $indexation_action->index(); $count = \count( $indexables ); $progress->tick( $count ); \usleep( $interval ); Utils\wp_clear_object_cache(); } while ( $count >= $limit ); $progress->finish(); } } /** * Clears the database related to the indexables. * * @return void */ protected function clear() { global $wpdb; // For the PreparedSQLPlaceholders issue, see: https://github.com/WordPress/WordPress-Coding-Standards/issues/1903. // For the DirectDBQuery issue, see: https://github.com/WordPress/WordPress-Coding-Standards/issues/1947. // phpcs:disable WordPress.DB -- Table names should not be quoted and truncate queries can not be cached. $wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %1$s', Model::get_table_name( 'Indexable' ) ) ); $wpdb->query( $wpdb->prepare( 'TRUNCATE TABLE %1$s', Model::get_table_name( 'Indexable_Hierarchy' ) ) ); // phpcs:enable } } introductions/readme.md 0000644 00000003402 15025657560 0011241 0 ustar 00 # Introductions Is for showing introductions to a user, on Yoast admin pages. Based on plugin version, page, user capabilities and whether the user has seen it already. - `Introduction_Interface` defines what data is needed - `id` as unique identifier - `plugin` and `version` to determine if the introduction is new (version > plugin version) - `pages` to be able to only show on certain Yoast admin pages - `capabilities` to be able to only show for certain users - `Introductions_Collector` uses that data to determine whether an introduction should be "shown" to a user - uses the `wpseo_introductions` filter to be extendable from our other plugins - uses `Introductions_Seen_Repository` to get the data to determine if the user saw an introduction already - `Introductions_Seen_Repository` is the doorway whether a user has seen an introduction or not - uses the `_yoast_introductions` user metadata - `Introduction_Bucket` and `Introduction_Item` are used by the collector to get an array - `Introductions_Integration` runs on the Yoast Admin pages and loads the assets - only loads on our Yoast admin pages, but never on our installation success pages as to not disturb onboarding - only loads assets if there is an introduction to show - `js/src/introductions` holds the JS - `wpseoIntroductions` is the localized script to transfer data from PHP to JS - `css/src/ai-generator.css` holds the CSS Inside JS, register the modal content via `window.YoastSEO._registerIntroductionComponent`, which takes a `id` and a `Component`. The id needs to be the same as the id in the `Introduction_Interface`. The action `yoast.introductions.ready` can be used to know whether the registration function is available and ready for use. introductions/domain/introductions-bucket.php 0000644 00000001537 15025657560 0015630 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Domain; /** * A collection domain object. */ class Introductions_Bucket { /** * Holds the introductions. * * @var Introduction_Item[] */ private $introductions; /** * The constructor. */ public function __construct() { $this->introductions = []; } /** * Adds an introduction to this bucket. * * @param Introduction_Item $introduction The introduction. * * @return void */ public function add_introduction( Introduction_Item $introduction ) { $this->introductions[] = $introduction; } /** * Returns the array representation of the introductions. * * @return array */ public function to_array() { // No sorting here because that is done in JS. return \array_map( static function ( $item ) { return $item->to_array(); }, $this->introductions ); } } introductions/domain/introduction-interface.php 0000644 00000001126 15025657560 0016122 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Domain; /** * Represents an introduction. */ interface Introduction_Interface { /** * Returns the ID. * * @return string */ public function get_id(); /** * Returns the unique name. * * @deprecated 21.6 * @codeCoverageIgnore * * @return string */ public function get_name(); /** * Returns the requested pagination priority. Lower means earlier. * * @return int */ public function get_priority(); /** * Returns whether this introduction should show. * * @return bool */ public function should_show(); } introductions/domain/invalid-user-id-exception.php 0000644 00000001453 15025657560 0016436 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Domain; use InvalidArgumentException; use Throwable; /** * Invalid user ID. */ class Invalid_User_Id_Exception extends InvalidArgumentException { /** * Constructs the exception. * * @link https://php.net/manual/en/exception.construct.php * * @param string $message Optional. The Exception message to throw. * @param int $code Optional. The Exception code. * @param Throwable|null $previous Optional. The previous throwable used for the exception chaining. */ public function __construct( // phpcs:ignore Generic.CodeAnalysis.UselessOverridingMethod.Found -- Reason: A default message is used. $message = 'Invalid User ID', $code = 0, $previous = null ) { parent::__construct( $message, $code, $previous ); } } introductions/domain/introduction-item.php 0000644 00000001745 15025657560 0015127 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Domain; /** * Domain object that holds introduction information. */ class Introduction_Item { /** * The ID. * * @var string */ private $id; /** * The priority. * * @var int */ private $priority; /** * Constructs the instance. * * @param string $id The ID. * @param int $priority The priority. */ public function __construct( $id, $priority ) { $this->id = $id; $this->priority = $priority; } /** * Returns an array representation of the data. * * @return array Returns in an array format. */ public function to_array() { return [ 'id' => $this->get_id(), 'priority' => $this->get_priority(), ]; } /** * Returns the ID. * * @return string */ public function get_id() { return $this->id; } /** * Returns the requested pagination priority. Higher means earlier. * * @return int */ public function get_priority() { return $this->priority; } } introductions/application/user-allowed-trait.php 0000644 00000000746 15025657560 0016232 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Application; trait User_Allowed_Trait { /** * Determines whether the user has the required capabilities. * * @param string[] $capabilities The required capabilities. * * @return bool Whether the user has the required capabilities. */ private function is_user_allowed( $capabilities ) { foreach ( $capabilities as $capability ) { if ( ! \current_user_can( $capability ) ) { return false; } } return true; } } introductions/application/introductions-collector.php 0000644 00000006613 15025657560 0017375 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Application; use Yoast\WP\SEO\Introductions\Domain\Introduction_Interface; use Yoast\WP\SEO\Introductions\Domain\Introduction_Item; use Yoast\WP\SEO\Introductions\Domain\Introductions_Bucket; use Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository; /** * Manages the collection of introductions. */ class Introductions_Collector { /** * Holds all the introductions. * * @var Introduction_Interface[] */ private $introductions; /** * Constructs the collector. * * @param Introduction_Interface ...$introductions All the introductions. */ public function __construct( Introduction_Interface ...$introductions ) { $this->introductions = $this->add_introductions( ...$introductions ); } /** * Gets the data for the introductions. * * @param int $user_id The user ID. * * @return array The list of introductions. */ public function get_for( $user_id ) { $bucket = new Introductions_Bucket(); $metadata = $this->get_metadata( $user_id ); foreach ( $this->introductions as $introduction ) { if ( ! $introduction->should_show() ) { continue; } if ( $this->is_seen( $introduction->get_id(), $metadata ) ) { continue; } $bucket->add_introduction( new Introduction_Item( $introduction->get_id(), $introduction->get_priority() ) ); } return $bucket->to_array(); } /** * Filters introductions with the 'wpseo_introductions' filter. * * @param Introduction_Interface ...$introductions The introductions. * * @return Introduction_Interface[] */ private function add_introductions( Introduction_Interface ...$introductions ) { /** * Filter: Adds the possibility to add additional introductions to be included. * * @internal * * @param Introduction_Interface $introductions This filter expects a list of Introduction_Interface instances and * expects only Introduction_Interface implementations to be added to the list. */ $filtered_introductions = (array) \apply_filters( 'wpseo_introductions', $introductions ); return \array_filter( $filtered_introductions, static function ( $introduction ) { return \is_a( $introduction, Introduction_Interface::class ); } ); } /** * Retrieves the introductions metadata for the user. * * @param int $user_id The user ID. * * @return array The introductions' metadata. */ private function get_metadata( $user_id ) { $metadata = \get_user_meta( $user_id, Introductions_Seen_Repository::USER_META_KEY, true ); if ( \is_array( $metadata ) ) { return $metadata; } return []; } /** * Determines whether the user has seen the introduction. * * @param string $name The name. * @param string[] $metadata The metadata. * * @return bool Whether the user has seen the introduction. */ private function is_seen( $name, $metadata ) { if ( \array_key_exists( $name, $metadata ) ) { return (bool) $metadata[ $name ]; } return false; } /** * Checks if the given introduction ID is a known ID to the system. * * @param string $introduction_id The introduction ID to check. * * @return bool */ public function is_available_introduction( string $introduction_id ): bool { foreach ( $this->introductions as $introduction ) { if ( $introduction->get_id() === $introduction_id ) { return true; } } return false; } } introductions/application/current-page-trait.php 0000644 00000002512 15025657560 0016214 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Application; trait Current_Page_Trait { /** * Determines whether the current page is applicable. * * @param string[] $pages The applicable pages. * * @return bool Whether the current page is applicable. */ private function is_on_yoast_page( $pages ) { return \in_array( $this->get_page(), $pages, true ); } /** * Determines whether the current page is one of our installation pages. * * @return bool Whether the current page is one of our installation pages. */ private function is_on_installation_page() { return $this->is_on_yoast_page( [ 'wpseo_installation_successful_free', 'wpseo_installation_successful' ] ); } /** * Retrieve the page variable. * * Note: the result is not safe to use in anything than strict comparisons! * * @return string The page variable. */ private function get_page() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['page'] ) && \is_string( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, only using it in strict comparison. return \wp_unslash( $_GET['page'] ); } return ''; } } introductions/application/version-trait.php 0000644 00000001141 15025657560 0015302 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Application; trait Version_Trait { /** * Determines whether the version is between a min (inclusive) and max (exclusive). * * @param string $version The version to compare. * @param string $min_version The minimum version. * @param string $max_version The maximum version. * * @return bool Whether the version is between a min and max. */ private function is_version_between( $version, $min_version, $max_version ) { return ( \version_compare( $version, $min_version, '>=' ) && \version_compare( $version, $max_version, '<' ) ); } } introductions/application/ai-fix-assessments-upsell.php 0000644 00000003244 15025657560 0017527 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Application; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\User_Helper; use Yoast\WP\SEO\Introductions\Domain\Introduction_Interface; /** * Represents the introduction for the AI fix assessments feature. */ class Ai_Fix_Assessments_Upsell implements Introduction_Interface { use User_Allowed_Trait; public const ID = 'ai-fix-assessments-upsell'; /** * Holds the user helper. * * @var User_Helper */ private $user_helper; /** * Holds the product helper. * * @var Product_Helper */ private $product_helper; /** * Constructs the introduction. * * @param User_Helper $user_helper The user helper. * @param Product_Helper $product_helper The product helper. */ public function __construct( User_Helper $user_helper, Product_Helper $product_helper ) { $this->user_helper = $user_helper; $this->product_helper = $product_helper; } /** * Returns the ID. * * @return string The ID. */ public function get_id() { return self::ID; } /** * Returns the name of the introdyction. * * @return string The name. */ public function get_name() { \_deprecated_function( __METHOD__, 'Yoast SEO Premium 21.6', 'Please use get_id() instead' ); return self::ID; } /** * Returns the requested pagination priority. Lower means earlier. * * @return int The priority. */ public function get_priority() { return 10; } /** * Returns whether this introduction should show. * We no longer show this introduction, so we always return false. * * @return bool Whether this introduction should show. */ public function should_show() { return false; } } introductions/infrastructure/wistia-embed-permission-repository.php 0000644 00000004072 15025657560 0022234 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Infrastructure; use Exception; use Yoast\WP\SEO\Helpers\User_Helper; /** * Takes care of the get/set in the WP user meta. * * @makePublic */ class Wistia_Embed_Permission_Repository { public const USER_META_KEY = '_yoast_wpseo_wistia_embed_permission'; public const DEFAULT_VALUE = false; /** * Holds the User_Helper instance. * * @var User_Helper */ private $user_helper; /** * Constructs the class. * * @param User_Helper $user_helper The User_Helper. */ public function __construct( User_Helper $user_helper ) { $this->user_helper = $user_helper; } /** * Retrieves the current value for a user. * * @param int $user_id User ID. * * @return bool The current value. * * @throws Exception If an invalid user ID is supplied. */ public function get_value_for_user( $user_id ) { $value = $this->user_helper->get_meta( $user_id, self::USER_META_KEY, true ); if ( $value === false ) { throw new Exception( 'Invalid User ID' ); } if ( $value === '0' || $value === '1' ) { // The value is stored as a string because otherwise we can not see the difference between false and an invalid user ID. return $value === '1'; } /** * Why could $value be invalid? * - When the database row does not exist yet, $value can be an empty string. * - Faulty data was stored? */ return self::DEFAULT_VALUE; } /** * Sets the Wistia embed permission value for the current user. * * @param int $user_id The user ID. * @param bool $value The value. * * @return bool Whether the update was successful. * * @throws Exception If an invalid user ID is supplied. */ public function set_value_for_user( $user_id, $value ) { // The value is stored as a string because otherwise we can not see the difference between false and an invalid user ID. $value_as_string = ( $value === true ) ? '1' : '0'; // Checking for only false, not interested in not having to update. return $this->user_helper->update_meta( $user_id, self::USER_META_KEY, $value_as_string ) !== false; } } introductions/infrastructure/introductions-seen-repository.php 0000644 00000006370 15025657560 0021333 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\Infrastructure; use Yoast\WP\SEO\Helpers\User_Helper; use Yoast\WP\SEO\Introductions\Domain\Invalid_User_Id_Exception; /** * Stores and retrieves whether the user has seen certain introductions. */ class Introductions_Seen_Repository { public const USER_META_KEY = '_yoast_wpseo_introductions'; public const DEFAULT_VALUE = []; /** * Holds the User_Helper instance. * * @var User_Helper */ private $user_helper; /** * Constructs the class. * * @param User_Helper $user_helper The User_Helper. */ public function __construct( User_Helper $user_helper ) { $this->user_helper = $user_helper; } /** * Retrieves the introductions. * * @param int $user_id User ID. * * @return array The introductions. * * @throws Invalid_User_Id_Exception If an invalid user ID is supplied. */ public function get_all_introductions( $user_id ): array { $seen_introductions = $this->user_helper->get_meta( $user_id, self::USER_META_KEY, true ); if ( $seen_introductions === false ) { throw new Invalid_User_Id_Exception(); } if ( \is_array( $seen_introductions ) ) { return $seen_introductions; } /** * Why could $value be invalid? * - When the database row does not exist yet, $value can be an empty string. * - Faulty data was stored? */ return self::DEFAULT_VALUE; } /** * Sets the introductions. * * @param int $user_id The user ID. * @param array $introductions The introductions. * * @return bool True on successful update, false on failure or if the value passed to the function is the same as * the one that is already in the database. */ public function set_all_introductions( $user_id, array $introductions ): bool { return $this->user_helper->update_meta( $user_id, self::USER_META_KEY, $introductions ) !== false; } /** * Retrieves whether an introduction is seen. * * @param int $user_id User ID. * @param string $introduction_id The introduction ID. * * @return bool Whether the introduction is seen. * * @throws Invalid_User_Id_Exception If an invalid user ID is supplied. */ public function is_introduction_seen( $user_id, string $introduction_id ): bool { $introductions = $this->get_all_introductions( $user_id ); if ( \array_key_exists( $introduction_id, $introductions ) ) { return (bool) $introductions[ $introduction_id ]; } return false; } /** * Sets the introduction as seen. * * @param int $user_id The user ID. * @param string $introduction_id The introduction ID. * @param bool $is_seen Whether the introduction is seen. Defaults to true. * * @return bool False on failure. Not having to update is a success. * * @throws Invalid_User_Id_Exception If an invalid user ID is supplied. */ public function set_introduction( $user_id, string $introduction_id, bool $is_seen = true ): bool { $introductions = $this->get_all_introductions( $user_id ); // Check if the wanted value is already set. if ( \array_key_exists( $introduction_id, $introductions ) && $introductions[ $introduction_id ] === $is_seen ) { return true; } $introductions[ $introduction_id ] = $is_seen; return $this->set_all_introductions( $user_id, $introductions ); } } introductions/user-interface/introductions-seen-route.php 0000644 00000007301 15025657560 0020101 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\User_Interface; use Exception; use WP_Error; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\User_Helper; use Yoast\WP\SEO\Introductions\Application\Introductions_Collector; use Yoast\WP\SEO\Introductions\Infrastructure\Introductions_Seen_Repository; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; /** * Registers a route to set whether the user has seen an introduction. * * @makePublic */ class Introductions_Seen_Route implements Route_Interface { use No_Conditionals; /** * Represents the prefix. * * @var string */ public const ROUTE_PREFIX = '/introductions/(?P<introduction_id>[\w-]+)/seen'; /** * Holds the introductions collector instance. * * @var Introductions_Collector */ private $introductions_collector; /** * Holds the repository. * * @var Introductions_Seen_Repository */ private $introductions_seen_repository; /** * Holds the user helper. * * @var User_Helper */ private $user_helper; /** * Constructs the class. * * @param Introductions_Seen_Repository $introductions_seen_repository The repository. * @param User_Helper $user_helper The user helper. * @param Introductions_Collector $introductions_collector The introduction collector. */ public function __construct( Introductions_Seen_Repository $introductions_seen_repository, User_Helper $user_helper, Introductions_Collector $introductions_collector ) { $this->introductions_seen_repository = $introductions_seen_repository; $this->user_helper = $user_helper; $this->introductions_collector = $introductions_collector; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { \register_rest_route( Main::API_V1_NAMESPACE, self::ROUTE_PREFIX, [ [ 'methods' => 'POST', 'callback' => [ $this, 'set_introduction_seen' ], 'permission_callback' => [ $this, 'permission_edit_posts' ], 'args' => [ 'introduction_id' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], 'is_seen' => [ 'required' => false, 'type' => 'bool', 'default' => true, 'sanitize_callback' => 'rest_sanitize_boolean', ], ], ], ] ); } /** * Sets whether the introduction is seen. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response|WP_Error The success or failure response. */ public function set_introduction_seen( WP_REST_Request $request ) { $params = $request->get_params(); $introduction_id = $params['introduction_id']; $is_seen = $params['is_seen']; if ( $this->introductions_collector->is_available_introduction( $introduction_id ) ) { try { $user_id = $this->user_helper->get_current_user_id(); $result = $this->introductions_seen_repository->set_introduction( $user_id, $introduction_id, $is_seen ); } catch ( Exception $exception ) { return new WP_Error( 'wpseo_introductions_seen_error', $exception->getMessage(), (object) [] ); } return new WP_REST_Response( [ 'json' => (object) [ 'success' => $result, ], ], ( $result ) ? 200 : 400 ); } return new WP_REST_Response( [], 400 ); } /** * Permission callback. * * @return bool True when user has 'edit_posts' permission. */ public function permission_edit_posts() { return \current_user_can( 'edit_posts' ); } } introductions/user-interface/introductions-integration.php 0000644 00000011652 15025657560 0020342 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\User_Interface; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Helpers\User_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Introductions\Application\Current_Page_Trait; use Yoast\WP\SEO\Introductions\Application\Introductions_Collector; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; /** * Loads introduction modal scripts, when there are applicable introductions. */ class Introductions_Integration implements Integration_Interface { use Current_Page_Trait; public const SCRIPT_HANDLE = 'introductions'; /** * Holds the admin asset manager. * * @var WPSEO_Admin_Asset_Manager */ private $admin_asset_manager; /** * Holds the introduction collector. * * @var Introductions_Collector */ private $introductions_collector; /** * Holds the product helper. * * @var Product_Helper */ private $product_helper; /** * Holds the user helper. * * @var User_Helper */ private $user_helper; /** * Holds the short link helper. * * @var Short_Link_Helper */ private $short_link_helper; /** * Holds the repository. * * @var Wistia_Embed_Permission_Repository */ private $wistia_embed_permission_repository; /** * Returns the conditionals based in which this loadable should be active. * * In this case: when on an admin page. * * @return array<string> */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Constructs the integration. * * @param WPSEO_Admin_Asset_Manager $admin_asset_manager The admin asset manager. * @param Introductions_Collector $introductions_collector The introductions' collector. * @param Product_Helper $product_helper The product helper. * @param User_Helper $user_helper The user helper. * @param Short_Link_Helper $short_link_helper The short link helper. * @param Wistia_Embed_Permission_Repository $wistia_embed_permission_repository The repository. */ public function __construct( WPSEO_Admin_Asset_Manager $admin_asset_manager, Introductions_Collector $introductions_collector, Product_Helper $product_helper, User_Helper $user_helper, Short_Link_Helper $short_link_helper, Wistia_Embed_Permission_Repository $wistia_embed_permission_repository ) { $this->admin_asset_manager = $admin_asset_manager; $this->introductions_collector = $introductions_collector; $this->product_helper = $product_helper; $this->user_helper = $user_helper; $this->short_link_helper = $short_link_helper; $this->wistia_embed_permission_repository = $wistia_embed_permission_repository; } /** * Registers the action to enqueue the needed script(s). * * @return void */ public function register_hooks() { if ( $this->is_on_installation_page() ) { return; } \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } /** * Enqueue the new features assets. * * @return void */ public function enqueue_assets() { $user_id = $this->user_helper->get_current_user_id(); $introductions = $this->introductions_collector->get_for( $user_id ); if ( ! $introductions ) { // Bail when there are no introductions to show. return; } // Update user meta to have "seen" these introductions. $this->update_user_introductions( $user_id, $introductions ); $this->admin_asset_manager->enqueue_script( self::SCRIPT_HANDLE ); $this->admin_asset_manager->localize_script( self::SCRIPT_HANDLE, 'wpseoIntroductions', [ 'introductions' => $introductions, 'isPremium' => $this->product_helper->is_premium(), 'isRtl' => \is_rtl(), 'linkParams' => $this->short_link_helper->get_query_params(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'wistiaEmbedPermission' => $this->wistia_embed_permission_repository->get_value_for_user( $user_id ), ] ); $this->admin_asset_manager->enqueue_style( 'introductions' ); } /** * Updates the user metadata to have "seen" the introductions. * * @param int $user_id The user ID. * @param array $introductions The introductions. * * @return void */ private function update_user_introductions( $user_id, $introductions ) { $metadata = $this->user_helper->get_meta( $user_id, '_yoast_wpseo_introductions', true ); if ( ! \is_array( $metadata ) ) { $metadata = []; } foreach ( $introductions as $introduction ) { $metadata[ $introduction['id'] ] = true; } $this->user_helper->update_meta( $user_id, '_yoast_wpseo_introductions', $metadata ); } } introductions/user-interface/wistia-embed-permission-route.php 0000644 00000007254 15025657560 0021014 0 ustar 00 <?php namespace Yoast\WP\SEO\Introductions\User_Interface; use Exception; use WP_Error; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\User_Helper; use Yoast\WP\SEO\Introductions\Infrastructure\Wistia_Embed_Permission_Repository; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; /** * Registers a route to offer get/set of the wistia embed permission for a user. */ class Wistia_Embed_Permission_Route implements Route_Interface { use No_Conditionals; /** * Represents the prefix. * * @var string */ public const ROUTE_PREFIX = '/wistia_embed_permission'; /** * Holds the repository. * * @var Wistia_Embed_Permission_Repository */ private $wistia_embed_permission_repository; /** * Holds the user helper. * * @var User_Helper */ private $user_helper; /** * Constructs the class. * * @param Wistia_Embed_Permission_Repository $wistia_embed_permission_repository The repository. * @param User_Helper $user_helper The user helper. */ public function __construct( Wistia_Embed_Permission_Repository $wistia_embed_permission_repository, User_Helper $user_helper ) { $this->wistia_embed_permission_repository = $wistia_embed_permission_repository; $this->user_helper = $user_helper; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { \register_rest_route( Main::API_V1_NAMESPACE, self::ROUTE_PREFIX, [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_wistia_embed_permission' ], 'permission_callback' => [ $this, 'permission_edit_posts' ], ], [ 'methods' => 'POST', 'callback' => [ $this, 'set_wistia_embed_permission' ], 'permission_callback' => [ $this, 'permission_edit_posts' ], 'args' => [ 'value' => [ 'required' => false, 'type' => 'bool', 'default' => true, ], ], ], ] ); } /** * Gets the value of the wistia embed permission. * * @return WP_REST_Response|WP_Error The response, or an error. */ public function get_wistia_embed_permission() { try { $user_id = $this->user_helper->get_current_user_id(); $value = $this->wistia_embed_permission_repository->get_value_for_user( $user_id ); } catch ( Exception $exception ) { return new WP_Error( 'wpseo_wistia_embed_permission_error', $exception->getMessage(), (object) [] ); } return new WP_REST_Response( [ 'json' => (object) [ 'value' => $value, ], ] ); } /** * Sets the value of the wistia embed permission. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response|WP_Error The success or failure response. */ public function set_wistia_embed_permission( WP_REST_Request $request ) { $params = $request->get_json_params(); $value = \boolval( $params['value'] ); try { $user_id = $this->user_helper->get_current_user_id(); $result = $this->wistia_embed_permission_repository->set_value_for_user( $user_id, $value ); } catch ( Exception $exception ) { return new WP_Error( 'wpseo_wistia_embed_permission_error', $exception->getMessage(), (object) [] ); } return new WP_REST_Response( [ 'json' => (object) [ 'success' => $result, ], ], ( $result ) ? 200 : 400 ); } /** * Permission callback. * * @return bool True when user has 'edit_posts' permission. */ public function permission_edit_posts() { return \current_user_can( 'edit_posts' ); } } initializers/plugin-headers.php 0000644 00000001344 15025657560 0012707 0 ustar 00 <?php namespace Yoast\WP\SEO\Initializers; use Yoast\WP\SEO\Conditionals\No_Conditionals; /** * Adds custom headers to the list of plugin headers to read. */ class Plugin_Headers implements Initializer_Interface { use No_Conditionals; /** * Hooks into the list of the plugin headers. * * @return void */ public function initialize() { \add_filter( 'extra_plugin_headers', [ $this, 'add_requires_yoast_seo_header' ] ); } /** * Add the `Requires Yoast SEO` header to the list of headers. * * @param array<string> $headers The headers. * * @return array<string> The updated headers. */ public function add_requires_yoast_seo_header( $headers ) { $headers[] = 'Requires Yoast SEO'; return $headers; } } initializers/migration-runner.php 0000644 00000010171 15025657560 0013276 0 ustar 00 <?php namespace Yoast\WP\SEO\Initializers; use Exception; use Yoast\WP\Lib\Migrations\Adapter; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Config\Migration_Status; use Yoast\WP\SEO\Loader; /** * Triggers database migrations and handles results. */ class Migration_Runner implements Initializer_Interface { use No_Conditionals; /** * The migrations adapter. * * @var Adapter */ protected $adapter; /** * The loader. * * @var Loader */ protected $loader; /** * The migration status. * * @var Migration_Status */ protected $migration_status; /** * Retrieves the conditionals for the migrations. * * @return array The conditionals. */ public static function get_conditionals() { return []; } /** * Migrations constructor. * * @param Migration_Status $migration_status The migration status. * @param Loader $loader The loader. * @param Adapter $adapter The migrations adapter. */ public function __construct( Migration_Status $migration_status, Loader $loader, Adapter $adapter ) { $this->migration_status = $migration_status; $this->loader = $loader; $this->adapter = $adapter; } /** * Runs this initializer. * * @return void * * @throws Exception When a migration errored. */ public function initialize() { $this->run_free_migrations(); // The below actions is used for when queries fail, this may happen in a multisite environment when switch_to_blog is used. \add_action( '_yoast_run_migrations', [ $this, 'run_free_migrations' ] ); } /** * Runs the free migrations. * * @return void * * @throws Exception When a migration errored. */ public function run_free_migrations() { $this->run_migrations( 'free' ); } /** * Initializes the migrations. * * @param string $name The name of the migration. * @param string $version The current version. * * @return bool True on success, false on failure. * * @throws Exception If the migration fails and YOAST_ENVIRONMENT is not production. */ public function run_migrations( $name, $version = \WPSEO_VERSION ) { if ( ! $this->migration_status->should_run_migration( $name, $version ) ) { return true; } if ( ! $this->migration_status->lock_migration( $name ) ) { return false; } $migrations = $this->loader->get_migrations( $name ); if ( $migrations === false ) { $this->migration_status->set_error( $name, "Could not perform $name migrations. No migrations found.", $version ); return false; } try { $this->adapter->create_schema_version_table(); $all_versions = \array_keys( $migrations ); $migrated_versions = $this->adapter->get_migrated_versions(); $to_do_versions = \array_diff( $all_versions, $migrated_versions ); \sort( $to_do_versions, \SORT_STRING ); foreach ( $to_do_versions as $to_do_version ) { $class = $migrations[ $to_do_version ]; $this->run_migration( $to_do_version, $class ); } } catch ( Exception $exception ) { // Something went wrong... $this->migration_status->set_error( $name, $exception->getMessage(), $version ); if ( \defined( 'YOAST_ENVIRONMENT' ) && \YOAST_ENVIRONMENT !== 'production' ) { throw $exception; } return false; } $this->migration_status->set_success( $name, $version ); return true; } /** * Runs a single migration. * * @param string $version The version. * @param string $migration_class The migration class. * * @return void * * @throws Exception If the migration failed. Caught by the run_migrations function. */ protected function run_migration( $version, $migration_class ) { /** * The migration to run. * * @var Migration $migration */ $migration = new $migration_class( $this->adapter ); try { $this->adapter->start_transaction(); $migration->up(); $this->adapter->add_version( $version ); $this->adapter->commit_transaction(); } catch ( Exception $e ) { $this->adapter->rollback_transaction(); throw new Exception( \sprintf( '%s - %s', $migration_class, $e->getMessage() ), 0, $e ); } } } initializers/initializer-interface.php 0000644 00000000531 15025657560 0014256 0 ustar 00 <?php namespace Yoast\WP\SEO\Initializers; use Yoast\WP\SEO\Loadable_Interface; /** * Integration interface definition. * * An interface for registering integrations with WordPress. */ interface Initializer_Interface extends Loadable_Interface { /** * Runs this initializer. * * @return void */ public function initialize(); } initializers/crawl-cleanup-permalinks.php 0000644 00000012623 15025657560 0014702 0 ustar 00 <?php namespace Yoast\WP\SEO\Initializers; use Yoast\WP\SEO\Conditionals\Front_End_Conditional; use Yoast\WP\SEO\Helpers\Crawl_Cleanup_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Redirect_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; /** * Class Crawl_Cleanup_Permalinks. */ class Crawl_Cleanup_Permalinks implements Initializer_Interface { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The URL helper. * * @var Url_Helper */ private $url_helper; /** * The Redirect_Helper. * * @var Redirect_Helper */ private $redirect_helper; /** * The Crawl_Cleanup_Helper. * * @var Crawl_Cleanup_Helper */ private $crawl_cleanup_helper; /** * Crawl Cleanup Basic integration constructor. * * @param Options_Helper $options_helper The option helper. * @param Url_Helper $url_helper The URL helper. * @param Redirect_Helper $redirect_helper The Redirect Helper. * @param Crawl_Cleanup_Helper $crawl_cleanup_helper The Crawl_Cleanup_Helper. */ public function __construct( Options_Helper $options_helper, Url_Helper $url_helper, Redirect_Helper $redirect_helper, Crawl_Cleanup_Helper $crawl_cleanup_helper ) { $this->options_helper = $options_helper; $this->url_helper = $url_helper; $this->redirect_helper = $redirect_helper; $this->crawl_cleanup_helper = $crawl_cleanup_helper; } /** * Initializes the integration. * * @return void */ public function initialize() { // We need to hook after 10 because otherwise our options helper isn't available yet. \add_action( 'plugins_loaded', [ $this, 'register_hooks' ], 15 ); } /** * Hooks our required hooks. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { if ( $this->options_helper->get( 'clean_campaign_tracking_urls' ) && ! empty( \get_option( 'permalink_structure' ) ) ) { \add_action( 'template_redirect', [ $this, 'utm_redirect' ], 0 ); } if ( $this->options_helper->get( 'clean_permalinks' ) && ! empty( \get_option( 'permalink_structure' ) ) ) { \add_action( 'template_redirect', [ $this, 'clean_permalinks' ], 1 ); } } /** * Returns the conditionals based in which this loadable should be active. * * @return array The array of conditionals. */ public static function get_conditionals() { return [ Front_End_Conditional::class ]; } /** * Redirect utm variables away. * * @return void */ public function utm_redirect() { // Prevents WP CLI from throwing an error. // phpcs:ignore WordPress.Security.ValidatedSanitizedInput if ( ! isset( $_SERVER['REQUEST_URI'] ) || \strpos( $_SERVER['REQUEST_URI'], '?' ) === false ) { return; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput if ( ! \stripos( $_SERVER['REQUEST_URI'], 'utm_' ) ) { return; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $parsed = \wp_parse_url( $_SERVER['REQUEST_URI'] ); $query = \explode( '&', $parsed['query'] ); $utms = []; $other_args = []; foreach ( $query as $query_arg ) { if ( \stripos( $query_arg, 'utm_' ) === 0 ) { $utms[] = $query_arg; continue; } $other_args[] = $query_arg; } if ( empty( $utms ) ) { return; } $other_args_str = ''; if ( \count( $other_args ) > 0 ) { $other_args_str = '?' . \implode( '&', $other_args ); } $new_path = $parsed['path'] . $other_args_str . '#' . \implode( '&', $utms ); $message = \sprintf( /* translators: %1$s: Yoast SEO */ \__( '%1$s: redirect utm variables to #', 'wordpress-seo' ), 'Yoast SEO' ); $this->redirect_helper->do_safe_redirect( \trailingslashit( $this->url_helper->recreate_current_url( false ) ) . \ltrim( $new_path, '/' ), 301, $message ); } /** * Removes unneeded query variables from the URL. * * @return void */ public function clean_permalinks() { if ( $this->crawl_cleanup_helper->should_avoid_redirect() ) { return; } $current_url = $this->url_helper->recreate_current_url(); $allowed_params = $this->crawl_cleanup_helper->allowed_params( $current_url ); // If we had only allowed params, let's just bail out, no further processing needed. if ( empty( $allowed_params['query'] ) ) { return; } $url_type = $this->crawl_cleanup_helper->get_url_type(); switch ( $url_type ) { case 'singular_url': $proper_url = $this->crawl_cleanup_helper->singular_url(); break; case 'front_page_url': $proper_url = $this->crawl_cleanup_helper->front_page_url(); break; case 'page_for_posts_url': $proper_url = $this->crawl_cleanup_helper->page_for_posts_url(); break; case 'taxonomy_url': $proper_url = $this->crawl_cleanup_helper->taxonomy_url(); break; case 'search_url': $proper_url = $this->crawl_cleanup_helper->search_url(); break; case 'page_not_found_url': $proper_url = $this->crawl_cleanup_helper->page_not_found_url( $current_url ); break; default: $proper_url = ''; } if ( $this->crawl_cleanup_helper->is_query_var_page( $proper_url ) ) { $proper_url = $this->crawl_cleanup_helper->query_var_page_url( $proper_url ); } $proper_url = \add_query_arg( $allowed_params['allowed_query'], $proper_url ); if ( empty( $proper_url ) || $current_url === $proper_url ) { return; } $this->crawl_cleanup_helper->do_clean_redirect( $proper_url ); } } initializers/woocommerce.php 0000644 00000001417 15025657560 0012320 0 ustar 00 <?php namespace Yoast\WP\SEO\Initializers; use Automattic\WooCommerce\Utilities\FeaturesUtil; use Yoast\WP\SEO\Conditionals\No_Conditionals; /** * Declares compatibility with the WooCommerce HPOS feature. */ class Woocommerce implements Initializer_Interface { use No_Conditionals; /** * Hooks into WooCommerce. * * @return void */ public function initialize() { \add_action( 'before_woocommerce_init', [ $this, 'declare_custom_order_tables_compatibility' ] ); } /** * Declares compatibility with the WooCommerce HPOS feature. * * @return void */ public function declare_custom_order_tables_compatibility() { if ( \class_exists( FeaturesUtil::class ) ) { FeaturesUtil::declare_compatibility( 'custom_order_tables', \WPSEO_FILE, true ); } } } initializers/disable-core-sitemaps.php 0000644 00000005571 15025657560 0014162 0 ustar 00 <?php namespace Yoast\WP\SEO\Initializers; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Redirect_Helper; /** * Disables the WP core sitemaps. */ class Disable_Core_Sitemaps implements Initializer_Interface { use No_Conditionals; /** * The options helper. * * @var Options_Helper */ private $options; /** * The redirect helper. * * @var Redirect_Helper */ private $redirect; /** * Sitemaps_Enabled_Conditional constructor. * * @codeCoverageIgnore * * @param Options_Helper $options The options helper. * @param Redirect_Helper $redirect The redirect helper. */ public function __construct( Options_Helper $options, Redirect_Helper $redirect ) { $this->options = $options; $this->redirect = $redirect; } /** * Disable the WP core XML sitemaps. * * @return void */ public function initialize() { // This needs to be on priority 15 as that is after our options initialize. \add_action( 'plugins_loaded', [ $this, 'maybe_disable_core_sitemaps' ], 15 ); } /** * Disables the core sitemaps if Yoast SEO sitemaps are enabled. * * @return void */ public function maybe_disable_core_sitemaps() { if ( $this->options->get( 'enable_xml_sitemap' ) ) { \add_filter( 'wp_sitemaps_enabled', '__return_false' ); \add_action( 'template_redirect', [ $this, 'template_redirect' ], 0 ); } } /** * Redirects requests to the WordPress sitemap to the Yoast sitemap. * * @return void */ public function template_redirect() { // If there is no path, nothing to do. if ( empty( $_SERVER['REQUEST_URI'] ) ) { return; } $path = \sanitize_text_field( \wp_unslash( $_SERVER['REQUEST_URI'] ) ); // If it's not a wp-sitemap request, nothing to do. if ( \substr( $path, 0, 11 ) !== '/wp-sitemap' ) { return; } $redirect = $this->get_redirect_url( $path ); if ( ! $redirect ) { return; } $this->redirect->do_safe_redirect( \home_url( $redirect ), 301 ); } /** * Returns the relative sitemap URL to redirect to. * * @param string $path The original path. * * @return string|false The path to redirct to. False if no redirect should be done. */ private function get_redirect_url( $path ) { // Start with the simple string comparison so we avoid doing unnecessary regexes. if ( $path === '/wp-sitemap.xml' ) { return '/sitemap_index.xml'; } if ( \preg_match( '/^\/wp-sitemap-(posts|taxonomies)-(\w+)-(\d+)\.xml$/', $path, $matches ) ) { $index = ( (int) $matches[3] - 1 ); $index = ( $index === 0 ) ? '' : (string) $index; return '/' . $matches[2] . '-sitemap' . $index . '.xml'; } if ( \preg_match( '/^\/wp-sitemap-users-(\d+)\.xml$/', $path, $matches ) ) { $index = ( (int) $matches[1] - 1 ); $index = ( $index === 0 ) ? '' : (string) $index; return '/author-sitemap' . $index . '.xml'; } return false; } } presenters/webmaster/pinterest-presenter.php 0000644 00000001130 15025657560 0015470 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Webmaster; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Pinterest Webmaster verification setting. */ class Pinterest_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'p:domain_verify'; /** * Retrieves the webmaster tool site verification value from the settings. * * @return string The webmaster tool site verification value. */ public function get() { return $this->helpers->options->get( 'pinterestverify', '' ); } } presenters/webmaster/yandex-presenter.php 0000644 00000001123 15025657560 0014745 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Webmaster; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Yandex Webmaster verification setting. */ class Yandex_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'yandex-verification'; /** * Retrieves the webmaster tool site verification value from the settings. * * @return string The webmaster tool site verification value. */ public function get() { return $this->helpers->options->get( 'yandexverify', '' ); } } presenters/webmaster/bing-presenter.php 0000644 00000001105 15025657560 0014374 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Webmaster; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Bing Webmaster verification setting. */ class Bing_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'msvalidate.01'; /** * Retrieves the webmaster tool site verification value from the settings. * * @return string The webmaster tool site verification value. */ public function get() { return $this->helpers->options->get( 'msverify', '' ); } } presenters/webmaster/google-presenter.php 0000644 00000001135 15025657560 0014734 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Webmaster; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Google Search Console verification setting. */ class Google_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'google-site-verification'; /** * Retrieves the webmaster tool site verification value from the settings. * * @return string The webmaster tool site verification value. */ public function get() { return $this->helpers->options->get( 'googleverify', '' ); } } presenters/webmaster/baidu-presenter.php 0000644 00000001132 15025657560 0014541 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Webmaster; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Baidu Webmaster Tools verification setting. */ class Baidu_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'baidu-site-verification'; /** * Retrieves the webmaster tool site verification value from the settings. * * @return string The webmaster tool site verification value. */ public function get() { return $this->helpers->options->get( 'baiduverify', '' ); } } presenters/admin/indexing-notification-presenter.php 0000644 00000013141 15025657560 0017050 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Config\Indexing_Reasons; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Class Indexing_Notification_Presenter. * * @package Yoast\WP\SEO\Presenters\Admin */ class Indexing_Notification_Presenter extends Abstract_Presenter { /** * The total number of unindexed objects. * * @var int */ protected $total_unindexed; /** * The message to show in the notification. * * @var string */ protected $reason; /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * Indexing_Notification_Presenter constructor. * * @param Short_Link_Helper $short_link_helper The short link helper. * @param int $total_unindexed Total number of unindexed objects. * @param string $reason The reason to show in the notification. */ public function __construct( $short_link_helper, $total_unindexed, $reason ) { $this->short_link_helper = $short_link_helper; $this->total_unindexed = $total_unindexed; $this->reason = $reason; } /** * Returns the notification as an HTML string. * * @return string The HTML string representation of the notification. */ public function present() { $notification_text = '<p>' . $this->get_message( $this->reason ); $notification_text .= $this->get_time_estimate( $this->total_unindexed ) . '</p>'; $notification_text .= '<a class="button" href="' . \get_admin_url( null, 'admin.php?page=wpseo_tools&start-indexation=true' ) . '">'; $notification_text .= \esc_html__( 'Start SEO data optimization', 'wordpress-seo' ); $notification_text .= '</a>'; return $notification_text; } /** * Determines the message to show in the indexing notification. * * @param string $reason The reason identifier. * * @return string The message to show in the notification. */ protected function get_message( $reason ) { switch ( $reason ) { case Indexing_Reasons::REASON_PERMALINK_SETTINGS: $text = \esc_html__( 'Because of a change in your permalink structure, some of your SEO data needs to be reprocessed.', 'wordpress-seo' ); break; case Indexing_Reasons::REASON_HOME_URL_OPTION: $text = \esc_html__( 'Because of a change in your home URL setting, some of your SEO data needs to be reprocessed.', 'wordpress-seo' ); break; case Indexing_Reasons::REASON_CATEGORY_BASE_PREFIX: $text = \esc_html__( 'Because of a change in your category base setting, some of your SEO data needs to be reprocessed.', 'wordpress-seo' ); break; case Indexing_Reasons::REASON_TAG_BASE_PREFIX: $text = \esc_html__( 'Because of a change in your tag base setting, some of your SEO data needs to be reprocessed.', 'wordpress-seo' ); break; case Indexing_Reasons::REASON_POST_TYPE_MADE_PUBLIC: $text = \esc_html__( 'We need to re-analyze some of your SEO data because of a change in the visibility of your post types. Please help us do that by running the SEO data optimization.', 'wordpress-seo' ); break; case Indexing_Reasons::REASON_TAXONOMY_MADE_PUBLIC: $text = \esc_html__( 'We need to re-analyze some of your SEO data because of a change in the visibility of your taxonomies. Please help us do that by running the SEO data optimization.', 'wordpress-seo' ); break; case Indexing_Reasons::REASON_ATTACHMENTS_MADE_ENABLED: $text = \esc_html__( 'It looks like you\'ve enabled media pages. We recommend that you help us to re-analyze your site by running the SEO data optimization.', 'wordpress-seo' ); break; default: $text = \esc_html__( 'You can speed up your site and get insight into your internal linking structure by letting us perform a few optimizations to the way SEO data is stored.', 'wordpress-seo' ); } /** * Filter: 'wpseo_indexables_indexation_alert' - Allow developers to filter the reason of the indexation * * @param string $text The text to show as reason. * @param string $reason The reason value. */ return (string) \apply_filters( 'wpseo_indexables_indexation_alert', $text, $reason ); } /** * Creates a time estimate based on the total number on unindexed objects. * * @param int $total_unindexed The total number of unindexed objects. * * @return string The time estimate as a HTML string. */ protected function get_time_estimate( $total_unindexed ) { if ( $total_unindexed < 400 ) { return \esc_html__( ' We estimate this will take less than a minute.', 'wordpress-seo' ); } if ( $total_unindexed < 2500 ) { return \esc_html__( ' We estimate this will take a couple of minutes.', 'wordpress-seo' ); } $estimate = \esc_html__( ' We estimate this could take a long time, due to the size of your site. As an alternative to waiting, you could:', 'wordpress-seo' ); $estimate .= '<ul class="ul-disc">'; $estimate .= '<li>'; $estimate .= \sprintf( /* translators: 1: Expands to Yoast SEO */ \esc_html__( 'Wait for a week or so, until %1$s automatically processes most of your content in the background.', 'wordpress-seo' ), 'Yoast SEO' ); $estimate .= '</li>'; $estimate .= '<li>'; $estimate .= \sprintf( /* translators: 1: Link to article about indexation command, 2: Anchor closing tag, 3: Link to WP CLI. */ \esc_html__( '%1$sRun the indexation process on your server%2$s using %3$sWP CLI%2$s.', 'wordpress-seo' ), '<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/3-w' ) ) . '" target="_blank">', '</a>', '<a href="https://wp-cli.org/" target="_blank">' ); $estimate .= '</li>'; $estimate .= '</ul>'; return $estimate; } } presenters/admin/migration-error-presenter.php 0000644 00000004264 15025657560 0015705 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WPSEO_Shortlinker; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Presenter class for the migration error. */ class Migration_Error_Presenter extends Abstract_Presenter { /** * Holds the migration error. * * The array holds the following values if filled: * - int|false $time The timestamp. * - string $version The Yoast SEO version. * - string $message The error message. * * @var array */ protected $migration_error; /** * Migration_Error_Presenter constructor. * * @param array $migration_error The migration error. */ public function __construct( $migration_error ) { $this->migration_error = $migration_error; } /** * Presents the migration error that occurred. * * @return string The error HTML. */ public function present() { $header = \sprintf( /* translators: %s: Yoast SEO. */ \esc_html__( '%s is unable to create database tables', 'wordpress-seo' ), 'Yoast SEO' ); $message = \sprintf( /* translators: %s: Yoast SEO. */ \esc_html__( '%s had problems creating the database tables needed to speed up your site.', 'wordpress-seo' ), 'Yoast SEO' ); $support = \sprintf( /* translators: %1$s: link to help article about solving table issue. %2$s: is anchor closing. */ \esc_html__( 'Please read %1$sthis help article%2$s to find out how to resolve this problem.', 'wordpress-seo' ), '<a href="' . WPSEO_Shortlinker::get( 'https://yoa.st/3-6' ) . '">', '</a>' ); $reassurance = \sprintf( /* translators: %s: Yoast SEO. */ \esc_html__( 'Your site will continue to work normally, but won\'t take full advantage of %s.', 'wordpress-seo' ), 'Yoast SEO' ); $debug_info = \sprintf( '<details><summary>%1$s</summary><p>%2$s</p></details>', \esc_html__( 'Show debug information', 'wordpress-seo' ), \esc_html( $this->migration_error['message'] ) ); return \sprintf( '<div class="notice notice-error yoast-migrated-notice"><h4 class="yoast-notice-migrated-header">%1$s</h4><div class="notice-yoast-content"><p>%2$s</p><p>%3$s</p><p>%4$s</p>%5$s</div></div>', $header, $message, $support, $reassurance, $debug_info ); } } presenters/admin/woocommerce-beta-editor-presenter.php 0000644 00000003360 15025657560 0017275 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Class Woocommerce_Beta_Editor_Presenter. */ class Woocommerce_Beta_Editor_Presenter extends Abstract_Presenter { /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * Woocommerce_Beta_Editor_Presenter constructor. * * @param Short_Link_Helper $short_link_helper The short link helper. */ public function __construct( Short_Link_Helper $short_link_helper ) { $this->short_link_helper = $short_link_helper; } /** * Returns the notification as an HTML string. * * @return string The notification in an HTML string representation. */ public function present() { $notification_text = '<p>'; $notification_text .= $this->get_message(); $notification_text .= '</p>'; return $notification_text; } /** * Returns the message to show. * * @return string The message. */ protected function get_message() { return \sprintf( '<strong>%1$s</strong> %2$s', \esc_html__( 'Compatibility issue: Yoast SEO is incompatible with the beta WooCommerce product editor.', 'wordpress-seo' ), \sprintf( /* translators: 1: Yoast SEO, 2: Link start tag to the Learn more link, 3: Link closing tag. */ \esc_html__( 'The %1$s interface is currently unavailable in the beta WooCommerce product editor. To resolve any issues, please disable the beta editor. %2$sLearn how to disable the beta WooCommerce product editor.%3$s', 'wordpress-seo' ), 'Yoast SEO', '<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/learn-how-disable-beta-woocommerce-product-editor' ) ) . '" target="_blank">', '</a>' ) ); } } presenters/admin/help-link-presenter.php 0000644 00000004121 15025657560 0014440 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Represents the presenter class for Help link. */ class Help_Link_Presenter extends Abstract_Presenter { /** * Help link. * * @var string */ private $link; /** * Help link visually hidden text. * * @var string */ private $link_text; /** * Whether the Help link opens in a new browser tab. * * @var bool */ private $opens_in_new_browser_tab; /** * An instance of the WPSEO_Admin_Asset_Manager class. * * @var WPSEO_Admin_Asset_Manager */ private $asset_manager; /** * Help_Link_Presenter constructor. * * @param string $link Help link. * @param string $link_text Help link visually hidden text. * @param bool $opens_in_new_browser_tab Whether the link opens in a new browser tab. Default true. */ public function __construct( $link = '', $link_text = '', $opens_in_new_browser_tab = true ) { $this->link = $link; $this->link_text = $link_text; $this->opens_in_new_browser_tab = $opens_in_new_browser_tab; if ( ! $this->asset_manager ) { $this->asset_manager = new WPSEO_Admin_Asset_Manager(); } $this->asset_manager->enqueue_style( 'admin-global' ); } /** * Presents the Help link. * * @return string The styled Help link. */ public function present() { if ( $this->link === '' || $this->link_text === '' ) { return; } $target_blank_attribute = ''; $new_tab_message = ''; if ( $this->opens_in_new_browser_tab ) { $target_blank_attribute = ' target="_blank"'; /* translators: Hidden accessibility text. */ $new_tab_message = ' ' . \__( '(Opens in a new browser tab)', 'wordpress-seo' ); } return \sprintf( '<a href="%1$s"%2$s class="yoast_help yoast-help-link dashicons"><span class="yoast-help-icon" aria-hidden="true"></span><span class="screen-reader-text">%3$s</span></a>', \esc_url( $this->link ), $target_blank_attribute, \esc_html( $this->link_text . $new_tab_message ) ); } } presenters/admin/meta-fields-presenter.php 0000644 00000003100 15025657560 0014743 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WP_Post; use WPSEO_Meta; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Presenter class for meta fields in the post editor. * * Outputs the hidden fields for a particular field group and post. */ class Meta_Fields_Presenter extends Abstract_Presenter { /** * The meta fields for which we are going to output hidden input. * * @var array */ private $meta_fields; /** * The metabox post. * * @var WP_Post The metabox post. */ private $post; /** * Meta_Fields_Presenter constructor. * * @param WP_Post $post The metabox post. * @param string $field_group The key under which a group of fields is grouped. * @param string $post_type The post type. */ public function __construct( $post, $field_group, $post_type = 'post' ) { $this->post = $post; $this->meta_fields = WPSEO_Meta::get_meta_field_defs( $field_group, $post_type ); } /** * Presents the Meta Fields. * * @return string The styled Alert. */ public function present() { $output = ''; foreach ( $this->meta_fields as $key => $meta_field ) { $form_key = \esc_attr( WPSEO_Meta::$form_prefix . $key ); $meta_value = WPSEO_Meta::get_value( $key, $this->post->ID ); $default = ''; if ( isset( $meta_field['default'] ) ) { $default = \sprintf( ' data-default="%s"', \esc_attr( $meta_field['default'] ) ); } $output .= '<input type="hidden" id="' . $form_key . '" name="' . $form_key . '" value="' . \esc_attr( $meta_value ) . '"' . $default . '/>' . "\n"; } return $output; } } presenters/admin/light-switch-presenter.php 0000644 00000010764 15025657560 0015175 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Class Light_Switch_Presenter. * * @package Yoast\WP\SEO\Presenters\Admin */ class Light_Switch_Presenter extends Abstract_Presenter { /** * The variable to create the checkbox for. * * @var string */ protected $var; /** * The visual label text for the toggle. * * @var string */ protected $label; /** * Array of two visual labels for the buttons. * * @var array */ protected $buttons; /** * The name of the underlying checkbox. * * @var string */ protected $name; /** * The variable current value. * * @var string|bool */ protected $value; /** * Reverse order of buttons. * * @var bool */ protected $reverse; /** * The inline Help HTML. * * @var string */ protected $help; /** * Whether the visual label is displayed in strong text. * * @var bool */ protected $strong; /** * The disabled attribute HTML. * * @var string */ protected $disabled_attribute; /** * Light_Switch_Presenter constructor. * * @param string $variable The variable to create the checkbox for. * @param string $label The visual label text for the toggle. * @param array $buttons Array of two visual labels for the buttons (defaults Disabled/Enabled). * @param string $name The name of the underlying checkbox. * @param string|bool $value The variable current value, to determine the checked attribute. * @param bool $reverse Optional. Reverse order of buttons (default true). * @param string $help Optional. Inline Help HTML that will be printed out before the toggle. Default is empty. * @param bool $strong Optional. Whether the visual label is displayed in strong text. Default is false. * Starting from Yoast SEO 16.5, the visual label is forced to bold via CSS. * @param string $disabled_attribute Optional. The disabled HTML attribute. Default is empty. */ public function __construct( $variable, $label, $buttons, $name, $value, $reverse = true, $help = '', $strong = false, $disabled_attribute = '' ) { $this->var = $variable; $this->label = $label; $this->buttons = $buttons; $this->name = $name; $this->value = $value; $this->reverse = $reverse; $this->help = $help; $this->strong = $strong; $this->disabled_attribute = $disabled_attribute; } /** * Presents the light switch toggle. * * @return string The light switch's HTML. */ public function present() { if ( empty( $this->buttons ) ) { $this->buttons = [ \__( 'Disabled', 'wordpress-seo' ), \__( 'Enabled', 'wordpress-seo' ) ]; } list( $off_button, $on_button ) = $this->buttons; $class = 'switch-light switch-candy switch-yoast-seo'; if ( $this->reverse ) { $class .= ' switch-yoast-seo-reverse'; } $help_class = ! empty( $this->help ) ? ' switch-container__has-help' : ''; $strong_class = ( $this->strong ) ? ' switch-light-visual-label__strong' : ''; $output = '<div class="switch-container' . $help_class . '">'; $output .= \sprintf( '<span class="switch-light-visual-label%1$s" id="%2$s">%3$s</span>%4$s', $strong_class, // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $strong_class output is hardcoded. \esc_attr( $this->var . '-label' ), \esc_html( $this->label ), $this->help // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: The help contains HTML. ); $output .= '<label class="' . $class . '"><b class="switch-yoast-seo-jaws-a11y"> </b>'; $output .= \sprintf( '<input type="checkbox" aria-labelledby="%1$s" id="%2$s" name="%3$s" value="on"%4$s%5$s/>', \esc_attr( $this->var . '-label' ), \esc_attr( $this->var ), \esc_attr( $this->name ), \checked( $this->value, 'on', false ), // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: The output is hardcoded by WordPress. $this->disabled_attribute // phpcs:ignore WordPress.Security.EscapeOutput -- Reason: $disabled_attribute output is hardcoded. ); $output .= '<span aria-hidden="true">'; $output .= '<span>' . \esc_html( $off_button ) . '</span>'; $output .= '<span>' . \esc_html( $on_button ) . '</span>'; $output .= '<a></a>'; $output .= '</span></label><div class="clear"></div></div>'; return $output; } } presenters/admin/indexing-failed-notification-presenter.php 0000644 00000005764 15025657560 0020306 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Class Indexing_Failed_Notification_Presenter. * * @package Yoast\WP\SEO\Presenters\Notifications */ class Indexing_Failed_Notification_Presenter extends Abstract_Presenter { /** * The product helper. * * @var Product_Helper */ protected $product_helper; /** * The addon manager. * * @var WPSEO_Addon_Manager */ protected $class_addon_manager; /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * Indexing_Failed_Notification_Presenter constructor. * * @param Product_Helper $product_helper The product helper. * @param Short_Link_Helper $short_link_helper The addon manager. * @param WPSEO_Addon_Manager $class_addon_manager The addon manager. */ public function __construct( $product_helper, $short_link_helper, $class_addon_manager ) { $this->class_addon_manager = $class_addon_manager; $this->short_link_helper = $short_link_helper; $this->product_helper = $product_helper; } /** * Returns the notification as an HTML string. * * @return string The notification in an HTML string representation. */ public function present() { $notification_text = \sprintf( /* Translators: %1$s expands to an opening anchor tag for a link leading to the Yoast SEO tools page, %2$s expands to a closing anchor tag. */ \esc_html__( 'Something has gone wrong and we couldn\'t complete the optimization of your SEO data. Please %1$sre-start the process%2$s.', 'wordpress-seo' ), '<a href="' . \get_admin_url( null, 'admin.php?page=wpseo_tools' ) . '">', '</a>' ); if ( $this->product_helper->is_premium() ) { if ( $this->has_valid_premium_subscription() ) { // Add a support message for premium customers. $notification_text .= ' '; $notification_text .= \esc_html__( 'If the problem persists, please contact support.', 'wordpress-seo' ); } else { // Premium plugin with inactive addon; overwrite the entire error message. $notification_text = \sprintf( /* Translators: %1$s expands to an opening anchor tag for a link leading to the Premium installation page, %2$s expands to a closing anchor tag. */ \esc_html__( 'Oops, something has gone wrong and we couldn\'t complete the optimization of your SEO data. Please make sure to activate your subscription in MyYoast by completing %1$sthese steps%2$s.', 'wordpress-seo' ), '<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/3wv' ) ) . '">', '</a>' ); } } return '<p>' . $notification_text . '</p>'; } /** * Determines if the site has a valid Premium subscription. * * @return bool */ protected function has_valid_premium_subscription() { return $this->class_addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ); } } presenters/admin/alert-presenter.php 0000644 00000003063 15025657560 0013670 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Represents the presenter class for Alert boxes. */ class Alert_Presenter extends Abstract_Presenter { /** * Content of the Alert. * * @var string */ private $content = ''; /** * The type of the Alert. * * Can be: "error", "info", "success" or "warning". * Controls the colours and icon of the Alert. * * @var string */ private $type; /** * An instance of the WPSEO_Admin_Asset_Manager class. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * Alert_Presenter constructor. * * @param string $content Content of the Alert. * @param string $type Type of the Alert (error/info/success/warning), default is warning. */ public function __construct( $content, $type = 'warning' ) { $this->content = $content; $this->type = $type; if ( ! $this->asset_manager ) { $this->asset_manager = new WPSEO_Admin_Asset_Manager(); } $this->asset_manager->enqueue_style( 'alert' ); } /** * Presents the Alert. * * @return string The styled Alert. */ public function present() { $icon_file = 'images/alert-' . $this->type . '-icon.svg'; $out = '<div class="yoast-alert yoast-alert--' . $this->type . '">'; $out .= '<span>'; $out .= '<img class="yoast-alert__icon" src="' . \esc_url( \plugin_dir_url( \WPSEO_FILE ) . $icon_file ) . '" alt="" />'; $out .= '</span>'; $out .= '<span>' . $this->content . '</span>'; $out .= '</div>'; return $out; } } presenters/admin/search-engines-discouraged-presenter.php 0000644 00000002666 15025657560 0017753 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Class Search_Engines_Discouraged_Presenter. */ class Search_Engines_Discouraged_Presenter extends Abstract_Presenter { /** * Returns the notification as an HTML string. * * @return string The notification in an HTML string representation. */ public function present() { $notification_text = '<p>'; $notification_text .= $this->get_message(); $notification_text .= '</p>'; return $notification_text; } /** * Returns the message to show. * * @return string The message. */ protected function get_message() { return \sprintf( '<strong>%1$s</strong> %2$s <button type="button" id="robotsmessage-dismiss-button" class="button-link hide-if-no-js" data-nonce="%3$s">%4$s</button>', \esc_html__( 'Huge SEO Issue: You\'re blocking access to robots.', 'wordpress-seo' ), \sprintf( /* translators: 1: Link start tag to the WordPress Reading Settings page, 2: Link closing tag. */ \esc_html__( 'If you want search engines to show this site in their results, you must %1$sgo to your Reading Settings%2$s and uncheck the box for Search Engine Visibility.', 'wordpress-seo' ), '<a href="' . \esc_url( \admin_url( 'options-reading.php' ) ) . '">', '</a>' ), \esc_js( \wp_create_nonce( 'wpseo-ignore' ) ), \esc_html__( 'I don\'t want this site to show in the search results.', 'wordpress-seo' ) ); } } presenters/admin/badge-presenter.php 0000644 00000005221 15025657560 0013621 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Config\Badge_Group_Names; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Represents the presenter class for "New" badges. */ class Badge_Presenter extends Abstract_Presenter { /** * Identifier of the badge. * * @var string */ private $id; /** * Optional link of the badge. * * @var string */ private $link; /** * Optional group which the badge belongs to. * * Each group has a fixed period after which the group will no longer be considered new and the badges will disappear. * * @var string */ private $group; /** * Optional object storing the group names and expiration versions. * * The group names set in Yoast SEO are used by default, but they can be overridden to use custom ones for an add-on. * * @var Badge_Group_Names */ private $badge_group_names; /** * Badge_Presenter constructor. * * @param string $id Id of the badge. * @param string $link Optional link of the badge. * @param string $group Optional group which the badge belongs to. * @param Badge_Group_Names|null $badge_group_names Optional object storing the group names. */ public function __construct( $id, $link = '', $group = '', $badge_group_names = null ) { $this->id = $id; $this->link = $link; $this->group = $group; if ( ! $badge_group_names instanceof Badge_Group_Names ) { $badge_group_names = new Badge_Group_Names(); } $this->badge_group_names = $badge_group_names; } /** * Presents the New Badge. If a link has been passed, the badge is presented with the link. * Otherwise a static badge is presented. * * @return string The styled New Badge. */ public function present() { if ( ! $this->is_group_still_new() ) { return ''; } if ( $this->link !== '' ) { return \sprintf( '<a class="yoast-badge yoast-badge__is-link yoast-new-badge" id="%1$s-new-badge" href="%2$s">%3$s</a>', \esc_attr( $this->id ), \esc_url( $this->link ), \esc_html__( 'New', 'wordpress-seo' ) ); } return \sprintf( '<span class="yoast-badge yoast-new-badge" id="%1$s-new-badge">%2$s</span>', \esc_attr( $this->id ), \esc_html__( 'New', 'wordpress-seo' ) ); } /** * Check whether the new badge should be shown according to the group it is in. * * @return bool True if still new. */ public function is_group_still_new() { // If there's no group configured, the new badge is always active. if ( ! $this->group ) { return true; } return $this->badge_group_names->is_still_eligible_for_new_badge( $this->group ); } } presenters/admin/indexing-error-presenter.php 0000644 00000010463 15025657560 0015517 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * An error that should be shown when indexation has failed. */ class Indexing_Error_Presenter extends Abstract_Presenter { /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * The product helper * * @var Product_Helper */ protected $product_helper; /** * The addon manager. * * @var WPSEO_Addon_Manager */ protected $addon_manager; /** * Indexing_Error_Presenter constructor. * * @param Short_Link_Helper $short_link_helper Represents the short link helper. * @param Product_Helper $product_helper The product helper. * @param WPSEO_Addon_Manager $addon_manager The addon manager. */ public function __construct( Short_Link_Helper $short_link_helper, Product_Helper $product_helper, WPSEO_Addon_Manager $addon_manager ) { $this->short_link_helper = $short_link_helper; $this->product_helper = $product_helper; $this->addon_manager = $addon_manager; } /** * Generates the first paragraph of the error message to show when indexing failed. * * The contents of the paragraph varies based on whether WordPress SEO Premium has a valid, activated subscription or not. * * @param bool $is_premium Whether WordPress SEO Premium is currently active. * @param bool $has_valid_premium_subscription Whether WordPress SEO Premium currently has a valid subscription. * * @return string */ protected function generate_first_paragraph( $is_premium, $has_valid_premium_subscription ) { $message = \__( 'Oops, something has gone wrong and we couldn\'t complete the optimization of your SEO data. Please click the button again to re-start the process. ', 'wordpress-seo' ); if ( $is_premium ) { if ( $has_valid_premium_subscription ) { $message .= \__( 'If the problem persists, please contact support.', 'wordpress-seo' ); } else { $message = \sprintf( /* translators: %1$s expands to an opening anchor tag for a link leading to the Premium installation page, %2$s expands to a closing anchor tag. */ \__( 'Oops, something has gone wrong and we couldn\'t complete the optimization of your SEO data. Please make sure to activate your subscription in MyYoast by completing %1$sthese steps%2$s.', 'wordpress-seo' ), '<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/3wv' ) ) . '">', '</a>' ); } } return $message; } /** * Generates the second paragraph of the error message to show when indexing failed. * * The error message varies based on whether WordPress SEO Premium has a valid, activated subscription or not. * * @param bool $is_premium Whether WordPress SEO Premium is currently active. * @param bool $has_valid_premium_subscription Whether WordPress SEO Premium currently has a valid subscription. * * @return string The second paragraph of the error message. */ protected function generate_second_paragraph( $is_premium, $has_valid_premium_subscription ) { return \sprintf( /* translators: %1$s expands to an opening anchor tag for a link leading to the Premium installation page, %2$s expands to a closing anchor tag. */ \__( 'Below are the technical details for the error. See %1$sthis page%2$s for a more detailed explanation.', 'wordpress-seo' ), '<a href="' . \esc_url( $this->short_link_helper->get( 'https://yoa.st/4f3' ) ) . '">', '</a>' ); } /** * Presents the error message to show if SEO optimization failed. * * The error message varies based on whether WordPress SEO Premium has a valid, activated subscription or not. * * @return string The error message to show. */ public function present() { $is_premium = $this->product_helper->is_premium(); $has_valid_premium_subscription = $this->addon_manager->has_valid_subscription( WPSEO_Addon_Manager::PREMIUM_SLUG ); $output = '<p>' . $this->generate_first_paragraph( $is_premium, $has_valid_premium_subscription ) . '</p>'; $output .= '<p>' . $this->generate_second_paragraph( $is_premium, $has_valid_premium_subscription ) . '</p>'; return $output; } } presenters/admin/notice-presenter.php 0000644 00000006450 15025657560 0014045 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Represents the presenter class for Yoast-styled WordPress admin notices. */ class Notice_Presenter extends Abstract_Presenter { /** * The title of the admin notice. * * @var string */ private $title; /** * The content of the admin notice. * * @var string */ private $content; /** * The filename of the image for the notice. Should be a file in the 'images' folder. * * @var string */ private $image_filename; /** * HTML string to be displayed after the main content, usually a button. * * @var string */ private $button; /** * Whether the notice should be dismissible. * * @var bool */ private $is_dismissible; /** * The id for the div of the notice. * * @var string */ private $id; /** * An instance of the WPSEO_Admin_Asset_Manager class. * * @var WPSEO_Admin_Asset_Manager */ protected $asset_manager; /** * Notice_Presenter constructor. * * @param string $title Title of the admin notice. * @param string $content Content of the admin notice. * @param string|null $image_filename Optional. The filename of the image of the admin notice, * should be inside the 'images' folder. * @param string|null $button Optional. An HTML string to be displayed after the main content, * usually a button. * @param bool $is_dismissible Optional. Whether the admin notice should be dismissible. * @param string $id Optional. The id of the notice. */ public function __construct( $title, $content, $image_filename = null, $button = null, $is_dismissible = false, $id = '' ) { $this->title = $title; $this->content = $content; $this->image_filename = $image_filename; $this->button = $button; $this->is_dismissible = $is_dismissible; $this->id = $id; if ( ! $this->asset_manager ) { $this->asset_manager = new WPSEO_Admin_Asset_Manager(); } $this->asset_manager->enqueue_style( 'notifications' ); } /** * Presents the Notice. * * @return string The styled Notice. */ public function present() { $dismissible = ( $this->is_dismissible ) ? ' is-dismissible' : ''; $id = ( $this->id ) ? ' id="' . $this->id . '"' : ''; // WordPress admin notice. $out = '<div' . $id . ' class="notice notice-yoast yoast' . $dismissible . '">'; $out .= '<div class="notice-yoast__container">'; // Header. $out .= '<div>'; $out .= '<div class="notice-yoast__header">'; $out .= '<span class="yoast-icon"></span>'; $out .= \sprintf( '<h2 class="notice-yoast__header-heading yoast-notice-migrated-header">%s</h2>', \esc_html( $this->title ) ); $out .= '</div>'; $out .= '<div class="notice-yoast-content">'; $out .= '<p>' . $this->content . '</p>'; if ( $this->button !== null ) { $out .= '<p>' . $this->button . '</p>'; } $out .= '</div>'; $out .= '</div>'; if ( $this->image_filename !== null ) { $out .= '<img src="' . \esc_url( \plugin_dir_url( \WPSEO_FILE ) . 'images/' . $this->image_filename ) . '" alt="" height="60" width="75"/>'; } $out .= '</div>'; $out .= '</div>'; return $out; } } presenters/admin/beta-badge-presenter.php 0000644 00000002447 15025657560 0014541 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Represents the presenter class for "Beta" badges. */ class Beta_Badge_Presenter extends Abstract_Presenter { /** * Identifier of the badge. * * @var string */ private $id; /** * Optional link of the badge. * * @var string */ private $link; /** * Beta_Badge_Presenter constructor. * * @param string $id Id of the badge. * @param string $link Optional link of the badge. */ public function __construct( $id, $link = '' ) { $this->id = $id; $this->link = $link; } /** * Presents the Beta Badge. If a link has been passed, the badge is presented with the link. * Otherwise a static badge is presented. * * @return string The styled Beta Badge. */ public function present() { if ( $this->link !== '' ) { return \sprintf( '<a class="yoast-badge yoast-badge__is-link yoast-beta-badge" id="%1$s-beta-badge" href="%2$s">%3$s</a>', \esc_attr( $this->id ), \esc_url( $this->link ), 'Beta' // We don't want this string to be translatable. ); } return \sprintf( '<span class="yoast-badge yoast-beta-badge" id="%1$s-beta-badge">%2$s</span>', \esc_attr( $this->id ), 'Beta' // We don't want this string to be translatable. ); } } presenters/admin/premium-badge-presenter.php 0000644 00000002510 15025657560 0015273 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Represents the presenter class for "Premium" badges. */ class Premium_Badge_Presenter extends Abstract_Presenter { /** * Identifier of the badge. * * @var string */ private $id; /** * Optional link of the badge. * * @var string */ private $link; /** * Premium_Badge_Presenter constructor. * * @param string $id Id of the badge. * @param string $link Optional link of the badge. */ public function __construct( $id, $link = '' ) { $this->id = $id; $this->link = $link; } /** * Presents the Premium Badge. If a link has been passed, the badge is presented with the link. * Otherwise a static badge is presented. * * @return string The styled Premium Badge. */ public function present() { if ( $this->link !== '' ) { return \sprintf( '<a class="yoast-badge yoast-badge__is-link yoast-premium-badge" id="%1$s-premium-badge" href="%2$s">%3$s</a>', \esc_attr( $this->id ), \esc_url( $this->link ), 'Premium' // We don't want this string to be translatable. ); } return \sprintf( '<span class="yoast-badge yoast-premium-badge" id="%1$s-premium-badge">%2$s</span>', \esc_attr( $this->id ), 'Premium' // We don't want this string to be translatable. ); } } presenters/admin/sidebar-presenter.php 0000644 00000015047 15025657560 0014177 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use WPSEO_Shortlinker; use Yoast\WP\SEO\Presenters\Abstract_Presenter; use Yoast\WP\SEO\Promotions\Application\Promotion_Manager; /** * Presenter class for the Yoast SEO sidebar. */ class Sidebar_Presenter extends Abstract_Presenter { /** * Presents the sidebar. * * @return string The sidebar HTML. */ public function present() { $title = \__( '30% OFF - BLACK FRIDAY', 'wordpress-seo' ); $assets_uri = \trailingslashit( \plugin_dir_url( \WPSEO_FILE ) ); $buy_yoast_seo_shortlink = WPSEO_Shortlinker::get( 'https://yoa.st/jj' ); \ob_start(); ?> <div class="wpseo_content_cell" id="sidebar-container"> <div id="sidebar" class="yoast-sidebar"> <div class="wpseo_content_cell_title yoast-sidebar__title"> <?php /* translators: %1$s expands to Yoast */ \printf( \esc_html__( '%1$s recommendations for you', 'wordpress-seo' ), 'Yoast' ); ?> </div> <div class="yoast-sidebar__product"> <figure class="product-image"> <figure class="product-image"> <img width="75" height="75" src="<?php echo \esc_url( $assets_uri . 'packages/js/images/Yoast_SEO_Icon.svg' ); ?>" class="attachment-full size-full content-visible" alt="Yoast SEO logo" loading="lazy" decoding="asyc" fetchpriority="low" sizes="(min-width: 1321px) 75px"> </figure> </figure> <?php if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) : ?> <div class="sidebar__sale_banner_container"> <div class="sidebar__sale_banner"> <span class="banner_text"><?php echo \esc_html( $title ); ?></span> </div> </div> <?php endif; ?> <h2 class="yoast-get-premium-title"> <?php if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) { /* translators: %1$s and %2$s expand to a span wrap to avoid linebreaks. %3$s expands to "Yoast SEO Premium". */ \printf( \esc_html__( '%1$sBuy%2$s %3$s', 'wordpress-seo' ), '<span>', '</span>', 'Yoast SEO Premium' ); } else { /* translators: %1$s and %2$s expand to a span wrap to avoid linebreaks. %3$s expands to "Yoast SEO Premium". */ \printf( \esc_html__( '%1$sGet%2$s %3$s', 'wordpress-seo' ), '<span>', '</span>', 'Yoast SEO Premium' ); } ?> </h2> <p> <?php if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) { echo \esc_html__( 'If you were thinking about upgrading, now\'s the time! 30% OFF ends 3rd Dec 11am (CET)', 'wordpress-seo' ); } else { echo \esc_html__( 'Use AI to generate titles and meta descriptions, automatically redirect deleted pages, get 24/7 support, and much, much more!', 'wordpress-seo' ); } ?> </p> <p class="plugin-buy-button"> <a class="yoast-button-upsell" data-action="load-nfd-ctb" data-ctb-id="f6a84663-465f-4cb5-8ba5-f7a6d72224b2" target="_blank" href="<?php echo \esc_url( $buy_yoast_seo_shortlink ); ?>"> <?php if ( \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) { echo \esc_html__( 'Buy now', 'wordpress-seo' ); } else { /* translators: %s expands to Yoast SEO Premium */ \printf( \esc_html__( 'Get %1$s', 'wordpress-seo' ), 'Yoast SEO Premium' ); } ?> <span aria-hidden="true" class="yoast-button-upsell__caret"></span> </a> </p> <p class="yoast-price-micro-copy"> <?php if ( ! \YoastSEO()->classes->get( Promotion_Manager::class )->is( 'black-friday-2024-promotion' ) ) { echo \esc_html__( 'Only $/€/£99 per year (ex VAT)', 'wordpress-seo' ), '<br />'; } ?> <?php echo \esc_html__( '30-day money back guarantee.', 'wordpress-seo' ); ?> </p> <hr class="yoast-upsell-hr" aria-hidden="true"> <div class="review-container"> <a href="https://www.g2.com/products/yoast-yoast/reviews" target="_blank" rel="noopener"> <span class="claim"> <?php \esc_html_e( 'Read reviews from real users', 'wordpress-seo' ); ?> </span> <span class="rating"> <img alt="" loading="lazy" fetchpriority="low" decoding="async" height="22" width="22" src="<?php echo \esc_url( $assets_uri . 'packages/js/images/g2_logo_white_optm.svg' ); ?>"> <img alt="" loading="lazy" fetchpriority="low" decoding="async" height="20" width="20" src="<?php echo \esc_url( $assets_uri . 'packages/js/images/star-rating-star.svg' ); ?>"> <img alt="" loading="lazy" fetchpriority="low" decoding="async" height="20" width="20" src="<?php echo \esc_url( $assets_uri . 'packages/js/images/star-rating-star.svg' ); ?>"> <img alt="" loading="lazy" fetchpriority="low" decoding="async" height="20" width="20" src="<?php echo \esc_url( $assets_uri . 'packages/js/images/star-rating-star.svg' ); ?>"> <img alt="" loading="lazy" fetchpriority="low" decoding="async" height="20" width="20" src="<?php echo \esc_url( $assets_uri . 'packages/js/images/star-rating-star.svg' ); ?>"> <img alt="" loading="lazy" fetchpriority="low" decoding="async" height="20" width="20" src="<?php echo \esc_url( $assets_uri . 'packages/js/images/star-rating-half.svg' ); ?>"> <span class="rating-text">4.6 / 5</span> </span> </a> </div> </div> </div> <div class="yoast-sidebar__section"> <h2> <?php \esc_html_e( 'Learn SEO', 'wordpress-seo' ); ?> </h2> <p> <?php $academy_shortlink = WPSEO_Shortlinker::get( 'https://yoa.st/3t6' ); /* translators: %1$s expands to Yoast SEO academy, which is a clickable link. */ \printf( \esc_html__( 'Want to learn SEO from Team Yoast? Check out our %1$s!', 'wordpress-seo' ), '<a href="' . \esc_url( $academy_shortlink ) . '" target="_blank"><strong>Yoast SEO academy</strong></a>' ); echo '<br/>'; \esc_html_e( 'We have both free and premium online courses to learn everything you need to know about SEO.', 'wordpress-seo' ); ?> </p> <p> <a href="<?php echo \esc_url( $academy_shortlink ); ?>" target="_blank"> <?php /* translators: %1$s expands to Yoast SEO academy */ \printf( \esc_html__( 'Check out %1$s', 'wordpress-seo' ), 'Yoast SEO academy' ); ?> </a> </p> </div> </div> <?php return \ob_get_clean(); } } presenters/admin/indexing-list-item-presenter.php 0000644 00000003012 15025657560 0016265 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Admin; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Presenters\Abstract_Presenter; /** * Class Indexing_List_Item_Presenter. * * @package Yoast\WP\SEO\Presenters\Admin */ class Indexing_List_Item_Presenter extends Abstract_Presenter { /** * The short link helper. * * @var Short_Link_Helper */ protected $short_link_helper; /** * Indexing_List_Item_Presenter constructor. * * @param Short_Link_Helper $short_link_helper Represents the short link helper. */ public function __construct( Short_Link_Helper $short_link_helper ) { $this->short_link_helper = $short_link_helper; } /** * Presents the list item for the tools menu. * * @return string The list item HTML. */ public function present() { $output = \sprintf( '<li><strong>%s</strong><br/>', \esc_html__( 'Optimize SEO Data', 'wordpress-seo' ) ); $output .= \sprintf( '%1$s <a href="%2$s" target="_blank">%3$s</a>', \esc_html__( 'You can speed up your site and get insight into your internal linking structure by letting us perform a few optimizations to the way SEO data is stored. If you have a lot of content it might take a while, but trust us, it\'s worth it.', 'wordpress-seo' ), \esc_url( $this->short_link_helper->get( 'https://yoa.st/3-z' ) ), \esc_html__( 'Learn more about the benefits of optimized SEO data.', 'wordpress-seo' ) ); $output .= '<div id="yoast-seo-indexing-action" style="margin: 16px 0;"></div>'; $output .= '</li>'; return $output; } } presenters/rel-next-presenter.php 0000644 00000003203 15025657560 0013223 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use Yoast\WP\SEO\Presentations\Indexable_Presentation; /** * Presenter class for the rel next meta tag. */ class Rel_Next_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'next'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::LINK_REL_HREF; /** * The method of escaping to use. * * @var string */ protected $escaping = 'url'; /** * Returns the rel next meta tag. * * @return string The rel next tag. */ public function present() { $output = parent::present(); if ( ! empty( $output ) ) { /** * Filter: 'wpseo_next_rel_link' - Allow changing link rel output by Yoast SEO. * * @param string $unsigned The full `<link` element. */ return \apply_filters( 'wpseo_next_rel_link', $output ); } return ''; } /** * Run the canonical content through the `wpseo_adjacent_rel_url` filter. * * @return string The filtered adjacent link. */ public function get() { if ( \in_array( 'noindex', $this->presentation->robots, true ) ) { return ''; } /** * Filter: 'wpseo_adjacent_rel_url' - Allow filtering of the rel next URL put out by Yoast SEO. * * @param string $rel_next The rel next URL. * @param string $rel Link relationship, prev or next. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return (string) \trim( \apply_filters( 'wpseo_adjacent_rel_url', $this->presentation->rel_next, 'next', $this->presentation ) ); } } presenters/abstract-indexable-presenter.php 0000644 00000003063 15025657560 0015225 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use WPSEO_Replace_Vars; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Surfaces\Helpers_Surface; /** * Abstract presenter class for indexable presentations. */ abstract class Abstract_Indexable_Presenter extends Abstract_Presenter { /** * The WPSEO Replace Vars object. * * @var WPSEO_Replace_Vars */ public $replace_vars; /** * The indexable presentation. * * @var Indexable_Presentation */ public $presentation; /** * The helpers surface * * @var Helpers_Surface */ public $helpers; /** * The tag key name. * * @var string */ protected $key = 'NO KEY PROVIDED'; /** * Gets the raw value of a presentation. * * @return string|array The raw value. */ abstract public function get(); /** * Transforms an indexable presenter's key to a json safe key string. * * @return string|null */ public function escape_key() { if ( $this->key === 'NO KEY PROVIDED' ) { return null; } return \str_replace( [ ':', ' ', '-' ], '_', $this->key ); } /** * Returns the metafield's property key. * * @return string The property key. */ public function get_key() { return $this->key; } /** * Replace replacement variables in a string. * * @param string $replacevar_string The string with replacement variables. * * @return string The string with replacement variables replaced. */ protected function replace_vars( $replacevar_string ) { return $this->replace_vars->replace( $replacevar_string, $this->presentation->source ); } } presenters/abstract-presenter.php 0000644 00000000571 15025657560 0013275 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; /** * Abstract_Presenter class. */ abstract class Abstract_Presenter { /** * Returns the output as string. * * @return string The output. */ abstract public function present(); /** * Returns the output as string. * * @return string The output. */ public function __toString() { return $this->present(); } } presenters/robots-txt-presenter.php 0000644 00000010267 15025657560 0013622 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use Yoast\WP\SEO\Helpers\Robots_Txt_Helper; /** * Presenter class for the robots.txt file helper. */ class Robots_Txt_Presenter extends Abstract_Presenter { public const YOAST_OUTPUT_BEFORE_COMMENT = '# START YOAST BLOCK' . \PHP_EOL . '# ---------------------------' . \PHP_EOL; public const YOAST_OUTPUT_AFTER_COMMENT = '# ---------------------------' . \PHP_EOL . '# END YOAST BLOCK'; /** * Text to be outputted for the allow directive. * * @var string */ public const ALLOW_DIRECTIVE = 'Allow'; /** * Text to be outputted for the disallow directive. * * @var string */ public const DISALLOW_DIRECTIVE = 'Disallow'; /** * Text to be outputted for the user-agent rule. * * @var string */ public const USER_AGENT_FIELD = 'User-agent'; /** * Text to be outputted for the sitemap rule. * * @var string */ public const SITEMAP_FIELD = 'Sitemap'; /** * Holds the Robots_Txt_Helper. * * @var Robots_Txt_Helper */ protected $robots_txt_helper; /** * Constructor. * * @param Robots_Txt_Helper $robots_txt_helper The robots txt helper. */ public function __construct( Robots_Txt_Helper $robots_txt_helper ) { $this->robots_txt_helper = $robots_txt_helper; } /** * Generate content to be placed in a robots.txt file. * * @return string Content to be placed in a robots.txt file. */ public function present() { $robots_txt_content = self::YOAST_OUTPUT_BEFORE_COMMENT; $robots_txt_content = $this->handle_user_agents( $robots_txt_content ); $robots_txt_content = $this->handle_site_maps( $robots_txt_content ); return $robots_txt_content . self::YOAST_OUTPUT_AFTER_COMMENT; } /** * Adds user agent directives to the robots txt output string. * * @param array $user_agents The list if available user agents. * @param string $robots_txt_content The current working robots txt string. * * @return string */ private function add_user_agent_directives( $user_agents, $robots_txt_content ) { foreach ( $user_agents as $user_agent ) { $robots_txt_content .= self::USER_AGENT_FIELD . ': ' . $user_agent->get_user_agent() . \PHP_EOL; $robots_txt_content = $this->add_directive_path( $robots_txt_content, $user_agent->get_disallow_paths(), self::DISALLOW_DIRECTIVE ); $robots_txt_content = $this->add_directive_path( $robots_txt_content, $user_agent->get_allow_paths(), self::ALLOW_DIRECTIVE ); $robots_txt_content .= \PHP_EOL; } return $robots_txt_content; } /** * Adds user agent directives path content to the robots txt output string. * * @param string $robots_txt_content The current working robots txt string. * @param array $paths The list of paths for which to add a txt entry. * @param string $directive_identifier The identifier for the directives. (Disallow of Allow). * * @return string */ private function add_directive_path( $robots_txt_content, $paths, $directive_identifier ) { if ( \count( $paths ) > 0 ) { foreach ( $paths as $path ) { $robots_txt_content .= $directive_identifier . ': ' . $path . \PHP_EOL; } } return $robots_txt_content; } /** * Handles adding user agent content to the robots txt content if there is any. * * @param string $robots_txt_content The current working robots txt string. * * @return string */ private function handle_user_agents( $robots_txt_content ) { $user_agents = $this->robots_txt_helper->get_robots_txt_user_agents(); if ( ! isset( $user_agents['*'] ) ) { $robots_txt_content .= 'User-agent: *' . \PHP_EOL; $robots_txt_content .= 'Disallow:' . \PHP_EOL . \PHP_EOL; } $robots_txt_content = $this->add_user_agent_directives( $user_agents, $robots_txt_content ); return $robots_txt_content; } /** * Handles adding sitemap content to the robots txt content. * * @param string $robots_txt_content The current working robots txt string. * * @return string */ private function handle_site_maps( $robots_txt_content ) { $registered_sitemaps = $this->robots_txt_helper->get_sitemap_rules(); foreach ( $registered_sitemaps as $sitemap ) { $robots_txt_content .= self::SITEMAP_FIELD . ': ' . $sitemap . \PHP_EOL; } return $robots_txt_content; } } presenters/title-presenter.php 0000644 00000003344 15025657560 0012614 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use Yoast\WP\SEO\Presentations\Indexable_Presentation; /** * Presenter class for the document title. */ class Title_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'title'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = '<title>%s</title>'; /** * The method of escaping to use. * * @var string */ protected $escaping = 'html'; /** * Gets the raw value of a presentation. * * @return string The raw value. */ public function get() { // This ensures backwards compatibility with other plugins using this filter as well. \add_filter( 'pre_get_document_title', [ $this, 'get_title' ], 15 ); $title = \wp_get_document_title(); \remove_filter( 'pre_get_document_title', [ $this, 'get_title' ], 15 ); return $title; } /** * Returns a tag in the head. * * @return string The tag. */ public function present() { $value = $this->get(); if ( \is_string( $value ) && $value !== '' ) { return \sprintf( $this->tag_format, $this->escape_value( $value ) ); } return ''; } /** * Returns the presentation title. * * @return string The title. */ public function get_title() { $title = $this->replace_vars( $this->presentation->title ); /** * Filter: 'wpseo_title' - Allow changing the Yoast SEO generated title. * * @param string $title The title. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $title = \apply_filters( 'wpseo_title', $title, $this->presentation ); $title = $this->helpers->string->strip_all_tags( $title ); return \trim( $title ); } } presenters/score-icon-presenter.php 0000644 00000001660 15025657560 0013533 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; /** * Presenter class for a score icon. */ class Score_Icon_Presenter extends Abstract_Presenter { /** * Holds the title. * * @var string */ protected $title; /** * Holds the CSS class. * * @var string */ protected $css_class; /** * Constructs a Score_Icon_Presenter. * * @param string $title The title and screen reader text. * @param string $css_class The CSS class. */ public function __construct( $title, $css_class ) { $this->title = $title; $this->css_class = $css_class; } /** * Presents the score icon. * * @return string The score icon. */ public function present() { return \sprintf( '<div aria-hidden="true" title="%1$s" class="wpseo-score-icon %3$s"><span class="wpseo-score-text screen-reader-text">%2$s</span></div>', \esc_attr( $this->title ), \esc_html( $this->title ), \esc_attr( $this->css_class ) ); } } presenters/twitter/card-presenter.php 0000644 00000001551 15025657560 0014104 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Twitter; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Twitter Card tag. */ class Card_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'twitter:card'; /** * Runs the card type through the `wpseo_twitter_card_type` filter. * * @return string The filtered card type. */ public function get() { /** * Filter: 'wpseo_twitter_card_type' - Allow changing the Twitter card type. * * @param string $card_type The card type. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \trim( \apply_filters( 'wpseo_twitter_card_type', $this->presentation->twitter_card, $this->presentation ) ); } } presenters/twitter/title-presenter.php 0000644 00000001641 15025657560 0014314 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Twitter; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Twitter title. */ class Title_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'twitter:title'; /** * Run the Twitter title through replace vars and the `wpseo_twitter_title` filter. * * @return string The filtered Twitter title. */ public function get() { /** * Filter: 'wpseo_twitter_title' - Allow changing the Twitter title. * * @param string $twitter_title The Twitter title. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \trim( \apply_filters( 'wpseo_twitter_title', $this->replace_vars( $this->presentation->twitter_title ), $this->presentation ) ); } } presenters/twitter/description-presenter.php 0000644 00000002020 15025657560 0015506 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Twitter; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Twitter description. */ class Description_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'twitter:description'; /** * Run the Twitter description through replace vars and the `wpseo_twitter_description` filter. * * @return string The filtered Twitter description. */ public function get() { /** * Filter: 'wpseo_twitter_description' - Allow changing the Twitter description as output in the Twitter card by Yoast SEO. * * @param string $twitter_description The description string. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \apply_filters( 'wpseo_twitter_description', $this->replace_vars( $this->presentation->twitter_description ), $this->presentation ); } } presenters/twitter/creator-presenter.php 0000644 00000000753 15025657560 0014635 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Twitter; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Twitter creator. */ class Creator_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'twitter:creator'; /** * Gets the raw value of a presentation. * * @return string The raw value. */ public function get() { return $this->presentation->twitter_creator; } } presenters/twitter/image-presenter.php 0000644 00000001743 15025657560 0014260 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Twitter; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Twitter image. */ class Image_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'twitter:image'; /** * The method of escaping to use. * * @var string */ protected $escaping = 'url'; /** * Run the Twitter image value through the `wpseo_twitter_image` filter. * * @return string The filtered Twitter image. */ public function get() { /** * Filter: 'wpseo_twitter_image' - Allow changing the Twitter Card image. * * @param string $twitter_image Image URL string. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return (string) \apply_filters( 'wpseo_twitter_image', $this->presentation->twitter_image, $this->presentation ); } } presenters/twitter/site-presenter.php 0000644 00000003223 15025657560 0014135 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Twitter; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Twitter site tag. */ class Site_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'twitter:site'; /** * Run the Twitter site through the `wpseo_twitter_site` filter. * * @return string The filtered Twitter site. */ public function get() { /** * Filter: 'wpseo_twitter_site' - Allow changing the Twitter site account as output in the Twitter card by Yoast SEO. * * @param string $twitter_site Twitter site account string. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $twitter_site = \apply_filters( 'wpseo_twitter_site', $this->presentation->twitter_site, $this->presentation ); $twitter_site = $this->get_twitter_id( $twitter_site ); if ( ! \is_string( $twitter_site ) || $twitter_site === '' ) { return ''; } return '@' . $twitter_site; } /** * Checks if the given id is actually an id or a url and if url, distills the id from it. * * Solves issues with filters returning urls and theme's/other plugins also adding a user meta * twitter field which expects url rather than an id (which is what we expect). * * @param string $id Twitter ID or url. * * @return string|bool Twitter ID or false if it failed to get a valid Twitter ID. */ private function get_twitter_id( $id ) { if ( \preg_match( '`([A-Za-z0-9_]{1,25})$`', $id, $match ) ) { return $match[1]; } return false; } } presenters/debug/marker-close-presenter.php 0000644 00000001505 15025657560 0015142 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Debug; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter; /** * Presenter class for the debug close marker. */ final class Marker_Close_Presenter extends Abstract_Indexable_Presenter { /** * Returns the debug close marker. * * @return string The debug close marker. */ public function present() { /** * Filter: 'wpseo_debug_markers' - Allow disabling the debug markers. * * @param bool $show_markers True when the debug markers should be shown. */ if ( ! \apply_filters( 'wpseo_debug_markers', true ) ) { return ''; } return \sprintf( '<!-- / %s. -->', \esc_html( $this->helpers->product->get_name() ) ); } /** * Gets the raw value of a presentation. * * @return string The raw value. */ public function get() { return ''; } } presenters/debug/marker-open-presenter.php 0000644 00000003132 15025657560 0014774 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Debug; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter; /** * Presenter class for the debug open marker. */ final class Marker_Open_Presenter extends Abstract_Indexable_Presenter { /** * Returns the debug close marker. * * @return string The debug close marker. */ public function present() { /** * Filter: 'wpseo_debug_markers' - Allow disabling the debug markers. * * @param bool $show_markers True when the debug markers should be shown. */ if ( ! \apply_filters( 'wpseo_debug_markers', true ) ) { return ''; } $version_info = 'v' . \WPSEO_VERSION; if ( $this->helpers->product->is_premium() ) { $version_info = $this->construct_version_info(); } return \sprintf( '<!-- This site is optimized with the %1$s %2$s - https://yoast.com/wordpress/plugins/seo/ -->', \esc_html( $this->helpers->product->get_name() ), $version_info ); } /** * Gets the plugin version information, including the free version if Premium is used. * * @return string The constructed version information. */ private function construct_version_info() { /** * Filter: 'wpseo_hide_version' - can be used to hide the Yoast SEO version in the debug marker (only available in Yoast SEO Premium). * * @param bool $hide_version */ if ( \apply_filters( 'wpseo_hide_version', false ) ) { return ''; } return 'v' . \WPSEO_PREMIUM_VERSION . ' (Yoast SEO v' . \WPSEO_VERSION . ')'; } /** * Gets the raw value of a presentation. * * @return string The raw value. */ public function get() { return ''; } } presenters/robots-presenter.php 0000644 00000001307 15025657560 0013000 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; /** * Presenter class for the robots output. */ class Robots_Presenter extends Abstract_Indexable_Presenter { /** * The tag key name. * * @var string */ protected $key = 'robots'; /** * Returns the robots output. * * @return string The robots output tag. */ public function present() { $robots = \implode( ', ', $this->get() ); if ( \is_string( $robots ) && $robots !== '' ) { return \sprintf( '<meta name="robots" content="%s" />', \esc_attr( $robots ) ); } return ''; } /** * Gets the raw value of a presentation. * * @return array The raw value. */ public function get() { return $this->presentation->robots; } } presenters/abstract-indexable-tag-presenter.php 0000644 00000003373 15025657560 0016002 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; /** * Abstract presenter class for indexable tag presentations. * * @phpcs:disable Yoast.Files.FileName.InvalidClassFileName */ abstract class Abstract_Indexable_Tag_Presenter extends Abstract_Indexable_Presenter { public const META_PROPERTY_CONTENT = '<meta property="%2$s" content="%1$s"%3$s />'; public const META_NAME_CONTENT = '<meta name="%2$s" content="%1$s"%3$s />'; public const LINK_REL_HREF = '<link rel="%2$s" href="%1$s"%3$s />'; public const DEFAULT_TAG_FORMAT = self::META_NAME_CONTENT; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::DEFAULT_TAG_FORMAT; /** * The method of escaping to use. * * @var string */ protected $escaping = 'attribute'; /** * Returns a tag in the head. * * @return string The tag. */ public function present() { $value = $this->get(); if ( ! \is_string( $value ) || $value === '' ) { return ''; } /** * There may be some classes that are derived from this class that do not use the $key property * in their $tag_format string. In that case the key property will simply not be used. */ return \sprintf( $this->tag_format, $this->escape_value( $value ), $this->key, \is_admin_bar_showing() ? ' class="yoast-seo-meta-tag"' : '' ); } /** * Escaped the output. * * @param string $value The desired method of escaping; 'html', 'url' or 'attribute'. * * @return string The escaped value. */ protected function escape_value( $value ) { switch ( $this->escaping ) { case 'html': return \esc_html( $value ); case 'url': return \esc_url( $value, null, 'attribute' ); case 'attribute': default: return \esc_attr( $value ); } } } presenters/meta-author-presenter.php 0000644 00000002541 15025657560 0013717 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use WP_User; use Yoast\WP\SEO\Presentations\Indexable_Presentation; /** * Presenter class for the meta author tag. */ class Meta_Author_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'author'; /** * Returns the author for a post in a meta author tag. * * @return string The meta author tag. */ public function present() { $output = parent::present(); if ( ! empty( $output ) ) { return $output; } return ''; } /** * Get the author's display name. * * @return string The author's display name. */ public function get() { if ( $this->presentation->model->object_sub_type !== 'post' ) { return ''; } $user_data = \get_userdata( $this->presentation->context->post->post_author ); if ( ! $user_data instanceof WP_User ) { return ''; } /** * Filter: 'wpseo_meta_author' - Allow developers to filter the article's author meta tag. * * @param string $author_name The article author's display name. Return empty to disable the tag. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \trim( $this->helpers->schema->html->smart_strip_tags( \apply_filters( 'wpseo_meta_author', $user_data->display_name, $this->presentation ) ) ); } } presenters/slack/enhanced-data-presenter.php 0000644 00000004216 15025657560 0015243 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Slack; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter; /** * Presenter class for the Slack enhanced data. */ class Enhanced_Data_Presenter extends Abstract_Indexable_Presenter { /** * The tag key name. * * @var string */ protected $key = 'twitter:misc'; /** * Presents the enhanced data for Slack * * @return string The Twitter tags for Slack. */ public function present() { $enhanced_data = $this->get(); $twitter_tags = ''; $i = 1; $class = \is_admin_bar_showing() ? ' class="yoast-seo-meta-tag"' : ''; foreach ( $enhanced_data as $label => $value ) { $twitter_tags .= \sprintf( "\t" . '<meta name="twitter:label%1$d" content="%2$s"' . $class . ' />' . "\n", $i, \esc_attr( $label ) ); $twitter_tags .= \sprintf( "\t" . '<meta name="twitter:data%1$d" content="%2$s"' . $class . ' />' . "\n", $i, \esc_attr( $value ) ); ++$i; } return \trim( $twitter_tags ); } /** * Gets the enhanced data array. * * @return array The enhanced data array */ public function get() { $data = []; $author_id = $this->presentation->source->post_author; $estimated_reading_time = $this->presentation->estimated_reading_time_minutes; if ( $this->presentation->model->object_sub_type === 'post' && $author_id ) { $data[ \__( 'Written by', 'wordpress-seo' ) ] = \get_the_author_meta( 'display_name', $author_id ); } if ( ! empty( $estimated_reading_time ) ) { /* translators: %s expands to the reading time number, in minutes */ $data[ \__( 'Est. reading time', 'wordpress-seo' ) ] = \sprintf( \_n( '%s minute', '%s minutes', $estimated_reading_time, 'default' ), $estimated_reading_time ); } /** * Filter: 'wpseo_enhanced_slack_data' - Allows filtering of the enhanced data for sharing on Slack. * * @param array $data The enhanced Slack sharing data. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \apply_filters( 'wpseo_enhanced_slack_data', $data, $this->presentation ); } } presenters/meta-description-presenter.php 0000644 00000003531 15025657560 0014740 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use Yoast\WP\SEO\Presentations\Indexable_Presentation; /** * Presenter class for the meta description. */ class Meta_Description_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'description'; /** * Returns the meta description for a post. * * @return string The meta description tag. */ public function present() { $output = parent::present(); if ( ! empty( $output ) ) { return $output; } if ( \current_user_can( 'wpseo_manage_options' ) ) { return '<!-- ' . \sprintf( /* translators: %1$s resolves to Yoast SEO, %2$s resolves to the Settings submenu item. */ \esc_html__( 'Admin only notice: this page does not show a meta description because it does not have one, either write it for this page specifically or go into the [%1$s - %2$s] menu and set up a template.', 'wordpress-seo' ), \esc_html__( 'Yoast SEO', 'wordpress-seo' ), \esc_html__( 'Settings', 'wordpress-seo' ) ) . ' -->'; } return ''; } /** * Run the meta description content through replace vars, the `wpseo_metadesc` filter and sanitization. * * @return string The filtered meta description. */ public function get() { $meta_description = $this->replace_vars( $this->presentation->meta_description ); /** * Filter: 'wpseo_metadesc' - Allow changing the Yoast SEO meta description sentence. * * @param string $meta_description The description sentence. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $meta_description = \apply_filters( 'wpseo_metadesc', $meta_description, $this->presentation ); $meta_description = $this->helpers->string->strip_all_tags( \stripslashes( $meta_description ) ); return \trim( $meta_description ); } } presenters/canonical-presenter.php 0000644 00000002175 15025657560 0013423 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use Yoast\WP\SEO\Presentations\Indexable_Presentation; /** * Presenter class for the canonical. */ class Canonical_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'canonical'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::LINK_REL_HREF; /** * The method of escaping to use. * * @var string */ protected $escaping = 'url'; /** * Run the canonical content through the `wpseo_canonical` filter. * * @return string The filtered canonical. */ public function get() { if ( \in_array( 'noindex', $this->presentation->robots, true ) ) { return ''; } /** * Filter: 'wpseo_canonical' - Allow filtering of the canonical URL put out by Yoast SEO. * * @param string $canonical The canonical URL. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \urldecode( \trim( (string) \apply_filters( 'wpseo_canonical', $this->presentation->canonical, $this->presentation ) ) ); } } presenters/rel-prev-presenter.php 0000644 00000003301 15025657560 0013220 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; /** * Presenter class for the rel prev meta tag. */ class Rel_Prev_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'prev'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::LINK_REL_HREF; /** * The method of escaping to use. * * @var string */ protected $escaping = 'url'; /** * Returns the rel prev meta tag. * * @param bool $output_tag Optional. Whether or not to output the HTML tag. Defaults to true. * * @return string The rel prev tag. */ public function present( $output_tag = true ) { $output = parent::present(); if ( ! empty( $output ) ) { /** * Filter: 'wpseo_prev_rel_link' - Allow changing link rel output by Yoast SEO. * * @param string $unsigned The full `<link` element. */ return \apply_filters( 'wpseo_prev_rel_link', $output ); } return ''; } /** * Run the rel prev content through the `wpseo_adjacent_rel_url` filter. * * @return string The filtered adjacent link. */ public function get() { if ( \in_array( 'noindex', $this->presentation->robots, true ) ) { return ''; } /** * Filter: 'wpseo_adjacent_rel_url' - Allow filtering of the rel prev URL put out by Yoast SEO. * * @param string $canonical The rel prev URL. * @param string $rel Link relationship, prev or next. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return (string) \trim( \apply_filters( 'wpseo_adjacent_rel_url', $this->presentation->rel_prev, 'prev', $this->presentation ) ); } } presenters/breadcrumbs-presenter.php 0000644 00000017055 15025657560 0013770 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use Yoast\WP\SEO\Presentations\Indexable_Presentation; /** * Presenter class for the breadcrumbs. */ class Breadcrumbs_Presenter extends Abstract_Indexable_Presenter { /** * The id attribute. * * @var string */ private $id; /** * The class name attribute. * * @var string */ private $class; /** * The wrapper element name. * * @var string */ private $wrapper; /** * Separator to use. * * @var string */ private $separator; /** * The element. * * @var string */ private $element; /** * Presents the breadcrumbs. * * @return string The breadcrumbs HTML. */ public function present() { $breadcrumbs = $this->get(); if ( ! \is_array( $breadcrumbs ) || empty( $breadcrumbs ) ) { return ''; } $links = []; $total = \count( $breadcrumbs ); foreach ( $breadcrumbs as $index => $breadcrumb ) { $links[ $index ] = $this->crumb_to_link( $breadcrumb, $index, $total ); } // Removes any effectively empty links. $links = \array_map( 'trim', $links ); $links = \array_filter( $links ); $output = \implode( $this->get_separator(), $links ); if ( empty( $output ) ) { return ''; } $output = '<' . $this->get_wrapper() . $this->get_id() . $this->get_class() . '>' . $output . '</' . $this->get_wrapper() . '>'; $output = $this->filter( $output ); $prefix = $this->helpers->options->get( 'breadcrumbs-prefix' ); if ( $prefix !== '' ) { $output = "\t" . $prefix . "\n" . $output; } return $output; } /** * Gets the raw value of a presentation. * * @return array The raw value. */ public function get() { return $this->presentation->breadcrumbs; } /** * Filters the output. * * @param string $output The HTML output. * * @return string The filtered output. */ protected function filter( $output ) { /** * Filter: 'wpseo_breadcrumb_output' - Allow changing the HTML output of the Yoast SEO breadcrumbs class. * * @param string $output The HTML output. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \apply_filters( 'wpseo_breadcrumb_output', $output, $this->presentation ); } /** * Create a breadcrumb element string. * * @param array $breadcrumb Link info array containing the keys: * 'text' => (string) link text. * 'url' => (string) link url. * (optional) 'title' => (string) link title attribute text. * @param int $index Index for the current breadcrumb. * @param int $total The total number of breadcrumbs. * * @return string The breadcrumb link. */ protected function crumb_to_link( $breadcrumb, $index, $total ) { $link = ''; if ( ! isset( $breadcrumb['text'] ) || ! \is_string( $breadcrumb['text'] ) || empty( $breadcrumb['text'] ) ) { return $link; } $text = \trim( $breadcrumb['text'] ); if ( $index < ( $total - 1 ) && isset( $breadcrumb['url'] ) && \is_string( $breadcrumb['url'] ) && ! empty( $breadcrumb['url'] ) ) { // If it's not the last element and we have a url. $link .= '<' . $this->get_element() . '>'; $title_attr = isset( $breadcrumb['title'] ) ? ' title="' . \esc_attr( $breadcrumb['title'] ) . '"' : ''; $link .= '<a'; if ( $this->should_link_target_blank() ) { $link .= ' target="_blank"'; } $link .= ' href="' . \esc_url( $breadcrumb['url'] ) . '"' . $title_attr . '>' . $text . '</a>'; $link .= '</' . $this->get_element() . '>'; } elseif ( $index === ( $total - 1 ) ) { // If it's the last element. if ( $this->helpers->options->get( 'breadcrumbs-boldlast' ) === true ) { $text = '<strong>' . $text . '</strong>'; } $link .= '<' . $this->get_element() . ' class="breadcrumb_last" aria-current="page">' . $text . '</' . $this->get_element() . '>'; } else { // It's not the last element and has no url. $link .= '<' . $this->get_element() . '>' . $text . '</' . $this->get_element() . '>'; } /** * Filter: 'wpseo_breadcrumb_single_link' - Allow changing of each link being put out by the Yoast SEO breadcrumbs class. * * @param string $link_output The output string. * @param array $link The breadcrumb link array. */ return \apply_filters( 'wpseo_breadcrumb_single_link', $link, $breadcrumb ); } /** * Retrieves HTML ID attribute. * * @return string The id attribute. */ protected function get_id() { if ( ! $this->id ) { /** * Filter: 'wpseo_breadcrumb_output_id' - Allow changing the HTML ID on the Yoast SEO breadcrumbs wrapper element. * * @param string $unsigned ID to add to the wrapper element. */ $this->id = \apply_filters( 'wpseo_breadcrumb_output_id', '' ); if ( ! \is_string( $this->id ) ) { return ''; } if ( $this->id !== '' ) { $this->id = ' id="' . \esc_attr( $this->id ) . '"'; } } return $this->id; } /** * Retrieves HTML Class attribute. * * @return string The class attribute. */ protected function get_class() { if ( ! $this->class ) { /** * Filter: 'wpseo_breadcrumb_output_class' - Allow changing the HTML class on the Yoast SEO breadcrumbs wrapper element. * * @param string $unsigned Class to add to the wrapper element. */ $this->class = \apply_filters( 'wpseo_breadcrumb_output_class', '' ); if ( ! \is_string( $this->class ) ) { return ''; } if ( $this->class !== '' ) { $this->class = ' class="' . \esc_attr( $this->class ) . '"'; } } return $this->class; } /** * Retrieves the wrapper element name. * * @return string The wrapper element name. */ protected function get_wrapper() { if ( ! $this->wrapper ) { $this->wrapper = \apply_filters( 'wpseo_breadcrumb_output_wrapper', 'span' ); $this->wrapper = \tag_escape( $this->wrapper ); if ( ! \is_string( $this->wrapper ) || $this->wrapper === '' ) { $this->wrapper = 'span'; } } return $this->wrapper; } /** * Retrieves the separator. * * @return string The separator. */ protected function get_separator() { if ( ! $this->separator ) { $this->separator = \apply_filters( 'wpseo_breadcrumb_separator', $this->helpers->options->get( 'breadcrumbs-sep' ) ); $this->separator = ' ' . $this->separator . ' '; } return $this->separator; } /** * Retrieves the crumb element name. * * @return string The element to use. */ protected function get_element() { if ( ! $this->element ) { $this->element = \esc_attr( \apply_filters( 'wpseo_breadcrumb_single_link_wrapper', 'span' ) ); } return $this->element; } /** * This is needed because when the editor is loaded in an Iframe the link needs to open in a different browser window. * We don't want this behaviour in the front-end and the way to check this is to check if the block is rendered in a REST request with the `context` set as 'edit'. Thus being in the editor. * * @return bool returns if the breadcrumb should be opened in another window. */ private function should_link_target_blank(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['context'] ) && \is_string( $_GET['context'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, We are only strictly comparing. if ( \wp_unslash( $_GET['context'] ) === 'edit' ) { return true; } } return false; } } presenters/url-list-presenter.php 0000644 00000002431 15025657560 0013242 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; /** * Presenter class for the URL list. */ class Url_List_Presenter extends Abstract_Presenter { /** * If the url should be target blank. * * @var bool */ private $target_blank; /** * A list of arrays containing titles and URLs. * * @var array */ private $links; /** * Classname for the URL list. * * @var string */ private $class_name; /** * Url_List_Presenter constructor. * * @param array $links A list of arrays containing titles and urls. * @param string $class_name Classname for the url list. * @param bool $target_blank If the url should be target blank. */ public function __construct( $links, $class_name = 'yoast-url-list', $target_blank = false ) { $this->links = $links; $this->class_name = $class_name; $this->target_blank = $target_blank; } /** * Presents the URL list. * * @return string The URL list. */ public function present() { $output = '<ul class="' . $this->class_name . '">'; foreach ( $this->links as $link ) { $output .= '<li><a'; if ( $this->target_blank ) { $output .= ' target = "_blank"'; } $output .= ' href="' . $link['permalink'] . '">' . $link['title'] . '</a></li>'; } $output .= '</ul>'; return $output; } } presenters/schema-presenter.php 0000644 00000002710 15025657560 0012727 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters; use WPSEO_Utils; /** * Presenter class for the schema object. */ class Schema_Presenter extends Abstract_Indexable_Presenter { /** * The tag key name. * * @var string */ protected $key = 'schema'; /** * Returns the schema output. * * @return string The schema tag. */ public function present() { $deprecated_data = [ '_deprecated' => 'Please use the "wpseo_schema_*" filters to extend the Yoast SEO schema data - see the WPSEO_Schema class.', ]; /** * Filter: 'wpseo_json_ld_output' - Allows disabling Yoast's schema output entirely. * * @param mixed $deprecated If false or an empty array is returned, disable our output. * @param string $empty */ $return = \apply_filters( 'wpseo_json_ld_output', $deprecated_data, '' ); if ( $return === [] || $return === false ) { return ''; } /** * Action: 'wpseo_json_ld' - Output Schema before the main schema from Yoast SEO is output. */ \do_action( 'wpseo_json_ld' ); $schema = $this->get(); if ( \is_array( $schema ) ) { $output = WPSEO_Utils::format_json_encode( $schema ); $output = \str_replace( "\n", \PHP_EOL . "\t", $output ); return '<script type="application/ld+json" class="yoast-schema-graph">' . $output . '</script>'; } return ''; } /** * Gets the raw value of a presentation. * * @return array The raw value. */ public function get() { return $this->presentation->schema; } } presenters/open-graph/url-presenter.php 0000644 00000002161 15025657560 0014331 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph URL. */ class Url_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'og:url'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * The method of escaping to use. * * @var string */ protected $escaping = 'attribute'; /** * Run the url content through the `wpseo_opengraph_url` filter. * * @return string The filtered url. */ public function get() { /** * Filter: 'wpseo_opengraph_url' - Allow changing the Yoast SEO generated open graph URL. * * @param string $url The open graph URL. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \urldecode( (string) \apply_filters( 'wpseo_opengraph_url', $this->presentation->open_graph_url, $this->presentation ) ); } } presenters/open-graph/title-presenter.php 0000644 00000002215 15025657560 0014650 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph title. */ class Title_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'og:title'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Run the title content through replace vars, the `wpseo_opengraph_title` filter and sanitization. * * @return string The filtered title. */ public function get() { $title = $this->replace_vars( $this->presentation->open_graph_title ); /** * Filter: 'wpseo_opengraph_title' - Allow changing the Yoast SEO generated title. * * @param string $title The title. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $title = \trim( (string) \apply_filters( 'wpseo_opengraph_title', $title, $this->presentation ) ); return $this->helpers->string->strip_all_tags( $title ); } } presenters/open-graph/description-presenter.php 0000644 00000002472 15025657560 0016057 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph description. */ class Description_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'og:description'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Run the Open Graph description through replace vars and the `wpseo_opengraph_desc` filter and sanitization. * * @return string The filtered description. */ public function get() { $meta_og_description = $this->replace_vars( $this->presentation->open_graph_description ); /** * Filter: 'wpseo_opengraph_desc' - Allow changing the Yoast SEO generated Open Graph description. * * @param string $description The description. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $meta_og_description = \apply_filters( 'wpseo_opengraph_desc', $meta_og_description, $this->presentation ); $meta_og_description = $this->helpers->string->strip_all_tags( \stripslashes( $meta_og_description ) ); return \trim( $meta_og_description ); } } presenters/open-graph/article-publisher-presenter.php 0000644 00000002270 15025657560 0017146 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph article publisher. */ class Article_Publisher_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'article:publisher'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Run the article publisher's Facebook URL through the `wpseo_og_article_publisher` filter. * * @return string The filtered article publisher's Facebook URL. */ public function get() { /** * Filter: 'wpseo_og_article_publisher' - Allow developers to filter the article publisher's Facebook URL. * * @param bool|string $article_publisher The article publisher's Facebook URL, return false to disable. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \trim( \apply_filters( 'wpseo_og_article_publisher', $this->presentation->open_graph_article_publisher, $this->presentation ) ); } } presenters/open-graph/article-published-time-presenter.php 0000644 00000001250 15025657560 0020061 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph article published time. */ class Article_Published_Time_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'article:published_time'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Gets the raw value of a presentation. * * @return string The raw value. */ public function get() { return $this->presentation->open_graph_article_published_time; } } presenters/open-graph/article-modified-time-presenter.php 0000644 00000001244 15025657560 0017665 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph article modified time. */ class Article_Modified_Time_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'article:modified_time'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Gets the raw value of a presentation. * * @return string The raw value. */ public function get() { return $this->presentation->open_graph_article_modified_time; } } presenters/open-graph/type-presenter.php 0000644 00000001762 15025657560 0014516 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph type. */ class Type_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'og:type'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Run the opengraph type content through the `wpseo_opengraph_type` filter. * * @return string The filtered type. */ public function get() { /** * Filter: 'wpseo_opengraph_type' - Allow changing the opengraph type. * * @param string $type The type. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return (string) \apply_filters( 'wpseo_opengraph_type', $this->presentation->open_graph_type, $this->presentation ); } } presenters/open-graph/site-name-presenter.php 0000644 00000002066 15025657560 0015415 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph site name. */ class Site_Name_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'og:site_name'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Runs the site name through the `wpseo_opengraph_site_name` filter. * * @return string The filtered site_name. */ public function get() { /** * Filter: 'wpseo_opengraph_site_name' - Allow changing the Yoast SEO generated Open Graph site name. * * @param string $site_name The site_name. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return (string) \trim( \apply_filters( 'wpseo_opengraph_site_name', $this->presentation->open_graph_site_name, $this->presentation ) ); } } presenters/open-graph/article-author-presenter.php 0000644 00000002251 15025657560 0016452 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Presenter class for the Open Graph article author. */ class Article_Author_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'article:author'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Run the article author's Facebook URL through the `wpseo_opengraph_author_facebook` filter. * * @return string The filtered article author's Facebook URL. */ public function get() { /** * Filter: 'wpseo_opengraph_author_facebook' - Allow developers to filter the article author's Facebook URL. * * @param bool|string $article_author The article author's Facebook URL, return false to disable. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \trim( \apply_filters( 'wpseo_opengraph_author_facebook', $this->presentation->open_graph_article_author, $this->presentation ) ); } } presenters/open-graph/image-presenter.php 0000644 00000010377 15025657560 0014621 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Presenter; /** * Presenter class for the Open Graph image. */ class Image_Presenter extends Abstract_Indexable_Presenter { /** * The tag key name. * * @var string */ protected $key = 'og:image'; /** * Image tags that we output for each image. * * @var array<string> */ protected static $image_tags = [ 'width' => 'width', 'height' => 'height', 'type' => 'type', ]; /** * Returns the image for a post. * * @return string The image tag. */ public function present() { $images = $this->get(); if ( empty( $images ) ) { return ''; } $return = ''; foreach ( $images as $image_meta ) { $image_url = $image_meta['url']; if ( \is_attachment() ) { global $wp; $image_url = \home_url( $wp->request ); } $class = \is_admin_bar_showing() ? ' class="yoast-seo-meta-tag"' : ''; $return .= '<meta property="og:image" content="' . \esc_url( $image_url, null, 'attribute' ) . '"' . $class . ' />'; foreach ( static::$image_tags as $key => $value ) { if ( empty( $image_meta[ $key ] ) ) { continue; } $return .= \PHP_EOL . "\t" . '<meta property="og:image:' . \esc_attr( $key ) . '" content="' . \esc_attr( $image_meta[ $key ] ) . '"' . $class . ' />'; } } return $return; } /** * Gets the raw value of a presentation. * * @return array<string, int> The raw value. */ public function get() { $images = []; foreach ( $this->presentation->open_graph_images as $open_graph_image ) { $images[] = \array_intersect_key( // First filter the object. $this->filter( $open_graph_image ), // Then strip all keys that aren't in the image tags or the url. \array_flip( \array_merge( static::$image_tags, [ 'url' ] ) ) ); } return \array_filter( $images ); } /** * Run the image content through the `wpseo_opengraph_image` filter. * * @param array<string, string|int> $image The image. * * @return array<string, string|int> The filtered image. */ protected function filter( $image ) { /** * Filter: 'wpseo_opengraph_image' - Allow changing the Open Graph image url. * * @param string $image_url The URL of the Open Graph image. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $image_url = \apply_filters( 'wpseo_opengraph_image', $image['url'], $this->presentation ); if ( ! empty( $image_url ) && \is_string( $image_url ) ) { $image['url'] = \trim( $image_url ); } $image_type = ( $image['type'] ?? '' ); /** * Filter: 'wpseo_opengraph_image_type' - Allow changing the Open Graph image type. * * @param string $image_type The type of the Open Graph image. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $image_type = \apply_filters( 'wpseo_opengraph_image_type', $image_type, $this->presentation ); if ( ! empty( $image_type ) && \is_string( $image_type ) ) { $image['type'] = \trim( $image_type ); } else { $image['type'] = ''; } $image_width = ( $image['width'] ?? '' ); /** * Filter: 'wpseo_opengraph_image_width' - Allow changing the Open Graph image width. * * @param int $image_width The width of the Open Graph image. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $image_width = (int) \apply_filters( 'wpseo_opengraph_image_width', $image_width, $this->presentation ); if ( ! empty( $image_width ) && $image_width > 0 ) { $image['width'] = $image_width; } else { $image['width'] = ''; } $image_height = ( $image['height'] ?? '' ); /** * Filter: 'wpseo_opengraph_image_height' - Allow changing the Open Graph image height. * * @param int $image_height The height of the Open Graph image. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $image_height = (int) \apply_filters( 'wpseo_opengraph_image_height', $image_height, $this->presentation ); if ( ! empty( $image_height ) && $image_height > 0 ) { $image['height'] = $image_height; } else { $image['height'] = ''; } return $image; } } presenters/open-graph/locale-presenter.php 0000644 00000002007 15025657560 0014765 0 ustar 00 <?php namespace Yoast\WP\SEO\Presenters\Open_Graph; use Yoast\WP\SEO\Presentations\Indexable_Presentation; use Yoast\WP\SEO\Presenters\Abstract_Indexable_Tag_Presenter; /** * Final presenter class for the Open Graph locale. */ final class Locale_Presenter extends Abstract_Indexable_Tag_Presenter { /** * The tag key name. * * @var string */ protected $key = 'og:locale'; /** * The tag format including placeholders. * * @var string */ protected $tag_format = self::META_PROPERTY_CONTENT; /** * Run the locale through the `wpseo_og_locale` filter. * * @return string The filtered locale. */ public function get() { /** * Filter: 'wpseo_og_locale' - Allow changing the Yoast SEO Open Graph locale. * * @param string $locale The locale string * @param Indexable_Presentation $presentation The presentation of an indexable. */ return (string) \trim( \apply_filters( 'wpseo_og_locale', $this->presentation->open_graph_locale, $this->presentation ) ); } } routes/first-time-configuration-route.php 0000644 00000020076 15025657560 0014702 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Actions\Configuration\First_Time_Configuration_Action; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Main; /** * First_Time_Configuration_Route class. */ class First_Time_Configuration_Route implements Route_Interface { use No_Conditionals; /** * Represents the first time configuration route. * * @var string */ public const CONFIGURATION_ROUTE = '/configuration'; /** * Represents a site representation route. * * @var string */ public const SITE_REPRESENTATION_ROUTE = '/site_representation'; /** * Represents a social profiles route. * * @var string */ public const SOCIAL_PROFILES_ROUTE = '/social_profiles'; /** * Represents a route to enable/disable tracking. * * @var string */ public const ENABLE_TRACKING_ROUTE = '/enable_tracking'; /** * Represents a route to check if current user has the correct capabilities to edit another user's profile. * * @var string */ public const CHECK_CAPABILITY_ROUTE = '/check_capability'; /** * Represents a route to save the first time configuration state. * * @var string */ public const SAVE_CONFIGURATION_STATE_ROUTE = '/save_configuration_state'; /** * Represents a route to save the first time configuration state. * * @var string */ public const GET_CONFIGURATION_STATE_ROUTE = '/get_configuration_state'; /** * The first tinme configuration action. * * @var First_Time_Configuration_Action */ private $first_time_configuration_action; /** * First_Time_Configuration_Route constructor. * * @param First_Time_Configuration_Action $first_time_configuration_action The first-time configuration action. */ public function __construct( First_Time_Configuration_Action $first_time_configuration_action ) { $this->first_time_configuration_action = $first_time_configuration_action; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $site_representation_route = [ 'methods' => 'POST', 'callback' => [ $this, 'set_site_representation' ], 'permission_callback' => [ $this, 'can_manage_options' ], 'args' => [ 'company_or_person' => [ 'type' => 'string', 'enum' => [ 'company', 'person', ], 'required' => true, ], 'company_name' => [ 'type' => 'string', ], 'company_logo' => [ 'type' => 'string', ], 'company_logo_id' => [ 'type' => 'integer', ], 'person_logo' => [ 'type' => 'string', ], 'person_logo_id' => [ 'type' => 'integer', ], 'company_or_person_user_id' => [ 'type' => 'integer', ], 'description' => [ 'type' => 'string', ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::CONFIGURATION_ROUTE . self::SITE_REPRESENTATION_ROUTE, $site_representation_route ); $social_profiles_route = [ 'methods' => 'POST', 'callback' => [ $this, 'set_social_profiles' ], 'permission_callback' => [ $this, 'can_manage_options' ], 'args' => [ 'facebook_site' => [ 'type' => 'string', ], 'twitter_site' => [ 'type' => 'string', ], 'other_social_urls' => [ 'type' => 'array', ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::CONFIGURATION_ROUTE . self::SOCIAL_PROFILES_ROUTE, $social_profiles_route ); $check_capability_route = [ 'methods' => 'GET', 'callback' => [ $this, 'check_capability' ], 'permission_callback' => [ $this, 'can_manage_options' ], 'args' => [ 'user_id' => [ 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::CONFIGURATION_ROUTE . self::CHECK_CAPABILITY_ROUTE, $check_capability_route ); $enable_tracking_route = [ 'methods' => 'POST', 'callback' => [ $this, 'set_enable_tracking' ], 'permission_callback' => [ $this, 'can_manage_options' ], 'args' => [ 'tracking' => [ 'type' => 'boolean', 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::CONFIGURATION_ROUTE . self::ENABLE_TRACKING_ROUTE, $enable_tracking_route ); $save_configuration_state_route = [ 'methods' => 'POST', 'callback' => [ $this, 'save_configuration_state' ], 'permission_callback' => [ $this, 'can_manage_options' ], 'args' => [ 'finishedSteps' => [ 'type' => 'array', 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::CONFIGURATION_ROUTE . self::SAVE_CONFIGURATION_STATE_ROUTE, $save_configuration_state_route ); $get_configuration_state_route = [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_configuration_state' ], 'permission_callback' => [ $this, 'can_manage_options' ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::CONFIGURATION_ROUTE . self::GET_CONFIGURATION_STATE_ROUTE, $get_configuration_state_route ); } /** * Sets the site representation values. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response */ public function set_site_representation( WP_REST_Request $request ) { $data = $this ->first_time_configuration_action ->set_site_representation( $request->get_json_params() ); return new WP_REST_Response( $data, $data->status ); } /** * Sets the social profiles values. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response */ public function set_social_profiles( WP_REST_Request $request ) { $data = $this ->first_time_configuration_action ->set_social_profiles( $request->get_json_params() ); return new WP_REST_Response( [ 'json' => $data ] ); } /** * Checks if the current user has the correct capability to edit a specific user. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response */ public function check_capability( WP_REST_Request $request ) { $data = $this ->first_time_configuration_action ->check_capability( $request->get_param( 'user_id' ) ); return new WP_REST_Response( $data ); } /** * Enables or disables tracking. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response */ public function set_enable_tracking( WP_REST_Request $request ) { $data = $this ->first_time_configuration_action ->set_enable_tracking( $request->get_json_params() ); return new WP_REST_Response( $data, $data->status ); } /** * Checks if the current user has the right capability. * * @return bool */ public function can_manage_options() { return \current_user_can( 'wpseo_manage_options' ); } /** * Checks if the current user has the capability to edit a specific user. * * @param WP_REST_Request $request The request. * * @return bool */ public function can_edit_user( WP_REST_Request $request ) { $response = $this->first_time_configuration_action->check_capability( $request->get_param( 'user_id' ) ); return $response->success; } /** * Checks if the current user has the capability to edit posts of other users. * * @return bool */ public function can_edit_other_posts() { return \current_user_can( 'edit_others_posts' ); } /** * Saves the first time configuration state. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response */ public function save_configuration_state( WP_REST_Request $request ) { $data = $this ->first_time_configuration_action ->save_configuration_state( $request->get_json_params() ); return new WP_REST_Response( $data, $data->status ); } /** * Returns the first time configuration state. * * @return WP_REST_Response the state of the configuration. */ public function get_configuration_state() { $data = $this ->first_time_configuration_action ->get_configuration_state(); return new WP_REST_Response( $data, $data->status ); } } routes/yoast-head-rest-field.php 0000644 00000013456 15025657560 0012714 0 ustar 00 <?php // phpcs:ignore Yoast.Files.FileName.InvalidClassFileName -- Reason: this explicitly concerns the Yoast head fields. namespace Yoast\WP\SEO\Routes; use Yoast\WP\SEO\Actions\Indexables\Indexable_Head_Action; use Yoast\WP\SEO\Conditionals\Headless_Rest_Endpoints_Enabled_Conditional; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; /** * Yoast_Head_REST_Field class. * * Registers the yoast head REST field. * Not technically a route but behaves the same so is included here. */ class Yoast_Head_REST_Field implements Route_Interface { /** * The name of the Yoast head field. * * @var string */ public const YOAST_HEAD_ATTRIBUTE_NAME = 'yoast_head'; /** * The name of the Yoast head JSON field. * * @var string */ public const YOAST_JSON_HEAD_ATTRIBUTE_NAME = 'yoast_head_json'; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * The taxonomy helper. * * @var Taxonomy_Helper */ protected $taxonomy_helper; /** * The post helper. * * @var Post_Helper */ protected $post_helper; /** * The head action. * * @var Indexable_Head_Action */ protected $head_action; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Headless_Rest_Endpoints_Enabled_Conditional::class ]; } /** * Yoast_Head_REST_Field constructor. * * @param Post_Type_Helper $post_type_helper The post type helper. * @param Taxonomy_Helper $taxonomy_helper The taxonomy helper. * @param Post_Helper $post_helper The post helper. * @param Indexable_Head_Action $head_action The head action. */ public function __construct( Post_Type_Helper $post_type_helper, Taxonomy_Helper $taxonomy_helper, Post_Helper $post_helper, Indexable_Head_Action $head_action ) { $this->post_type_helper = $post_type_helper; $this->taxonomy_helper = $taxonomy_helper; $this->post_helper = $post_helper; $this->head_action = $head_action; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $public_post_types = $this->post_type_helper->get_indexable_post_types(); foreach ( $public_post_types as $post_type ) { $this->register_rest_fields( $post_type, 'for_post' ); } $public_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies(); foreach ( $public_taxonomies as $taxonomy ) { if ( $taxonomy === 'post_tag' ) { $taxonomy = 'tag'; } $this->register_rest_fields( $taxonomy, 'for_term' ); } $this->register_rest_fields( 'user', 'for_author' ); $this->register_rest_fields( 'type', 'for_post_type_archive' ); } /** * Returns the head for a post. * * @param array $params The rest request params. * @param string $format The desired output format. * * @return string|null The head. */ public function for_post( $params, $format = self::YOAST_HEAD_ATTRIBUTE_NAME ) { if ( ! isset( $params['id'] ) ) { return null; } if ( ! $this->post_helper->is_post_indexable( $params['id'] ) ) { return null; } $obj = $this->head_action->for_post( $params['id'] ); return $this->render_object( $obj, $format ); } /** * Returns the head for a term. * * @param array $params The rest request params. * @param string $format The desired output format. * * @return string|null The head. */ public function for_term( $params, $format = self::YOAST_HEAD_ATTRIBUTE_NAME ) { $obj = $this->head_action->for_term( $params['id'] ); return $this->render_object( $obj, $format ); } /** * Returns the head for an author. * * @param array $params The rest request params. * @param string $format The desired output format. * * @return string|null The head. */ public function for_author( $params, $format = self::YOAST_HEAD_ATTRIBUTE_NAME ) { $obj = $this->head_action->for_author( $params['id'] ); return $this->render_object( $obj, $format ); } /** * Returns the head for a post type archive. * * @param array $params The rest request params. * @param string $format The desired output format. * * @return string|null The head. */ public function for_post_type_archive( $params, $format = self::YOAST_HEAD_ATTRIBUTE_NAME ) { if ( $params['slug'] === 'post' ) { $obj = $this->head_action->for_posts_page(); } elseif ( ! $this->post_type_helper->has_archive( $params['slug'] ) ) { return null; } else { $obj = $this->head_action->for_post_type_archive( $params['slug'] ); } return $this->render_object( $obj, $format ); } /** * Registers the Yoast rest fields. * * @param string $object_type The object type. * @param string $callback The function name of the callback. * * @return void */ protected function register_rest_fields( $object_type, $callback ) { // Output metadata in page head meta tags. \register_rest_field( $object_type, self::YOAST_HEAD_ATTRIBUTE_NAME, [ 'get_callback' => [ $this, $callback ] ] ); // Output metadata in a json object in a head meta tag. \register_rest_field( $object_type, self::YOAST_JSON_HEAD_ATTRIBUTE_NAME, [ 'get_callback' => [ $this, $callback ] ] ); } /** * Returns the correct property for the Yoast head. * * @param stdObject $head The Yoast head. * @param string $format The format to return. * * @return string|array|null The output value. String if HTML was requested, array otherwise. */ protected function render_object( $head, $format = self::YOAST_HEAD_ATTRIBUTE_NAME ) { if ( $head->status === 404 ) { return null; } switch ( $format ) { case self::YOAST_HEAD_ATTRIBUTE_NAME: return $head->html; case self::YOAST_JSON_HEAD_ATTRIBUTE_NAME: return $head->json; } return null; } } routes/integrations-route.php 0000644 00000004301 15025657560 0012451 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Actions\Integrations_Action; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Main; /** * Integrations_Route class. */ class Integrations_Route implements Route_Interface { use No_Conditionals; /** * Represents the integrations route. * * @var string */ public const INTEGRATIONS_ROUTE = '/integrations'; /** * Represents a route to set the state of an integration. * * @var string */ public const SET_ACTIVE_ROUTE = '/set_active'; /** * The integrations action. * * @var Integrations_Action */ private $integrations_action; /** * Integrations_Route constructor. * * @param Integrations_Action $integrations_action The integrations action. */ public function __construct( Integrations_Action $integrations_action ) { $this->integrations_action = $integrations_action; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $set_active_route = [ 'methods' => 'POST', 'callback' => [ $this, 'set_integration_active' ], 'permission_callback' => [ $this, 'can_manage_options' ], 'args' => [ 'active' => [ 'type' => 'boolean', 'required' => true, ], 'integration' => [ 'type' => 'string', 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::INTEGRATIONS_ROUTE . self::SET_ACTIVE_ROUTE, $set_active_route ); } /** * Checks if the current user has the right capability. * * @return bool */ public function can_manage_options() { return \current_user_can( 'wpseo_manage_options' ); } /** * Sets integration state. * * @param WP_REST_Request $request The request. * * @return WP_REST_Response */ public function set_integration_active( WP_REST_Request $request ) { $params = $request->get_json_params(); $integration_name = $params['integration']; $value = $params['active']; $data = $this ->integrations_action ->set_integration_active( $integration_name, $value ); return new WP_REST_Response( [ 'json' => $data ] ); } } routes/abstract-indexation-route.php 0000644 00000001604 15025657560 0013711 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Response; use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface; /** * Abstract_Indexation_Route class. * * Reindexing route for indexables. */ abstract class Abstract_Indexation_Route extends Abstract_Action_Route { /** * Runs an indexing action and returns the response. * * @param Indexation_Action_Interface $indexation_action The indexing action. * @param string $url The url of the indexing route. * * @return WP_REST_Response The response. */ protected function run_indexation_action( Indexation_Action_Interface $indexation_action, $url ) { $indexables = $indexation_action->index(); $next_url = false; if ( \count( $indexables ) >= $indexation_action->get_limit() ) { $next_url = \rest_url( $url ); } return $this->respond_with( $indexables, $next_url ); } } routes/wincher-route.php 0000644 00000021400 15025657560 0011401 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Actions\Wincher\Wincher_Account_Action; use Yoast\WP\SEO\Actions\Wincher\Wincher_Keyphrases_Action; use Yoast\WP\SEO\Actions\Wincher\Wincher_Login_Action; use Yoast\WP\SEO\Conditionals\Wincher_Enabled_Conditional; use Yoast\WP\SEO\Main; /** * Wincher_Route class. */ class Wincher_Route implements Route_Interface { /** * The Wincher route prefix. * * @var string */ public const ROUTE_PREFIX = 'wincher'; /** * The authorize route constant. * * @var string */ public const AUTHORIZATION_URL_ROUTE = self::ROUTE_PREFIX . '/authorization-url'; /** * The authenticate route constant. * * @var string */ public const AUTHENTICATION_ROUTE = self::ROUTE_PREFIX . '/authenticate'; /** * The track bulk keyphrases route constant. * * @var string */ public const KEYPHRASES_TRACK_ROUTE = self::ROUTE_PREFIX . '/keyphrases/track'; /** * The keyphrases route constant. * * @var string */ public const TRACKED_KEYPHRASES_ROUTE = self::ROUTE_PREFIX . '/keyphrases'; /** * The untrack keyphrase route constant. * * @var string */ public const UNTRACK_KEYPHRASE_ROUTE = self::ROUTE_PREFIX . '/keyphrases/untrack'; /** * The check limit route constant. * * @var string */ public const CHECK_LIMIT_ROUTE = self::ROUTE_PREFIX . '/account/limit'; /** * The upgrade campaign route constant. * * @var string */ public const UPGRADE_CAMPAIGN_ROUTE = self::ROUTE_PREFIX . '/account/upgrade-campaign'; /** * The login action. * * @var Wincher_Login_Action */ private $login_action; /** * The account action. * * @var Wincher_Account_Action */ private $account_action; /** * The keyphrases action. * * @var Wincher_Keyphrases_Action */ private $keyphrases_action; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Wincher_Enabled_Conditional::class ]; } /** * Wincher_Route constructor. * * @param Wincher_Login_Action $login_action The login action. * @param Wincher_Account_Action $account_action The account action. * @param Wincher_Keyphrases_Action $keyphrases_action The keyphrases action. */ public function __construct( Wincher_Login_Action $login_action, Wincher_Account_Action $account_action, Wincher_Keyphrases_Action $keyphrases_action ) { $this->login_action = $login_action; $this->account_action = $account_action; $this->keyphrases_action = $keyphrases_action; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $authorize_route_args = [ 'methods' => 'GET', 'callback' => [ $this, 'get_authorization_url' ], 'permission_callback' => [ $this, 'can_use_wincher' ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::AUTHORIZATION_URL_ROUTE, $authorize_route_args ); $authentication_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'authenticate' ], 'permission_callback' => [ $this, 'can_use_wincher' ], 'args' => [ 'code' => [ 'validate_callback' => [ $this, 'has_valid_code' ], 'required' => true, ], 'websiteId' => [ 'validate_callback' => [ $this, 'has_valid_website_id' ], 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::AUTHENTICATION_ROUTE, $authentication_route_args ); $track_keyphrases_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'track_keyphrases' ], 'permission_callback' => [ $this, 'can_use_wincher' ], 'args' => [ 'keyphrases' => [ 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::KEYPHRASES_TRACK_ROUTE, $track_keyphrases_route_args ); $get_keyphrases_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'get_tracked_keyphrases' ], 'permission_callback' => [ $this, 'can_use_wincher' ], 'args' => [ 'keyphrases' => [ 'required' => false, ], 'permalink' => [ 'required' => false, ], 'startAt' => [ 'required' => false, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::TRACKED_KEYPHRASES_ROUTE, $get_keyphrases_route_args ); $delete_keyphrase_route_args = [ 'methods' => 'DELETE', 'callback' => [ $this, 'untrack_keyphrase' ], 'permission_callback' => [ $this, 'can_use_wincher' ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::UNTRACK_KEYPHRASE_ROUTE, $delete_keyphrase_route_args ); $check_limit_route_args = [ 'methods' => 'GET', 'callback' => [ $this, 'check_limit' ], 'permission_callback' => [ $this, 'can_use_wincher' ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::CHECK_LIMIT_ROUTE, $check_limit_route_args ); $get_upgrade_campaign_route_args = [ 'methods' => 'GET', 'callback' => [ $this, 'get_upgrade_campaign' ], 'permission_callback' => [ $this, 'can_use_wincher' ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::UPGRADE_CAMPAIGN_ROUTE, $get_upgrade_campaign_route_args ); } /** * Returns the authorization URL. * * @return WP_REST_Response The response. */ public function get_authorization_url() { $data = $this->login_action->get_authorization_url(); return new WP_REST_Response( $data, $data->status ); } /** * Authenticates with Wincher. * * @param WP_REST_Request $request The request. This request should have a code param set. * * @return WP_REST_Response The response. */ public function authenticate( WP_REST_Request $request ) { $data = $this ->login_action ->authenticate( $request['code'], (string) $request['websiteId'] ); return new WP_REST_Response( $data, $data->status ); } /** * Posts keyphrases to track. * * @param WP_REST_Request $request The request. This request should have a code param set. * * @return WP_REST_Response The response. */ public function track_keyphrases( WP_REST_Request $request ) { $limits = $this->account_action->check_limit(); if ( $limits->status !== 200 ) { return new WP_REST_Response( $limits, $limits->status ); } $data = $this->keyphrases_action->track_keyphrases( $request['keyphrases'], $limits ); return new WP_REST_Response( $data, $data->status ); } /** * Gets the tracked keyphrases via POST. * This is done via POST, so we don't potentially run into URL limit issues when a lot of long keyphrases are tracked. * * @param WP_REST_Request $request The request. This request should have a code param set. * * @return WP_REST_Response The response. */ public function get_tracked_keyphrases( WP_REST_Request $request ) { $data = $this->keyphrases_action->get_tracked_keyphrases( $request['keyphrases'], $request['permalink'], $request['startAt'] ); return new WP_REST_Response( $data, $data->status ); } /** * Untracks the tracked keyphrase. * * @param WP_REST_Request $request The request. This request should have a code param set. * * @return WP_REST_Response The response. */ public function untrack_keyphrase( WP_REST_Request $request ) { $data = $this->keyphrases_action->untrack_keyphrase( $request['keyphraseID'] ); return new WP_REST_Response( $data, $data->status ); } /** * Checks the account limit. * * @return WP_REST_Response The response. */ public function check_limit() { $data = $this->account_action->check_limit(); return new WP_REST_Response( $data, $data->status ); } /** * Gets the upgrade campaign. * If it's not a free user, no campaign is returned. * * @return WP_REST_Response The response. */ public function get_upgrade_campaign() { $data = $this->account_action->get_upgrade_campaign(); return new WP_REST_Response( $data, $data->status ); } /** * Checks if a valid code was returned. * * @param string $code The code to check. * * @return bool Whether the code is valid. */ public function has_valid_code( $code ) { return $code !== ''; } /** * Checks if a valid website_id was returned. * * @param int $website_id The website_id to check. * * @return bool Whether the website_id is valid. */ public function has_valid_website_id( $website_id ) { return ! empty( $website_id ) && \is_int( $website_id ); } /** * Whether the current user is allowed to publish post/pages and thus use the Wincher integration. * * @return bool Whether the current user is allowed to use Wincher. */ public function can_use_wincher() { return \current_user_can( 'publish_posts' ) || \current_user_can( 'publish_pages' ); } } routes/route-interface.php 0000644 00000000413 15025657560 0011703 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use Yoast\WP\SEO\Loadable_Interface; /** * Route interface. */ interface Route_Interface extends Loadable_Interface { /** * Registers routes with WordPress. * * @return void */ public function register_routes(); } routes/alert-dismissal-route.php 0000644 00000004521 15025657560 0013044 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Main; /** * Class Alert_Dismissal_Route. */ class Alert_Dismissal_Route implements Route_Interface { use No_Conditionals; /** * Represents the alerts route prefix. * * @var string */ public const ROUTE_PREFIX = 'alerts'; /** * Represents the dismiss route. * * @var string */ public const DISMISS_ROUTE = self::ROUTE_PREFIX . '/dismiss'; /** * Represents the full dismiss route. * * @var string */ public const FULL_DISMISS_ROUTE = Main::API_V1_NAMESPACE . '/' . self::DISMISS_ROUTE; /** * Represents the alert dismissal action. * * @var Alert_Dismissal_Action */ protected $alert_dismissal_action; /** * Constructs Alert_Dismissal_Route. * * @param Alert_Dismissal_Action $alert_dismissal_action The alert dismissal action. */ public function __construct( Alert_Dismissal_Action $alert_dismissal_action ) { $this->alert_dismissal_action = $alert_dismissal_action; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $dismiss_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'dismiss' ], 'permission_callback' => [ $this, 'can_dismiss' ], 'args' => [ 'key' => [ 'validate_callback' => [ $this->alert_dismissal_action, 'is_allowed' ], 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::DISMISS_ROUTE, $dismiss_route_args ); } /** * Dismisses an alert. * * @param WP_REST_Request $request The request. This request should have a key param set. * * @return WP_REST_Response The response. */ public function dismiss( WP_REST_Request $request ) { $success = $this->alert_dismissal_action->dismiss( $request['key'] ); $status = $success === ( true ) ? 200 : 400; return new WP_REST_Response( (object) [ 'success' => $success, 'status' => $status, ], $status ); } /** * Whether or not the current user is allowed to dismiss alerts. * * @return bool Whether or not the current user is allowed to dismiss alerts. */ public function can_dismiss() { return \current_user_can( 'edit_posts' ); } } routes/indexables-head-route.php 0000644 00000004622 15025657560 0012766 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use WPSEO_Utils; use Yoast\WP\SEO\Actions\Indexables\Indexable_Head_Action; use Yoast\WP\SEO\Conditionals\Headless_Rest_Endpoints_Enabled_Conditional; use Yoast\WP\SEO\Main; /** * Head route for indexables. */ class Indexables_Head_Route implements Route_Interface { /** * The posts route constant. * * @var string */ public const HEAD_FOR_URL_ROUTE = 'get_head'; /** * The full posts route constant. * * @var string */ public const FULL_HEAD_FOR_URL_ROUTE = Main::API_V1_NAMESPACE . '/' . self::HEAD_FOR_URL_ROUTE; /** * The head action. * * @var Indexable_Head_Action */ private $head_action; /** * Indexable_Indexation_Route constructor. * * @param Indexable_Head_Action $head_action The head action. */ public function __construct( Indexable_Head_Action $head_action ) { $this->head_action = $head_action; } /** * Returns the conditionals based in which this loadable should be active. * * @return array<string> */ public static function get_conditionals() { return [ Headless_Rest_Endpoints_Enabled_Conditional::class ]; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $route_args = [ 'methods' => 'GET', 'callback' => [ $this, 'get_head' ], 'permission_callback' => '__return_true', 'args' => [ 'url' => [ 'validate_callback' => [ $this, 'is_valid_url' ], 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::HEAD_FOR_URL_ROUTE, $route_args ); } /** * Gets the head of a page for a given URL. * * @param WP_REST_Request $request The request. This request should have a url param set. * * @return WP_REST_Response The response. */ public function get_head( WP_REST_Request $request ) { $url = \esc_url_raw( \utf8_uri_encode( $request['url'] ) ); $data = $this->head_action->for_url( $url ); return new WP_REST_Response( $data, $data->status ); } /** * Checks if a url is a valid url. * * @param string $url The url to check. * * @return bool Whether or not the url is valid. */ public function is_valid_url( $url ) { $url = WPSEO_Utils::sanitize_url( \utf8_uri_encode( $url ) ); if ( \filter_var( $url, \FILTER_VALIDATE_URL ) === false ) { return false; } return true; } } routes/importing-route.php 0000644 00000010106 15025657560 0011753 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use Exception; use WP_Error; use WP_REST_Response; use Yoast\WP\SEO\Actions\Importing\Importing_Action_Interface; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Exceptions\Importing\Aioseo_Validation_Exception; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Services\Importing\Importable_Detector_Service; /** * Importing_Route class. * * Importing route for importing from other SEO plugins. */ class Importing_Route extends Abstract_Action_Route { use No_Conditionals; /** * The import route constant. * * @var string */ public const ROUTE = '/import/(?P<plugin>[\w-]+)/(?P<type>[\w-]+)'; /** * List of available importers. * * @var Importing_Action_Interface[] */ protected $importers = []; /** * The importable detector service. * * @var Importable_Detector_Service */ protected $importable_detector; /** * Importing_Route constructor. * * @param Importable_Detector_Service $importable_detector The importable detector service. * @param Importing_Action_Interface ...$importers All available importers. */ public function __construct( Importable_Detector_Service $importable_detector, Importing_Action_Interface ...$importers ) { $this->importable_detector = $importable_detector; $this->importers = $importers; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { \register_rest_route( Main::API_V1_NAMESPACE, self::ROUTE, [ 'callback' => [ $this, 'execute' ], 'permission_callback' => [ $this, 'is_user_permitted_to_import' ], 'methods' => [ 'POST' ], ] ); } /** * Executes the rest request, but only if the respective action is enabled. * * @param mixed $data The request parameters. * * @return WP_REST_Response|false Response or false on non-existent route. */ public function execute( $data ) { $plugin = (string) $data['plugin']; $type = (string) $data['type']; $next_url = $this->get_endpoint( $plugin, $type ); try { $importer = $this->get_importer( $plugin, $type ); if ( $importer === false || ! $importer->is_enabled() ) { return new WP_Error( 'rest_no_route', 'Requested importer not found', [ 'status' => 404, ] ); } $result = $importer->index(); if ( $result === false || \count( $result ) === 0 ) { $next_url = false; } return $this->respond_with( $result, $next_url ); } catch ( Exception $exception ) { if ( $exception instanceof Aioseo_Validation_Exception ) { return new WP_Error( 'wpseo_error_validation', $exception->getMessage(), [ 'stackTrace' => $exception->getTraceAsString() ] ); } return new WP_Error( 'wpseo_error_indexing', $exception->getMessage(), [ 'stackTrace' => $exception->getTraceAsString() ] ); } } /** * Gets the right importer for the given arguments. * * @param string $plugin The plugin to import from. * @param string $type The type of entity to import. * * @return Importing_Action_Interface|false The importer, or false if no importer was found. */ protected function get_importer( $plugin, $type ) { $importers = $this->importable_detector->filter_actions( $this->importers, $plugin, $type ); if ( \count( $importers ) !== 1 ) { return false; } return \current( $importers ); } /** * Gets the right endpoint for the given arguments. * * @param string $plugin The plugin to import from. * @param string $type The type of entity to import. * * @return string|false The endpoint for the given action or false on failure of finding the one. */ public function get_endpoint( $plugin, $type ) { if ( empty( $plugin ) || empty( $type ) ) { return false; } return Main::API_V1_NAMESPACE . "/import/{$plugin}/{$type}"; } /** * Whether or not the current user is allowed to import. * * @return bool Whether or not the current user is allowed to import. */ public function is_user_permitted_to_import() { return \current_user_can( 'activate_plugins' ); } } routes/supported-features-route.php 0000644 00000002436 15025657560 0013613 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\Addon_Installation_Conditional; use Yoast\WP\SEO\Main; /** * Supported_Features_Route class. */ class Supported_Features_Route implements Route_Interface { /** * Represents the supported features route. * * @var string */ public const SUPPORTED_FEATURES_ROUTE = '/supported-features'; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Addon_Installation_Conditional::class, ]; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $supported_features_route = [ 'methods' => 'GET', 'callback' => [ $this, 'get_supported_features' ], 'permission_callback' => '__return_true', ]; \register_rest_route( Main::API_V1_NAMESPACE, self::SUPPORTED_FEATURES_ROUTE, $supported_features_route ); } /** * Returns a list of features supported by this yoast seo installation. * * @return WP_REST_Response a list of features supported by this yoast seo installation. */ public function get_supported_features() { return new WP_REST_Response( [ 'addon-installation' => 1, ] ); } } routes/semrush-route.php 0000644 00000014407 15025657560 0011441 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Actions\SEMrush\SEMrush_Login_Action; use Yoast\WP\SEO\Actions\SEMrush\SEMrush_Options_Action; use Yoast\WP\SEO\Actions\SEMrush\SEMrush_Phrases_Action; use Yoast\WP\SEO\Conditionals\SEMrush_Enabled_Conditional; use Yoast\WP\SEO\Main; /** * SEMrush_Route class. */ class SEMrush_Route implements Route_Interface { /** * The SEMrush route prefix. * * @var string */ public const ROUTE_PREFIX = 'semrush'; /** * The authenticate route constant. * * @var string */ public const AUTHENTICATION_ROUTE = self::ROUTE_PREFIX . '/authenticate'; /** * The country code option route constant. * * @var string */ public const COUNTRY_CODE_OPTION_ROUTE = self::ROUTE_PREFIX . '/country_code'; /** * The request related keyphrases route constant. * * @var string */ public const RELATED_KEYPHRASES_ROUTE = self::ROUTE_PREFIX . '/related_keyphrases'; /** * The full login route constant. * * @var string */ public const FULL_AUTHENTICATION_ROUTE = Main::API_V1_NAMESPACE . '/' . self::AUTHENTICATION_ROUTE; /** * The full country code option route constant. * * @var string */ public const FULL_COUNTRY_CODE_OPTION_ROUTE = Main::API_V1_NAMESPACE . '/' . self::COUNTRY_CODE_OPTION_ROUTE; /** * The login action. * * @var SEMrush_Login_Action */ private $login_action; /** * The options action. * * @var SEMrush_Options_Action */ private $options_action; /** * The phrases action. * * @var SEMrush_Phrases_Action */ private $phrases_action; /** * Returns the conditionals based in which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ SEMrush_Enabled_Conditional::class ]; } /** * SEMrush_Route constructor. * * @param SEMrush_Login_Action $login_action The login action. * @param SEMrush_Options_Action $options_action The options action. * @param SEMrush_Phrases_Action $phrases_action The phrases action. */ public function __construct( SEMrush_Login_Action $login_action, SEMrush_Options_Action $options_action, SEMrush_Phrases_Action $phrases_action ) { $this->login_action = $login_action; $this->options_action = $options_action; $this->phrases_action = $phrases_action; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $authentication_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'authenticate' ], 'permission_callback' => [ $this, 'can_use_semrush' ], 'args' => [ 'code' => [ 'validate_callback' => [ $this, 'has_valid_code' ], 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::AUTHENTICATION_ROUTE, $authentication_route_args ); $set_country_code_option_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'set_country_code_option' ], 'permission_callback' => [ $this, 'can_use_semrush' ], 'args' => [ 'country_code' => [ 'validate_callback' => [ $this, 'has_valid_country_code' ], 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::COUNTRY_CODE_OPTION_ROUTE, $set_country_code_option_route_args ); $related_keyphrases_route_args = [ 'methods' => 'GET', 'callback' => [ $this, 'get_related_keyphrases' ], 'permission_callback' => [ $this, 'can_use_semrush' ], 'args' => [ 'keyphrase' => [ 'validate_callback' => [ $this, 'has_valid_keyphrase' ], 'required' => true, ], 'country_code' => [ 'required' => true, ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::RELATED_KEYPHRASES_ROUTE, $related_keyphrases_route_args ); } /** * Authenticates with SEMrush. * * @param WP_REST_Request $request The request. This request should have a code param set. * * @return WP_REST_Response The response. */ public function authenticate( WP_REST_Request $request ) { $data = $this ->login_action ->authenticate( $request['code'] ); return new WP_REST_Response( $data, $data->status ); } /** * Sets the SEMrush country code option. * * @param WP_REST_Request $request The request. This request should have a country code param set. * * @return WP_REST_Response The response. */ public function set_country_code_option( WP_REST_Request $request ) { $data = $this ->options_action ->set_country_code( $request['country_code'] ); return new WP_REST_Response( $data, $data->status ); } /** * Checks if a valid code was returned. * * @param string $code The code to check. * * @return bool Whether or not the code is valid. */ public function has_valid_code( $code ) { return $code !== ''; } /** * Checks if a valid keyphrase is provided. * * @param string $keyphrase The keyphrase to check. * * @return bool Whether or not the keyphrase is valid. */ public function has_valid_keyphrase( $keyphrase ) { return \trim( $keyphrase ) !== ''; } /** * Gets the related keyphrases based on the passed keyphrase and database code. * * @param WP_REST_Request $request The request. This request should have a keyphrase and country_code param set. * * @return WP_REST_Response The response. */ public function get_related_keyphrases( WP_REST_Request $request ) { $data = $this ->phrases_action ->get_related_keyphrases( $request['keyphrase'], $request['country_code'] ); return new WP_REST_Response( $data, $data->status ); } /** * Checks if a valid country code was submitted. * * @param string $country_code The country code to check. * * @return bool Whether or not the country code is valid. */ public function has_valid_country_code( $country_code ) { return ( $country_code !== '' && \preg_match( '/^[a-z]{2}$/', $country_code ) === 1 ); } /** * Whether or not the current user is allowed to edit post/pages and thus use the SEMrush integration. * * @return bool Whether or not the current user is allowed to use SEMrush. */ public function can_use_semrush() { return \current_user_can( 'edit_posts' ) || \current_user_can( 'edit_pages' ); } } routes/meta-search-route.php 0000644 00000003716 15025657560 0012145 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Main; /** * Meta_Search_Route class */ class Meta_Search_Route implements Route_Interface { use No_Conditionals; /** * Represents meta search route. * * @var string */ public const META_SEARCH_ROUTE = '/meta/search'; /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $route = [ [ 'methods' => 'GET', 'callback' => [ $this, 'search_meta' ], 'permission_callback' => [ $this, 'permission_check' ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::META_SEARCH_ROUTE, $route ); } /** * Performs the permission check. * * @param WP_REST_Request $request The request. * * @return bool */ public function permission_check( $request ) { if ( ! isset( $request['post_id'] ) ) { return false; } $post_type = \get_post_type( $request['post_id'] ); $post_type_object = \get_post_type_object( $post_type ); return \current_user_can( $post_type_object->cap->edit_posts ); } /** * Searches meta fields of a given post. * * @param WP_REST_Request $request The REST request. * * @return WP_REST_Response */ public function search_meta( $request ) { $post_id = $request['post_id']; $query = $request['query']; $meta = \get_post_custom( $post_id ); $matches = []; foreach ( $meta as $key => $values ) { if ( \substr( $key, 0, \strlen( $query ) ) !== $query ) { continue; } if ( empty( $query ) && \substr( $key, 0, 1 ) === '_' ) { continue; } // Skip custom field values that are serialized. if ( \is_serialized( $values[0] ) ) { continue; } $matches[] = [ 'key' => $key, 'value' => $values[0], ]; if ( \count( $matches ) >= 25 ) { break; } } return \rest_ensure_response( [ 'meta' => $matches ] ); } } routes/workouts-route.php 0000644 00000006145 15025657560 0011650 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Main; /** * Workouts_Route class. */ class Workouts_Route implements Route_Interface { use No_Conditionals; /** * Represents workouts route. * * @var string */ public const WORKOUTS_ROUTE = '/workouts'; /** * The Options helper. * * @var Options_Helper */ private $options_helper; /** * Workouts_Route constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $edit_others_posts = static function () { return \current_user_can( 'edit_others_posts' ); }; $workouts_route = [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_workouts' ], 'permission_callback' => $edit_others_posts, ], [ 'methods' => 'POST', 'callback' => [ $this, 'set_workouts' ], 'permission_callback' => $edit_others_posts, 'args' => $this->get_workouts_routes_args(), ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::WORKOUTS_ROUTE, $workouts_route ); } /** * Returns the workouts as configured for the site. * * @return WP_REST_Response the configuration of the workouts. */ public function get_workouts() { $workouts_option = $this->options_helper->get( 'workouts_data' ); /** * Filter: 'Yoast\WP\SEO\workouts_options' - Allows adding workouts options by the add-ons. * * @param array $workouts_option The content of the `workouts_data` option in Free. */ $workouts_option = \apply_filters( 'Yoast\WP\SEO\workouts_options', $workouts_option ); return new WP_REST_Response( [ 'json' => $workouts_option ] ); } /** * Sets the workout configuration. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response the configuration of the workouts. */ public function set_workouts( $request ) { $workouts_data = $request->get_json_params(); /** * Filter: 'Yoast\WP\SEO\workouts_route_save' - Allows the add-ons to save the options data in their own options. * * @param mixed|null $result The result of the previous saving operation. * * @param array $workouts_data The full set of workouts option data to save. */ $result = \apply_filters( 'Yoast\WP\SEO\workouts_route_save', null, $workouts_data ); return new WP_REST_Response( [ 'json' => $result ] ); } /** * Gets the args for all the registered workouts. * * @return array */ private function get_workouts_routes_args() { $args_array = []; /** * Filter: 'Yoast\WP\SEO\workouts_route_args' - Allows the add-ons add their own arguments to the route registration. * * @param array $args_array The array of arguments for the route registration. */ return \apply_filters( 'Yoast\WP\SEO\workouts_route_args', $args_array ); } } routes/indexing-route.php 0000644 00000030522 15025657560 0011554 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use Exception; use WP_Error; use WP_REST_Response; use Yoast\WP\SEO\Actions\Indexing\Indexable_General_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Indexing_Complete_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Post_Type_Archive_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexable_Term_Indexation_Action; use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface; use Yoast\WP\SEO\Actions\Indexing\Indexing_Complete_Action; use Yoast\WP\SEO\Actions\Indexing\Indexing_Prepare_Action; use Yoast\WP\SEO\Actions\Indexing\Post_Link_Indexing_Action; use Yoast\WP\SEO\Actions\Indexing\Term_Link_Indexing_Action; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Indexing_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Main; /** * Indexing_Route class. * * Indexing route for indexables. */ class Indexing_Route extends Abstract_Indexation_Route { use No_Conditionals; /** * The indexing complete route constant. * * @var string */ public const COMPLETE_ROUTE = 'indexing/complete'; /** * The full indexing complete route constant. * * @var string */ public const FULL_COMPLETE_ROUTE = Main::API_V1_NAMESPACE . '/' . self::COMPLETE_ROUTE; /** * The indexables complete route constant. * * @var string */ public const INDEXABLES_COMPLETE_ROUTE = 'indexing/indexables-complete'; /** * The full indexing complete route constant. * * @var string */ public const FULL_INDEXABLES_COMPLETE_ROUTE = Main::API_V1_NAMESPACE . '/' . self::INDEXABLES_COMPLETE_ROUTE; /** * The indexing prepare route constant. * * @var string */ public const PREPARE_ROUTE = 'indexing/prepare'; /** * The full indexing prepare route constant. * * @var string */ public const FULL_PREPARE_ROUTE = Main::API_V1_NAMESPACE . '/' . self::PREPARE_ROUTE; /** * The posts route constant. * * @var string */ public const POSTS_ROUTE = 'indexing/posts'; /** * The full posts route constant. * * @var string */ public const FULL_POSTS_ROUTE = Main::API_V1_NAMESPACE . '/' . self::POSTS_ROUTE; /** * The terms route constant. * * @var string */ public const TERMS_ROUTE = 'indexing/terms'; /** * The full terms route constant. * * @var string */ public const FULL_TERMS_ROUTE = Main::API_V1_NAMESPACE . '/' . self::TERMS_ROUTE; /** * The terms route constant. * * @var string */ public const POST_TYPE_ARCHIVES_ROUTE = 'indexing/post-type-archives'; /** * The full terms route constant. * * @var string */ public const FULL_POST_TYPE_ARCHIVES_ROUTE = Main::API_V1_NAMESPACE . '/' . self::POST_TYPE_ARCHIVES_ROUTE; /** * The general route constant. * * @var string */ public const GENERAL_ROUTE = 'indexing/general'; /** * The full general route constant. * * @var string */ public const FULL_GENERAL_ROUTE = Main::API_V1_NAMESPACE . '/' . self::GENERAL_ROUTE; /** * The posts route constant. * * @var string */ public const POST_LINKS_INDEXING_ROUTE = 'link-indexing/posts'; /** * The full posts route constant. * * @var string */ public const FULL_POST_LINKS_INDEXING_ROUTE = Main::API_V1_NAMESPACE . '/' . self::POST_LINKS_INDEXING_ROUTE; /** * The terms route constant. * * @var string */ public const TERM_LINKS_INDEXING_ROUTE = 'link-indexing/terms'; /** * The full terms route constant. * * @var string */ public const FULL_TERM_LINKS_INDEXING_ROUTE = Main::API_V1_NAMESPACE . '/' . self::TERM_LINKS_INDEXING_ROUTE; /** * The post indexing action. * * @var Indexable_Post_Indexation_Action */ protected $post_indexation_action; /** * The term indexing action. * * @var Indexable_Term_Indexation_Action */ protected $term_indexation_action; /** * The post type archive indexing action. * * @var Indexable_Post_Type_Archive_Indexation_Action */ protected $post_type_archive_indexation_action; /** * Represents the general indexing action. * * @var Indexable_General_Indexation_Action */ protected $general_indexation_action; /** * The prepare indexing action. * * @var Indexing_Prepare_Action */ protected $prepare_indexing_action; /** * The indexable indexing complete action. * * @var Indexable_Indexing_Complete_Action */ protected $indexable_indexing_complete_action; /** * The indexing complete action. * * @var Indexing_Complete_Action */ protected $indexing_complete_action; /** * The post link indexing action. * * @var Post_Link_Indexing_Action */ protected $post_link_indexing_action; /** * The term link indexing action. * * @var Term_Link_Indexing_Action */ protected $term_link_indexing_action; /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * Indexing_Route constructor. * * @param Indexable_Post_Indexation_Action $post_indexation_action The post indexing action. * @param Indexable_Term_Indexation_Action $term_indexation_action The term indexing action. * @param Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action The post type archive indexing action. * @param Indexable_General_Indexation_Action $general_indexation_action The general indexing action. * @param Indexable_Indexing_Complete_Action $indexable_indexing_complete_action The complete indexing action. * @param Indexing_Complete_Action $indexing_complete_action The complete indexing action. * @param Indexing_Prepare_Action $prepare_indexing_action The prepare indexing action. * @param Post_Link_Indexing_Action $post_link_indexing_action The post link indexing action. * @param Term_Link_Indexing_Action $term_link_indexing_action The term link indexing action. * @param Options_Helper $options_helper The options helper. * @param Indexing_Helper $indexing_helper The indexing helper. */ public function __construct( Indexable_Post_Indexation_Action $post_indexation_action, Indexable_Term_Indexation_Action $term_indexation_action, Indexable_Post_Type_Archive_Indexation_Action $post_type_archive_indexation_action, Indexable_General_Indexation_Action $general_indexation_action, Indexable_Indexing_Complete_Action $indexable_indexing_complete_action, Indexing_Complete_Action $indexing_complete_action, Indexing_Prepare_Action $prepare_indexing_action, Post_Link_Indexing_Action $post_link_indexing_action, Term_Link_Indexing_Action $term_link_indexing_action, Options_Helper $options_helper, Indexing_Helper $indexing_helper ) { $this->post_indexation_action = $post_indexation_action; $this->term_indexation_action = $term_indexation_action; $this->post_type_archive_indexation_action = $post_type_archive_indexation_action; $this->general_indexation_action = $general_indexation_action; $this->indexable_indexing_complete_action = $indexable_indexing_complete_action; $this->indexing_complete_action = $indexing_complete_action; $this->prepare_indexing_action = $prepare_indexing_action; $this->options_helper = $options_helper; $this->post_link_indexing_action = $post_link_indexing_action; $this->term_link_indexing_action = $term_link_indexing_action; $this->indexing_helper = $indexing_helper; } /** * Registers the routes used to index indexables. * * @return void */ public function register_routes() { $route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'index_posts' ], 'permission_callback' => [ $this, 'can_index' ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::POSTS_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'index_terms' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::TERMS_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'index_post_type_archives' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::POST_TYPE_ARCHIVES_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'index_general' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::GENERAL_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'prepare' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::PREPARE_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'indexables_complete' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::INDEXABLES_COMPLETE_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'complete' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::COMPLETE_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'index_post_links' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::POST_LINKS_INDEXING_ROUTE, $route_args ); $route_args['callback'] = [ $this, 'index_term_links' ]; \register_rest_route( Main::API_V1_NAMESPACE, self::TERM_LINKS_INDEXING_ROUTE, $route_args ); } /** * Indexes a number of unindexed posts. * * @return WP_REST_Response The response. */ public function index_posts() { return $this->run_indexation_action( $this->post_indexation_action, self::FULL_POSTS_ROUTE ); } /** * Indexes a number of unindexed terms. * * @return WP_REST_Response The response. */ public function index_terms() { return $this->run_indexation_action( $this->term_indexation_action, self::FULL_TERMS_ROUTE ); } /** * Indexes a number of unindexed post type archive pages. * * @return WP_REST_Response The response. */ public function index_post_type_archives() { return $this->run_indexation_action( $this->post_type_archive_indexation_action, self::FULL_POST_TYPE_ARCHIVES_ROUTE ); } /** * Indexes a number of unindexed general items. * * @return WP_REST_Response The response. */ public function index_general() { return $this->run_indexation_action( $this->general_indexation_action, self::FULL_GENERAL_ROUTE ); } /** * Indexes a number of posts for post links. * * @return WP_REST_Response The response. */ public function index_post_links() { return $this->run_indexation_action( $this->post_link_indexing_action, self::FULL_POST_LINKS_INDEXING_ROUTE ); } /** * Indexes a number of terms for term links. * * @return WP_REST_Response The response. */ public function index_term_links() { return $this->run_indexation_action( $this->term_link_indexing_action, self::FULL_TERM_LINKS_INDEXING_ROUTE ); } /** * Prepares the indexation. * * @return WP_REST_Response The response. */ public function prepare() { $this->prepare_indexing_action->prepare(); return $this->respond_with( [], false ); } /** * Completes the indexable indexation. * * @return WP_REST_Response The response. */ public function indexables_complete() { $this->indexable_indexing_complete_action->complete(); return $this->respond_with( [], false ); } /** * Completes the indexation. * * @return WP_REST_Response The response. */ public function complete() { $this->indexing_complete_action->complete(); return $this->respond_with( [], false ); } /** * Whether or not the current user is allowed to index. * * @return bool Whether or not the current user is allowed to index. */ public function can_index() { return \current_user_can( 'edit_posts' ); } /** * Runs an indexing action and returns the response. * * @param Indexation_Action_Interface $indexation_action The indexing action. * @param string $url The url of the indexing route. * * @return WP_REST_Response|WP_Error The response, or an error when running the indexing action failed. */ protected function run_indexation_action( Indexation_Action_Interface $indexation_action, $url ) { try { return parent::run_indexation_action( $indexation_action, $url ); } catch ( Exception $exception ) { $this->indexing_helper->indexing_failed(); return new WP_Error( 'wpseo_error_indexing', $exception->getMessage(), [ 'stackTrace' => $exception->getTraceAsString() ] ); } } } routes/abstract-action-route.php 0000644 00000001172 15025657560 0013024 0 ustar 00 <?php namespace Yoast\WP\SEO\Routes; use WP_REST_Response; /** * Abstract_Action_Route class. * * Abstract class for action routes. */ abstract class Abstract_Action_Route implements Route_Interface { /** * Responds to an indexing request. * * @param array $objects The objects that have been indexed. * @param string $next_url The url that should be called to continue reindexing. False if done. * * @return WP_REST_Response The response. */ protected function respond_with( $objects, $next_url ) { return new WP_REST_Response( [ 'objects' => $objects, 'next_url' => $next_url, ] ); } } presentations/indexable-error-page-presentation.php 0000644 00000001265 15025657560 0016677 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; /** * Class Indexable_Error_Page_Presentation. * * Presentation object for indexables. */ class Indexable_Error_Page_Presentation extends Indexable_Presentation { /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); $robots['index'] = 'noindex'; return $this->filter_robots( $robots ); } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } return $this->options->get_title_default( 'title-404-wpseo' ); } } presentations/indexable-term-archive-presentation.php 0000644 00000012302 15025657560 0017214 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use WP_Term; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; use Yoast\WP\SEO\Wrappers\WP_Query_Wrapper; /** * Class Indexable_Term_Archive_Presentation. * * Presentation object for indexables. * * @property WP_Term $source */ class Indexable_Term_Archive_Presentation extends Indexable_Presentation { use Archive_Adjacent; /** * Holds the WP query wrapper instance. * * @var WP_Query_Wrapper */ private $wp_query_wrapper; /** * Holds the taxonomy helper instance. * * @var Taxonomy_Helper */ private $taxonomy; /** * Indexable_Post_Type_Presentation constructor. * * @codeCoverageIgnore * * @param WP_Query_Wrapper $wp_query_wrapper The wp query wrapper. * @param Taxonomy_Helper $taxonomy The Taxonomy helper. */ public function __construct( WP_Query_Wrapper $wp_query_wrapper, Taxonomy_Helper $taxonomy ) { $this->wp_query_wrapper = $wp_query_wrapper; $this->taxonomy = $taxonomy; } /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { if ( $this->is_multiple_terms_query() ) { return ''; } if ( $this->model->canonical ) { return $this->model->canonical; } if ( ! $this->permalink ) { return ''; } $current_page = $this->pagination->get_current_archive_page_number(); if ( $current_page > 1 ) { return $this->pagination->get_paginated_url( $this->permalink, $current_page ); } return $this->permalink; } /** * Generates the meta description. * * @return string The meta description. */ public function generate_meta_description() { if ( $this->model->description ) { return $this->model->description; } return $this->options->get( 'metadesc-tax-' . $this->model->object_sub_type ); } /** * Generates the source. * * @return array The source. */ public function generate_source() { if ( ! empty( $this->model->object_id ) || \get_queried_object() === null ) { return \get_term( $this->model->object_id, $this->model->object_sub_type ); } return \get_term( \get_queried_object()->term_id, \get_queried_object()->taxonomy ); } /** * Generates the Open Graph description. * * @return string The Open Graph description. */ public function generate_open_graph_description() { $open_graph_description = parent::generate_open_graph_description(); if ( $open_graph_description ) { return $open_graph_description; } return $this->taxonomy->get_term_description( $this->model->object_id ); } /** * Generates the Twitter description. * * @return string The Twitter description. */ public function generate_twitter_description() { $twitter_description = parent::generate_twitter_description(); if ( $twitter_description ) { return $twitter_description; } if ( $this->open_graph_description && $this->context->open_graph_enabled === true ) { return ''; } return $this->taxonomy->get_term_description( $this->model->object_id ); } /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); /** * If its a multiple terms archive page return a noindex. */ if ( $this->current_page->is_multiple_terms_page() ) { $robots['index'] = 'noindex'; return $this->filter_robots( $robots ); } /** * First we get the no index option for this taxonomy, because it can be overwritten the indexable value for * this specific term. */ if ( \is_wp_error( $this->source ) || ! $this->taxonomy->is_indexable( $this->source->taxonomy ) ) { $robots['index'] = 'noindex'; } /** * Overwrite the index directive when there is a term specific directive set. */ if ( $this->model->is_robots_noindex !== null ) { $robots['index'] = ( $this->model->is_robots_noindex ) ? 'noindex' : 'index'; } return $this->filter_robots( $robots ); } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } if ( \is_wp_error( $this->source ) ) { return $this->model->title; } // Get the SEO title as entered in Search Appearance. $title = $this->options->get( 'title-tax-' . $this->source->taxonomy ); if ( $title ) { return $title; } // Get the installation default title. $title = $this->options->get_title_default( 'title-tax-' . $this->source->taxonomy ); return $title; } /** * Generates the Open Graph type. * * @return string The Open Graph type. */ public function generate_open_graph_type() { return 'article'; } /** * Checks if term archive query is for multiple terms (/term-1,term-2/ or /term-1+term-2/). * * @return bool Whether the query contains multiple terms. */ protected function is_multiple_terms_query() { $query = $this->wp_query_wrapper->get_query(); if ( ! isset( $query->tax_query ) ) { return false; } if ( \is_wp_error( $this->source ) ) { return false; } $queried_terms = $query->tax_query->queried_terms; if ( empty( $queried_terms[ $this->source->taxonomy ]['terms'] ) ) { return false; } return \count( $queried_terms[ $this->source->taxonomy ]['terms'] ) > 1; } } presentations/abstract-presentation.php 0000644 00000004530 15025657560 0014504 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use AllowDynamicProperties; use Exception; /** * The abstract presentation class. */ #[AllowDynamicProperties] class Abstract_Presentation { /** * The model. * * @var mixed */ public $model; /** * Whether or not there is a presentation prototype. * * @var bool */ private $is_prototype = true; /** * Creates a model presentation. * * @param array $data The data that this is a presentation of. * * @return static A model presentation. * * @throws Exception If attempting to create a model presentation from another model presentation. */ public function of( $data ) { if ( ! $this->is_prototype() ) { throw new Exception( 'Attempting to create a model presentation from another model presentation. Use the prototype presentation gained from DI instead.' ); } // Clone self to allow stateful services that do benefit from DI. $presentation = clone $this; foreach ( $data as $key => $value ) { $presentation->{$key} = $value; } $presentation->is_prototype = false; return $presentation; } /** * Magic getter for lazy loading of generate functions. * * @param string $name The property to get. * * @return mixed The value if it could be generated. * * @throws Exception If there is no generator for the property. */ public function __get( $name ) { if ( $this->is_prototype() ) { throw new Exception( 'Attempting property access on prototype presentation. Use Presentation::of( $data ) to get a model presentation.' ); } $generator = "generate_$name"; if ( \method_exists( $this, $generator ) ) { $this->{$name} = $this->$generator(); return $this->{$name}; } throw new Exception( "Property $name has no generator. Expected function $generator." ); } /** * Magic isset for ensuring methods that have a generator are recognised. * * @codeCoverageIgnore Wrapper method. * * @param string $name The property to get. * * @return bool Whether or not there is a generator for the requested property. */ public function __isset( $name ) { return \method_exists( $this, "generate_$name" ); } /** * Returns `true` if this class is a prototype. * * @codeCoverageIgnore Wrapper method. * * @return bool If this class is a prototype or not. */ protected function is_prototype() { return $this->is_prototype; } } presentations/archive-adjacent-trait.php 0000644 00000003235 15025657560 0014502 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use Yoast\WP\SEO\Helpers\Pagination_Helper; use Yoast\WP\SEO\Models\Indexable; /** * Class Archive_Adjacent. * * Presentation object for indexables. * * @property Indexable $model The indexable. */ trait Archive_Adjacent { /** * Holds the Pagination_Helper instance. * * @var Pagination_Helper */ protected $pagination; /** * Sets the helpers for the trait. * * @required * * @codeCoverageIgnore * * @param Pagination_Helper $pagination The pagination helper. * * @return void */ public function set_archive_adjacent_helpers( Pagination_Helper $pagination ) { $this->pagination = $pagination; } /** * Generates the rel prev. * * @return string */ public function generate_rel_prev() { if ( $this->pagination->is_rel_adjacent_disabled() ) { return ''; } $current_page = \max( 1, $this->pagination->get_current_archive_page_number() ); // Check if there is a previous page. if ( $current_page === 1 ) { return ''; } // Check if the previous page is the first page. if ( $current_page === 2 ) { return $this->permalink; } return $this->pagination->get_paginated_url( $this->permalink, ( $current_page - 1 ) ); } /** * Generates the rel next. * * @return string */ public function generate_rel_next() { if ( $this->pagination->is_rel_adjacent_disabled() ) { return ''; } $current_page = \max( 1, $this->pagination->get_current_archive_page_number() ); if ( $this->pagination->get_number_of_archive_pages() <= $current_page ) { return ''; } return $this->pagination->get_paginated_url( $this->permalink, ( $current_page + 1 ) ); } } presentations/indexable-search-result-page-presentation.php 0000644 00000002635 15025657560 0020331 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; /** * Class Indexable_Search_Result_Page_Presentation. * * Presentation object for indexables. */ class Indexable_Search_Result_Page_Presentation extends Indexable_Presentation { /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); $robots['index'] = 'noindex'; return $this->filter_robots( $robots ); } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } return $this->options->get_title_default( 'title-search-wpseo' ); } /** * Generates the Twitter title. * * @return string The Twitter title. */ public function generate_twitter_title() { return $this->title; } /** * Generates the Open Graph URL. * * @return string The Open Graph URL. */ public function generate_open_graph_url() { $search_query = \get_search_query(); // Regex catches case when /search/page/N without search term is itself mistaken for search term. if ( ! empty( $search_query ) && ! \preg_match( '|^page/\d+$|', $search_query ) ) { return \get_search_link(); } return ''; } /** * Generates the Open Graph type. * * @return string The Open Graph type. */ public function generate_open_graph_type() { return 'article'; } } presentations/indexable-post-type-archive-presentation.php 0000644 00000002715 15025657560 0020220 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; /** * Class Indexable_Post_Type_Archive_Presentation. * * Presentation object for indexables. */ class Indexable_Post_Type_Archive_Presentation extends Indexable_Presentation { use Archive_Adjacent; /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { $permalink = $this->permalink; if ( ! $permalink ) { return ''; } $current_page = $this->pagination->get_current_archive_page_number(); if ( $current_page > 1 ) { return $this->pagination->get_paginated_url( $permalink, $current_page ); } return $permalink; } /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); if ( $this->options->get( 'noindex-ptarchive-' . $this->model->object_sub_type, false ) ) { $robots['index'] = 'noindex'; } return $this->filter_robots( $robots ); } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } $post_type = $this->model->object_sub_type; $title = $this->options->get_title_default( 'title-ptarchive-' . $post_type ); return $title; } /** * Generates the source. * * @return array The source. */ public function generate_source() { return [ 'post_type' => $this->model->object_sub_type ]; } } presentations/indexable-static-posts-page-presentation.php 0000644 00000001721 15025657560 0020200 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use Yoast\WP\SEO\Helpers\Pagination_Helper; /** * Class Indexable_Static_Posts_Page_Presentation. * * Presentation object for indexables. */ class Indexable_Static_Posts_Page_Presentation extends Indexable_Post_Type_Presentation { use Archive_Adjacent; /** * The pagination helper. * * @var Pagination_Helper */ protected $pagination; /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { if ( $this->model->canonical ) { return $this->model->canonical; } $current_page = $this->pagination->get_current_archive_page_number(); if ( $current_page > 1 ) { return $this->pagination->get_paginated_url( $this->permalink, $current_page ); } return $this->permalink; } /** * Generates the Open Graph URL. * * @return string The Open Graph URL. */ public function generate_open_graph_url() { return $this->permalink; } } presentations/indexable-post-type-presentation.php 0000644 00000024677 15025657560 0016614 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use Yoast\WP\SEO\Helpers\Date_Helper; use Yoast\WP\SEO\Helpers\Pagination_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; /** * Class Indexable_Post_Type_Presentation. * * Presentation object for indexables. */ class Indexable_Post_Type_Presentation extends Indexable_Presentation { /** * Holds the Post_Type_Helper instance. * * @var Post_Type_Helper */ protected $post_type; /** * Holds the Date_Helper instance. * * @var Date_Helper */ protected $date; /** * Holds the Pagination_Helper instance. * * @var Pagination_Helper */ protected $pagination; /** * Holds the Post_Helper instance. * * @var Post_Helper */ protected $post; /** * Indexable_Post_Type_Presentation constructor. * * @codeCoverageIgnore * * @param Post_Type_Helper $post_type The post type helper. * @param Date_Helper $date The date helper. * @param Pagination_Helper $pagination The pagination helper. * @param Post_Helper $post The post helper. */ public function __construct( Post_Type_Helper $post_type, Date_Helper $date, Pagination_Helper $pagination, Post_Helper $post ) { $this->post_type = $post_type; $this->date = $date; $this->pagination = $pagination; $this->post = $post; } /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { if ( $this->model->canonical ) { return $this->model->canonical; } $permalink = $this->permalink; // Fix paginated pages canonical, but only if the page is truly paginated. $current_page = $this->pagination->get_current_post_page_number(); if ( $current_page > 1 ) { $number_of_pages = $this->model->number_of_pages; if ( $number_of_pages && $current_page <= $number_of_pages ) { $permalink = $this->get_paginated_url( $permalink, $current_page ); } } return $this->url->ensure_absolute_url( $permalink ); } /** * Generates the rel prev. * * @return string The rel prev value. */ public function generate_rel_prev() { if ( $this->model->number_of_pages === null ) { return ''; } if ( $this->pagination->is_rel_adjacent_disabled() ) { return ''; } $current_page = \max( 1, $this->pagination->get_current_post_page_number() ); // Check if there is a previous page. if ( $current_page < 2 ) { return ''; } // Check if the previous page is the first page. if ( $current_page === 2 ) { return $this->model->permalink; } return $this->get_paginated_url( $this->model->permalink, ( $current_page - 1 ) ); } /** * Generates the rel next. * * @return string The rel prev next. */ public function generate_rel_next() { if ( $this->model->number_of_pages === null ) { return ''; } if ( $this->pagination->is_rel_adjacent_disabled() ) { return ''; } $current_page = \max( 1, $this->pagination->get_current_post_page_number() ); if ( $this->model->number_of_pages <= $current_page ) { return ''; } return $this->get_paginated_url( $this->model->permalink, ( $current_page + 1 ) ); } /** * Generates the open graph title. * * @return string The open graph title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } // Get SEO title as entered in Search appearance. $post_type = $this->model->object_sub_type; $title = $this->options->get( 'title-' . $this->model->object_sub_type ); if ( $title ) { return $title; } // Get installation default title. return $this->options->get_title_default( 'title-' . $post_type ); } /** * Generates the meta description. * * @return string The meta description. */ public function generate_meta_description() { if ( $this->model->description ) { return $this->model->description; } return $this->options->get( 'metadesc-' . $this->model->object_sub_type ); } /** * Generates the open graph description. * * @return string The open graph description. */ public function generate_open_graph_description() { if ( $this->model->open_graph_description ) { $open_graph_description = $this->model->open_graph_description; } if ( empty( $open_graph_description ) ) { // The helper applies a filter, but we don't have a default value at this stage so we pass an empty string. $open_graph_description = $this->values_helper->get_open_graph_description( '', $this->model->object_type, $this->model->object_sub_type ); } if ( empty( $open_graph_description ) ) { $open_graph_description = $this->meta_description; } if ( empty( $open_graph_description ) ) { $open_graph_description = $this->post->get_the_excerpt( $this->model->object_id ); $open_graph_description = $this->post->strip_shortcodes( $open_graph_description ); } return $open_graph_description; } /** * Generates the open graph images. * * @return array The open graph images. */ public function generate_open_graph_images() { if ( \post_password_required() ) { return []; } return parent::generate_open_graph_images(); } /** * Generates the Open Graph type. * * @return string The Open Graph type. */ public function generate_open_graph_type() { return 'article'; } /** * Generates the open graph article author. * * @return string The open graph article author. */ public function generate_open_graph_article_author() { if ( $this->model->object_sub_type !== 'post' ) { return ''; } $post = $this->source; $open_graph_article_author = $this->user->get_the_author_meta( 'facebook', $post->post_author ); if ( $open_graph_article_author ) { return $open_graph_article_author; } return ''; } /** * Generates the open graph article publisher. * * @return string The open graph article publisher. */ public function generate_open_graph_article_publisher() { $open_graph_article_publisher = $this->context->open_graph_publisher; if ( $open_graph_article_publisher ) { return $open_graph_article_publisher; } return ''; } /** * Generates the open graph article published time. * * @return string The open graph article published time. */ public function generate_open_graph_article_published_time() { if ( $this->model->object_sub_type !== 'post' ) { /** * Filter: 'wpseo_opengraph_show_publish_date' - Allow showing publication date for other post types. * * @param bool $show Whether or not to show publish date. * @param string $post_type The current URL's post type. */ if ( ! \apply_filters( 'wpseo_opengraph_show_publish_date', false, $this->post->get_post_type( $this->source ) ) ) { return ''; } } return $this->date->format( $this->source->post_date_gmt ); } /** * Generates the open graph article modified time. * * @return string The open graph article modified time. */ public function generate_open_graph_article_modified_time() { if ( \strtotime( $this->source->post_modified_gmt ) > \strtotime( $this->source->post_date_gmt ) ) { return $this->date->format( $this->source->post_modified_gmt ); } return ''; } /** * Generates the source. * * @return array The source. */ public function generate_source() { return \get_post( $this->model->object_id ); } /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); $robots = \array_merge( $robots, [ 'imageindex' => ( $this->model->is_robots_noimageindex === true ) ? 'noimageindex' : null, 'archive' => ( $this->model->is_robots_noarchive === true ) ? 'noarchive' : null, 'snippet' => ( $this->model->is_robots_nosnippet === true ) ? 'nosnippet' : null, ] ); // No snippet means max snippet can be omitted. if ( $this->model->is_robots_nosnippet === true ) { $robots['max-snippet'] = null; } // No image index means max image preview can be omitted. if ( $this->model->is_robots_noimageindex === true ) { $robots['max-image-preview'] = null; } // When the post specific index is not set, look to the post status and default of the post type. if ( $this->model->is_robots_noindex === null ) { $post_status_private = \get_post_status( $this->model->object_id ) === 'private'; $post_type_noindex = ! $this->post_type->is_indexable( $this->model->object_sub_type ); if ( $post_status_private || $post_type_noindex ) { $robots['index'] = 'noindex'; } } return $this->filter_robots( $robots ); } /** * Generates the Twitter description. * * @return string The Twitter description. */ public function generate_twitter_description() { $twitter_description = parent::generate_twitter_description(); if ( $twitter_description ) { return $twitter_description; } if ( $this->open_graph_description && $this->context->open_graph_enabled === true ) { return ''; } return $this->post->get_the_excerpt( $this->model->object_id ); } /** * Generates the Twitter image. * * @return string The Twitter image. */ public function generate_twitter_image() { if ( \post_password_required() ) { return ''; } return parent::generate_twitter_image(); } /** * Generates the Twitter creator. * * @return string The Twitter creator. */ public function generate_twitter_creator() { if ( $this->model->object_sub_type !== 'post' ) { return ''; } $twitter_creator = \ltrim( \trim( \get_the_author_meta( 'twitter', $this->source->post_author ) ), '@' ); /** * Filter: 'wpseo_twitter_creator_account' - Allow changing the X account as output in the X card by Yoast SEO. * * @param string $twitter The twitter account name string. */ $twitter_creator = \apply_filters( 'wpseo_twitter_creator_account', $twitter_creator ); if ( \is_string( $twitter_creator ) && $twitter_creator !== '' ) { return '@' . $twitter_creator; } $site_twitter = $this->options->get( 'twitter_site', '' ); if ( \is_string( $site_twitter ) && $site_twitter !== '' ) { return '@' . $site_twitter; } return ''; } /** * Wraps the get_paginated_url pagination helper method. * * @codeCoverageIgnore A wrapper method. * * @param string $url The un-paginated URL of the current archive. * @param string $page The page number to add on to $url for the $link tag. * * @return string The paginated URL. */ protected function get_paginated_url( $url, $page ) { return $this->pagination->get_paginated_url( $url, $page, false ); } } presentations/indexable-author-archive-presentation.php 0000644 00000010327 15025657560 0017554 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use Yoast\WP\SEO\Helpers\Author_Archive_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; /** * Class Indexable_Author_Archive_Presentation. * * Presentation object for indexables. */ class Indexable_Author_Archive_Presentation extends Indexable_Presentation { use Archive_Adjacent; /** * Holds the post type helper instance. * * @var Post_Type_Helper */ protected $post_type; /** * Holds the author archive helper instance. * * @var Author_Archive_Helper */ protected $author_archive; /** * Indexable_Author_Archive_Presentation constructor. * * @codeCoverageIgnore * * @param Post_Type_Helper $post_type The post type helper. * @param Author_Archive_Helper $author_archive The author archive helper. */ public function __construct( Post_Type_Helper $post_type, Author_Archive_Helper $author_archive ) { $this->post_type = $post_type; $this->author_archive = $author_archive; } /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { if ( $this->model->canonical ) { return $this->model->canonical; } if ( ! $this->permalink ) { return ''; } $current_page = $this->pagination->get_current_archive_page_number(); if ( $current_page > 1 ) { return $this->pagination->get_paginated_url( $this->permalink, $current_page ); } return $this->permalink; } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } $option_titles_key = 'title-author-wpseo'; $title = $this->options->get( $option_titles_key ); if ( $title ) { return $title; } return $this->options->get_title_default( $option_titles_key ); } /** * Generates the meta description. * * @return string The meta description. */ public function generate_meta_description() { if ( $this->model->description ) { return $this->model->description; } $option_titles_key = 'metadesc-author-wpseo'; $description = $this->options->get( $option_titles_key ); if ( $description ) { return $description; } return $this->options->get_title_default( $option_titles_key ); } /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); // Global option: "Show author archives in search results". if ( $this->options->get( 'noindex-author-wpseo', false ) ) { $robots['index'] = 'noindex'; return $this->filter_robots( $robots ); } $current_author = \get_userdata( $this->model->object_id ); // Safety check. The call to `get_user_data` could return false (called in `get_queried_object`). if ( $current_author === false ) { $robots['index'] = 'noindex'; return $this->filter_robots( $robots ); } $author_archive_post_types = $this->author_archive->get_author_archive_post_types(); // Global option: "Show archives for authors without posts in search results". if ( $this->options->get( 'noindex-author-noposts-wpseo', false ) && $this->user->count_posts( $current_author->ID, $author_archive_post_types ) === 0 ) { $robots['index'] = 'noindex'; return $this->filter_robots( $robots ); } // User option: "Do not allow search engines to show this author's archives in search results". if ( $this->user->get_meta( $current_author->ID, 'wpseo_noindex_author', true ) === 'on' ) { $robots['index'] = 'noindex'; return $this->filter_robots( $robots ); } return $this->filter_robots( $robots ); } /** * Generates the Open Graph type. * * @return string The Open Graph type. */ public function generate_open_graph_type() { return 'profile'; } /** * Generates the open graph images. * * @return array The open graph images. */ public function generate_open_graph_images() { if ( $this->context->open_graph_enabled === false ) { return []; } return $this->open_graph_image_generator->generate_for_author_archive( $this->context ); } /** * Generates the source. * * @return array The source. */ public function generate_source() { return [ 'post_author' => $this->model->object_id ]; } } presentations/indexable-presentation.php 0000644 00000046064 15025657560 0014644 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Generators\Breadcrumbs_Generator; use Yoast\WP\SEO\Generators\Open_Graph_Image_Generator; use Yoast\WP\SEO\Generators\Open_Graph_Locale_Generator; use Yoast\WP\SEO\Generators\Schema_Generator; use Yoast\WP\SEO\Generators\Twitter_Image_Generator; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Open_Graph\Values_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Permalink_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Helpers\User_Helper; use Yoast\WP\SEO\Models\Indexable; /** * Class Indexable_Presentation. * * Presentation object for indexables. * * @property string $title * @property string $meta_description * @property array $robots * @property string $canonical * @property string $rel_next * @property string $rel_prev * @property string $open_graph_type * @property string $open_graph_title * @property string $open_graph_description * @property array $open_graph_images * @property int $open_graph_image_id * @property string $open_graph_image * @property string $open_graph_url * @property string $open_graph_site_name * @property string $open_graph_article_publisher * @property string $open_graph_article_author * @property string $open_graph_article_published_time * @property string $open_graph_article_modified_time * @property string $open_graph_locale * @property string $open_graph_fb_app_id * @property string $permalink * @property array $schema * @property string $twitter_card * @property string $twitter_title * @property string $twitter_description * @property string $twitter_image * @property string $twitter_creator * @property string $twitter_site * @property object|array $source * @property array $breadcrumbs * @property int $estimated_reading_time_minutes * @property array $googlebot * @property array $bingbot */ class Indexable_Presentation extends Abstract_Presentation { /** * The indexable. * * @var Indexable */ public $model; /** * The meta tags context. * * @var Meta_Tags_Context */ public $context; /** * The Schema generator. * * @var Schema_Generator */ protected $schema_generator; /** * The Open Graph image generator. * * @var Open_Graph_Image_Generator */ protected $open_graph_image_generator; /** * The Twitter image generator. * * @var Twitter_Image_Generator */ protected $twitter_image_generator; /** * The Open Graph locale generator. * * @var Open_Graph_Locale_Generator */ private $open_graph_locale_generator; /** * The breadcrumbs generator. * * @var Breadcrumbs_Generator */ private $breadcrumbs_generator; /** * The current page helper. * * @var Current_Page_Helper */ protected $current_page; /** * The image helper. * * @var Image_Helper */ protected $image; /** * The options helper. * * @var Options_Helper */ protected $options; /** * The URL helper. * * @var Url_Helper */ protected $url; /** * The user helper. * * @var User_Helper */ protected $user; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The permalink helper. * * @var Permalink_Helper */ protected $permalink_helper; /** * The values helper. * * @var Values_Helper */ protected $values_helper; /** * Sets the generator dependencies. * * @required * * @param Schema_Generator $schema_generator The schema generator. * @param Open_Graph_Locale_Generator $open_graph_locale_generator The Open Graph locale generator. * @param Open_Graph_Image_Generator $open_graph_image_generator The Open Graph image generator. * @param Twitter_Image_Generator $twitter_image_generator The Twitter image generator. * @param Breadcrumbs_Generator $breadcrumbs_generator The breadcrumbs generator. * * @return void */ public function set_generators( Schema_Generator $schema_generator, Open_Graph_Locale_Generator $open_graph_locale_generator, Open_Graph_Image_Generator $open_graph_image_generator, Twitter_Image_Generator $twitter_image_generator, Breadcrumbs_Generator $breadcrumbs_generator ) { $this->schema_generator = $schema_generator; $this->open_graph_locale_generator = $open_graph_locale_generator; $this->open_graph_image_generator = $open_graph_image_generator; $this->twitter_image_generator = $twitter_image_generator; $this->breadcrumbs_generator = $breadcrumbs_generator; } /** * Used by dependency injection container to inject the helpers. * * @required * * @param Image_Helper $image The image helper. * @param Options_Helper $options The options helper. * @param Current_Page_Helper $current_page The current page helper. * @param Url_Helper $url The URL helper. * @param User_Helper $user The user helper. * @param Indexable_Helper $indexable The indexable helper. * @param Permalink_Helper $permalink The permalink helper. * @param Values_Helper $values The values helper. * * @return void */ public function set_helpers( Image_Helper $image, Options_Helper $options, Current_Page_Helper $current_page, Url_Helper $url, User_Helper $user, Indexable_Helper $indexable, Permalink_Helper $permalink, Values_Helper $values ) { $this->image = $image; $this->options = $options; $this->current_page = $current_page; $this->url = $url; $this->user = $user; $this->indexable_helper = $indexable; $this->permalink_helper = $permalink; $this->values_helper = $values; } /** * Gets the permalink from the indexable or generates it if dynamic permalinks are enabled. * * @return string The permalink. */ public function generate_permalink() { if ( $this->indexable_helper->dynamic_permalinks_enabled() ) { return $this->permalink_helper->get_permalink_for_indexable( $this->model ); } if ( \is_date() ) { return $this->current_page->get_date_archive_permalink(); } if ( \is_attachment() ) { global $wp; return \trailingslashit( \home_url( $wp->request ) ); } return $this->model->permalink; } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } return ''; } /** * Generates the meta description. * * @return string The meta description. */ public function generate_meta_description() { if ( $this->model->description ) { return $this->model->description; } return ''; } /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); return $this->filter_robots( $robots ); } /** * Gets the base robots value. * * @return array The base robots value. */ protected function get_base_robots() { return [ 'index' => ( $this->model->is_robots_noindex === true ) ? 'noindex' : 'index', 'follow' => ( $this->model->is_robots_nofollow === true ) ? 'nofollow' : 'follow', 'max-snippet' => 'max-snippet:-1', 'max-image-preview' => 'max-image-preview:large', 'max-video-preview' => 'max-video-preview:-1', ]; } /** * Run the robots output content through the `wpseo_robots` filter. * * @param array $robots The meta robots values to filter. * * @return array The filtered meta robots values. */ protected function filter_robots( $robots ) { // Remove values that are only listened to when indexing. if ( $robots['index'] === 'noindex' ) { $robots['imageindex'] = null; $robots['archive'] = null; $robots['snippet'] = null; $robots['max-snippet'] = null; $robots['max-image-preview'] = null; $robots['max-video-preview'] = null; } $robots_string = \implode( ', ', \array_filter( $robots ) ); /** * Filter: 'wpseo_robots' - Allows filtering of the meta robots output of Yoast SEO. * * @param string $robots The meta robots directives to be echoed. * @param Indexable_Presentation $presentation The presentation of an indexable. */ $robots_filtered = \apply_filters( 'wpseo_robots', $robots_string, $this ); // Convert the robots string back to an array. if ( \is_string( $robots_filtered ) ) { $robots_values = \explode( ', ', $robots_filtered ); $robots_new = []; foreach ( $robots_values as $value ) { $key = $value; // Change `noindex` to `index. if ( \strpos( $key, 'no' ) === 0 ) { $key = \substr( $value, 2 ); } // Change `max-snippet:-1` to `max-snippet`. $colon_position = \strpos( $key, ':' ); if ( $colon_position !== false ) { $key = \substr( $value, 0, $colon_position ); } $robots_new[ $key ] = $value; } $robots = $robots_new; } if ( \is_bool( $robots_filtered ) && ( $robots_filtered === false ) ) { return [ 'index' => 'noindex', 'follow' => 'nofollow', ]; } if ( ! $robots_filtered ) { return []; } /** * Filter: 'wpseo_robots_array' - Allows filtering of the meta robots output array of Yoast SEO. * * @param array $robots The meta robots directives to be used. * @param Indexable_Presentation $presentation The presentation of an indexable. */ return \apply_filters( 'wpseo_robots_array', \array_filter( $robots ), $this ); } /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { if ( $this->model->canonical ) { return $this->model->canonical; } if ( $this->permalink ) { return $this->permalink; } return ''; } /** * Generates the rel prev. * * @return string The rel prev value. */ public function generate_rel_prev() { return ''; } /** * Generates the rel next. * * @return string The rel prev next. */ public function generate_rel_next() { return ''; } /** * Generates the Open Graph type. * * @return string The Open Graph type. */ public function generate_open_graph_type() { return 'website'; } /** * Generates the open graph title. * * @return string The open graph title. */ public function generate_open_graph_title() { if ( $this->model->open_graph_title ) { $open_graph_title = $this->model->open_graph_title; } if ( empty( $open_graph_title ) ) { // The helper applies a filter, but we don't have a default value at this stage so we pass an empty string. $open_graph_title = $this->values_helper->get_open_graph_title( '', $this->model->object_type, $this->model->object_sub_type ); } if ( empty( $open_graph_title ) ) { $open_graph_title = $this->title; } return $open_graph_title; } /** * Generates the open graph description. * * @return string The open graph description. */ public function generate_open_graph_description() { if ( $this->model->open_graph_description ) { $open_graph_description = $this->model->open_graph_description; } if ( empty( $open_graph_description ) ) { // The helper applies a filter, but we don't have a default value at this stage so we pass an empty string. $open_graph_description = $this->values_helper->get_open_graph_description( '', $this->model->object_type, $this->model->object_sub_type ); } if ( empty( $open_graph_description ) ) { $open_graph_description = $this->meta_description; } return $open_graph_description; } /** * Generates the open graph images. * * @return array The open graph images. */ public function generate_open_graph_images() { if ( $this->context->open_graph_enabled === false ) { return []; } return $this->open_graph_image_generator->generate( $this->context ); } /** * Generates the open graph image ID. * * @return string The open graph image ID. */ public function generate_open_graph_image_id() { if ( $this->model->open_graph_image_id ) { return $this->model->open_graph_image_id; } return $this->values_helper->get_open_graph_image_id( 0, $this->model->object_type, $this->model->object_sub_type ); } /** * Generates the open graph image URL. * * @return string The open graph image URL. */ public function generate_open_graph_image() { if ( $this->model->open_graph_image ) { return $this->model->open_graph_image; } return $this->values_helper->get_open_graph_image( '', $this->model->object_type, $this->model->object_sub_type ); } /** * Generates the open graph url. * * @return string The open graph url. */ public function generate_open_graph_url() { if ( $this->model->canonical ) { return $this->model->canonical; } return $this->permalink; } /** * Generates the open graph article publisher. * * @return string The open graph article publisher. */ public function generate_open_graph_article_publisher() { return ''; } /** * Generates the open graph article author. * * @return string The open graph article author. */ public function generate_open_graph_article_author() { return ''; } /** * Generates the open graph article published time. * * @return string The open graph article published time. */ public function generate_open_graph_article_published_time() { return ''; } /** * Generates the open graph article modified time. * * @return string The open graph article modified time. */ public function generate_open_graph_article_modified_time() { return ''; } /** * Generates the open graph locale. * * @return string The open graph locale. */ public function generate_open_graph_locale() { return $this->open_graph_locale_generator->generate( $this->context ); } /** * Generates the open graph site name. * * @return string The open graph site name. */ public function generate_open_graph_site_name() { return $this->context->wordpress_site_name; } /** * Generates the Twitter card type. * * @return string The Twitter card type. */ public function generate_twitter_card() { return $this->context->twitter_card; } /** * Generates the Twitter title. * * @return string The Twitter title. */ public function generate_twitter_title() { if ( $this->model->twitter_title ) { return $this->model->twitter_title; } if ( $this->context->open_graph_enabled === true ) { $social_template_title = $this->values_helper->get_open_graph_title( '', $this->model->object_type, $this->model->object_sub_type ); $open_graph_title = $this->open_graph_title; // If the helper returns a value and it's different from the OG value in the indexable, // output it in a twitter: tag. if ( ! empty( $social_template_title ) && $social_template_title !== $open_graph_title ) { return $social_template_title; } // If the OG title is set, let og: tag take care of this. if ( ! empty( $open_graph_title ) ) { return ''; } } if ( $this->title ) { return $this->title; } return ''; } /** * Generates the Twitter description. * * @return string The Twitter description. */ public function generate_twitter_description() { if ( $this->model->twitter_description ) { return $this->model->twitter_description; } if ( $this->context->open_graph_enabled === true ) { $social_template_description = $this->values_helper->get_open_graph_description( '', $this->model->object_type, $this->model->object_sub_type ); $open_graph_description = $this->open_graph_description; // If the helper returns a value and it's different from the OG value in the indexable, // output it in a twitter: tag. if ( ! empty( $social_template_description ) && $social_template_description !== $open_graph_description ) { return $social_template_description; } // If the OG description is set, let og: tag take care of this. if ( ! empty( $open_graph_description ) ) { return ''; } } if ( $this->meta_description ) { return $this->meta_description; } return ''; } /** * Generates the Twitter image. * * @return string The Twitter image. */ public function generate_twitter_image() { $images = $this->twitter_image_generator->generate( $this->context ); $image = \reset( $images ); // Use a user-defined Twitter image, if present. if ( $image && $this->context->indexable->twitter_image_source === 'set-by-user' ) { return $image['url']; } // Let the Open Graph tags, if enabled, handle the rest of the fallback hierarchy. if ( $this->context->open_graph_enabled === true && $this->open_graph_images ) { return ''; } // Set a Twitter tag with the featured image, or a prominent image from the content, if present. if ( $image ) { return $image['url']; } return ''; } /** * Generates the Twitter creator. * * @return string The Twitter creator. */ public function generate_twitter_creator() { return ''; } /** * Generates the Twitter site. * * @return string The Twitter site. */ public function generate_twitter_site() { switch ( $this->context->site_represents ) { case 'person': $twitter = $this->user->get_the_author_meta( 'twitter', (int) $this->context->site_user_id ); if ( empty( $twitter ) ) { $twitter = $this->options->get( 'twitter_site' ); } break; case 'company': default: $twitter = $this->options->get( 'twitter_site' ); break; } return $twitter; } /** * Generates the source. * * @return array The source. */ public function generate_source() { return []; } /** * Generates the schema for the page. * * @codeCoverageIgnore Wrapper method. * * @return array The Schema object. */ public function generate_schema() { return $this->schema_generator->generate( $this->context ); } /** * Generates the breadcrumbs for the page. * * @codeCoverageIgnore Wrapper method. * * @return array The breadcrumbs. */ public function generate_breadcrumbs() { return $this->breadcrumbs_generator->generate( $this->context ); } /** * Generates the estimated reading time. * * @codeCoverageIgnore Wrapper method. * * @return int|null The estimated reading time. */ public function generate_estimated_reading_time_minutes() { if ( $this->model->estimated_reading_time_minutes !== null ) { return $this->model->estimated_reading_time_minutes; } if ( $this->context->post === null ) { return null; } // 200 is the approximate estimated words per minute across languages. $words_per_minute = 200; $words = \str_word_count( \wp_strip_all_tags( $this->context->post->post_content ) ); return (int) \round( $words / $words_per_minute ); } /** * Strips all nested dependencies from the debug info. * * @return array */ public function __debugInfo() { return [ 'model' => $this->model, 'context' => $this->context, ]; } } presentations/indexable-static-home-page-presentation.php 0000644 00000001411 15025657560 0017754 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; /** * Class Indexable_Static_Home_Page_Presentation. * * Presentation object for indexables. */ class Indexable_Static_Home_Page_Presentation extends Indexable_Post_Type_Presentation { /** * Wraps the get_paginated_url pagination helper method. * * @param string $url The un-paginated URL of the current archive. * @param string $page The page number to add on to $url for the $link tag. * * @return string The paginated URL. */ protected function get_paginated_url( $url, $page ) { return $this->pagination->get_paginated_url( $url, $page ); } /** * Generates the Open Graph type. * * @return string The Open Graph type. */ public function generate_open_graph_type() { return 'website'; } } presentations/indexable-home-page-presentation.php 0000644 00000002300 15025657560 0016465 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; /** * Class Indexable_Home_Page_Presentation. * * Presentation object for indexables. */ class Indexable_Home_Page_Presentation extends Indexable_Presentation { use Archive_Adjacent; /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { if ( $this->model->canonical ) { return $this->model->canonical; } if ( ! $this->permalink ) { return ''; } $current_page = $this->pagination->get_current_archive_page_number(); if ( $current_page > 1 ) { return $this->pagination->get_paginated_url( $this->permalink, $current_page ); } return $this->permalink; } /** * Generates the meta description. * * @return string The meta description. */ public function generate_meta_description() { if ( $this->model->description ) { return $this->model->description; } return $this->options->get( 'metadesc-home-wpseo' ); } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } return $this->options->get_title_default( 'title-home-wpseo' ); } } presentations/indexable-date-archive-presentation.php 0000644 00000005512 15025657560 0017167 0 ustar 00 <?php namespace Yoast\WP\SEO\Presentations; use Yoast\WP\SEO\Helpers\Pagination_Helper; /** * Class Indexable_Date_Archive_Presentation. * * Presentation object for indexables. */ class Indexable_Date_Archive_Presentation extends Indexable_Presentation { /** * Holds the Pagination_Helper instance. * * @var Pagination_Helper */ protected $pagination; /** * Indexable_Date_Archive_Presentation constructor. * * @param Pagination_Helper $pagination The pagination helper. */ public function __construct( Pagination_Helper $pagination ) { $this->pagination = $pagination; } /** * Generates the canonical. * * @return string The canonical. */ public function generate_canonical() { $canonical = $this->current_page->get_date_archive_permalink(); $current_page = $this->pagination->get_current_archive_page_number(); if ( $current_page > 1 ) { return $this->pagination->get_paginated_url( $canonical, $current_page ); } return $canonical; } /** * Generates the robots value. * * @return array The robots value. */ public function generate_robots() { $robots = $this->get_base_robots(); if ( $this->options->get( 'noindex-archive-wpseo', false ) ) { $robots['index'] = 'noindex'; } return $this->filter_robots( $robots ); } /** * Generates the title. * * @return string The title. */ public function generate_title() { if ( $this->model->title ) { return $this->model->title; } return $this->options->get_title_default( 'title-archive-wpseo' ); } /** * Generates the rel prev. * * @return string The rel prev value. */ public function generate_rel_prev() { if ( $this->pagination->is_rel_adjacent_disabled() ) { return ''; } $current_page = \max( 1, $this->pagination->get_current_archive_page_number() ); // Check if there is a previous page. if ( $current_page === 1 ) { return ''; } // Check if the previous page is the first page. if ( $current_page === 2 ) { return $this->current_page->get_date_archive_permalink(); } return $this->pagination->get_paginated_url( $this->current_page->get_date_archive_permalink(), ( $current_page - 1 ) ); } /** * Generates the rel next. * * @return string The rel prev next. */ public function generate_rel_next() { if ( $this->pagination->is_rel_adjacent_disabled() ) { return ''; } $current_page = \max( 1, $this->pagination->get_current_archive_page_number() ); if ( $this->pagination->get_number_of_archive_pages() <= $current_page ) { return ''; } return $this->pagination->get_paginated_url( $this->current_page->get_date_archive_permalink(), ( $current_page + 1 ) ); } /** * Generates the open graph url. * * @return string The open graph url. */ public function generate_open_graph_url() { return $this->current_page->get_date_archive_permalink(); } } config/oauth-client.php 0000644 00000021617 15025657560 0011140 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; use Exception; use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Failed_Storage_Exception; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Values\OAuth\OAuth_Token; use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException; use YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider; /** * Class OAuth_Client */ abstract class OAuth_Client { /** * The option's key. * * @var string */ protected $token_option = null; /** * The provider. * * @var Wincher_PKCE_Provider|GenericProvider */ protected $provider; /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The token. * * @var OAuth_Token|null */ protected $token = null; /** * OAuth_Client constructor. * * @param string $token_option The option's name to save the token as. * @param Wincher_PKCE_Provider|GenericProvider $provider The provider. * @param Options_Helper $options_helper The Options_Helper instance. * * @throws Empty_Property_Exception Exception thrown if a token property is empty. */ public function __construct( $token_option, $provider, Options_Helper $options_helper ) { $this->provider = $provider; $this->token_option = $token_option; $this->options_helper = $options_helper; $tokens = $this->options_helper->get( $this->token_option ); if ( ! empty( $tokens ) ) { $this->token = new OAuth_Token( $tokens['access_token'], $tokens['refresh_token'], $tokens['expires'], $tokens['has_expired'], $tokens['created_at'], ( $tokens['error_count'] ?? 0 ) ); } } /** * Requests the access token and refresh token based on the passed code. * * @param string $code The code to send. * * @return OAuth_Token The requested tokens. * * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. */ public function request_tokens( $code ) { try { $response = $this->provider ->getAccessToken( 'authorization_code', [ 'code' => $code, ] ); $token = OAuth_Token::from_response( $response ); return $this->store_token( $token ); } catch ( Exception $exception ) { throw new Authentication_Failed_Exception( $exception ); } } /** * Performs an authenticated GET request to the desired URL. * * @param string $url The URL to send the request to. * @param array $options The options to pass along to the request. * * @return mixed The parsed API response. * * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data. * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. * @throws Empty_Token_Exception Exception thrown if the token is empty. */ public function get( $url, $options = [] ) { return $this->do_request( 'GET', $url, $options ); } /** * Performs an authenticated POST request to the desired URL. * * @param string $url The URL to send the request to. * @param mixed $body The data to send along in the request's body. * @param array $options The options to pass along to the request. * * @return mixed The parsed API response. * * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data. * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. * @throws Empty_Token_Exception Exception thrown if the token is empty. */ public function post( $url, $body, $options = [] ) { $options['body'] = $body; return $this->do_request( 'POST', $url, $options ); } /** * Performs an authenticated DELETE request to the desired URL. * * @param string $url The URL to send the request to. * @param array $options The options to pass along to the request. * * @return mixed The parsed API response. * * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data. * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. * @throws Empty_Token_Exception Exception thrown if the token is empty. */ public function delete( $url, $options = [] ) { return $this->do_request( 'DELETE', $url, $options ); } /** * Determines whether there are valid tokens available. * * @return bool Whether there are valid tokens. */ public function has_valid_tokens() { return ! empty( $this->token ) && $this->token->has_expired() === false; } /** * Gets the stored tokens and refreshes them if they've expired. * * @return OAuth_Token The stored tokens. * * @throws Empty_Token_Exception Exception thrown if the token is empty. */ public function get_tokens() { if ( empty( $this->token ) ) { throw new Empty_Token_Exception(); } if ( $this->token->has_expired() ) { $this->token = $this->refresh_tokens( $this->token ); } return $this->token; } /** * Stores the passed token. * * @param OAuth_Token $token The token to store. * * @return OAuth_Token The stored token. * * @throws Failed_Storage_Exception Exception thrown if storing of the token fails. */ public function store_token( OAuth_Token $token ) { $saved = $this->options_helper->set( $this->token_option, $token->to_array() ); if ( $saved === false ) { throw new Failed_Storage_Exception(); } return $token; } /** * Clears the stored token from storage. * * @return bool The stored token. * * @throws Failed_Storage_Exception Exception thrown if clearing of the token fails. */ public function clear_token() { $saved = $this->options_helper->set( $this->token_option, [] ); if ( $saved === false ) { throw new Failed_Storage_Exception(); } return true; } /** * Performs the specified request. * * @param string $method The HTTP method to use. * @param string $url The URL to send the request to. * @param array $options The options to pass along to the request. * * @return mixed The parsed API response. * * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data. * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. * @throws Empty_Token_Exception Exception thrown if the token is empty. */ protected function do_request( $method, $url, array $options ) { $defaults = [ 'headers' => $this->provider->getHeaders( $this->get_tokens()->access_token ), ]; $options = \array_merge_recursive( $defaults, $options ); if ( \array_key_exists( 'params', $options ) ) { $url .= '?' . \http_build_query( $options['params'] ); unset( $options['params'] ); } $request = $this->provider ->getAuthenticatedRequest( $method, $url, null, $options ); return $this->provider->getParsedResponse( $request ); } /** * Refreshes the outdated tokens. * * @param OAuth_Token $tokens The outdated tokens. * * @return OAuth_Token The refreshed tokens. * * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. */ protected function refresh_tokens( OAuth_Token $tokens ) { // We do this dance with transients since we need to make sure we don't // delete valid tokens because of a race condition when two calls are // made simultaneously to this function and refresh token rotation is // turned on in the OAuth server. This is not 100% safe, but should at // least be much better than not having any lock at all. $lock_name = \sprintf( 'lock:%s', $this->token_option ); $can_lock = \get_transient( $lock_name ) === false; $has_lock = $can_lock && \set_transient( $lock_name, true, 30 ); try { $new_tokens = $this->provider->getAccessToken( 'refresh_token', [ 'refresh_token' => $tokens->refresh_token, ] ); $token_obj = OAuth_Token::from_response( $new_tokens ); return $this->store_token( $token_obj ); } catch ( Exception $exception ) { // If we tried to refresh but the refresh token is invalid, delete // the tokens so that we don't try again. Only do this if we got the // lock at the beginning of the call. if ( $has_lock && $exception->getMessage() === 'invalid_grant' ) { try { // To protect from race conditions, only do this if we've // seen an error before with the same token. if ( $tokens->error_count >= 1 ) { $this->clear_token(); } else { $tokens->error_count += 1; $this->store_token( $tokens ); } } catch ( Exception $e ) { // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch // Pass through. } } throw new Authentication_Failed_Exception( $exception ); } finally { \delete_transient( $lock_name ); } } } config/migration-status.php 0000644 00000012227 15025657560 0012053 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Migration_Status class. * * Used to validate whether or not migrations have been run and whether or not they should be run again. */ class Migration_Status { /** * The migration option key. * * @var string */ public const MIGRATION_OPTION_KEY = 'yoast_migrations_'; /** * The migration options. * * @var array */ protected $migration_options = []; /** * Checks if a given migration should be run. * * @param string $name The name of the migration. * @param string $version The current version. * * @return bool Whether or not the migration should be run. */ public function should_run_migration( $name, $version = \WPSEO_VERSION ) { $migration_status = $this->get_migration_status( $name ); // Check if we've attempted to run this migration in the past 10 minutes. If so, it may still be running. if ( \array_key_exists( 'lock', $migration_status ) ) { $timestamp = \strtotime( '-10 minutes' ); return $timestamp > $migration_status['lock']; } // Is the migration version less than the current version. return \version_compare( $migration_status['version'], $version, '<' ); } /** * Checks whether or not the given migration is at least the given version, defaults to checking for the latest version. * * @param string $name The name of the migration. * @param string $version The version to check, defaults to the latest version. * * @return bool Whether or not the requested migration is at least the requested version. */ public function is_version( $name, $version = \WPSEO_VERSION ) { $migration_status = $this->get_migration_status( $name ); return \version_compare( $version, $migration_status['version'], '<=' ); } /** * Gets the error of a given migration if it exists. * * @param string $name The name of the migration. * * @return bool|array False if there is no error, otherwise the error. */ public function get_error( $name ) { $migration_status = $this->get_migration_status( $name ); if ( ! isset( $migration_status['error'] ) ) { return false; } return $migration_status['error']; } /** * Sets an error for the migration. * * @param string $name The name of the migration. * @param string $message Message explaining the reason for the error. * @param string $version The current version. * * @return void */ public function set_error( $name, $message, $version = \WPSEO_VERSION ) { $migration_status = $this->get_migration_status( $name ); $migration_status['error'] = [ 'time' => \strtotime( 'now' ), 'version' => $version, 'message' => $message, ]; $this->set_migration_status( $name, $migration_status ); } /** * Updates the migration version to the latest version. * * @param string $name The name of the migration. * @param string $version The current version. * * @return void */ public function set_success( $name, $version = \WPSEO_VERSION ) { $migration_status = $this->get_migration_status( $name ); unset( $migration_status['lock'] ); unset( $migration_status['error'] ); $migration_status['version'] = $version; $this->set_migration_status( $name, $migration_status ); } /** * Locks the migration status. * * @param string $name The name of the migration. * * @return bool Whether or not the migration was succesfully locked. */ public function lock_migration( $name ) { $migration_status = $this->get_migration_status( $name ); $migration_status['lock'] = \strtotime( 'now' ); return $this->set_migration_status( $name, $migration_status ); } /** * Retrieves the migration option. * * @param string $name The name of the migration. * * @return bool|array The status of the migration, false if no status exists. */ protected function get_migration_status( $name ) { $current_blog_id = \get_current_blog_id(); if ( ! isset( $this->migration_options[ $current_blog_id ][ $name ] ) ) { $migration_status = \get_option( self::MIGRATION_OPTION_KEY . $name ); if ( ! \is_array( $migration_status ) || ! isset( $migration_status['version'] ) ) { $migration_status = [ 'version' => '0.0' ]; } if ( ! isset( $this->migration_options[ $current_blog_id ] ) ) { $this->migration_options[ $current_blog_id ] = []; } $this->migration_options[ $current_blog_id ][ $name ] = $migration_status; } return $this->migration_options[ $current_blog_id ][ $name ]; } /** * Retrieves the migration option. * * @param string $name The name of the migration. * @param array $migration_status The migration status. * * @return bool True if the status was succesfully updated, false otherwise. */ protected function set_migration_status( $name, $migration_status ) { if ( ! \is_array( $migration_status ) || ! isset( $migration_status['version'] ) ) { return false; } $current_blog_id = \get_current_blog_id(); if ( ! isset( $this->migration_options[ $current_blog_id ] ) ) { $this->migration_options[ $current_blog_id ] = []; } $this->migration_options[ $current_blog_id ][ $name ] = $migration_status; return \update_option( self::MIGRATION_OPTION_KEY . $name, $migration_status ); } } config/wincher-pkce-provider.php 0000644 00000016025 15025657560 0012750 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; use Exception; use UnexpectedValueException; use YoastSEO_Vendor\GuzzleHttp\Exception\BadResponseException; use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException; use YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider; use YoastSEO_Vendor\League\OAuth2\Client\Token\AccessToken; use YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface; use YoastSEO_Vendor\League\OAuth2\Client\Tool\BearerAuthorizationTrait; use YoastSEO_Vendor\Psr\Http\Message\RequestInterface; use YoastSEO_Vendor\Psr\Log\InvalidArgumentException; /** * Class Wincher_PKCE_Provider * * @codeCoverageIgnore Ignoring as this class is purely a temporary wrapper until https://github.com/thephpleague/oauth2-client/pull/901 is merged. * * @phpcs:disable WordPress.NamingConventions.ValidVariableName.PropertyNotSnakeCase -- This class extends an external class. * @phpcs:disable WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- This class extends an external class. */ class Wincher_PKCE_Provider extends GenericProvider { use BearerAuthorizationTrait; /** * The method to use. * * @var string|null */ protected $pkceMethod = null; /** * The PKCE code. * * @var string */ protected $pkceCode; /** * Set the value of the pkceCode parameter. * * When using PKCE this should be set before requesting an access token. * * @param string $pkce_code The value for the pkceCode. * @return self */ public function setPkceCode( $pkce_code ) { $this->pkceCode = $pkce_code; return $this; } /** * Returns the current value of the pkceCode parameter. * * This can be accessed by the redirect handler during authorization. * * @return string */ public function getPkceCode() { return $this->pkceCode; } /** * Returns a new random string to use as PKCE code_verifier and * hashed as code_challenge parameters in an authorization flow. * Must be between 43 and 128 characters long. * * @param int $length Length of the random string to be generated. * * @return string * * @throws Exception Throws exception if an invalid value is passed to random_bytes. */ protected function getRandomPkceCode( $length = 64 ) { return \substr( \strtr( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode \base64_encode( \random_bytes( $length ) ), '+/', '-_' ), 0, $length ); } /** * Returns the current value of the pkceMethod parameter. * * @return string|null */ protected function getPkceMethod() { return $this->pkceMethod; } /** * Returns authorization parameters based on provided options. * * @param array $options The options to use in the authorization parameters. * * @return array The authorization parameters * * @throws InvalidArgumentException Throws exception if an invalid PCKE method is passed in the options. * @throws Exception When something goes wrong with generating the PKCE code. */ protected function getAuthorizationParameters( array $options ) { if ( empty( $options['state'] ) ) { $options['state'] = $this->getRandomState(); } if ( empty( $options['scope'] ) ) { $options['scope'] = $this->getDefaultScopes(); } $options += [ 'response_type' => 'code', ]; if ( \is_array( $options['scope'] ) ) { $separator = $this->getScopeSeparator(); $options['scope'] = \implode( $separator, $options['scope'] ); } // Store the state as it may need to be accessed later on. $this->state = $options['state']; $pkce_method = $this->getPkceMethod(); if ( ! empty( $pkce_method ) ) { $this->pkceCode = $this->getRandomPkceCode(); if ( $pkce_method === 'S256' ) { $options['code_challenge'] = \trim( \strtr( // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode \base64_encode( \hash( 'sha256', $this->pkceCode, true ) ), '+/', '-_' ), '=' ); } elseif ( $pkce_method === 'plain' ) { $options['code_challenge'] = $this->pkceCode; } else { throw new InvalidArgumentException( 'Unknown PKCE method "' . $pkce_method . '".' ); } $options['code_challenge_method'] = $pkce_method; } // Business code layer might set a different redirect_uri parameter. // Depending on the context, leave it as-is. if ( ! isset( $options['redirect_uri'] ) ) { $options['redirect_uri'] = $this->redirectUri; } $options['client_id'] = $this->clientId; return $options; } /** * Requests an access token using a specified grant and option set. * * @param mixed $grant The grant to request access for. * @param array $options The options to use with the current request. * * @return AccessToken|AccessTokenInterface The access token. * * @throws UnexpectedValueException Exception thrown if the provider response contains errors. */ public function getAccessToken( $grant, array $options = [] ) { $grant = $this->verifyGrant( $grant ); $params = [ 'client_id' => $this->clientId, 'client_secret' => $this->clientSecret, 'redirect_uri' => $this->redirectUri, ]; if ( ! empty( $this->pkceCode ) ) { $params['code_verifier'] = $this->pkceCode; } $params = $grant->prepareRequestParameters( $params, $options ); $request = $this->getAccessTokenRequest( $params ); $response = $this->getParsedResponse( $request ); if ( \is_array( $response ) === false ) { throw new UnexpectedValueException( 'Invalid response received from Authorization Server. Expected JSON.' ); } $prepared = $this->prepareAccessTokenResponse( $response ); $token = $this->createAccessToken( $prepared, $grant ); return $token; } /** * Returns all options that can be configured. * * @return array The configurable options. */ protected function getConfigurableOptions() { return \array_merge( $this->getRequiredOptions(), [ 'accessTokenMethod', 'accessTokenResourceOwnerId', 'scopeSeparator', 'responseError', 'responseCode', 'responseResourceOwnerId', 'scopes', 'pkceMethod', ] ); } /** * Parses the request response. * * @param RequestInterface $request The request interface. * * @return array The parsed response. * * @throws IdentityProviderException Exception thrown if there is no proper identity provider. */ public function getParsedResponse( RequestInterface $request ) { try { $response = $this->getResponse( $request ); } catch ( BadResponseException $e ) { $response = $e->getResponse(); } $parsed = $this->parseResponse( $response ); $this->checkResponse( $response, $parsed ); // We always expect an array from the API except for on DELETE requests. // We convert to an array here to prevent problems with array_key_exists on PHP8. if ( ! \is_array( $parsed ) ) { $parsed = [ 'data' => [] ]; } // Add the response code as this is omitted from Winchers API. if ( ! \array_key_exists( 'status', $parsed ) ) { $parsed['status'] = $response->getStatusCode(); } return $parsed; } } config/badge-group-names.php 0000644 00000002766 15025657560 0012045 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Class Badge_Group_Names. * * This class defines groups for "new" badges, with the version in which those groups are no longer considered * to be "new". */ class Badge_Group_Names { public const GROUP_GLOBAL_TEMPLATES = 'global-templates'; /** * Constant describing when certain groups of new badges will no longer be shown. */ public const GROUP_NAMES = [ self::GROUP_GLOBAL_TEMPLATES => '16.7-beta0', ]; /** * The current plugin version. * * @var string */ protected $version; /** * Badge_Group_Names constructor. * * @param string|null $version Optional: the current plugin version. */ public function __construct( $version = null ) { if ( ! $version ) { $version = \WPSEO_VERSION; } $this->version = $version; } /** * Check whether a group of badges is still eligible for a "new" badge. * * @param string $group One of the GROUP_* constants. * @param string|null $current_version The current version of the plugin that's being checked. * * @return bool Whether a group of badges is still eligible for a "new" badge. */ public function is_still_eligible_for_new_badge( $group, $current_version = null ) { if ( ! \array_key_exists( $group, $this::GROUP_NAMES ) ) { return false; } $group_version = $this::GROUP_NAMES[ $group ]; if ( $current_version === null ) { $current_version = $this->version; } return (bool) \version_compare( $group_version, $current_version, '>' ); } } config/migrations/20200428194858_ExpandIndexableColumnLengths.php 0000644 00000003413 15025657560 0020134 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class ExpandIndexableColumnLengths. */ class ExpandIndexableColumnLengths extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->change_column( $this->get_table_name(), 'title', 'text', [ 'null' => true ] ); $this->change_column( $this->get_table_name(), 'open_graph_title', 'text', [ 'null' => true ] ); $this->change_column( $this->get_table_name(), 'twitter_title', 'text', [ 'null' => true ] ); $this->change_column( $this->get_table_name(), 'open_graph_image_source', 'text', [ 'null' => true ] ); $this->change_column( $this->get_table_name(), 'twitter_image_source', 'text', [ 'null' => true ] ); } /** * Migration down. * * @return void */ public function down() { $attr_limit_191 = [ 'null' => true, 'limit' => 191, ]; $this->change_column( $this->get_table_name(), 'title', 'string', $attr_limit_191 ); $this->change_column( $this->get_table_name(), 'opengraph_title', 'string', $attr_limit_191 ); $this->change_column( $this->get_table_name(), 'twitter_title', 'string', $attr_limit_191 ); $this->change_column( $this->get_table_name(), 'open_graph_image_source', 'string', $attr_limit_191 ); $this->change_column( $this->get_table_name(), 'twitter_image_source', 'string', $attr_limit_191 ); } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200513133401_ResetIndexableHierarchyTable.php 0000644 00000001366 15025657560 0020054 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class ResetIndexableHierarchyTable. */ class ResetIndexableHierarchyTable extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->query( 'TRUNCATE TABLE ' . $this->get_table_name() ); } /** * Migration down. * * @return void */ public function down() { // Nothing to do. } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable_Hierarchy' ); } } config/migrations/20200617122511_CreateSEOLinksTable.php 0000644 00000004735 15025657560 0016102 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * CreateSEOLinksTable class. */ class CreateSEOLinksTable extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); $adapter = $this->get_adapter(); // The table may already have been created by legacy code. // If not, create it exactly as it was. if ( ! $adapter->table_exists( $table_name ) ) { $table = $this->create_table( $table_name, [ 'id' => false ] ); $table->column( 'id', 'biginteger', [ 'primary_key' => true, 'limit' => 20, 'unsigned' => true, 'auto_increment' => true, ] ); $table->column( 'url', 'string', [ 'limit' => 255 ] ); $table->column( 'post_id', 'biginteger', [ 'limit' => 20, 'unsigned' => true, ] ); $table->column( 'target_post_id', 'biginteger', [ 'limit' => 20, 'unsigned' => true, ] ); $table->column( 'type', 'string', [ 'limit' => 8 ] ); $table->finish(); } if ( ! $adapter->has_index( $table_name, [ 'post_id', 'type' ], [ 'name' => 'link_direction' ] ) ) { $this->add_index( $table_name, [ 'post_id', 'type' ], [ 'name' => 'link_direction' ] ); } // Add these columns outside of the initial table creation as these did not exist on the legacy table. $this->add_column( $table_name, 'indexable_id', 'integer', [ 'unsigned' => true ] ); $this->add_column( $table_name, 'target_indexable_id', 'integer', [ 'unsigned' => true ] ); $this->add_column( $table_name, 'height', 'integer', [ 'unsigned' => true ] ); $this->add_column( $table_name, 'width', 'integer', [ 'unsigned' => true ] ); $this->add_column( $table_name, 'size', 'integer', [ 'unsigned' => true ] ); $this->add_column( $table_name, 'language', 'string', [ 'limit' => 32 ] ); $this->add_column( $table_name, 'region', 'string', [ 'limit' => 32 ] ); $this->add_index( $table_name, [ 'indexable_id', 'type' ], [ 'name' => 'indexable_link_direction' ] ); } /** * Migration down. * * @return void */ public function down() { $this->drop_table( $this->get_table_name() ); } /** * Returns the SEO Links table name. * * @return string */ private function get_table_name() { return Model::get_table_name( 'SEO_Links' ); } } config/migrations/20191011111109_WpYoastIndexableHierarchy.php 0000644 00000003072 15025657560 0017427 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class WpYoastIndexableHierarchy. */ class WpYoastIndexableHierarchy extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); $indexable_table = $this->create_table( $table_name, [ 'id' => false ] ); $indexable_table->column( 'indexable_id', 'integer', [ 'primary_key' => true, 'unsigned' => true, 'null' => true, 'limit' => 11, ] ); $indexable_table->column( 'ancestor_id', 'integer', [ 'primary_key' => true, 'unsigned' => true, 'null' => true, 'limit' => 11, ] ); $indexable_table->column( 'depth', 'integer', [ 'unsigned' => true, 'null' => true, 'limit' => 11, ] ); $indexable_table->finish(); $this->add_index( $table_name, 'indexable_id', [ 'name' => 'indexable_id' ] ); $this->add_index( $table_name, 'ancestor_id', [ 'name' => 'ancestor_id' ] ); $this->add_index( $table_name, 'depth', [ 'name' => 'depth' ] ); } /** * Migration up. * * @return void */ public function down() { $this->drop_table( $this->get_table_name() ); } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable_Hierarchy' ); } } config/migrations/20171228151840_WpYoastIndexable.php 0000644 00000014256 15025657560 0015614 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Indexable migration. */ class WpYoastIndexable extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->add_table(); } /** * Migration down. * * @return void */ public function down() { $this->drop_table( $this->get_table_name() ); } /** * Creates the indexable table. * * @return void */ private function add_table() { $table_name = $this->get_table_name(); $indexable_table = $this->create_table( $table_name ); // Permalink. $indexable_table->column( 'permalink', 'mediumtext', [ 'null' => true ] ); $indexable_table->column( 'permalink_hash', 'string', [ 'null' => true, 'limit' => 191, ] ); // Object information. $indexable_table->column( 'object_id', 'integer', [ 'unsigned' => true, 'null' => true, 'limit' => 11, ] ); $indexable_table->column( 'object_type', 'string', [ 'null' => false, 'limit' => 32, ] ); $indexable_table->column( 'object_sub_type', 'string', [ 'null' => true, 'limit' => 32, ] ); // Ownership. $indexable_table->column( 'author_id', 'integer', [ 'unsigned' => true, 'null' => true, 'limit' => 11, ] ); $indexable_table->column( 'post_parent', 'integer', [ 'unsigned' => true, 'null' => true, 'limit' => 11, ] ); // Title and description. $indexable_table->column( 'title', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'description', 'text', [ 'null' => true ] ); $indexable_table->column( 'breadcrumb_title', 'string', [ 'null' => true, 'limit' => 191, ] ); // Post metadata: status, public, protected. $indexable_table->column( 'post_status', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'is_public', 'boolean', [ 'null' => true, 'default' => null, ] ); $indexable_table->column( 'is_protected', 'boolean', [ 'default' => false ] ); $indexable_table->column( 'has_public_posts', 'boolean', [ 'null' => true, 'default' => null, ] ); $indexable_table->column( 'number_of_pages', 'integer', [ 'unsigned' => true, 'null' => true, 'default' => null, 'limit' => 11, ] ); $indexable_table->column( 'canonical', 'mediumtext', [ 'null' => true ] ); // SEO and readability analysis. $indexable_table->column( 'primary_focus_keyword', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'primary_focus_keyword_score', 'integer', [ 'null' => true, 'limit' => 3, ] ); $indexable_table->column( 'readability_score', 'integer', [ 'null' => true, 'limit' => 3, ] ); $indexable_table->column( 'is_cornerstone', 'boolean', [ 'default' => false ] ); // Robots. $indexable_table->column( 'is_robots_noindex', 'boolean', [ 'null' => true, 'default' => false, ] ); $indexable_table->column( 'is_robots_nofollow', 'boolean', [ 'null' => true, 'default' => false, ] ); $indexable_table->column( 'is_robots_noarchive', 'boolean', [ 'null' => true, 'default' => false, ] ); $indexable_table->column( 'is_robots_noimageindex', 'boolean', [ 'null' => true, 'default' => false, ] ); $indexable_table->column( 'is_robots_nosnippet', 'boolean', [ 'null' => true, 'default' => false, ] ); // Twitter. $indexable_table->column( 'twitter_title', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'twitter_image', 'mediumtext', [ 'null' => true ] ); $indexable_table->column( 'twitter_description', 'mediumtext', [ 'null' => true ] ); $indexable_table->column( 'twitter_image_id', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'twitter_image_source', 'string', [ 'null' => true, 'limit' => 191, ] ); // Open-Graph. $indexable_table->column( 'open_graph_title', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'open_graph_description', 'mediumtext', [ 'null' => true ] ); $indexable_table->column( 'open_graph_image', 'mediumtext', [ 'null' => true ] ); $indexable_table->column( 'open_graph_image_id', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'open_graph_image_source', 'string', [ 'null' => true, 'limit' => 191, ] ); $indexable_table->column( 'open_graph_image_meta', 'text', [ 'null' => true ] ); // Link count. $indexable_table->column( 'link_count', 'integer', [ 'null' => true, 'limit' => 11, ] ); $indexable_table->column( 'incoming_link_count', 'integer', [ 'null' => true, 'limit' => 11, ] ); // Prominent words. $indexable_table->column( 'prominent_words_version', 'integer', [ 'null' => true, 'limit' => 11, 'unsigned' => true, 'default' => null, ] ); $indexable_table->finish(); $this->add_indexes( $table_name ); $this->add_timestamps( $table_name ); } /** * Adds indexes to the indexable table. * * @param string $indexable_table_name The name of the indexable table. * * @return void */ private function add_indexes( $indexable_table_name ) { $this->add_index( $indexable_table_name, [ 'object_type', 'object_sub_type', ], [ 'name' => 'object_type_and_sub_type', ] ); $this->add_index( $indexable_table_name, 'permalink_hash', [ 'name' => 'permalink_hash', ] ); } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20230417083836_AddInclusiveLanguageScore.php 0000644 00000001662 15025657560 0017406 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * AddInclusiveLanguageScore class. */ class AddInclusiveLanguageScore extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); $this->add_column( $table_name, 'inclusive_language_score', 'integer', [ 'null' => true, 'limit' => 3, ] ); } /** * Migration down. * * @return void */ public function down() { $table_name = $this->get_table_name(); $this->remove_column( $table_name, 'inclusive_language_score' ); } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200702141921_CreateIndexableSubpagesIndex.php 0000644 00000002354 15025657560 0020054 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * CreateIndexableSubpagesIndex class. */ class CreateIndexableSubpagesIndex extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->change_column( $this->get_table_name(), 'post_status', 'string', [ 'null' => true, 'limit' => 20, ] ); $this->add_index( $this->get_table_name(), [ 'post_parent', 'object_type', 'post_status', 'object_id' ], [ 'name' => 'subpages' ] ); } /** * Migration down. * * @return void */ public function down() { $this->change_column( $this->get_table_name(), 'post_status', 'string', [ 'null' => true, 'limit' => 191, ] ); $this->remove_index( $this->get_table_name(), [ 'post_parent', 'object_type', 'post_status', 'object_id' ], [ 'name' => 'subpages' ] ); } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20201216141134_ExpandPrimaryTermIDColumnLengths.php 0000644 00000002005 15025657560 0020674 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * ExpandPrimaryTermIDColumnLengths class. */ class ExpandPrimaryTermIDColumnLengths extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * The columns to change the column type and length of. * * @var string[] */ protected static $columns_to_change = [ 'post_id', 'term_id', ]; /** * Migration up. * * @return void */ public function up() { foreach ( self::$columns_to_change as $column ) { $this->change_column( $this->get_table_name(), $column, 'biginteger', [ 'limit' => 20 ] ); } } /** * Migration down. * * @return void */ public function down() { } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Primary_Term' ); } } config/migrations/20200420073606_AddColumnsToIndexables.php 0000644 00000004124 15025657560 0016704 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class AddColumnsToIndexables. */ class AddColumnsToIndexables extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $tables = $this->get_tables(); $blog_id = \get_current_blog_id(); foreach ( $tables as $table ) { $this->add_column( $table, 'blog_id', 'biginteger', [ 'null' => false, 'limit' => 20, 'default' => $blog_id, ] ); } $attr_limit_32 = [ 'null' => true, 'limit' => 32, ]; $attr_limit_64 = [ 'null' => true, 'limit' => 64, ]; $indexable_table = $this->get_indexable_table(); $this->add_column( $indexable_table, 'language', 'string', $attr_limit_32 ); $this->add_column( $indexable_table, 'region', 'string', $attr_limit_32 ); $this->add_column( $indexable_table, 'schema_page_type', 'string', $attr_limit_64 ); $this->add_column( $indexable_table, 'schema_article_type', 'string', $attr_limit_64 ); } /** * Migration down. * * @return void */ public function down() { $tables = $this->get_tables(); foreach ( $tables as $table ) { $this->remove_column( $table, 'blog_id' ); } $indexable_table = $this->get_indexable_table(); $this->remove_column( $indexable_table, 'language' ); $this->remove_column( $indexable_table, 'region' ); $this->remove_column( $indexable_table, 'schema_page_type' ); $this->remove_column( $indexable_table, 'schema_article_type' ); } /** * Retrieves the Indexable table. * * @return string The Indexable table name. */ protected function get_indexable_table() { return Model::get_table_name( 'Indexable' ); } /** * Retrieves the table names to use. * * @return string[] The table names to use. */ protected function get_tables() { return [ $this->get_indexable_table(), Model::get_table_name( 'Indexable_Hierarchy' ), Model::get_table_name( 'Primary_Term' ), ]; } } config/migrations/20200616130143_ReplacePermalinkHashIndex.php 0000644 00000004365 15025657560 0017367 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * ReplacePermalinkHashIndex class. */ class ReplacePermalinkHashIndex extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); $adapter = $this->get_adapter(); if ( ! $adapter->has_table( $table_name ) ) { return; } $this->change_column( $table_name, 'permalink_hash', 'string', [ 'null' => true, 'limit' => 40, ] ); if ( $adapter->has_index( $table_name, [ 'permalink_hash' ], [ 'name' => 'permalink_hash' ] ) ) { $this->remove_index( $table_name, [ 'permalink_hash', ], [ 'name' => 'permalink_hash', ] ); } if ( ! $adapter->has_index( $table_name, [ 'permalink_hash', 'object_type' ], [ 'name' => 'permalink_hash_and_object_type' ] ) ) { $this->add_index( $table_name, [ 'permalink_hash', 'object_type', ], [ 'name' => 'permalink_hash_and_object_type', ] ); } } /** * Migration down. * * @return void */ public function down() { $table_name = $this->get_table_name(); $adapter = $this->get_adapter(); if ( ! $adapter->has_table( $table_name ) ) { return; } if ( $adapter->has_index( $table_name, [ 'permalink_hash', 'object_type' ], [ 'name' => 'permalink_hash_and_object_type' ] ) ) { $this->remove_index( $table_name, [ 'permalink_hash', 'object_type', ], [ 'name' => 'permalink_hash_and_object_type', ] ); } $this->change_column( $table_name, 'permalink_hash', 'string', [ 'null' => true, 'limit' => 191, ] ); if ( ! $adapter->has_index( $table_name, [ 'permalink_hash' ], [ 'name' => 'permalink_hash' ] ) ) { $this->add_index( $table_name, [ 'permalink_hash', ], [ 'name' => 'permalink_hash', ] ); } } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200408101900_AddCollationToTables.php 0000644 00000001676 15025657560 0016350 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class AddCollationToTables. */ class AddCollationToTables extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); if ( empty( $charset_collate ) ) { return; } $tables = [ Model::get_table_name( 'migrations' ), Model::get_table_name( 'Indexable' ), Model::get_table_name( 'Indexable_Hierarchy' ), Model::get_table_name( 'Primary_Term' ), ]; foreach ( $tables as $table ) { $this->query( 'ALTER TABLE ' . $table . ' CONVERT TO ' . \str_replace( 'DEFAULT ', '', $charset_collate ) ); } } /** * Migration down. * * @return void */ public function down() { // No down required. } } config/migrations/20200430150130_ClearIndexableTables.php 0000644 00000002057 15025657560 0016336 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class ClearIndexableTables. */ class ClearIndexableTables extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->query( 'TRUNCATE TABLE ' . $this->get_indexable_table_name() ); $this->query( 'TRUNCATE TABLE ' . $this->get_indexable_hierarchy_table_name() ); } /** * Migration down. * * @return void */ public function down() { // Nothing to do. } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_indexable_table_name() { return Model::get_table_name( 'Indexable' ); } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_indexable_hierarchy_table_name() { return Model::get_table_name( 'Indexable_Hierarchy' ); } } config/migrations/20190529075038_WpYoastDropIndexableMetaTableIfExists.php 0000644 00000001573 15025657560 0021706 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class WpYoastDropIndexableMetaTableIfExists. */ class WpYoastDropIndexableMetaTableIfExists extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); // This can be done safely as it executes a DROP IF EXISTS. $this->drop_table( $table_name ); } /** * Migration down. * * @return void */ public function down() { // No down required. This specific table should never exist. } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable_Meta' ); } } config/migrations/20201202144329_AddEstimatedReadingTime.php 0000644 00000001703 15025657560 0017012 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * AddEstimatedReadingTime class. */ class AddEstimatedReadingTime extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); $this->add_column( $table_name, 'estimated_reading_time_minutes', 'integer', [ 'null' => true, 'default' => null, ] ); } /** * Migration down. * * @return void */ public function down() { $table_name = $this->get_table_name(); $this->remove_column( $table_name, 'estimated_reading_time_minutes' ); } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200507054848_DeleteDuplicateIndexables.php 0000644 00000002113 15025657560 0017416 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class DeleteDuplicateIndexables. */ class DeleteDuplicateIndexables extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); /* * Deletes duplicate indexables that have the same object_id and object_type. * The rows with a higher ID are deleted as those should be unused and could be outdated. */ $this->query( 'DELETE wyi FROM ' . $table_name . ' wyi INNER JOIN ' . $table_name . ' wyi2 WHERE wyi2.object_id = wyi.object_id AND wyi2.object_type = wyi.object_type AND wyi2.id < wyi.id;' ); } /** * Migration down. * * @return void */ public function down() { // Nothing to do. } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20211020091404_AddObjectTimestamps.php 0000644 00000003007 15025657560 0016230 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * AddObjectTimestamps class. */ class AddObjectTimestamps extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->add_column( $this->get_table_name(), 'object_last_modified', 'datetime', [ 'null' => true, 'default' => null, ] ); $this->add_column( $this->get_table_name(), 'object_published_at', 'datetime', [ 'null' => true, 'default' => null, ] ); $this->add_index( $this->get_table_name(), [ 'object_published_at', 'is_robots_noindex', 'object_type', 'object_sub_type', ], [ 'name' => 'published_sitemap_index', ] ); } /** * Migration down. * * @return void */ public function down() { $this->remove_column( $this->get_table_name(), 'object_last_modified' ); $this->remove_column( $this->get_table_name(), 'object_published_at' ); $this->remove_index( $this->get_table_name(), [ 'object_published_at', 'is_robots_noindex', 'object_type', 'object_sub_type', ], [ 'name' => 'published_sitemap_index', ] ); } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200728095334_AddIndexesForProminentWordsOnIndexables.php 0000644 00000002265 15025657560 0022260 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * AddIndexesForProminentWordsOnIndexables class. */ class AddIndexesForProminentWordsOnIndexables extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * The columns on which an index should be added. * * @var string[] */ private $columns_with_index = [ 'prominent_words_version', 'object_type', 'object_sub_type', 'post_status', ]; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); $adapter = $this->get_adapter(); if ( ! $adapter->has_index( $table_name, $this->columns_with_index, [ 'name' => 'prominent_words' ] ) ) { $this->add_index( $table_name, $this->columns_with_index, [ 'name' => 'prominent_words', ] ); } } /** * Migration down. * * @return void */ public function down() { } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200430075614_AddIndexableObjectIdAndTypeIndex.php 0000644 00000001737 15025657560 0020547 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class AddIndexableObjectIdAndTypeIndex. */ class AddIndexableObjectIdAndTypeIndex extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->add_index( $this->get_table_name(), [ 'object_id', 'object_type', ], [ 'name' => 'object_id_and_type', ] ); } /** * Migration down. * * @return void */ public function down() { $this->remove_index( $this->get_table_name(), [ 'object_id', 'object_type', ], [ 'name' => 'object_id_and_type', ] ); } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200609154515_AddHasAncestorsColumn.php 0000644 00000001661 15025657560 0016550 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\WordPress\Wrapper; /** * Class AddHasAncestorsColumn. */ class AddHasAncestorsColumn extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->add_column( Model::get_table_name( 'Indexable' ), 'has_ancestors', 'boolean', [ 'default' => false, ] ); Wrapper::get_wpdb()->query( ' UPDATE ' . Model::get_table_name( 'Indexable' ) . ' SET has_ancestors = 1 WHERE id IN ( SELECT indexable_id FROM ' . Model::get_table_name( 'Indexable_Hierarchy' ) . ' ) ' ); } /** * Migration down. * * @return void */ public function down() { $this->remove_column( Model::get_table_name( 'Indexable' ), 'has_ancestors' ); } } config/migrations/20210817092415_AddVersionColumnToIndexables.php 0000644 00000001547 15025657560 0020107 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * AddVersionColumnToIndexables class. */ class AddVersionColumnToIndexables extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->add_column( $this->get_table_name(), 'version', 'integer', [ 'default' => 1, ] ); } /** * Migration down. * * @return void */ public function down() { $this->remove_column( $this->get_table_name(), 'version' ); } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20200429105310_TruncateIndexableTables.php 0000644 00000002065 15025657560 0017104 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class TruncateIndexableTables. */ class TruncateIndexableTables extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->query( 'TRUNCATE TABLE ' . $this->get_indexable_table_name() ); $this->query( 'TRUNCATE TABLE ' . $this->get_indexable_hierarchy_table_name() ); } /** * Migration down. * * @return void */ public function down() { // Nothing to do. } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_indexable_table_name() { return Model::get_table_name( 'Indexable' ); } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_indexable_hierarchy_table_name() { return Model::get_table_name( 'Indexable_Hierarchy' ); } } config/migrations/20200428123747_BreadcrumbTitleAndHierarchyReset.php 0000644 00000002356 15025657560 0020725 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Class BreadcrumbTitleAndHierarchyReset. */ class BreadcrumbTitleAndHierarchyReset extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $this->change_column( $this->get_indexable_table_name(), 'breadcrumb_title', 'text', [ 'null' => true ] ); $this->query( 'DELETE FROM ' . $this->get_indexable_hierarchy_table_name() ); } /** * Migration down. * * @return void */ public function down() { $this->change_column( $this->get_indexable_table_name(), 'breadcrumb_title', 'string', [ 'null' => true, 'limit' => 191, ] ); } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_indexable_table_name() { return Model::get_table_name( 'Indexable' ); } /** * Retrieves the table name to use. * * @return string The table name to use. */ protected function get_indexable_hierarchy_table_name() { return Model::get_table_name( 'Indexable_Hierarchy' ); } } config/migrations/20201216124002_ExpandIndexableIDColumnLengths.php 0000644 00000002023 15025657560 0020307 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * ExpandIndexableIDColumnLengths class. */ class ExpandIndexableIDColumnLengths extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * The columns to change the column type and length of. * * @var string[] */ protected static $columns_to_change = [ 'object_id', 'author_id', 'post_parent', ]; /** * Migration up. * * @return void */ public function up() { foreach ( self::$columns_to_change as $column ) { $this->change_column( $this->get_table_name(), $column, 'biginteger', [ 'limit' => 20 ] ); } } /** * Migration down. * * @return void */ public function down() { } /** * Retrieves the table name to use for storing indexables. * * @return string The table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Indexable' ); } } config/migrations/20171228151841_WpYoastPrimaryTerm.php 0000644 00000003022 15025657560 0016162 0 ustar 00 <?php namespace Yoast\WP\SEO\Config\Migrations; use Yoast\WP\Lib\Migrations\Migration; use Yoast\WP\Lib\Model; /** * Migration for the Primary Term. */ class WpYoastPrimaryTerm extends Migration { /** * The plugin this migration belongs to. * * @var string */ public static $plugin = 'free'; /** * Migration up. * * @return void */ public function up() { $table_name = $this->get_table_name(); $indexable_table = $this->create_table( $table_name ); $indexable_table->column( 'post_id', 'integer', [ 'unsigned' => true, 'null' => false, 'limit' => 11, ] ); $indexable_table->column( 'term_id', 'integer', [ 'unsigned' => true, 'null' => false, 'limit' => 11, ] ); $indexable_table->column( 'taxonomy', 'string', [ 'null' => false, 'limit' => 32, ] ); // Executes the SQL to create the table. $indexable_table->finish(); $this->add_index( $table_name, [ 'post_id', 'taxonomy', ], [ 'name' => 'post_taxonomy', ] ); $this->add_index( $table_name, [ 'post_id', 'term_id', ], [ 'name' => 'post_term', ] ); $this->add_timestamps( $table_name ); } /** * Migration down. * * @return void */ public function down() { $this->drop_table( $this->get_table_name() ); } /** * Retrieves the table name to use. * * @return string Table name to use. */ protected function get_table_name() { return Model::get_table_name( 'Primary_Term' ); } } config/indexing-reasons.php 0000644 00000002443 15025657560 0012015 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Class Indexing_Reasons. Contains constants that aren't context specific. */ class Indexing_Reasons { /** * Represents the reason that the indexing process failed and should be tried again. */ public const REASON_INDEXING_FAILED = 'indexing_failed'; /** * Represents the reason that the permalink settings are changed. */ public const REASON_PERMALINK_SETTINGS = 'permalink_settings_changed'; /** * Represents the reason that the category base is changed. */ public const REASON_CATEGORY_BASE_PREFIX = 'category_base_changed'; /** * Represents the reason that the tag base is changed. */ public const REASON_TAG_BASE_PREFIX = 'tag_base_changed'; /** * Represents the reason that the home url option is changed. */ public const REASON_HOME_URL_OPTION = 'home_url_option_changed'; /** * Represents the reason that a post type has been made public. */ public const REASON_POST_TYPE_MADE_PUBLIC = 'post_type_made_public'; /** * Represents the reason that a post type has been made viewable. */ public const REASON_TAXONOMY_MADE_PUBLIC = 'taxonomy_made_public'; /** * Represents the reason that attachments have stopped being redirected. */ public const REASON_ATTACHMENTS_MADE_ENABLED = 'attachments_made_enabled'; } config/wincher-client.php 0000644 00000007523 15025657560 0011457 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; use WPSEO_Utils; use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Values\OAuth\OAuth_Token; use Yoast\WP\SEO\Wrappers\WP_Remote_Handler; use YoastSEO_Vendor\GuzzleHttp\Client; use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException; /** * Class Wincher_Client */ class Wincher_Client extends OAuth_Client { /** * The option's key. */ public const TOKEN_OPTION = 'wincher_tokens'; /** * Name of the temporary PKCE cookie. */ public const PKCE_TRANSIENT_NAME = 'yoast_wincher_pkce'; /** * The WP_Remote_Handler instance. * * @var WP_Remote_Handler */ protected $wp_remote_handler; /** * Wincher_Client constructor. * * @param Options_Helper $options_helper The Options_Helper instance. * @param WP_Remote_Handler $wp_remote_handler The request handler. * * @throws Empty_Property_Exception Exception thrown if a token property is empty. */ public function __construct( Options_Helper $options_helper, WP_Remote_Handler $wp_remote_handler ) { $provider = new Wincher_PKCE_Provider( [ 'clientId' => 'yoast', 'redirectUri' => 'https://auth.wincher.com/yoast/setup', 'urlAuthorize' => 'https://auth.wincher.com/connect/authorize', 'urlAccessToken' => 'https://auth.wincher.com/connect/token', 'urlResourceOwnerDetails' => 'https://api.wincher.com/beta/user', 'scopes' => [ 'profile', 'account', 'websites:read', 'websites:write', 'offline_access' ], 'scopeSeparator' => ' ', 'pkceMethod' => 'S256', ], [ 'httpClient' => new Client( [ 'handler' => $wp_remote_handler ] ), ] ); parent::__construct( self::TOKEN_OPTION, $provider, $options_helper ); } /** * Return the authorization URL. * * @return string The authentication URL. */ public function get_authorization_url() { $parsed_site_url = \wp_parse_url( \get_site_url() ); $url = $this->provider->getAuthorizationUrl( [ 'state' => WPSEO_Utils::format_json_encode( [ 'domain' => $parsed_site_url['host'] ] ), ] ); $pkce_code = $this->provider->getPkceCode(); // Store a transient value with the PKCE code that we need in order to // exchange the returned code for a token after authorization. \set_transient( self::PKCE_TRANSIENT_NAME, $pkce_code, \DAY_IN_SECONDS ); return $url; } /** * Requests the access token and refresh token based on the passed code. * * @param string $code The code to send. * * @return OAuth_Token The requested tokens. * * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. */ public function request_tokens( $code ) { $pkce_code = \get_transient( self::PKCE_TRANSIENT_NAME ); if ( $pkce_code ) { $this->provider->setPkceCode( $pkce_code ); } return parent::request_tokens( $code ); } /** * Performs the specified request. * * @codeCoverageIgnore * * @param string $method The HTTP method to use. * @param string $url The URL to send the request to. * @param array $options The options to pass along to the request. * * @return mixed The parsed API response. * * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data. * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. * @throws Empty_Token_Exception Exception thrown if the token is empty. */ protected function do_request( $method, $url, array $options ) { $options['headers'] = [ 'Content-Type' => 'application/json' ]; return parent::do_request( $method, $url, $options ); } } config/researcher-languages.php 0000644 00000000523 15025657560 0012624 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Holds all languages supported with specific researches for our readability analysis. */ class Researcher_Languages { public const SUPPORTED_LANGUAGES = [ 'ar', 'ca', 'de', 'en', 'es', 'fa', 'fr', 'he', 'hu', 'id', 'it', 'nb', 'nl', 'pl', 'pt', 'ru', 'sv', 'tr', 'cs', 'sk', 'el', 'ja' ]; } config/semrush-client.php 0000644 00000005165 15025657560 0011506 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Token_Exception; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Wrappers\WP_Remote_Handler; use YoastSEO_Vendor\GuzzleHttp\Client; use YoastSEO_Vendor\League\OAuth2\Client\Provider\Exception\IdentityProviderException; use YoastSEO_Vendor\League\OAuth2\Client\Provider\GenericProvider; /** * Class SEMrush_Client */ class SEMrush_Client extends OAuth_Client { /** * The option's key. */ public const TOKEN_OPTION = 'semrush_tokens'; /** * SEMrush_Client constructor. * * @param Options_Helper $options_helper The Options_Helper instance. * @param WP_Remote_Handler $wp_remote_handler The request handler. * * @throws Empty_Property_Exception Throws when one of the required properties is empty. */ public function __construct( Options_Helper $options_helper, WP_Remote_Handler $wp_remote_handler ) { $provider = new GenericProvider( [ 'clientId' => 'yoast', 'clientSecret' => 'YdqNsWwnP4vE54WO1ugThKEjGMxMAHJt', 'redirectUri' => 'https://oauth.semrush.com/oauth2/yoast/success', 'urlAuthorize' => 'https://oauth.semrush.com/oauth2/authorize', 'urlAccessToken' => 'https://oauth.semrush.com/oauth2/access_token', 'urlResourceOwnerDetails' => 'https://oauth.semrush.com/oauth2/resource', ], [ 'httpClient' => new Client( [ 'handler' => $wp_remote_handler ] ), ] ); parent::__construct( self::TOKEN_OPTION, $provider, $options_helper ); } /** * Performs the specified request. * * @codeCoverageIgnore * * @param string $method The HTTP method to use. * @param string $url The URL to send the request to. * @param array $options The options to pass along to the request. * * @return mixed The parsed API response. * * @throws IdentityProviderException Exception thrown if there's something wrong with the identifying data. * @throws Authentication_Failed_Exception Exception thrown if authentication has failed. * @throws Empty_Token_Exception Exception thrown if the token is empty. */ public function do_request( $method, $url, array $options ) { // Add the access token to the GET parameters as well since this is what // the SEMRush API expects. $options = \array_merge_recursive( $options, [ 'params' => [ 'access_token' => $this->get_tokens()->access_token, ], ] ); return parent::do_request( $method, $url, $options ); } } config/schema-ids.php 0000644 00000002103 15025657560 0010546 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Class Schema_IDs. */ class Schema_IDs { /** * Hash used for the Author `@id`. */ public const AUTHOR_HASH = '#author'; /** * Hash used for the Author Logo's `@id`. */ public const AUTHOR_LOGO_HASH = '#authorlogo'; /** * Hash used for the Breadcrumb's `@id`. */ public const BREADCRUMB_HASH = '#breadcrumb'; /** * Hash used for the Person `@id`. */ public const PERSON_HASH = '#/schema/person/'; /** * Hash used for the Article `@id`. */ public const ARTICLE_HASH = '#article'; /** * Hash used for the Organization `@id`. */ public const ORGANIZATION_HASH = '#organization'; /** * Hash used for the Organization `@id`. */ public const ORGANIZATION_LOGO_HASH = '#/schema/logo/image/'; /** * Hash used for the logo `@id`. */ public const PERSON_LOGO_HASH = '#/schema/person/image/'; /** * Hash used for an Article's primary image `@id`. */ public const PRIMARY_IMAGE_HASH = '#primaryimage'; /** * Hash used for the Website's `@id`. */ public const WEBSITE_HASH = '#website'; } config/conflicting-plugins.php 0000644 00000012443 15025657560 0012517 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Conflicting_Plugins class that holds all known conflicting plugins. */ class Conflicting_Plugins { public const OPEN_GRAPH_PLUGINS = [ '2-click-socialmedia-buttons/2-click-socialmedia-buttons.php', // 2 Click Social Media Buttons. 'add-link-to-facebook/add-link-to-facebook.php', // Add Link to Facebook. 'add-meta-tags/add-meta-tags.php', // Add Meta Tags. 'easy-facebook-share-thumbnails/esft.php', // Easy Facebook Share Thumbnail. 'facebook/facebook.php', // Facebook (official plugin). 'facebook-awd/AWD_facebook.php', // Facebook AWD All in one. 'facebook-featured-image-and-open-graph-meta-tags/fb-featured-image.php', // Facebook Featured Image & OG Meta Tags. 'facebook-meta-tags/facebook-metatags.php', // Facebook Meta Tags. 'wonderm00ns-simple-facebook-open-graph-tags/wonderm00n-open-graph.php', // Facebook Open Graph Meta Tags for WordPress. 'facebook-revised-open-graph-meta-tag/index.php', // Facebook Revised Open Graph Meta Tag. 'facebook-thumb-fixer/_facebook-thumb-fixer.php', // Facebook Thumb Fixer. 'facebook-and-digg-thumbnail-generator/facebook-and-digg-thumbnail-generator.php', // Fedmich's Facebook Open Graph Meta. 'network-publisher/networkpub.php', // Network Publisher. 'nextgen-facebook/nextgen-facebook.php', // NextGEN Facebook OG. 'opengraph/opengraph.php', // Open Graph. 'open-graph-protocol-framework/open-graph-protocol-framework.php', // Open Graph Protocol Framework. 'seo-facebook-comments/seofacebook.php', // SEO Facebook Comments. 'sexybookmarks/sexy-bookmarks.php', // Shareaholic. 'shareaholic/sexy-bookmarks.php', // Shareaholic. 'sharepress/sharepress.php', // SharePress. 'simple-facebook-connect/sfc.php', // Simple Facebook Connect. 'social-discussions/social-discussions.php', // Social Discussions. 'social-sharing-toolkit/social_sharing_toolkit.php', // Social Sharing Toolkit. 'socialize/socialize.php', // Socialize. 'only-tweet-like-share-and-google-1/tweet-like-plusone.php', // Tweet, Like, Google +1 and Share. 'wordbooker/wordbooker.php', // Wordbooker. 'wpsso/wpsso.php', // WordPress Social Sharing Optimization. 'wp-caregiver/wp-caregiver.php', // WP Caregiver. 'wp-facebook-like-send-open-graph-meta/wp-facebook-like-send-open-graph-meta.php', // WP Facebook Like Send & Open Graph Meta. 'wp-facebook-open-graph-protocol/wp-facebook-ogp.php', // WP Facebook Open Graph protocol. 'wp-ogp/wp-ogp.php', // WP-OGP. 'zoltonorg-social-plugin/zosp.php', // Zolton.org Social Plugin. ]; public const XML_SITEMAPS_PLUGINS = [ 'google-sitemap-plugin/google-sitemap-plugin.php', // Google Sitemap (BestWebSoft). 'xml-sitemaps/xml-sitemaps.php', // XML Sitemaps (Denis de Bernardy and Mike Koepke). 'bwp-google-xml-sitemaps/bwp-simple-gxs.php', // Better WordPress Google XML Sitemaps (Khang Minh). 'google-sitemap-generator/sitemap.php', // Google XML Sitemaps (Arne Brachhold). 'xml-sitemap-feed/xml-sitemap.php', // XML Sitemap & Google News feeds (RavanH). 'google-monthly-xml-sitemap/monthly-xml-sitemap.php', // Google Monthly XML Sitemap (Andrea Pernici). 'simple-google-sitemap-xml/simple-google-sitemap-xml.php', // Simple Google Sitemap XML (iTx Technologies). 'another-simple-xml-sitemap/another-simple-xml-sitemap.php', // Another Simple XML Sitemap. 'xml-maps/google-sitemap.php', // Xml Sitemap (Jason Martens). 'google-xml-sitemap-generator-by-anton-dachauer/adachauer-google-xml-sitemap.php', // Google XML Sitemap Generator by Anton Dachauer (Anton Dachauer). 'wp-xml-sitemap/wp-xml-sitemap.php', // WP XML Sitemap (Team Vivacity). 'sitemap-generator-for-webmasters/sitemap.php', // Sitemap Generator for Webmasters (iwebslogtech). 'xml-sitemap-xml-sitemapcouk/xmls.php', // XML Sitemap - XML-Sitemap.co.uk (Simon Hancox). 'sewn-in-xml-sitemap/sewn-xml-sitemap.php', // Sewn In XML Sitemap (jcow). 'rps-sitemap-generator/rps-sitemap-generator.php', // RPS Sitemap Generator (redpixelstudios). ]; public const CLOAKING_PLUGINS = [ 'rs-head-cleaner/rs-head-cleaner.php', // RS Head Cleaner Plus https://wordpress.org/plugins/rs-head-cleaner/. 'rs-head-cleaner-lite/rs-head-cleaner-lite.php', // RS Head Cleaner Lite https://wordpress.org/plugins/rs-head-cleaner-lite/. ]; public const SEO_PLUGINS = [ 'all-in-one-seo-pack/all_in_one_seo_pack.php', // All in One SEO Pack. 'seo-ultimate/seo-ultimate.php', // SEO Ultimate. 'seo-by-rank-math/rank-math.php', // Rank Math. ]; /** * Returns the list of all conflicting plugins. * * @return array The list of all conflicting plugins. */ public static function all_plugins() { return \array_merge( self::OPEN_GRAPH_PLUGINS, self::XML_SITEMAPS_PLUGINS, self::CLOAKING_PLUGINS, self::SEO_PLUGINS ); } } config/schema-types.php 0000644 00000010002 15025657560 0011130 0 ustar 00 <?php namespace Yoast\WP\SEO\Config; /** * Class Schema_Types. */ class Schema_Types { /** * Holds the possible schema page types. * * Capitalized in this way so the value can be directly used in the schema output. * * @var string[] */ public const PAGE_TYPES = [ 'WebPage' => '', 'ItemPage' => '', 'AboutPage' => '', 'FAQPage' => '', 'QAPage' => '', 'ProfilePage' => '', 'ContactPage' => '', 'MedicalWebPage' => '', 'CollectionPage' => '', 'CheckoutPage' => '', 'RealEstateListing' => '', 'SearchResultsPage' => '', ]; /** * Holds the possible schema article types. * * Capitalized in this way so the value can be directly used in the schema output. * * @var string[] */ public const ARTICLE_TYPES = [ 'Article' => '', 'BlogPosting' => '', 'SocialMediaPosting' => '', 'NewsArticle' => '', 'AdvertiserContentArticle' => '', 'SatiricalArticle' => '', 'ScholarlyArticle' => '', 'TechArticle' => '', 'Report' => '', 'None' => '', ]; /** * Gets the page type options. * * @return array[] The schema page type options. */ public function get_page_type_options() { return [ [ 'name' => \__( 'Web Page', 'wordpress-seo' ), 'value' => 'WebPage', ], [ 'name' => \__( 'Item Page', 'wordpress-seo' ), 'value' => 'ItemPage', ], [ 'name' => \__( 'About Page', 'wordpress-seo' ), 'value' => 'AboutPage', ], [ 'name' => \__( 'FAQ Page', 'wordpress-seo' ), 'value' => 'FAQPage', ], [ 'name' => \__( 'QA Page', 'wordpress-seo' ), 'value' => 'QAPage', ], [ 'name' => \__( 'Profile Page', 'wordpress-seo' ), 'value' => 'ProfilePage', ], [ 'name' => \__( 'Contact Page', 'wordpress-seo' ), 'value' => 'ContactPage', ], [ 'name' => \__( 'Medical Web Page', 'wordpress-seo' ), 'value' => 'MedicalWebPage', ], [ 'name' => \__( 'Collection Page', 'wordpress-seo' ), 'value' => 'CollectionPage', ], [ 'name' => \__( 'Checkout Page', 'wordpress-seo' ), 'value' => 'CheckoutPage', ], [ 'name' => \__( 'Real Estate Listing', 'wordpress-seo' ), 'value' => 'RealEstateListing', ], [ 'name' => \__( 'Search Results Page', 'wordpress-seo' ), 'value' => 'SearchResultsPage', ], ]; } /** * Gets the article type options. * * @return array[] The schema article type options. */ public function get_article_type_options() { /** * Filter: 'wpseo_schema_article_types_labels' - Allow developers to filter the available article types and their labels. * * Make sure when you filter this to also filter `wpseo_schema_article_types`. * * @param array $schema_article_types_labels The available schema article types and their labels. */ return \apply_filters( 'wpseo_schema_article_types_labels', [ [ 'name' => \__( 'Article', 'wordpress-seo' ), 'value' => 'Article', ], [ 'name' => \__( 'Blog Post', 'wordpress-seo' ), 'value' => 'BlogPosting', ], [ 'name' => \__( 'Social Media Posting', 'wordpress-seo' ), 'value' => 'SocialMediaPosting', ], [ 'name' => \__( 'News Article', 'wordpress-seo' ), 'value' => 'NewsArticle', ], [ 'name' => \__( 'Advertiser Content Article', 'wordpress-seo' ), 'value' => 'AdvertiserContentArticle', ], [ 'name' => \__( 'Satirical Article', 'wordpress-seo' ), 'value' => 'SatiricalArticle', ], [ 'name' => \__( 'Scholarly Article', 'wordpress-seo' ), 'value' => 'ScholarlyArticle', ], [ 'name' => \__( 'Tech Article', 'wordpress-seo' ), 'value' => 'TechArticle', ], [ 'name' => \__( 'Report', 'wordpress-seo' ), 'value' => 'Report', ], [ 'name' => \__( 'None', 'wordpress-seo' ), 'value' => 'None', ], ] ); } } services/importing/conflicting-plugins-service.php 0000644 00000006224 15025657560 0016543 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Importing; use Yoast\WP\SEO\Config\Conflicting_Plugins; /** * Detects plugin conflicts. */ class Conflicting_Plugins_Service { /** * Detects the conflicting plugins. * * @return array A list of all active conflicting plugins. */ public function detect_conflicting_plugins() { $all_active_plugins = $this->get_active_plugins(); // Search for active plugins. return $this->get_active_conflicting_plugins( $all_active_plugins ); } /** * Deactivates the specified plugin(s) if any, or the entire list of known conflicting plugins. * * @param string|array|false $plugins Optional. The plugin filename, or array of plugin filenames, to deactivate. * * @return void */ public function deactivate_conflicting_plugins( $plugins = false ) { // If no plugins are specified, deactivate any known conflicting plugins that are active. if ( ! $plugins ) { $plugins = $this->detect_conflicting_plugins(); } // In case of a single plugin, wrap it in an array. if ( \is_string( $plugins ) ) { $plugins = [ $plugins ]; } if ( ! \is_array( $plugins ) ) { return; } // Deactivate all specified plugins across the network, while retaining their deactivation hook. \deactivate_plugins( $plugins ); } /** * Loop through the list of known conflicting plugins to check if one of the plugins is active. * * @param array $all_active_plugins All plugins loaded by WordPress. * * @return array The array of activated conflicting plugins. */ protected function get_active_conflicting_plugins( $all_active_plugins ) { $active_conflicting_plugins = []; foreach ( Conflicting_Plugins::all_plugins() as $plugin ) { if ( \in_array( $plugin, $all_active_plugins, true ) ) { $active_conflicting_plugins[] = $plugin; } } return $active_conflicting_plugins; } /** * Get a list of all plugins active in the current WordPress instance. * * @return array|false The names of all active plugins. */ protected function get_active_plugins() { // Request a list of active plugins from WordPress. $all_active_plugins = \get_option( 'active_plugins' ); return $this->ignore_deactivating_plugin( $all_active_plugins ); } /** * While deactivating a plugin, we should ignore the plugin currently being deactivated. * * @param array $all_active_plugins All plugins currently loaded by WordPress. * * @return array The remaining active plugins. */ protected function ignore_deactivating_plugin( $all_active_plugins ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are strictly comparing only. if ( isset( $_GET['action'] ) && isset( $_GET['plugin'] ) && \is_string( $_GET['action'] ) && \is_string( $_GET['plugin'] ) && \wp_unslash( $_GET['action'] ) === 'deactivate' ) { $deactivated_plugin = \sanitize_text_field( \wp_unslash( $_GET['plugin'] ) ); \check_admin_referer( 'deactivate-plugin_' . $deactivated_plugin ); $key_to_remove = \array_search( $deactivated_plugin, $all_active_plugins, true ); if ( $key_to_remove !== false ) { unset( $all_active_plugins[ $key_to_remove ] ); } } return $all_active_plugins; } } services/importing/importable-detector-service.php 0000644 00000005031 15025657560 0016525 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Importing; use Yoast\WP\SEO\Actions\Importing\Importing_Action_Interface; /** * Detects if any data from other SEO plugins is available for importing. */ class Importable_Detector_Service { /** * All known import actions * * @var array|Importing_Action_Interface[] */ protected $importers; /** * Importable_Detector_Service constructor. * * @param Importing_Action_Interface ...$importers All of the known importers. */ public function __construct( Importing_Action_Interface ...$importers ) { $this->importers = $importers; } /** * Returns the detected importers that have data to work with. * * @param string|null $plugin The plugin name of the importer. * @param string|null $type The type of the importer. * * @return array The detected importers that have data to work with. */ public function detect_importers( $plugin = null, $type = null ) { $detectors = $this->filter_actions( $this->importers, $plugin, $type ); $detected = []; foreach ( $detectors as $detector ) { if ( $detector->is_enabled() && $detector->get_type() !== 'cleanup' && ! $detector->get_completed() && $detector->get_limited_unindexed_count( 1 ) > 0 ) { $detected[ $detector->get_plugin() ][] = $detector->get_type(); } } return $detected; } /** * Returns the detected cleanups that have data to work with. * * @param string|null $plugin The plugin name of the cleanup. * * @return array The detected importers that have data to work with. */ public function detect_cleanups( $plugin = null ) { $detectors = $this->filter_actions( $this->importers, $plugin, 'cleanup' ); $detected = []; foreach ( $detectors as $detector ) { if ( $detector->is_enabled() && ! $detector->get_completed() && $detector->get_limited_unindexed_count( 1 ) > 0 ) { $detected[ $detector->get_plugin() ][] = $detector->get_type(); } } return $detected; } /** * Filters all import actions from a list that do not match the given Plugin or Type. * * @param Importing_Action_Interface[] $all_actions The complete list of actions. * @param string|null $plugin The Plugin name whose actions to keep. * @param string|null $type The type of actions to keep. * * @return array */ public function filter_actions( $all_actions, $plugin = null, $type = null ) { return \array_filter( $all_actions, static function ( $action ) use ( $plugin, $type ) { return $action->is_compatible_with( $plugin, $type ); } ); } } services/importing/aioseo/aioseo-replacevar-service.php 0000644 00000006637 15025657560 0017455 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Services\Importing\Aioseo; /** * Replaces AISOEO replacevars with Yoast ones. */ class Aioseo_Replacevar_Service { /** * Mapping between the AiOSEO replace vars and the Yoast replace vars. * * @see https://yoast.com/help/list-available-snippet-variables-yoast-seo/ * * @var array */ protected $replace_vars_map = [ // The key is the AiOSEO replace var, the value is the Yoast replace var (see class-wpseo-replace-vars). '#archive_title' => '%%archive_title%%', '#archive_date' => '%%date%%', '#attachment_caption' => '%%caption%%', '#author_bio' => '%%user_description%%', '#author_first_name' => '%%author_first_name%%', '#author_last_name' => '%%author_last_name%%', '#author_name' => '%%name%%', '#blog_title' => '%%sitename%%', // Same with #site_title. '#categories' => '%%category%%', '#current_date' => '%%currentdate%%', '#current_day' => '%%currentday%%', '#current_month' => '%%currentmonth%%', '#current_year' => '%%currentyear%%', '#parent_title' => '%%parent_title%%', '#page_number' => '%%pagenumber%%', '#permalink' => '%%permalink%%', '#post_content' => '%%post_content%%', '#post_date' => '%%date%%', '#post_day' => '%%post_day%%', '#post_month' => '%%post_month%%', '#post_title' => '%%title%%', '#post_year' => '%%post_year%%', '#post_excerpt_only' => '%%excerpt_only%%', '#post_excerpt' => '%%excerpt%%', '#search_term' => '%%searchphrase%%', '#separator_sa' => '%%sep%%', '#site_title' => '%%sitename%%', '#tagline' => '%%sitedesc%%', '#taxonomy_title' => '%%category_title%%', '#taxonomy_description' => '%%term_description%%', ]; /** * Edits the replace_vars map of the class. * * @param string $aioseo_var The AIOSEO replacevar. * @param string $yoast_var The Yoast replacevar. * * @return void */ public function compose_map( $aioseo_var, $yoast_var ) { $map = $this->replace_vars_map; $map[ $aioseo_var ] = $yoast_var; $this->replace_vars_map = $map; } /** * Transforms AIOSEO replacevars into Yoast replacevars. * * @param string $aioseo_replacevar The AIOSEO replacevar. * * @return string The Yoast replacevar. */ public function transform( $aioseo_replacevar ) { $yoast_replacevar = \str_replace( \array_keys( $this->replace_vars_map ), \array_values( $this->replace_vars_map ), $aioseo_replacevar ); // Transform the '#custom_field-<custom_field>' tags into '%%cf_<custom_field>%%' ones. $yoast_replacevar = \preg_replace_callback( '/#custom_field-([a-zA-Z0-9_-]+)/', static function ( $cf_matches ) { return '%%cf_' . $cf_matches[1] . '%%'; }, $yoast_replacevar ); // Transform the '#tax_name-<custom-tax-name>' tags into '%%ct_<custom-tax-name>%%' ones. $yoast_replacevar = \preg_replace_callback( '/#tax_name-([a-zA-Z0-9_-]+)/', static function ( $ct_matches ) { return '%%ct_' . $ct_matches[1] . '%%'; }, $yoast_replacevar ); return $yoast_replacevar; } } services/importing/aioseo/aioseo-robots-provider-service.php 0000644 00000003251 15025657560 0020456 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Services\Importing\Aioseo; use Yoast\WP\SEO\Helpers\Aioseo_Helper; /** * Provides AISOEO search appearance robot settings. */ class Aioseo_Robots_Provider_Service { /** * The AIOSEO helper. * * @var Aioseo_Helper */ protected $aioseo_helper; /** * Class constructor. * * @param Aioseo_Helper $aioseo_helper The AIOSEO helper. */ public function __construct( Aioseo_Helper $aioseo_helper ) { $this->aioseo_helper = $aioseo_helper; } /** * Retrieves the robot setting set globally in AIOSEO. * * @param string $setting_name The name of the robot setting, eg. noindex. * * @return bool Whether global robot settings enable or not the specific setting. */ public function get_global_robot_settings( $setting_name ) { $aioseo_settings = $this->aioseo_helper->get_global_option(); if ( empty( $aioseo_settings ) ) { return false; } $global_robot_settings = $aioseo_settings['searchAppearance']['advanced']['globalRobotsMeta']; if ( $global_robot_settings['default'] === true ) { return false; } return $global_robot_settings[ $setting_name ]; } /** * Gets the subtype's robot setting from the db. * * @param array $mapping The mapping of the setting we're working with. * * @return bool The robot setting. */ public function get_subtype_robot_setting( $mapping ) { $aioseo_settings = \json_decode( \get_option( $mapping['option_name'], '' ), true ); return $aioseo_settings['searchAppearance'][ $mapping['type'] ][ $mapping['subtype'] ]['advanced']['robotsMeta'][ $mapping['robot_type'] ]; } } services/importing/aioseo/aioseo-social-images-provider-service.php 0000644 00000010646 15025657560 0021671 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Services\Importing\Aioseo; use Yoast\WP\SEO\Helpers\Aioseo_Helper; use Yoast\WP\SEO\Helpers\Image_Helper; /** * Provides AISOEO social images urls. */ class Aioseo_Social_Images_Provider_Service { /** * The AIOSEO helper. * * @var Aioseo_Helper */ protected $aioseo_helper; /** * The image helper. * * @var Image_Helper */ protected $image; /** * Class constructor. * * @param Aioseo_Helper $aioseo_helper The AIOSEO helper. * @param Image_Helper $image The image helper. */ public function __construct( Aioseo_Helper $aioseo_helper, Image_Helper $image ) { $this->aioseo_helper = $aioseo_helper; $this->image = $image; } /** * Retrieves the default source of social images. * * @param string $social_setting The social settings we're working with, eg. open-graph or twitter. * * @return string The default source of social images. */ public function get_default_social_image_source( $social_setting ) { return $this->get_social_defaults( 'source', $social_setting ); } /** * Retrieves the default custom social image if there is any. * * @param string $social_setting The social settings we're working with, eg. open-graph or twitter. * * @return string The global default social image. */ public function get_default_custom_social_image( $social_setting ) { return $this->get_social_defaults( 'custom_image', $social_setting ); } /** * Retrieves social defaults, be it Default Post Image Source or Default Post Image. * * @param string $setting The setting we want, eg. source or custom image. * @param string $social_setting The social settings we're working with, eg. open-graph or twitter. * * @return string The social default. */ public function get_social_defaults( $setting, $social_setting ) { switch ( $setting ) { case 'source': $setting_key = 'defaultImageSourcePosts'; break; case 'custom_image': $setting_key = 'defaultImagePosts'; break; default: return ''; } $aioseo_settings = $this->aioseo_helper->get_global_option(); if ( $social_setting === 'og' ) { $social_setting = 'facebook'; } if ( ! isset( $aioseo_settings['social'][ $social_setting ]['general'][ $setting_key ] ) ) { return ''; } return $aioseo_settings['social'][ $social_setting ]['general'][ $setting_key ]; } /** * Retrieves the url of the first image in content. * * @param int $post_id The post id to extract the image from. * * @return string The url of the first image in content. */ public function get_first_image_in_content( $post_id ) { $image = $this->image->get_gallery_image( $post_id ); if ( ! $image ) { $image = $this->image->get_post_content_image( $post_id ); } return $image; } /** * Retrieves the url of the first attached image. * * @param int $post_id The post id to extract the image from. * * @return string The url of the first attached image. */ public function get_first_attached_image( $post_id ) { if ( \get_post_type( $post_id ) === 'attachment' ) { return $this->image->get_attachment_image_source( $post_id, 'fullsize' ); } $attachments = \get_children( [ 'post_parent' => $post_id, 'post_status' => 'inherit', 'post_type' => 'attachment', 'post_mime_type' => 'image', ] ); if ( $attachments && ! empty( $attachments ) ) { return $this->image->get_attachment_image_source( \array_values( $attachments )[0]->ID, 'fullsize' ); } return ''; } /** * Retrieves the url of the featured image. * * @param int $post_id The post id to extract the image from. * * @return string The url of the featured image. */ public function get_featured_image( $post_id ) { $feature_image_id = \get_post_thumbnail_id( $post_id ); if ( $feature_image_id ) { return $this->image->get_attachment_image_source( $feature_image_id, 'fullsize' ); } return ''; } /** * Retrieves the url of the first available image. Tries each image source to get one image. * * @param int $post_id The post id to extract the image from. * * @return string The url of the featured image. */ public function get_auto_image( $post_id ) { $image = $this->get_first_attached_image( $post_id ); if ( ! $image ) { $image = $this->get_first_image_in_content( $post_id ); } return $image; } } services/importing/aioseo/aioseo-robots-transformer-service.php 0000644 00000003323 15025657560 0021166 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Services\Importing\Aioseo; /** * Transforms AISOEO search appearance robot settings. */ class Aioseo_Robots_Transformer_Service { /** * The robots transfomer service. * * @var Aioseo_Robots_Provider_Service */ protected $robots_provider; /** * Class constructor. * * @param Aioseo_Robots_Provider_Service $robots_provider The robots provider service. */ public function __construct( Aioseo_Robots_Provider_Service $robots_provider ) { $this->robots_provider = $robots_provider; } /** * Transforms the robot setting, taking into consideration whether they defer to global defaults. * * @param string $setting_name The name of the robot setting, eg. noindex. * @param bool $setting_value The value of the robot setting. * @param array $mapping The mapping of the setting we're working with. * * @return bool The transformed robot setting. */ public function transform_robot_setting( $setting_name, $setting_value, $mapping ) { $aioseo_settings = \json_decode( \get_option( $mapping['option_name'], '' ), true ); // Let's check first if it defers to global robot settings. if ( empty( $aioseo_settings ) || ! isset( $aioseo_settings['searchAppearance'][ $mapping['type'] ][ $mapping['subtype'] ]['advanced']['robotsMeta']['default'] ) ) { return $setting_value; } $defers_to_defaults = $aioseo_settings['searchAppearance'][ $mapping['type'] ][ $mapping['subtype'] ]['advanced']['robotsMeta']['default']; if ( $defers_to_defaults ) { return $this->robots_provider->get_global_robot_settings( $setting_name ); } return $setting_value; } } services/indexables/indexable-version-manager.php 0000644 00000004440 15025657560 0016261 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Indexables; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Handles version control for Indexables. */ class Indexable_Version_Manager { /** * Stores the version of each Indexable type. * * @var Indexable_Builder_Versions The current versions of all indexable builders. */ protected $indexable_builder_versions; /** * Indexable_Version_Manager constructor. * * @param Indexable_Builder_Versions $indexable_builder_versions The current versions of all indexable builders. */ public function __construct( Indexable_Builder_Versions $indexable_builder_versions ) { $this->indexable_builder_versions = $indexable_builder_versions; } /** * Determines if an Indexable has a lower version than the builder for that Indexable's type. * * @param Indexable $indexable The Indexable to check. * * @return bool True if the given version is older than the current latest version. */ public function indexable_needs_upgrade( $indexable ) { if ( ( ! $indexable ) || ( ! \is_a( $indexable, Indexable::class ) ) ) { return false; } return $this->needs_upgrade( $indexable->object_type, $indexable->version ); } /** * Determines if an Indexable version for the type is lower than the current version for that Indexable type. * * @param string $object_type The Indexable's object type. * @param int $indexable_version The Indexable's version. * * @return bool True if the given version is older than the current latest version. */ protected function needs_upgrade( $object_type, $indexable_version ) { $current_indexable_builder_version = $this->indexable_builder_versions->get_latest_version_for_type( $object_type ); // If the Indexable's version is below the current version, that Indexable needs updating. return $indexable_version < $current_indexable_builder_version; } /** * Sets an Indexable's version to the latest version. * * @param Indexable $indexable The Indexable to update. * * @return Indexable */ public function set_latest( $indexable ) { if ( ! $indexable ) { return $indexable; } $indexable->version = $this->indexable_builder_versions->get_latest_version_for_type( $indexable->object_type ); return $indexable; } } services/health-check/default-tagline-runner.php 0000644 00000002311 15025657560 0016004 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Runs the Default_Tagline health check. */ class Default_Tagline_Runner implements Runner_Interface { /** * The default WordPress tagline. */ public const DEFAULT_BLOG_DESCRIPTION = 'Just another WordPress site'; /** * Is set to true when the default tagline is set. * * @var bool */ private $has_default_tagline = true; /** * Runs the health check. Checks if the tagline is set to WordPress' default tagline, or to its set translation. * * @return void */ public function run() { $blog_description = \get_option( 'blogdescription' ); // We are using the WordPress internal translation. // @TODO: This doesn't work when checking in a cron for some reason, investigate. $translated_blog_description = \__( 'Just another WordPress site', 'default' ); $this->has_default_tagline = $translated_blog_description === $blog_description || $blog_description === self::DEFAULT_BLOG_DESCRIPTION; } /** * Returns true if the tagline is set to a non-default tagline. * * @return bool The boolean indicating if the health check was succesful. */ public function is_successful() { return ! $this->has_default_tagline; } } services/health-check/default-tagline-reports.php 0000644 00000004410 15025657560 0016173 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Presents a set of different messages for the Default_Tagline health check. */ class Default_Tagline_Reports { use Reports_Trait; /** * Constructor * * @param Report_Builder_Factory $report_builder_factory The factory for result builder objects. * This class uses the report builder to generate WordPress-friendly * health check results. */ public function __construct( Report_Builder_Factory $report_builder_factory ) { $this->report_builder_factory = $report_builder_factory; } /** * Returns the message for a successful health check. * * @return string[] The message as a WordPress site status report. */ public function get_success_result() { return $this->get_report_builder() ->set_label( \__( 'You changed the default WordPress tagline', 'wordpress-seo' ) ) ->set_status_good() ->set_description( \__( 'You are using a custom tagline or an empty one.', 'wordpress-seo' ) ) ->build(); } /** * Returns the message for a failed health check. In this case, when the user still has the default WordPress tagline set. * * @return string[] The message as a WordPress site status report. */ public function get_has_default_tagline_result() { return $this->get_report_builder() ->set_label( \__( 'You should change the default WordPress tagline', 'wordpress-seo' ) ) ->set_status_recommended() ->set_description( \__( 'You still have the default WordPress tagline. Even an empty one is probably better.', 'wordpress-seo' ) ) ->set_actions( $this->get_actions() ) ->build(); } /** * Returns the actions that the user should take when his tagline is still set to the WordPress default. * * @return string The actions as an HTML string. */ private function get_actions() { $query_args = [ 'autofocus[control]' => 'blogdescription', ]; $customize_url = \add_query_arg( $query_args, \wp_customize_url() ); return \sprintf( /* translators: 1: link open tag; 2: link close tag. */ \esc_html__( '%1$sYou can change the tagline in the customizer%2$s.', 'wordpress-seo' ), '<a href="' . \esc_url( $customize_url ) . '">', '</a>' ); } } services/health-check/health-check.php 0000644 00000004455 15025657560 0013763 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Abstract class for all health checks. Provides a uniform interface for the Health_Check_Integration. */ abstract class Health_Check { /** * The prefix to add to the test identifier. Used to differentiate between Yoast's health checks, and other health checks. */ public const TEST_IDENTIFIER_PREFIX = 'yoast-'; /** * The object that runs the actual health check. * * @var Runner_Interface */ private $runner; /** * The health check implementation sets the runner so this class can start a health check. * * @param Runner_Interface $runner The health check runner. * @return void */ protected function set_runner( $runner ) { $this->runner = $runner; } /** * Returns the identifier of health check implementation. WordPress needs this to manage the health check (https://developer.wordpress.org/reference/hooks/site_status_tests/). * * @return string The identifier that WordPress requires. */ public function get_test_identifier() { $full_class_name = static::class; $class_name_backslash_index = \strrpos( $full_class_name, '\\' ); $class_name = $full_class_name; if ( $class_name_backslash_index ) { $class_name_index = ( $class_name_backslash_index + 1 ); $class_name = \substr( $full_class_name, $class_name_index ); } $lowercase = \strtolower( $class_name ); $whitespace_as_dashes = \str_replace( '_', '-', $lowercase ); $with_prefix = self::TEST_IDENTIFIER_PREFIX . $whitespace_as_dashes; return $with_prefix; } /** * Runs the health check, and returns its result in the format that WordPress requires to show the results to the user (https://developer.wordpress.org/reference/hooks/site_status_test_result/). * * @return string[] The array containing a WordPress site status report. */ public function run_and_get_result() { $this->runner->run(); return $this->get_result(); } /** * Gets the result from the health check implementation. * * @return string[] The array containing a WordPress site status report. */ abstract protected function get_result(); /** * Returns whether the health check should be excluded from the results. * * @return bool Whether the check should be excluded. */ abstract public function is_excluded(); } services/health-check/postname-permalink-reports.php 0000644 00000005247 15025657560 0016745 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Presents a set of different messages for the Postname_Permalink health check. */ class Postname_Permalink_Reports { use Reports_Trait; /** * Constructor. * * @param Report_Builder_Factory $report_builder_factory The factory for result builder objects. * This class uses the report builder to generate WordPress-friendly * health check results. */ public function __construct( Report_Builder_Factory $report_builder_factory ) { $this->report_builder_factory = $report_builder_factory; } /** * Returns the report for when permalinks are set to contain the post name. * * @return string[] The message as a WordPress site status report. */ public function get_success_result() { return $this->get_report_builder() ->set_label( \esc_html__( 'Your permalink structure includes the post name', 'wordpress-seo' ) ) ->set_status_good() ->set_description( \__( 'You do have your postname in the URL of your posts and pages.', 'wordpress-seo' ) ) ->build(); } /** * Returns the report for when permalinks are not set to contain the post name. * * @return string[] The message as a WordPress site status report. */ public function get_has_no_postname_in_permalink_result() { return $this->get_report_builder() ->set_label( \__( 'You do not have your postname in the URL of your posts and pages', 'wordpress-seo' ) ) ->set_status_recommended() ->set_description( $this->get_has_no_postname_in_permalink_description() ) ->set_actions( $this->get_has_no_postname_in_permalink_actions() ) ->build(); } /** * Returns the description for when permalinks are not set to contain the post name. * * @return string The description as a string. */ private function get_has_no_postname_in_permalink_description() { return \sprintf( /* translators: %s expands to '/%postname%/' */ \__( 'It\'s highly recommended to have your postname in the URL of your posts and pages. Consider setting your permalink structure to %s.', 'wordpress-seo' ), '<strong>/%postname%/</strong>' ); } /** * Returns the actions for when permalinks are not set to contain the post name. * * @return string The actions as a string. */ private function get_has_no_postname_in_permalink_actions() { return \sprintf( /* translators: %1$s is a link start tag to the permalink settings page, %2$s is the link closing tag. */ \__( 'You can fix this on the %1$sPermalink settings page%2$s.', 'wordpress-seo' ), '<a href="' . \admin_url( 'options-permalink.php' ) . '">', '</a>' ); } } services/health-check/page-comments-runner.php 0000644 00000001503 15025657560 0015500 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Runs the Page_Comments health check. */ class Page_Comments_Runner implements Runner_Interface { /** * Is set to true when comments are set to display on a single page. * * @var bool */ private $comments_on_single_page; /** * Constructor. */ public function __construct() { $this->comments_on_single_page = false; } /** * Runs the health check. Checks if comments are displayed on a single page. * * @return void */ public function run() { $this->comments_on_single_page = \get_option( 'page_comments' ) !== '1'; } /** * Returns true if comments are displayed on a single page. * * @return bool True if comments are displayed on a single page. */ public function is_successful() { return $this->comments_on_single_page; } } services/health-check/myyoast-api-request-factory.php 0000644 00000000751 15025657560 0017045 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; use WPSEO_MyYoast_Api_Request; /** * Creates WPSEO_MyYoast_Api_Request objects. */ class MyYoast_Api_Request_Factory { /** * Creates a new WPSEO_MyYoast_API_Request. * * @param string $url The URL for the request. * @param array $args Optional arguments for the request. * @return WPSEO_MyYoast_Api_Request */ public function create( $url, $args = [] ) { return new WPSEO_MyYoast_Api_Request( $url, $args ); } } services/health-check/page-comments-check.php 0000644 00000002570 15025657560 0015251 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Paasses when comments are set to be on a single page. */ class Page_Comments_Check extends Health_Check { /** * Runs the health check. * * @var Page_Comments_Runner */ private $runner; /** * Generates WordPress-friendly health check results. * * @var Page_Comments_Reports */ private $reports; /** * Constructor. * * @param Page_Comments_Runner $runner The object that implements the actual health check. * @param Page_Comments_Reports $reports The object that generates WordPress-friendly results. */ public function __construct( Page_Comments_Runner $runner, Page_Comments_Reports $reports ) { $this->runner = $runner; $this->reports = $reports; $this->reports->set_test_identifier( $this->get_test_identifier() ); $this->set_runner( $this->runner ); } /** * Returns the WordPress-friendly health check result. * * @return string[] The WordPress-friendly health check result. */ protected function get_result() { if ( $this->runner->is_successful() ) { return $this->reports->get_success_result(); } return $this->reports->get_has_comments_on_multiple_pages_result(); } /** * Returns whether the health check should be excluded from the results. * * @return bool false, because it's not excluded. */ public function is_excluded() { return false; } } services/health-check/postname-permalink-runner.php 0000644 00000001614 15025657560 0016552 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Runs the Postname_Permalink health check. */ class Postname_Permalink_Runner implements Runner_Interface { /** * Is set to true when permalinks are set to contain the post name * * @var bool */ private $permalinks_contain_postname; /** * Constructor. */ public function __construct() { $this->permalinks_contain_postname = false; } /** * Runs the health check. Checks if permalinks are set to contain the post name. * * @return void */ public function run() { $this->permalinks_contain_postname = ( \strpos( \get_option( 'permalink_structure' ), '%postname%' ) !== false ); } /** * Returns true if permalinks are set to contain the post name. * * @return bool True if permalinks are set to contain the post name. */ public function is_successful() { return $this->permalinks_contain_postname; } } services/health-check/links-table-reports.php 0000644 00000006650 15025657560 0015343 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; use WPSEO_Admin_Utils; use WPSEO_Shortlinker; /** * Presents a set of different messages for the Links_Table health check. */ class Links_Table_Reports { use Reports_Trait; /** * Shortlinker object used to create short links for reports. * * @var WPSEO_Shortlinker */ private $shortlinker; /** * Constructor * * @param Report_Builder_Factory $report_builder_factory The factory for result builder objects. * This class uses the report builder to generate WordPress-friendly * health check results. * @param WPSEO_Shortlinker $shortlinker Object used to add short links to the report description. */ public function __construct( Report_Builder_Factory $report_builder_factory, WPSEO_Shortlinker $shortlinker ) { $this->report_builder_factory = $report_builder_factory; $this->shortlinker = $shortlinker; } /** * Returns the message for a successful health check. * * @return string[] The message as a WordPress site status report. */ public function get_success_result() { return $this->get_report_builder() ->set_label( \__( 'The text link counter is working as expected', 'wordpress-seo' ) ) ->set_status_good() ->set_description( $this->get_success_description() ) ->build(); } /** * Returns the message for a failed health check. * * @return string[] The message as a WordPress site status report. */ public function get_links_table_not_accessible_result() { return $this->get_report_builder() ->set_label( \__( 'The text link counter feature is not working as expected', 'wordpress-seo' ) ) ->set_status_recommended() ->set_description( $this->get_links_table_not_accessible_description() ) ->set_actions( $this->get_actions() ) ->build(); } /** * Returns the description for when the health check was successful. * * @return string The description as a string. */ private function get_success_description() { return \sprintf( /* translators: 1: Link to the Yoast SEO blog, 2: Link closing tag. */ \esc_html__( 'The text link counter helps you improve your site structure. %1$sFind out how the text link counter can enhance your SEO%2$s.', 'wordpress-seo' ), '<a href="' . $this->shortlinker->get( 'https://yoa.st/3zw' ) . '" target="_blank">', WPSEO_Admin_Utils::get_new_tab_message() . '</a>' ); } /** * Returns the description for when the health couldn't access the links table. * * @return string The description as a string. */ private function get_links_table_not_accessible_description() { return \sprintf( /* translators: 1: Yoast SEO. */ \__( 'For this feature to work, %1$s needs to create a table in your database. We were unable to create this table automatically.', 'wordpress-seo' ), 'Yoast SEO' ); } /** * Returns the actions that the user should take when the links table is not accessible. * * @return string The actions as a string. */ private function get_actions() { return \sprintf( /* translators: 1: Link to the Yoast help center, 2: Link closing tag. */ \esc_html__( '%1$sFind out how to solve this problem on our help center%2$s.', 'wordpress-seo' ), '<a href="' . $this->shortlinker->get( 'https://yoa.st/3zv' ) . '" target="_blank">', WPSEO_Admin_Utils::get_new_tab_message() . '</a>' ); } } services/health-check/default-tagline-check.php 0000644 00000002633 15025657560 0015557 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Passes when the tagline is set to something other than the WordPress default tagline. */ class Default_Tagline_Check extends Health_Check { /** * Runs the health check. * * @var Default_Tagline_Runner */ private $runner; /** * Generates WordPress-friendly health check results. * * @var Default_Tagline_Reports */ private $reports; /** * Constructor. * * @param Default_Tagline_Runner $runner The object that implements the actual health check. * @param Default_Tagline_Reports $reports The object that generates WordPress-friendly results. */ public function __construct( Default_Tagline_Runner $runner, Default_Tagline_Reports $reports ) { $this->runner = $runner; $this->reports = $reports; $this->reports->set_test_identifier( $this->get_test_identifier() ); $this->set_runner( $this->runner ); } /** * Returns the WordPress-friendly health check result. * * @return string[] The WordPress-friendly health check result. */ protected function get_result() { if ( $this->runner->is_successful() ) { return $this->reports->get_success_result(); } return $this->reports->get_has_default_tagline_result(); } /** * Returns whether the health check should be excluded from the results. * * @return bool false, because it's not excluded. */ public function is_excluded() { return false; } } services/health-check/report-builder-factory.php 0000644 00000000720 15025657560 0016036 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Creates Report_Builder instances. */ class Report_Builder_Factory { /** * Creates a new Report_Builder instance. * * @param string $test_identifier The test identifier as a string. * @return Report_Builder The new Report_Builder instance. */ public function create( $test_identifier ) { $instance = new Report_Builder(); return $instance->set_test_identifier( $test_identifier ); } } services/health-check/page-comments-reports.php 0000644 00000004611 15025657560 0015670 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Presents a set of different messages for the Page_Comments health check. */ class Page_Comments_Reports { use Reports_Trait; /** * Constructor. * * @param Report_Builder_Factory $report_builder_factory The factory for result builder objects. * This class uses the report builder to generate WordPress-friendly * health check results. */ public function __construct( Report_Builder_Factory $report_builder_factory ) { $this->report_builder_factory = $report_builder_factory; } /** * Returns the report for when comments are set to be all on one page. * * @return string[] The message as a WordPress site status report. */ public function get_success_result() { return $this->get_report_builder() ->set_label( \esc_html__( 'Comments are displayed on a single page', 'wordpress-seo' ) ) ->set_status_good() ->set_description( \__( 'Comments on your posts are displayed on a single page. This is just like we\'d suggest it. You\'re doing well!', 'wordpress-seo' ) ) ->build(); } /** * Returns the report for when comments are set to be broken up across multiple pages. * * @return string[] The message as a WordPress site status report. */ public function get_has_comments_on_multiple_pages_result() { return $this->get_report_builder() ->set_label( \__( 'Comments break into multiple pages', 'wordpress-seo' ) ) ->set_status_recommended() ->set_description( \__( 'Comments on your posts break into multiple pages. As this is not needed in 999 out of 1000 cases, we recommend you disable it. To fix this, uncheck "Break comments into pages..." on the Discussion Settings page.', 'wordpress-seo' ) ) ->set_actions( $this->get_has_comments_on_multiple_pages_actions() ) ->build(); } /** * Returns the actions for when the comments are set to be broken up across multiple pages. * * @return string The actions as a string. */ private function get_has_comments_on_multiple_pages_actions() { return \sprintf( /* translators: 1: Opening tag of the link to the discussion settings page, 2: Link closing tag. */ \esc_html__( '%1$sGo to the Discussion Settings page%2$s', 'wordpress-seo' ), '<a href="' . \esc_url( \admin_url( 'options-discussion.php' ) ) . '">', '</a>' ); } } services/health-check/links-table-check.php 0000644 00000003773 15025657560 0014725 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; use Yoast\WP\SEO\Conditionals\Should_Index_Links_Conditional; /** * Passes when the links table is accessible. */ class Links_Table_Check extends Health_Check { /** * Runs the health check. * * @var Links_Table_Runner */ private $runner; /** * Generates WordPress-friendly health check results. * * @var Links_Table_Reports */ private $reports; /** * The conditional that checks if the links table should be indexed. * * @var Should_Index_Links_Conditional */ private $should_index_links_conditional; /** * Constructor. * * @param Links_Table_Runner $runner The object that implements the actual health check. * @param Links_Table_Reports $reports The object that generates WordPress-friendly results. * @param Should_Index_Links_Conditional $should_index_links_conditional The conditional that checks if the links table should be indexed. */ public function __construct( Links_Table_Runner $runner, Links_Table_Reports $reports, Should_Index_Links_Conditional $should_index_links_conditional ) { $this->runner = $runner; $this->reports = $reports; $this->should_index_links_conditional = $should_index_links_conditional; $this->reports->set_test_identifier( $this->get_test_identifier() ); $this->set_runner( $this->runner ); } /** * Returns the WordPress-friendly health check result. * * @return string[] The WordPress-friendly health check result. */ protected function get_result() { if ( $this->runner->is_successful() ) { return $this->reports->get_success_result(); } return $this->reports->get_links_table_not_accessible_result(); } /** * Returns whether the health check should be excluded from the results. * * @return bool false, because it's not excluded. */ public function is_excluded() { return ! $this->should_index_links_conditional->is_met(); } } services/health-check/reports-trait.php 0000644 00000001637 15025657560 0014261 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Used by classes that use a health check Report_Builder. */ trait Reports_Trait { /** * The factory for the builder object that generates WordPress-friendly test results. * * @var Report_Builder_Factory */ private $report_builder_factory; /** * The test identifier that's set on the Report_Builder. * * @var string */ private $test_identifier = ''; /** * Sets the name that WordPress uses to identify this health check. * * @param string $test_identifier The identifier. * @return void */ public function set_test_identifier( $test_identifier ) { $this->test_identifier = $test_identifier; } /** * Returns a new Report_Builder instance using the set test identifier. * * @return Report_Builder */ private function get_report_builder() { return $this->report_builder_factory->create( $this->test_identifier ); } } services/health-check/postname-permalink-check.php 0000644 00000002635 15025657560 0016322 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Paasses when permalinks are set to contain the post name. */ class Postname_Permalink_Check extends Health_Check { /** * Runs the health check. * * @var Postname_Permalink_Runner */ private $runner; /** * Generates WordPress-friendly health check results. * * @var Postname_Permalink_Reports */ private $reports; /** * Constructor. * * @param Postname_Permalink_Runner $runner The object that implements the actual health check. * @param Postname_Permalink_Reports $reports The object that generates WordPress-friendly results. */ public function __construct( Postname_Permalink_Runner $runner, Postname_Permalink_Reports $reports ) { $this->runner = $runner; $this->reports = $reports; $this->reports->set_test_identifier( $this->get_test_identifier() ); $this->set_runner( $this->runner ); } /** * Returns the WordPress-friendly health check result. * * @return string[] The WordPress-friendly health check result. */ protected function get_result() { if ( $this->runner->is_successful() ) { return $this->reports->get_success_result(); } return $this->reports->get_has_no_postname_in_permalink_result(); } /** * Returns whether the health check should be excluded from the results. * * @return bool false, because it's not excluded. */ public function is_excluded() { return false; } } services/health-check/report-builder.php 0000644 00000011667 15025657560 0014405 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Provides an interface to build WordPress-friendly health check results. */ class Report_Builder { /** * Passed health check. */ public const STATUS_GOOD = 'good'; /** * Changes are recommended but not necessary. */ public const STATUS_RECOMMENDED = 'recommended'; /** * Significant issues that the user should consider fixing. */ public const STATUS_CRITICAL = 'critical'; /** * The user-facing label. * * @var string */ private $label = ''; /** * The identifier that WordPress uses for the health check. * * @var string */ private $test_identifier = ''; /** * The test status (good, recommended, critical). * * @var string */ private $status = ''; /** * The short description for the result. * * @var string */ private $description = ''; /** * Actions that the user can take to solve the health check result. * * @var string */ private $actions = ''; /** * Sets the label for the health check that the user can see. * * @param string $label The label that the user can see. * @return Report_Builder This builder. */ public function set_label( $label ) { $this->label = $label; return $this; } /** * Sets the name for the test that the plugin uses to identify the test. * * @param string $test_identifier The identifier for the health check. * @return Report_Builder This builder. */ public function set_test_identifier( $test_identifier ) { $this->test_identifier = $test_identifier; return $this; } /** * Sets the status of the test result to GOOD (green label). * * @return Report_Builder This builder. */ public function set_status_good() { $this->status = self::STATUS_GOOD; return $this; } /** * Sets the status of the test result to RECOMMENDED (orange label). * * @return Report_Builder This builder. */ public function set_status_recommended() { $this->status = self::STATUS_RECOMMENDED; return $this; } /** * Sets the status of the test result to CRITICAL (red label). * * @return Report_Builder This builder. */ public function set_status_critical() { $this->status = self::STATUS_CRITICAL; return $this; } /** * Sets a description for the test result. This will be the heading for the result in the user interface. * * @param string $description The description for the test result. * @return Report_Builder This builder. */ public function set_description( $description ) { $this->description = $description; return $this; } /** * Sets a text that describes how the user can solve the failed health check. * * @param string $actions The descriptive text. * @return Report_Builder This builder. */ public function set_actions( $actions ) { $this->actions = $actions; return $this; } /** * Builds an array of strings in the format that WordPress uses to display health checks (https://developer.wordpress.org/reference/hooks/site_status_test_result/). * * @return array The report in WordPress' site status report format. */ public function build() { return [ 'label' => $this->label, 'status' => $this->status, 'badge' => $this->get_badge(), 'description' => $this->description, 'actions' => $this->get_actions_with_signature(), 'test' => $this->test_identifier, ]; } /** * Generates a badge that the user can see. * * @return string[] The badge. */ private function get_badge() { return [ 'label' => $this->get_badge_label(), 'color' => $this->get_badge_color(), ]; } /** * Generates the label for a badge. * * @return string The badge label. */ private function get_badge_label() { return \__( 'SEO', 'wordpress-seo' ); } /** * Generates the color for the badge using the current status. * * @return string The color for the badge's outline. */ private function get_badge_color() { if ( $this->status === self::STATUS_CRITICAL || $this->status === self::STATUS_RECOMMENDED ) { return 'red'; } return 'blue'; } /** * Concatenates the set actions with Yoast's signature. * * @return string A string containing the set actions and Yoast's signature. */ private function get_actions_with_signature() { return $this->actions . $this->get_signature(); } /** * Generates Yoast's signature that's displayed at the bottom of the health check result. * * @return string Yoast's signature as an HTML string. */ private function get_signature() { return \sprintf( /* translators: 1: Start of a paragraph beginning with the Yoast icon, 2: Expands to 'Yoast SEO', 3: Paragraph closing tag. */ \esc_html__( '%1$sThis was reported by the %2$s plugin%3$s', 'wordpress-seo' ), '<p class="yoast-site-health__signature"><img src="' . \esc_url( \plugin_dir_url( \WPSEO_FILE ) . 'packages/js/images/Yoast_SEO_Icon.svg' ) . '" alt="" height="20" width="20" class="yoast-site-health__signature-icon">', 'Yoast SEO', '</p>' ); } } services/health-check/links-table-runner.php 0000644 00000003121 15025657560 0015144 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; use Yoast\WP\SEO\Config\Migration_Status; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Runs the Links_Table health check. */ class Links_Table_Runner implements Runner_Interface { /** * Is set to true when the links table is accessible. * * @var bool */ private $links_table_accessible = false; /** * The Migration_Status object used to determine whether the links table is accessible. * * @var Migration_Status */ private $migration_status; /** * The Options_Helper object used to determine whether the health check should run or not. * * @var Options_Helper */ private $options_helper; /** * Constructor. * * @param Migration_Status $migration_status Object used to determine whether the links table is accessible. * @param Options_Helper $options_helper Object used to determine whether the health check should run. */ public function __construct( Migration_Status $migration_status, Options_Helper $options_helper ) { $this->migration_status = $migration_status; $this->options_helper = $options_helper; } /** * Runs the health check. Checks if the tagline is set to WordPress' default tagline, or to its set translation. * * @return void */ public function run() { $this->links_table_accessible = $this->migration_status->is_version( 'free', \WPSEO_VERSION ); } /** * Returns true if the links table is accessible * * @return bool The boolean indicating if the health check was succesful. */ public function is_successful() { return $this->links_table_accessible; } } services/health-check/runner-interface.php 0000644 00000000425 15025657560 0014703 0 ustar 00 <?php namespace Yoast\WP\SEO\Services\Health_Check; /** * Interface for the health check runner. The abstract Health_Check uses this to run a health check. */ interface Runner_Interface { /** * Runs the health check. * * @return void */ public function run(); } images/Application/image-content-extractor.php 0000644 00000012334 15025657560 0015546 0 ustar 00 <?php namespace Yoast\WP\SEO\Images\Application; use DOMDocument; use WP_HTML_Tag_Processor; /** * The image content extractor. */ class Image_Content_Extractor { /** * Gathers all images from content. * * @param string $content The content. * * @return int[] An associated array of image IDs, keyed by their URLs. */ public function gather_images( $content ) { /** * Filter 'wpseo_force_creating_and_using_attachment_indexables' - Filters if we should use attachment indexables to find all content images. Instead of scanning the content. * * The default value is false. * * @since 21.1 */ $should_not_parse_content = \apply_filters( 'wpseo_force_creating_and_using_attachment_indexables', false ); /** * Filter 'wpseo_force_skip_image_content_parsing' - Filters if we should force skip scanning the content to parse images. * This filter can be used if the regex gives a faster result than scanning the code. * * The default value is false. * * @since 21.1 */ $should_not_parse_content = \apply_filters( 'wpseo_force_skip_image_content_parsing', $should_not_parse_content ); if ( ! $should_not_parse_content && \class_exists( WP_HTML_Tag_Processor::class ) ) { return $this->gather_images_wp( $content ); } if ( ! $should_not_parse_content && \class_exists( DOMDocument::class ) ) { return $this->gather_images_DOMDocument( $content ); } if ( \strpos( $content, 'src' ) === false ) { // Nothing to do. return []; } $images = []; $regexp = '<img\s[^>]*src=("??)([^" >]*?)\\1[^>]*>'; // Used modifiers iU to match case insensitive and make greedy quantifiers lazy. if ( \preg_match_all( "/$regexp/iU", $content, $matches, \PREG_SET_ORDER ) ) { foreach ( $matches as $match ) { $images[ $match[2] ] = 0; } } return $images; } /** * Gathers all images from content with WP's WP_HTML_Tag_Processor() and returns them along with their IDs, if * possible. * * @param string $content The content. * * @return int[] An associated array of image IDs, keyed by their URL. */ protected function gather_images_wp( $content ) { $processor = new WP_HTML_Tag_Processor( $content ); $images = []; $query = [ 'tag_name' => 'img', ]; /** * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. * * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-<ID>` format. * * @api string The attribute to be used to extract image IDs from. */ $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); while ( $processor->next_tag( $query ) ) { $src_raw = $processor->get_attribute( 'src' ); if ( ! $src_raw ) { continue; } $src = \htmlentities( $src_raw, ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), \get_bloginfo( 'charset' ) ); $classes = $processor->get_attribute( $attribute ); $id = $this->extract_id_of_classes( $classes ); $images[ $src ] = $id; } return $images; } /** * Gathers all images from content with DOMDocument() and returns them along with their IDs, if possible. * * @param string $content The content. * * @return int[] An associated array of image IDs, keyed by their URL. */ protected function gather_images_domdocument( $content ) { $images = []; $charset = \get_bloginfo( 'charset' ); /** * Filter 'wpseo_image_attribute_containing_id' - Allows filtering what attribute will be used to extract image IDs from. * * Defaults to "class", which is where WP natively stores the image IDs, in a `wp-image-<ID>` format. * * @api string The attribute to be used to extract image IDs from. */ $attribute = \apply_filters( 'wpseo_image_attribute_containing_id', 'class' ); \libxml_use_internal_errors( true ); $post_dom = new DOMDocument(); $post_dom->loadHTML( '<?xml encoding="' . $charset . '">' . $content ); \libxml_clear_errors(); foreach ( $post_dom->getElementsByTagName( 'img' ) as $img ) { $src = \htmlentities( $img->getAttribute( 'src' ), ( \ENT_QUOTES | \ENT_SUBSTITUTE | \ENT_HTML401 ), $charset ); $classes = $img->getAttribute( $attribute ); $id = $this->extract_id_of_classes( $classes ); $images[ $src ] = $id; } return $images; } /** * Extracts image ID out of the image's classes. * * @param string $classes The classes assigned to the image. * * @return int The ID that's extracted from the classes. */ protected function extract_id_of_classes( $classes ) { if ( ! $classes ) { return 0; } /** * Filter 'wpseo_extract_id_pattern' - Allows filtering the regex patern to be used to extract image IDs from class/attribute names. * * Defaults to the pattern that extracts image IDs from core's `wp-image-<ID>` native format in image classes. * * @api string The regex pattern to be used to extract image IDs from class names. Empty string if the whole class/attribute should be returned. */ $pattern = \apply_filters( 'wpseo_extract_id_pattern', '/(?<!\S)wp-image-(\d+)(?!\S)/i' ); if ( $pattern === '' ) { return (int) $classes; } $matches = []; if ( \preg_match( $pattern, $classes, $matches ) ) { return (int) $matches[1]; } return 0; } } promotions/domain/time-interval.php 0000644 00000002545 15025657560 0013536 0 ustar 00 <?php namespace Yoast\WP\SEO\Promotions\Domain; /** * Class Time_Interval * * Value object for a time interval. */ class Time_Interval { /** * The starting time of the interval as a Unix timestamp. * * @var int */ public $time_start; /** * The ending time of the interval as a Unix timestamp. * * @var int */ public $time_end; /** * Time_Interval constructor. * * @codeCoverageIgnore * * @param int $time_start Interval start time. * @param int $time_end Interval end time. */ public function __construct( int $time_start, int $time_end ) { $this->time_start = $time_start; $this->time_end = $time_end; } /** * Checks if the given time is within the interval. * * @param int $time The time to check. * * @return bool Whether the given time is within the interval. */ public function contains( int $time ): bool { return ( ( $time > $this->time_start ) && ( $time < $this->time_end ) ); } /** * Sets the interval astarting date. * * @param int $time_start The interval start time. * * @return void */ public function set_start_date( int $time_start ) { $this->time_start = $time_start; } /** * Sets the interval ending date. * * @param int $time_end The interval end time. * * @return void */ public function set_end_date( int $time_end ) { $this->time_end = $time_end; } } promotions/domain/abstract-promotion.php 0000644 00000002030 15025657560 0014572 0 ustar 00 <?php namespace Yoast\WP\SEO\Promotions\Domain; /** * Abstract class for a promotion. */ abstract class Abstract_Promotion implements Promotion_Interface { /** * The promotion name. * * @var string */ private $promotion_name; /** * The time interval in which the promotion is active. * * @var Time_Interval */ private $time_interval; /** * Class constructor. * * @param string $promotion_name The promotion name. * @param Time_Interval $time_interval The time interval in which the promotion is active. */ public function __construct( string $promotion_name, Time_Interval $time_interval ) { $this->promotion_name = $promotion_name; $this->time_interval = $time_interval; } /** * Returns the promotion name. * * @return string */ public function get_promotion_name() { return $this->promotion_name; } /** * Returns the time interval in which the promotion is active. * * @return Time_Interval */ public function get_time_interval() { return $this->time_interval; } } promotions/domain/black-friday-checklist-promotion.php 0000644 00000000713 15025657560 0017274 0 ustar 00 <?php namespace Yoast\WP\SEO\Promotions\Domain; /** * Class to manage the Black Friday checklist promotion. */ class Black_Friday_Checklist_Promotion extends Abstract_Promotion implements Promotion_Interface { /** * Class constructor. */ public function __construct() { parent::__construct( 'black-friday-2023-checklist', new Time_Interval( \gmmktime( 11, 00, 00, 9, 19, 2023 ), \gmmktime( 11, 00, 00, 10, 31, 2023 ) ) ); } } promotions/domain/black-friday-promotion.php 0000644 00000000654 15025657560 0015331 0 ustar 00 <?php namespace Yoast\WP\SEO\Promotions\Domain; /** * Class to manage the Black Friday promotion. */ class Black_Friday_Promotion extends Abstract_Promotion implements Promotion_Interface { /** * Class constructor. */ public function __construct() { parent::__construct( 'black-friday-2024-promotion', new Time_Interval( \gmmktime( 10, 00, 00, 11, 28, 2024 ), \gmmktime( 10, 00, 00, 12, 3, 2024 ) ) ); } } promotions/domain/promotion-interface.php 0000644 00000000171 15025657560 0014733 0 ustar 00 <?php namespace Yoast\WP\SEO\Promotions\Domain; /** * Interface for a Promotion. */ interface Promotion_Interface {} promotions/application/promotion-manager-interface.php 0000644 00000000773 15025657560 0017407 0 ustar 00 <?php namespace Yoast\WP\SEO\Promotions\Application; /** * Interface for the promotion manager. */ interface Promotion_Manager_Interface { /** * Whether the promotion is effective. * * @param string $promotion_name The name of the promotion. * * @return bool Whether the promotion is effective. */ public function is( string $promotion_name ): bool; /** * Get the list of promotions. * * @return array The list of promotions. */ public function get_promotions_list(): array; } promotions/application/promotion-manager.php 0000644 00000003310 15025657560 0015437 0 ustar 00 <?php namespace Yoast\WP\SEO\Promotions\Application; use Yoast\WP\SEO\Promotions\Domain\Promotion_Interface; /** * Class to manage promotional promotions. * * @makePublic */ class Promotion_Manager implements Promotion_Manager_Interface { /** * The centralized list of promotions: all promotions should be passed to the constructor. * * @var array<Abstract_Promotion> */ private $promotions_list = []; /** * Class constructor. * * @param Promotion_Interface ...$promotions List of promotions. */ public function __construct( Promotion_Interface ...$promotions ) { $this->promotions_list = $promotions; } /** * Whether the promotion is effective. * * @param string $promotion_name The name of the promotion. * * @return bool Whether the promotion is effective. */ public function is( string $promotion_name ): bool { $time = \time(); foreach ( $this->promotions_list as $promotion ) { if ( $promotion->get_promotion_name() === $promotion_name ) { return $promotion->get_time_interval()->contains( $time ); } } return false; } /** * Get the list of promotions. * * @return array<Abstract_Promotion> The list of promotions. */ public function get_promotions_list(): array { return $this->promotions_list; } /** * Get the names of currently active promotions. * * @return array<string> The list of promotions. */ public function get_current_promotions(): array { $current_promotions = []; $time = \time(); foreach ( $this->promotions_list as $promotion ) { if ( $promotion->get_time_interval()->contains( $time ) ) { $current_promotions[] = $promotion->get_promotion_name(); } } return $current_promotions; } } loadable-interface.php 0000644 00000000436 15025657560 0010774 0 ustar 00 <?php namespace Yoast\WP\SEO; /** * An interface for registering integrations with WordPress */ interface Loadable_Interface { /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals(); } general/user-interface/general-page-integration.php 0000644 00000013732 15025657560 0016477 0 ustar 00 <?php namespace Yoast\WP\SEO\General\User_Interface; use WPSEO_Admin_Asset_Manager; use Yoast\WP\SEO\Actions\Alert_Dismissal_Action; use Yoast\WP\SEO\Conditionals\Admin\Non_Network_Admin_Conditional; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Dashboard\Application\Configuration\Dashboard_Configuration; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Notification_Helper; use Yoast\WP\SEO\Helpers\Product_Helper; use Yoast\WP\SEO\Helpers\Short_Link_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Promotions\Application\Promotion_Manager; /** * Class General_Page_Integration. */ class General_Page_Integration implements Integration_Interface { /** * The page name. */ public const PAGE = 'wpseo_dashboard'; /** * The notification helper. * * @var Notification_Helper */ protected $notification_helper; /** * The dashboard configuration. * * @var Dashboard_Configuration */ private $dashboard_configuration; /** * Holds the WPSEO_Admin_Asset_Manager. * * @var WPSEO_Admin_Asset_Manager */ private $asset_manager; /** * Holds the Current_Page_Helper. * * @var Current_Page_Helper */ private $current_page_helper; /** * Holds the Product_Helper. * * @var Product_Helper */ private $product_helper; /** * Holds the Short_Link_Helper. * * @var Short_Link_Helper */ private $shortlink_helper; /** * The promotion manager. * * @var Promotion_Manager */ private $promotion_manager; /** * The alert dismissal action. * * @var Alert_Dismissal_Action */ private $alert_dismissal_action; /** * Constructs Academy_Integration. * * @param WPSEO_Admin_Asset_Manager $asset_manager The WPSEO_Admin_Asset_Manager. * @param Current_Page_Helper $current_page_helper The Current_Page_Helper. * @param Product_Helper $product_helper The Product_Helper. * @param Short_Link_Helper $shortlink_helper The Short_Link_Helper. * @param Notification_Helper $notification_helper The Notification_Helper. * @param Alert_Dismissal_Action $alert_dismissal_action The alert dismissal action. * @param Promotion_Manager $promotion_manager The promotion manager. * @param Dashboard_Configuration $dashboard_configuration The dashboard configuration. */ public function __construct( WPSEO_Admin_Asset_Manager $asset_manager, Current_Page_Helper $current_page_helper, Product_Helper $product_helper, Short_Link_Helper $shortlink_helper, Notification_Helper $notification_helper, Alert_Dismissal_Action $alert_dismissal_action, Promotion_Manager $promotion_manager, Dashboard_Configuration $dashboard_configuration ) { $this->asset_manager = $asset_manager; $this->current_page_helper = $current_page_helper; $this->product_helper = $product_helper; $this->shortlink_helper = $shortlink_helper; $this->notification_helper = $notification_helper; $this->alert_dismissal_action = $alert_dismissal_action; $this->promotion_manager = $promotion_manager; $this->dashboard_configuration = $dashboard_configuration; } /** * Returns the conditionals based on which this loadable should be active. * * @return array<string> */ public static function get_conditionals() { return [ Admin_Conditional::class, Non_Network_Admin_Conditional::class ]; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { // Add page. \add_filter( 'wpseo_submenu_pages', [ $this, 'add_page' ] ); // Are we on the dashboard page? if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) { \add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_assets' ] ); } } /** * Adds the page. * * @param array<string, array<string>> $pages The pages. * * @return array<string, array<string>> The pages. */ public function add_page( $pages ) { \array_splice( $pages, 0, 0, [ [ self::PAGE, '', \__( 'General', 'wordpress-seo' ), 'wpseo_manage_options', self::PAGE, [ $this, 'display_page' ], ], ] ); return $pages; } /** * Displays the page. * * @return void */ public function display_page() { echo '<div id="yoast-seo-general"></div>'; } /** * Enqueues the assets. * * @return void */ 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' ); \wp_enqueue_media(); $this->asset_manager->enqueue_script( 'general-page' ); $this->asset_manager->enqueue_style( 'general-page' ); if ( $this->promotion_manager->is( 'black-friday-2024-promotion' ) ) { $this->asset_manager->enqueue_style( 'black-friday-banner' ); } $this->asset_manager->localize_script( 'general-page', 'wpseoScriptData', $this->get_script_data() ); } /** * Creates the script data. * * @return array The script data. */ private function get_script_data() { return [ 'preferences' => [ 'isPremium' => $this->product_helper->is_premium(), 'isRtl' => \is_rtl(), 'pluginUrl' => \plugins_url( '', \WPSEO_FILE ), 'upsellSettings' => [ 'actionId' => 'load-nfd-ctb', 'premiumCtbId' => 'f6a84663-465f-4cb5-8ba5-f7a6d72224b2', ], ], 'adminUrl' => \admin_url( 'admin.php' ), 'linkParams' => $this->shortlink_helper->get_query_params(), 'userEditUrl' => \add_query_arg( 'user_id', '{user_id}', \admin_url( 'user-edit.php' ) ), 'alerts' => $this->notification_helper->get_alerts(), 'currentPromotions' => $this->promotion_manager->get_current_promotions(), 'dismissedAlerts' => $this->alert_dismissal_action->all_dismissed(), 'dashboard' => $this->dashboard_configuration->get_configuration(), ]; } } builders/indexable-date-archive-builder.php 0000644 00000003227 15025657560 0015016 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Date Archive Builder for the indexables. * * Formats the date archive meta to indexable format. */ class Indexable_Date_Archive_Builder { /** * The options helper. * * @var Options_Helper */ private $options; /** * The latest version of the Indexable_Date_Archive_Builder. * * @var int */ protected $version; /** * Indexable_Date_Archive_Builder constructor. * * @param Options_Helper $options The options helper. * @param Indexable_Builder_Versions $versions The latest version for all indexable builders. */ public function __construct( Options_Helper $options, Indexable_Builder_Versions $versions ) { $this->options = $options; $this->version = $versions->get_latest_version_for_type( 'date-archive' ); } /** * Formats the data. * * @param Indexable $indexable The indexable to format. * * @return Indexable The extended indexable. */ public function build( $indexable ) { $indexable->object_type = 'date-archive'; $indexable->title = $this->options->get( 'title-archive-wpseo' ); $indexable->description = $this->options->get( 'metadesc-archive-wpseo' ); $indexable->is_robots_noindex = $this->options->get( 'noindex-archive-wpseo' ); $indexable->is_public = ( (int) $indexable->is_robots_noindex !== 1 ); $indexable->blog_id = \get_current_blog_id(); $indexable->permalink = null; $indexable->version = $this->version; return $indexable; } } builders/indexable-builder.php 0000644 00000032031 15025657560 0012457 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Exceptions\Indexable\Not_Built_Exception; use Yoast\WP\SEO\Exceptions\Indexable\Source_Exception; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Services\Indexables\Indexable_Version_Manager; /** * Builder for the indexables. * * Creates all the indexables. */ class Indexable_Builder { /** * The author builder. * * @var Indexable_Author_Builder */ private $author_builder; /** * The post builder. * * @var Indexable_Post_Builder */ private $post_builder; /** * The term builder. * * @var Indexable_Term_Builder */ private $term_builder; /** * The home page builder. * * @var Indexable_Home_Page_Builder */ private $home_page_builder; /** * The post type archive builder. * * @var Indexable_Post_Type_Archive_Builder */ private $post_type_archive_builder; /** * The data archive builder. * * @var Indexable_Date_Archive_Builder */ private $date_archive_builder; /** * The system page builder. * * @var Indexable_System_Page_Builder */ private $system_page_builder; /** * The indexable hierarchy builder. * * @var Indexable_Hierarchy_Builder */ private $hierarchy_builder; /** * The primary term builder * * @var Primary_Term_Builder */ private $primary_term_builder; /** * The link builder * * @var Indexable_Link_Builder */ private $link_builder; /** * The indexable repository. * * @var Indexable_Repository */ private $indexable_repository; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The Indexable Version Manager. * * @var Indexable_Version_Manager */ protected $version_manager; /** * Returns the instance of this class constructed through the ORM Wrapper. * * @param Indexable_Author_Builder $author_builder The author builder for creating missing indexables. * @param Indexable_Post_Builder $post_builder The post builder for creating missing indexables. * @param Indexable_Term_Builder $term_builder The term builder for creating missing indexables. * @param Indexable_Home_Page_Builder $home_page_builder The front page builder for creating missing indexables. * @param Indexable_Post_Type_Archive_Builder $post_type_archive_builder The post type archive builder for creating missing indexables. * @param Indexable_Date_Archive_Builder $date_archive_builder The date archive builder for creating missing indexables. * @param Indexable_System_Page_Builder $system_page_builder The search result builder for creating missing indexables. * @param Indexable_Hierarchy_Builder $hierarchy_builder The hierarchy builder for creating the indexable hierarchy. * @param Primary_Term_Builder $primary_term_builder The primary term builder for creating primary terms for posts. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Indexable_Version_Manager $version_manager The indexable version manager. * @param Indexable_Link_Builder $link_builder The link builder for creating missing SEO links. */ public function __construct( Indexable_Author_Builder $author_builder, Indexable_Post_Builder $post_builder, Indexable_Term_Builder $term_builder, Indexable_Home_Page_Builder $home_page_builder, Indexable_Post_Type_Archive_Builder $post_type_archive_builder, Indexable_Date_Archive_Builder $date_archive_builder, Indexable_System_Page_Builder $system_page_builder, Indexable_Hierarchy_Builder $hierarchy_builder, Primary_Term_Builder $primary_term_builder, Indexable_Helper $indexable_helper, Indexable_Version_Manager $version_manager, Indexable_Link_Builder $link_builder ) { $this->author_builder = $author_builder; $this->post_builder = $post_builder; $this->term_builder = $term_builder; $this->home_page_builder = $home_page_builder; $this->post_type_archive_builder = $post_type_archive_builder; $this->date_archive_builder = $date_archive_builder; $this->system_page_builder = $system_page_builder; $this->hierarchy_builder = $hierarchy_builder; $this->primary_term_builder = $primary_term_builder; $this->indexable_helper = $indexable_helper; $this->version_manager = $version_manager; $this->link_builder = $link_builder; } /** * Sets the indexable repository. Done to avoid circular dependencies. * * @required * * @param Indexable_Repository $indexable_repository The indexable repository. * * @return void */ public function set_indexable_repository( Indexable_Repository $indexable_repository ) { $this->indexable_repository = $indexable_repository; } /** * Creates a clean copy of an Indexable to allow for later database operations. * * @param Indexable $indexable The Indexable to copy. * * @return bool|Indexable */ protected function deep_copy_indexable( $indexable ) { return $this->indexable_repository ->query() ->create( $indexable->as_array() ); } /** * Creates an indexable by its ID and type. * * @param int $object_id The indexable object ID. * @param string $object_type The indexable object type. * @param Indexable|bool $indexable Optional. An existing indexable to overwrite. * * @return bool|Indexable Instance of indexable. False when unable to build. */ public function build_for_id_and_type( $object_id, $object_type, $indexable = false ) { $defaults = [ 'object_type' => $object_type, 'object_id' => $object_id, ]; $indexable = $this->build( $indexable, $defaults ); return $indexable; } /** * Creates an indexable for the homepage. * * @param Indexable|bool $indexable Optional. An existing indexable to overwrite. * * @return Indexable The home page indexable. */ public function build_for_home_page( $indexable = false ) { return $this->build( $indexable, [ 'object_type' => 'home-page' ] ); } /** * Creates an indexable for the date archive. * * @param Indexable|bool $indexable Optional. An existing indexable to overwrite. * * @return Indexable The date archive indexable. */ public function build_for_date_archive( $indexable = false ) { return $this->build( $indexable, [ 'object_type' => 'date-archive' ] ); } /** * Creates an indexable for a post type archive. * * @param string $post_type The post type. * @param Indexable|bool $indexable Optional. An existing indexable to overwrite. * * @return Indexable The post type archive indexable. */ public function build_for_post_type_archive( $post_type, $indexable = false ) { $defaults = [ 'object_type' => 'post-type-archive', 'object_sub_type' => $post_type, ]; return $this->build( $indexable, $defaults ); } /** * Creates an indexable for a system page. * * @param string $page_type The type of system page. * @param Indexable|bool $indexable Optional. An existing indexable to overwrite. * * @return Indexable The search result indexable. */ public function build_for_system_page( $page_type, $indexable = false ) { $defaults = [ 'object_type' => 'system-page', 'object_sub_type' => $page_type, ]; return $this->build( $indexable, $defaults ); } /** * Ensures we have a valid indexable. Creates one if false is passed. * * @param Indexable|false $indexable The indexable. * @param array $defaults The initial properties of the Indexable. * * @return Indexable The indexable. */ protected function ensure_indexable( $indexable, $defaults = [] ) { if ( ! $indexable ) { return $this->indexable_repository->query()->create( $defaults ); } return $indexable; } /** * Build and author indexable from an author id if it does not exist yet, or if the author indexable needs to be upgraded. * * @param int $author_id The author id. * * @return Indexable|false The author indexable if it has been built, `false` if it could not be built. */ protected function maybe_build_author_indexable( $author_id ) { $author_indexable = $this->indexable_repository->find_by_id_and_type( $author_id, 'user', false ); if ( ! $author_indexable || $this->version_manager->indexable_needs_upgrade( $author_indexable ) ) { // Try to build the author. $author_defaults = [ 'object_type' => 'user', 'object_id' => $author_id, ]; $author_indexable = $this->build( $author_indexable, $author_defaults ); } return $author_indexable; } /** * Checks if the indexable type is one that is not supposed to have object ID for. * * @param string $type The type of the indexable. * * @return bool Whether the indexable type is one that is not supposed to have object ID for. */ protected function is_type_with_no_id( $type ) { return \in_array( $type, [ 'home-page', 'date-archive', 'post-type-archive', 'system-page' ], true ); } // phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing -- Exceptions are handled by the catch statement in the method. /** * Rebuilds an Indexable from scratch. * * @param Indexable $indexable The Indexable to (re)build. * @param array|null $defaults The object type of the Indexable. * * @return Indexable|false The resulting Indexable. */ public function build( $indexable, $defaults = null ) { // Backup the previous Indexable, if there was one. $indexable_before = ( $indexable ) ? $this->deep_copy_indexable( $indexable ) : null; // Make sure we have an Indexable to work with. $indexable = $this->ensure_indexable( $indexable, $defaults ); try { if ( $indexable->object_id === 0 ) { throw Not_Built_Exception::invalid_object_id( $indexable->object_id ); } switch ( $indexable->object_type ) { case 'post': $indexable = $this->post_builder->build( $indexable->object_id, $indexable ); // Save indexable, to make sure it can be queried when building related objects like the author indexable and hierarchy. $indexable = $this->indexable_helper->save_indexable( $indexable, $indexable_before ); // For attachments, we have to make sure to patch any potentially previously cleaned up SEO links. if ( \is_a( $indexable, Indexable::class ) && $indexable->object_sub_type === 'attachment' ) { $this->link_builder->patch_seo_links( $indexable ); } // Always rebuild the primary term. $this->primary_term_builder->build( $indexable->object_id ); // Always rebuild the hierarchy; this needs the primary term to run correctly. $this->hierarchy_builder->build( $indexable ); $this->maybe_build_author_indexable( $indexable->author_id ); // The indexable is already saved, so return early. return $indexable; case 'user': $indexable = $this->author_builder->build( $indexable->object_id, $indexable ); break; case 'term': $indexable = $this->term_builder->build( $indexable->object_id, $indexable ); // Save indexable, to make sure it can be queried when building hierarchy. $indexable = $this->indexable_helper->save_indexable( $indexable, $indexable_before ); $this->hierarchy_builder->build( $indexable ); // The indexable is already saved, so return early. return $indexable; case 'home-page': $indexable = $this->home_page_builder->build( $indexable ); break; case 'date-archive': $indexable = $this->date_archive_builder->build( $indexable ); break; case 'post-type-archive': $indexable = $this->post_type_archive_builder->build( $indexable->object_sub_type, $indexable ); break; case 'system-page': $indexable = $this->system_page_builder->build( $indexable->object_sub_type, $indexable ); break; } return $this->indexable_helper->save_indexable( $indexable, $indexable_before ); } catch ( Source_Exception $exception ) { if ( ! $this->is_type_with_no_id( $indexable->object_type ) && ! isset( $indexable->object_id ) ) { return false; } /** * The current indexable could not be indexed. Create a placeholder indexable, so we can * skip this indexable in future indexing runs. * * @var Indexable $indexable */ $indexable = $this->ensure_indexable( $indexable, [ 'object_id' => $indexable->object_id, 'object_type' => $indexable->object_type, 'post_status' => 'unindexed', 'version' => 0, ] ); // If we already had an existing indexable, mark it as unindexed. We cannot rely on its validity anymore. $indexable->post_status = 'unindexed'; // Make sure that the indexing process doesn't get stuck in a loop on this broken indexable. $indexable = $this->version_manager->set_latest( $indexable ); return $this->indexable_helper->save_indexable( $indexable, $indexable_before ); } catch ( Not_Built_Exception $exception ) { return false; } } // phpcs:enable } builders/indexable-social-image-trait.php 0000644 00000011504 15025657560 0014506 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use WPSEO_Utils; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Open_Graph\Image_Helper as Open_Graph_Image_Helper; use Yoast\WP\SEO\Helpers\Twitter\Image_Helper as Twitter_Image_Helper; use Yoast\WP\SEO\Models\Indexable; /** * Trait for determine the social image to use in the indexable. * * Represents the trait used in builders for handling social images. */ trait Indexable_Social_Image_Trait { /** * The image helper. * * @var Image_Helper */ protected $image; /** * The Open Graph image helper. * * @var Open_Graph_Image_Helper */ protected $open_graph_image; /** * The Twitter image helper. * * @var Twitter_Image_Helper */ protected $twitter_image; /** * Sets the helpers for the trait. * * @required * * @param Image_Helper $image The image helper. * @param Open_Graph_Image_Helper $open_graph_image The Open Graph image helper. * @param Twitter_Image_Helper $twitter_image The Twitter image helper. * * @return void */ public function set_social_image_helpers( Image_Helper $image, Open_Graph_Image_Helper $open_graph_image, Twitter_Image_Helper $twitter_image ) { $this->image = $image; $this->open_graph_image = $open_graph_image; $this->twitter_image = $twitter_image; } /** * Sets the alternative on an indexable. * * @param array $alternative_image The alternative image to set. * @param Indexable $indexable The indexable to set image for. * * @return void */ protected function set_alternative_image( array $alternative_image, Indexable $indexable ) { if ( ! empty( $alternative_image['image_id'] ) ) { if ( ! $indexable->open_graph_image_source && ! $indexable->open_graph_image_id ) { $indexable->open_graph_image_id = $alternative_image['image_id']; $indexable->open_graph_image_source = $alternative_image['source']; $this->set_open_graph_image_meta_data( $indexable ); } if ( ! $indexable->twitter_image && ! $indexable->twitter_image_id ) { $indexable->twitter_image = $this->twitter_image->get_by_id( $alternative_image['image_id'] ); $indexable->twitter_image_id = $alternative_image['image_id']; $indexable->twitter_image_source = $alternative_image['source']; } } if ( ! empty( $alternative_image['image'] ) ) { if ( ! $indexable->open_graph_image_source && ! $indexable->open_graph_image_id ) { $indexable->open_graph_image = $alternative_image['image']; $indexable->open_graph_image_source = $alternative_image['source']; } if ( ! $indexable->twitter_image && ! $indexable->twitter_image_id ) { $indexable->twitter_image = $alternative_image['image']; $indexable->twitter_image_source = $alternative_image['source']; } } } /** * Sets the Open Graph image meta data for an og image * * @param Indexable $indexable The indexable. * * @return void */ protected function set_open_graph_image_meta_data( Indexable $indexable ) { if ( ! $indexable->open_graph_image_id ) { return; } $image = $this->open_graph_image->get_image_by_id( $indexable->open_graph_image_id ); if ( ! empty( $image ) ) { $indexable->open_graph_image = $image['url']; $indexable->open_graph_image_meta = WPSEO_Utils::format_json_encode( $image ); } } /** * Handles the social images. * * @param Indexable $indexable The indexable to handle. * * @return void */ protected function handle_social_images( Indexable $indexable ) { // When the image or image id is set. if ( $indexable->open_graph_image || $indexable->open_graph_image_id ) { $indexable->open_graph_image_source = 'set-by-user'; $this->set_open_graph_image_meta_data( $indexable ); } if ( $indexable->twitter_image || $indexable->twitter_image_id ) { $indexable->twitter_image_source = 'set-by-user'; } if ( $indexable->twitter_image_id ) { $indexable->twitter_image = $this->twitter_image->get_by_id( $indexable->twitter_image_id ); } // When image sources are set already. if ( $indexable->open_graph_image_source && $indexable->twitter_image_source ) { return; } $alternative_image = $this->find_alternative_image( $indexable ); if ( ! empty( $alternative_image ) ) { $this->set_alternative_image( $alternative_image, $indexable ); } } /** * Resets the social images. * * @param Indexable $indexable The indexable to set images for. * * @return void */ protected function reset_social_images( Indexable $indexable ) { $indexable->open_graph_image = null; $indexable->open_graph_image_id = null; $indexable->open_graph_image_source = null; $indexable->open_graph_image_meta = null; $indexable->twitter_image = null; $indexable->twitter_image_id = null; $indexable->twitter_image_source = null; } } builders/primary-term-builder.php 0000644 00000005222 15025657560 0013156 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Meta_Helper; use Yoast\WP\SEO\Helpers\Primary_Term_Helper; use Yoast\WP\SEO\Repositories\Primary_Term_Repository; /** * Primary term builder. * * Creates the primary term for a post. */ class Primary_Term_Builder { /** * The primary term repository. * * @var Primary_Term_Repository */ protected $repository; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * The primary term helper. * * @var Primary_Term_Helper */ private $primary_term; /** * The meta helper. * * @var Meta_Helper */ private $meta; /** * Primary_Term_Builder constructor. * * @param Primary_Term_Repository $repository The primary term repository. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Primary_Term_Helper $primary_term The primary term helper. * @param Meta_Helper $meta The meta helper. */ public function __construct( Primary_Term_Repository $repository, Indexable_Helper $indexable_helper, Primary_Term_Helper $primary_term, Meta_Helper $meta ) { $this->repository = $repository; $this->indexable_helper = $indexable_helper; $this->primary_term = $primary_term; $this->meta = $meta; } /** * Formats and saves the primary terms for the post with the given post id. * * @param int $post_id The post ID. * * @return void */ public function build( $post_id ) { foreach ( $this->primary_term->get_primary_term_taxonomies( $post_id ) as $taxonomy ) { $this->save_primary_term( $post_id, $taxonomy->name ); } } /** * Save the primary term for a specific taxonomy. * * @param int $post_id Post ID to save primary term for. * @param string $taxonomy Taxonomy to save primary term for. * * @return void */ protected function save_primary_term( $post_id, $taxonomy ) { $term_id = $this->meta->get_value( 'primary_' . $taxonomy, $post_id ); $term_selected = ! empty( $term_id ); $primary_term_indexable = $this->repository->find_by_post_id_and_taxonomy( $post_id, $taxonomy, $term_selected ); // Removes the indexable when no term found. if ( ! $term_selected ) { if ( $primary_term_indexable ) { $primary_term_indexable->delete(); } return; } $primary_term_indexable->term_id = $term_id; $primary_term_indexable->post_id = $post_id; $primary_term_indexable->taxonomy = $taxonomy; $primary_term_indexable->blog_id = \get_current_blog_id(); $this->indexable_helper->save_indexable( $primary_term_indexable ); } } builders/indexable-hierarchy-builder.php 0000644 00000026361 15025657560 0014444 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use WP_Post; use WP_Term; use WPSEO_Meta; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Hierarchy_Repository; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\Primary_Term_Repository; /** * Builder for the indexables hierarchy. * * Builds the indexable hierarchy for indexables. */ class Indexable_Hierarchy_Builder { /** * Holds a list of indexable ids where the ancestors are saved for. * * @var array<int> */ protected $saved_ancestors = []; /** * The indexable repository. * * @var Indexable_Repository */ private $indexable_repository; /** * The indexable hierarchy repository. * * @var Indexable_Hierarchy_Repository */ private $indexable_hierarchy_repository; /** * The primary term repository. * * @var Primary_Term_Repository */ private $primary_term_repository; /** * The options helper. * * @var Options_Helper */ private $options; /** * Holds the Post_Helper instance. * * @var Post_Helper */ private $post; /** * Holds the Indexable_Helper instance. * * @var Indexable_Helper */ private $indexable_helper; /** * Indexable_Author_Builder constructor. * * @param Indexable_Hierarchy_Repository $indexable_hierarchy_repository The indexable hierarchy repository. * @param Primary_Term_Repository $primary_term_repository The primary term repository. * @param Options_Helper $options The options helper. * @param Post_Helper $post The post helper. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Indexable_Hierarchy_Repository $indexable_hierarchy_repository, Primary_Term_Repository $primary_term_repository, Options_Helper $options, Post_Helper $post, Indexable_Helper $indexable_helper ) { $this->indexable_hierarchy_repository = $indexable_hierarchy_repository; $this->primary_term_repository = $primary_term_repository; $this->options = $options; $this->post = $post; $this->indexable_helper = $indexable_helper; } /** * Sets the indexable repository. Done to avoid circular dependencies. * * @required * * @param Indexable_Repository $indexable_repository The indexable repository. * * @return void */ public function set_indexable_repository( Indexable_Repository $indexable_repository ) { $this->indexable_repository = $indexable_repository; } /** * Builds the ancestor hierarchy for an indexable. * * @param Indexable $indexable The indexable. * * @return Indexable The indexable. */ public function build( Indexable $indexable ) { if ( $this->hierarchy_is_built( $indexable ) ) { return $indexable; } if ( ! $this->indexable_helper->should_index_indexable( $indexable ) ) { return $indexable; } $this->indexable_hierarchy_repository->clear_ancestors( $indexable->id ); $indexable_id = $this->get_indexable_id( $indexable ); $ancestors = []; if ( $indexable->object_type === 'post' ) { $this->add_ancestors_for_post( $indexable_id, $indexable->object_id, $ancestors ); } if ( $indexable->object_type === 'term' ) { $this->add_ancestors_for_term( $indexable_id, $indexable->object_id, $ancestors ); } $indexable->ancestors = \array_reverse( \array_values( $ancestors ) ); $indexable->has_ancestors = ! empty( $ancestors ); if ( $indexable->id ) { $this->save_ancestors( $indexable ); } return $indexable; } /** * Checks if a hierarchy is built already for the given indexable. * * @param Indexable $indexable The indexable to check. * * @return bool True when indexable has a built hierarchy. */ protected function hierarchy_is_built( Indexable $indexable ) { if ( \in_array( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array<WP_Term> $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } builders/indexable-post-builder.php 0000644 00000030307 15025657560 0013446 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use WP_Error; use WP_Post; use Yoast\WP\SEO\Exceptions\Indexable\Post_Not_Built_Exception; use Yoast\WP\SEO\Exceptions\Indexable\Post_Not_Found_Exception; use Yoast\WP\SEO\Helpers\Meta_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Post Builder for the indexables. * * Formats the post meta to indexable format. */ class Indexable_Post_Builder { use Indexable_Social_Image_Trait; /** * The indexable repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * Holds the Post_Helper instance. * * @var Post_Helper */ protected $post_helper; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * Knows the latest version of the Indexable post builder type. * * @var int */ protected $version; /** * The meta helper. * * @var Meta_Helper */ protected $meta; /** * Indexable_Post_Builder constructor. * * @param Post_Helper $post_helper The post helper. * @param Post_Type_Helper $post_type_helper The post type helper. * @param Indexable_Builder_Versions $versions The indexable builder versions. * @param Meta_Helper $meta The meta helper. */ public function __construct( Post_Helper $post_helper, Post_Type_Helper $post_type_helper, Indexable_Builder_Versions $versions, Meta_Helper $meta ) { $this->post_helper = $post_helper; $this->post_type_helper = $post_type_helper; $this->version = $versions->get_latest_version_for_type( 'post' ); $this->meta = $meta; } /** * Sets the indexable repository. Done to avoid circular dependencies. * * @required * * @param Indexable_Repository $indexable_repository The indexable repository. * * @return void */ public function set_indexable_repository( Indexable_Repository $indexable_repository ) { $this->indexable_repository = $indexable_repository; } /** * Formats the data. * * @param int $post_id The post ID to use. * @param Indexable $indexable The indexable to format. * * @return bool|Indexable The extended indexable. False when unable to build. * * @throws Post_Not_Found_Exception When the post could not be found. * @throws Post_Not_Built_Exception When the post should not be indexed. */ public function build( $post_id, $indexable ) { if ( ! $this->post_helper->is_post_indexable( $post_id ) ) { throw Post_Not_Built_Exception::because_not_indexable( $post_id ); } $post = $this->post_helper->get_post( $post_id ); if ( $post === null ) { throw new Post_Not_Found_Exception(); } if ( $this->should_exclude_post( $post ) ) { throw Post_Not_Built_Exception::because_post_type_excluded( $post_id ); } $indexable->object_id = $post_id; $indexable->object_type = 'post'; $indexable->object_sub_type = $post->post_type; $indexable->permalink = $this->get_permalink( $post->post_type, $post_id ); $indexable->primary_focus_keyword_score = $this->get_keyword_score( $this->meta->get_value( 'focuskw', $post_id ), (int) $this->meta->get_value( 'linkdex', $post_id ) ); $indexable->readability_score = (int) $this->meta->get_value( 'content_score', $post_id ); $indexable->inclusive_language_score = (int) $this->meta->get_value( 'inclusive_language_score', $post_id ); $indexable->is_cornerstone = ( $this->meta->get_value( 'is_cornerstone', $post_id ) === '1' ); $indexable->is_robots_noindex = $this->get_robots_noindex( (int) $this->meta->get_value( 'meta-robots-noindex', $post_id ) ); // Set additional meta-robots values. $indexable->is_robots_nofollow = ( $this->meta->get_value( 'meta-robots-nofollow', $post_id ) === '1' ); $noindex_advanced = $this->meta->get_value( 'meta-robots-adv', $post_id ); $meta_robots = \explode( ',', $noindex_advanced ); foreach ( $this->get_robots_options() as $meta_robots_option ) { $indexable->{'is_robots_' . $meta_robots_option} = \in_array( $meta_robots_option, $meta_robots, true ) ? 1 : null; } $this->reset_social_images( $indexable ); foreach ( $this->get_indexable_lookup() as $meta_key => $indexable_key ) { $indexable->{$indexable_key} = $this->empty_string_to_null( $this->meta->get_value( $meta_key, $post_id ) ); } if ( empty( $indexable->breadcrumb_title ) ) { $indexable->breadcrumb_title = \wp_strip_all_tags( \get_the_title( $post_id ), true ); } $this->handle_social_images( $indexable ); $indexable->author_id = $post->post_author; $indexable->post_parent = $post->post_parent; $indexable->number_of_pages = $this->get_number_of_pages_for_post( $post ); $indexable->post_status = $post->post_status; $indexable->is_protected = $post->post_password !== ''; $indexable->is_public = $this->is_public( $indexable ); $indexable->has_public_posts = $this->has_public_posts( $indexable ); $indexable->blog_id = \get_current_blog_id(); $indexable->schema_page_type = $this->empty_string_to_null( $this->meta->get_value( 'schema_page_type', $post_id ) ); $indexable->schema_article_type = $this->empty_string_to_null( $this->meta->get_value( 'schema_article_type', $post_id ) ); $indexable->object_last_modified = $post->post_modified_gmt; $indexable->object_published_at = $post->post_date_gmt; $indexable->version = $this->version; return $indexable; } /** * Retrieves the permalink for a post with the given post type and ID. * * @param string $post_type The post type. * @param int $post_id The post ID. * * @return WP_Error|string|false The permalink. */ protected function get_permalink( $post_type, $post_id ) { if ( $post_type !== 'attachment' ) { return \get_permalink( $post_id ); } return \wp_get_attachment_url( $post_id ); } /** * Determines the value of is_public. * * @param Indexable $indexable The indexable. * * @return bool|null Whether or not the post type is public. Null if no override is set. */ protected function is_public( $indexable ) { if ( $indexable->is_protected === true ) { return false; } if ( $indexable->is_robots_noindex === true ) { return false; } // Attachments behave differently than the other post types, since they inherit from their parent. if ( $indexable->object_sub_type === 'attachment' ) { return $this->is_public_attachment( $indexable ); } if ( ! \in_array( $indexable->post_status, $this->post_helper->get_public_post_statuses(), true ) ) { return false; } if ( $indexable->is_robots_noindex === false ) { return true; } return null; } /** * Determines the value of is_public for attachments. * * @param Indexable $indexable The indexable. * * @return bool|null False when it has no parent. Null when it has a parent. */ protected function is_public_attachment( $indexable ) { // If the attachment has no parent, it should not be public. if ( empty( $indexable->post_parent ) ) { return false; } // If the attachment has a parent, the is_public should be NULL. return null; } /** * Determines the value of has_public_posts. * * @param Indexable $indexable The indexable. * * @return bool|null Whether the attachment has a public parent, can be true, false and null. Null when it is not an attachment. */ protected function has_public_posts( $indexable ) { // Only attachments (and authors) have this value. if ( $indexable->object_sub_type !== 'attachment' ) { return null; } // The attachment should have a post parent. if ( empty( $indexable->post_parent ) ) { return false; } // The attachment should inherit the post status. if ( $indexable->post_status !== 'inherit' ) { return false; } // The post parent should be public. $post_parent_indexable = $this->indexable_repository->find_by_id_and_type( $indexable->post_parent, 'post' ); if ( $post_parent_indexable !== false ) { return $post_parent_indexable->is_public; } return false; } /** * Converts the meta robots noindex value to the indexable value. * * @param int $value Meta value to convert. * * @return bool|null True for noindex, false for index, null for default of parent/type. */ protected function get_robots_noindex( $value ) { $value = (int) $value; switch ( $value ) { case 1: return true; case 2: return false; } return null; } /** * Retrieves the robot options to search for. * * @return array List of robots values. */ protected function get_robots_options() { return [ 'noimageindex', 'noarchive', 'nosnippet' ]; } /** * Determines the focus keyword score. * * @param string $keyword The focus keyword that is set. * @param int $score The score saved on the meta data. * * @return int|null Score to use. */ protected function get_keyword_score( $keyword, $score ) { if ( empty( $keyword ) ) { return null; } return $score; } /** * Retrieves the lookup table. * * @return array Lookup table for the indexable fields. */ protected function get_indexable_lookup() { return [ 'focuskw' => 'primary_focus_keyword', 'canonical' => 'canonical', 'title' => 'title', 'metadesc' => 'description', 'bctitle' => 'breadcrumb_title', 'opengraph-title' => 'open_graph_title', 'opengraph-image' => 'open_graph_image', 'opengraph-image-id' => 'open_graph_image_id', 'opengraph-description' => 'open_graph_description', 'twitter-title' => 'twitter_title', 'twitter-image' => 'twitter_image', 'twitter-image-id' => 'twitter_image_id', 'twitter-description' => 'twitter_description', 'estimated-reading-time-minutes' => 'estimated_reading_time_minutes', ]; } /** * Finds an alternative image for the social image. * * @param Indexable $indexable The indexable. * * @return array|bool False when not found, array with data when found. */ protected function find_alternative_image( Indexable $indexable ) { if ( $indexable->object_sub_type === 'attachment' && $this->image->is_valid_attachment( $indexable->object_id ) ) { return [ 'image_id' => $indexable->object_id, 'source' => 'attachment-image', ]; } $featured_image_id = $this->image->get_featured_image_id( $indexable->object_id ); if ( $featured_image_id ) { return [ 'image_id' => $featured_image_id, 'source' => 'featured-image', ]; } $gallery_image = $this->image->get_gallery_image( $indexable->object_id ); if ( $gallery_image ) { return [ 'image' => $gallery_image, 'source' => 'gallery-image', ]; } $content_image = $this->image->get_post_content_image( $indexable->object_id ); if ( $content_image ) { return [ 'image' => $content_image, 'source' => 'first-content-image', ]; } return false; } /** * Gets the number of pages for a post. * * @param object $post The post object. * * @return int|null The number of pages or null if the post isn't paginated. */ protected function get_number_of_pages_for_post( $post ) { $number_of_pages = ( \substr_count( $post->post_content, '<!--nextpage-->' ) + 1 ); if ( $number_of_pages <= 1 ) { return null; } return $number_of_pages; } /** * Checks whether an indexable should be built for this post. * * @param WP_Post $post The post for which an indexable should be built. * * @return bool `true` if the post should be excluded from building, `false` if not. */ protected function should_exclude_post( $post ) { return $this->post_type_helper->is_excluded( $post->post_type ); } /** * Transforms an empty string into null. Leaves non-empty strings intact. * * @param string $text The string. * * @return string|null The input string or null. */ protected function empty_string_to_null( $text ) { if ( ! \is_string( $text ) || $text === '' ) { return null; } return $text; } } builders/indexable-home-page-builder.php 0000644 00000010542 15025657560 0014322 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Homepage Builder for the indexables. * * Formats the homepage meta to indexable format. */ class Indexable_Home_Page_Builder { use Indexable_Social_Image_Trait; /** * The options helper. * * @var Options_Helper */ protected $options; /** * The URL helper. * * @var Url_Helper */ protected $url_helper; /** * The latest version of the Indexable-Home-Page-Builder. * * @var int */ protected $version; /** * Holds the taxonomy helper instance. * * @var Post_Helper */ protected $post_helper; /** * Indexable_Home_Page_Builder constructor. * * @param Options_Helper $options The options helper. * @param Url_Helper $url_helper The url helper. * @param Indexable_Builder_Versions $versions Knows the latest version of each Indexable type. * @param Post_Helper $post_helper The post helper. */ public function __construct( Options_Helper $options, Url_Helper $url_helper, Indexable_Builder_Versions $versions, Post_Helper $post_helper ) { $this->options = $options; $this->url_helper = $url_helper; $this->version = $versions->get_latest_version_for_type( 'home-page' ); $this->post_helper = $post_helper; } /** * Formats the data. * * @param Indexable $indexable The indexable to format. * * @return Indexable The extended indexable. */ public function build( $indexable ) { $indexable->object_type = 'home-page'; $indexable->title = $this->options->get( 'title-home-wpseo' ); $indexable->breadcrumb_title = $this->options->get( 'breadcrumbs-home' ); $indexable->permalink = $this->url_helper->home(); $indexable->blog_id = \get_current_blog_id(); $indexable->description = $this->options->get( 'metadesc-home-wpseo' ); if ( empty( $indexable->description ) ) { $indexable->description = \get_bloginfo( 'description' ); } $indexable->is_robots_noindex = \get_option( 'blog_public' ) === '0'; $indexable->open_graph_title = $this->options->get( 'open_graph_frontpage_title' ); $indexable->open_graph_image = $this->options->get( 'open_graph_frontpage_image' ); $indexable->open_graph_image_id = $this->options->get( 'open_graph_frontpage_image_id' ); $indexable->open_graph_description = $this->options->get( 'open_graph_frontpage_desc' ); // Reset the OG image source & meta. $indexable->open_graph_image_source = null; $indexable->open_graph_image_meta = null; // When the image or image id is set. if ( $indexable->open_graph_image || $indexable->open_graph_image_id ) { $indexable->open_graph_image_source = 'set-by-user'; $this->set_open_graph_image_meta_data( $indexable ); } $timestamps = $this->get_object_timestamps(); $indexable->object_published_at = $timestamps->published_at; $indexable->object_last_modified = $timestamps->last_modified; $indexable->version = $this->version; return $indexable; } /** * Returns the timestamps for the homepage. * * @return object An object with last_modified and published_at timestamps. */ protected function get_object_timestamps() { global $wpdb; $post_statuses = $this->post_helper->get_public_post_statuses(); $replacements = []; $replacements[] = 'post_modified_gmt'; $replacements[] = 'post_date_gmt'; $replacements[] = $wpdb->posts; $replacements[] = 'post_status'; $replacements = \array_merge( $replacements, $post_statuses ); $replacements[] = 'post_password'; $replacements[] = 'post_type'; //phpcs:disable WordPress.DB.PreparedSQLPlaceholders -- %i placeholder is still not recognized. //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. return $wpdb->get_row( $wpdb->prepare( ' SELECT MAX(p.%i) AS last_modified, MIN(p.%i) AS published_at FROM %i AS p WHERE p.%i IN (' . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ") AND p.%i = '' AND p.%i = 'post' ", $replacements ) ); //phpcs:enable } } builders/indexable-term-builder.php 0000644 00000021030 15025657560 0013421 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Exceptions\Indexable\Invalid_Term_Exception; use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Built_Exception; use Yoast\WP\SEO\Exceptions\Indexable\Term_Not_Found_Exception; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Term Builder for the indexables. * * Formats the term meta to indexable format. */ class Indexable_Term_Builder { use Indexable_Social_Image_Trait; /** * Holds the taxonomy helper instance. * * @var Taxonomy_Helper */ protected $taxonomy_helper; /** * The latest version of the Indexable_Term_Builder. * * @var int */ protected $version; /** * Holds the taxonomy helper instance. * * @var Post_Helper */ protected $post_helper; /** * Indexable_Term_Builder constructor. * * @param Taxonomy_Helper $taxonomy_helper The taxonomy helper. * @param Indexable_Builder_Versions $versions The latest version of each Indexable Builder. * @param Post_Helper $post_helper The post helper. */ public function __construct( Taxonomy_Helper $taxonomy_helper, Indexable_Builder_Versions $versions, Post_Helper $post_helper ) { $this->taxonomy_helper = $taxonomy_helper; $this->version = $versions->get_latest_version_for_type( 'term' ); $this->post_helper = $post_helper; } /** * Formats the data. * * @param int $term_id ID of the term to save data for. * @param Indexable $indexable The indexable to format. * * @return bool|Indexable The extended indexable. False when unable to build. * * @throws Invalid_Term_Exception When the term is invalid. * @throws Term_Not_Built_Exception When the term is not viewable. * @throws Term_Not_Found_Exception When the term is not found. */ public function build( $term_id, $indexable ) { $term = \get_term( $term_id ); if ( $term === null ) { throw new Term_Not_Found_Exception(); } if ( \is_wp_error( $term ) ) { throw new Invalid_Term_Exception( $term->get_error_message() ); } $indexable_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies(); if ( ! \in_array( $term->taxonomy, $indexable_taxonomies, true ) ) { throw Term_Not_Built_Exception::because_not_indexable( $term_id ); } $term_link = \get_term_link( $term, $term->taxonomy ); if ( \is_wp_error( $term_link ) ) { throw new Invalid_Term_Exception( $term_link->get_error_message() ); } $term_meta = $this->taxonomy_helper->get_term_meta( $term ); $indexable->object_id = $term_id; $indexable->object_type = 'term'; $indexable->object_sub_type = $term->taxonomy; $indexable->permalink = $term_link; $indexable->blog_id = \get_current_blog_id(); $indexable->primary_focus_keyword_score = $this->get_keyword_score( $this->get_meta_value( 'wpseo_focuskw', $term_meta ), $this->get_meta_value( 'wpseo_linkdex', $term_meta ) ); $indexable->is_robots_noindex = $this->get_noindex_value( $this->get_meta_value( 'wpseo_noindex', $term_meta ) ); $indexable->is_public = ( $indexable->is_robots_noindex === null ) ? null : ! $indexable->is_robots_noindex; $this->reset_social_images( $indexable ); foreach ( $this->get_indexable_lookup() as $meta_key => $indexable_key ) { $indexable->{$indexable_key} = $this->get_meta_value( $meta_key, $term_meta ); } if ( empty( $indexable->breadcrumb_title ) ) { $indexable->breadcrumb_title = $term->name; } $this->handle_social_images( $indexable ); $indexable->is_cornerstone = $this->get_meta_value( 'wpseo_is_cornerstone', $term_meta ); // Not implemented yet. $indexable->is_robots_nofollow = null; $indexable->is_robots_noarchive = null; $indexable->is_robots_noimageindex = null; $indexable->is_robots_nosnippet = null; $timestamps = $this->get_object_timestamps( $term_id, $term->taxonomy ); $indexable->object_published_at = $timestamps->published_at; $indexable->object_last_modified = $timestamps->last_modified; $indexable->version = $this->version; return $indexable; } /** * Converts the meta noindex value to the indexable value. * * @param string $meta_value Term meta to base the value on. * * @return bool|null */ protected function get_noindex_value( $meta_value ) { if ( $meta_value === 'noindex' ) { return true; } if ( $meta_value === 'index' ) { return false; } return null; } /** * Determines the focus keyword score. * * @param string $keyword The focus keyword that is set. * @param int $score The score saved on the meta data. * * @return int|null Score to use. */ protected function get_keyword_score( $keyword, $score ) { if ( empty( $keyword ) ) { return null; } return $score; } /** * Retrieves the lookup table. * * @return array Lookup table for the indexable fields. */ protected function get_indexable_lookup() { return [ 'wpseo_canonical' => 'canonical', 'wpseo_focuskw' => 'primary_focus_keyword', 'wpseo_title' => 'title', 'wpseo_desc' => 'description', 'wpseo_content_score' => 'readability_score', 'wpseo_inclusive_language_score' => 'inclusive_language_score', 'wpseo_bctitle' => 'breadcrumb_title', 'wpseo_opengraph-title' => 'open_graph_title', 'wpseo_opengraph-description' => 'open_graph_description', 'wpseo_opengraph-image' => 'open_graph_image', 'wpseo_opengraph-image-id' => 'open_graph_image_id', 'wpseo_twitter-title' => 'twitter_title', 'wpseo_twitter-description' => 'twitter_description', 'wpseo_twitter-image' => 'twitter_image', 'wpseo_twitter-image-id' => 'twitter_image_id', ]; } /** * Retrieves a meta value from the given meta data. * * @param string $meta_key The key to extract. * @param array $term_meta The meta data. * * @return string|null The meta value. */ protected function get_meta_value( $meta_key, $term_meta ) { if ( ! $term_meta || ! \array_key_exists( $meta_key, $term_meta ) ) { return null; } $value = $term_meta[ $meta_key ]; if ( \is_string( $value ) && $value === '' ) { return null; } return $value; } /** * Finds an alternative image for the social image. * * @param Indexable $indexable The indexable. * * @return array|bool False when not found, array with data when found. */ protected function find_alternative_image( Indexable $indexable ) { $content_image = $this->image->get_term_content_image( $indexable->object_id ); if ( $content_image ) { return [ 'image' => $content_image, 'source' => 'first-content-image', ]; } return false; } /** * Returns the timestamps for a given term. * * @param int $term_id The term ID. * @param string $taxonomy The taxonomy. * * @return object An object with last_modified and published_at timestamps. */ protected function get_object_timestamps( $term_id, $taxonomy ) { global $wpdb; $post_statuses = $this->post_helper->get_public_post_statuses(); $replacements = []; $replacements[] = 'post_modified_gmt'; $replacements[] = 'post_date_gmt'; $replacements[] = $wpdb->posts; $replacements[] = $wpdb->term_relationships; $replacements[] = 'object_id'; $replacements[] = 'ID'; $replacements[] = $wpdb->term_taxonomy; $replacements[] = 'term_taxonomy_id'; $replacements[] = 'term_taxonomy_id'; $replacements[] = 'taxonomy'; $replacements[] = $taxonomy; $replacements[] = 'term_id'; $replacements[] = $term_id; $replacements[] = 'post_status'; $replacements = \array_merge( $replacements, $post_statuses ); $replacements[] = 'post_password'; //phpcs:disable WordPress.DB.PreparedSQLPlaceholders -- %i placeholder is still not recognized. //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. return $wpdb->get_row( $wpdb->prepare( ' SELECT MAX(p.%i) AS last_modified, MIN(p.%i) AS published_at FROM %i AS p INNER JOIN %i AS term_rel ON term_rel.%i = p.%i INNER JOIN %i AS term_tax ON term_tax.%i = term_rel.%i AND term_tax.%i = %s AND term_tax.%i = %d WHERE p.%i IN (' . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ") AND p.%i = '' ", $replacements ) ); //phpcs:enable } } builders/indexable-system-page-builder.php 0000644 00000004150 15025657560 0014714 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * System page builder for the indexables. * * Formats system pages ( search and error ) meta to indexable format. */ class Indexable_System_Page_Builder { /** * Mapping of object type to title option keys. */ public const OPTION_MAPPING = [ 'search-result' => [ 'title' => 'title-search-wpseo', ], '404' => [ 'title' => 'title-404-wpseo', 'breadcrumb_title' => 'breadcrumbs-404crumb', ], ]; /** * The options helper. * * @var Options_Helper */ protected $options; /** * The latest version of the Indexable_System_Page_Builder. * * @var int */ protected $version; /** * Indexable_System_Page_Builder constructor. * * @param Options_Helper $options The options helper. * @param Indexable_Builder_Versions $versions The latest version of each Indexable Builder. */ public function __construct( Options_Helper $options, Indexable_Builder_Versions $versions ) { $this->options = $options; $this->version = $versions->get_latest_version_for_type( 'system-page' ); } /** * Formats the data. * * @param string $object_sub_type The object sub type of the system page. * @param Indexable $indexable The indexable to format. * * @return Indexable The extended indexable. */ public function build( $object_sub_type, Indexable $indexable ) { $indexable->object_type = 'system-page'; $indexable->object_sub_type = $object_sub_type; $indexable->title = $this->options->get( static::OPTION_MAPPING[ $object_sub_type ]['title'] ); $indexable->is_robots_noindex = true; $indexable->blog_id = \get_current_blog_id(); if ( \array_key_exists( 'breadcrumb_title', static::OPTION_MAPPING[ $object_sub_type ] ) ) { $indexable->breadcrumb_title = $this->options->get( static::OPTION_MAPPING[ $object_sub_type ]['breadcrumb_title'] ); } $indexable->version = $this->version; return $indexable; } } builders/indexable-post-type-archive-builder.php 0000644 00000012324 15025657560 0016043 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Exceptions\Indexable\Post_Type_Not_Built_Exception; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Post type archive builder for the indexables. * * Formats the post type archive meta to indexable format. */ class Indexable_Post_Type_Archive_Builder { /** * The options helper. * * @var Options_Helper */ protected $options; /** * The latest version of the Indexable_Post_Type_Archive_Builder. * * @var int */ protected $version; /** * Holds the post helper instance. * * @var Post_Helper */ protected $post_helper; /** * Holds the post type helper instance. * * @var Post_Type_Helper */ protected $post_type_helper; /** * Indexable_Post_Type_Archive_Builder constructor. * * @param Options_Helper $options The options helper. * @param Indexable_Builder_Versions $versions The latest version of each Indexable builder. * @param Post_Helper $post_helper The post helper. * @param Post_Type_Helper $post_type_helper The post type helper. */ public function __construct( Options_Helper $options, Indexable_Builder_Versions $versions, Post_Helper $post_helper, Post_Type_Helper $post_type_helper ) { $this->options = $options; $this->version = $versions->get_latest_version_for_type( 'post-type-archive' ); $this->post_helper = $post_helper; $this->post_type_helper = $post_type_helper; } /** * Formats the data. * * @param string $post_type The post type to build the indexable for. * @param Indexable $indexable The indexable to format. * * @return Indexable The extended indexable. * @throws Post_Type_Not_Built_Exception Throws exception if the post type is excluded. */ public function build( $post_type, Indexable $indexable ) { if ( ! $this->post_type_helper->is_post_type_archive_indexable( $post_type ) ) { throw Post_Type_Not_Built_Exception::because_not_indexable( $post_type ); } $indexable->object_type = 'post-type-archive'; $indexable->object_sub_type = $post_type; $indexable->title = $this->options->get( 'title-ptarchive-' . $post_type ); $indexable->description = $this->options->get( 'metadesc-ptarchive-' . $post_type ); $indexable->breadcrumb_title = $this->get_breadcrumb_title( $post_type ); $indexable->permalink = \get_post_type_archive_link( $post_type ); $indexable->is_robots_noindex = $this->options->get( 'noindex-ptarchive-' . $post_type ); $indexable->is_public = ( (int) $indexable->is_robots_noindex !== 1 ); $indexable->blog_id = \get_current_blog_id(); $indexable->version = $this->version; $timestamps = $this->get_object_timestamps( $post_type ); $indexable->object_published_at = $timestamps->published_at; $indexable->object_last_modified = $timestamps->last_modified; return $indexable; } /** * Returns the fallback breadcrumb title for a given post. * * @param string $post_type The post type to get the fallback breadcrumb title for. * * @return string */ private function get_breadcrumb_title( $post_type ) { $options_breadcrumb_title = $this->options->get( 'bctitle-ptarchive-' . $post_type ); if ( $options_breadcrumb_title !== '' ) { return $options_breadcrumb_title; } $post_type_obj = \get_post_type_object( $post_type ); if ( ! \is_object( $post_type_obj ) ) { return ''; } if ( isset( $post_type_obj->label ) && $post_type_obj->label !== '' ) { return $post_type_obj->label; } if ( isset( $post_type_obj->labels->menu_name ) && $post_type_obj->labels->menu_name !== '' ) { return $post_type_obj->labels->menu_name; } return $post_type_obj->name; } /** * Returns the timestamps for a given post type. * * @param string $post_type The post type. * * @return object An object with last_modified and published_at timestamps. */ protected function get_object_timestamps( $post_type ) { global $wpdb; $post_statuses = $this->post_helper->get_public_post_statuses(); $replacements = []; $replacements[] = 'post_modified_gmt'; $replacements[] = 'post_date_gmt'; $replacements[] = $wpdb->posts; $replacements[] = 'post_status'; $replacements = \array_merge( $replacements, $post_statuses ); $replacements[] = 'post_password'; $replacements[] = 'post_type'; $replacements[] = $post_type; //phpcs:disable WordPress.DB.PreparedSQLPlaceholders -- %i placeholder is still not recognized. //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- We need to use a direct query here. //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. return $wpdb->get_row( $wpdb->prepare( ' SELECT MAX(p.%i) AS last_modified, MIN(p.%i) AS published_at FROM %i AS p WHERE p.%i IN (' . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ") AND p.%i = '' AND p.%i = %s ", $replacements ) ); //phpcs:enable } } builders/indexable-link-builder.php 0000644 00000043543 15025657560 0013424 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use WPSEO_Image_Utils; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Images\Application\Image_Content_Extractor; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Repositories\SEO_Links_Repository; /** * Indexable link builder. */ class Indexable_Link_Builder { /** * The SEO links repository. * * @var SEO_Links_Repository */ protected $seo_links_repository; /** * The url helper. * * @var Url_Helper */ protected $url_helper; /** * The image helper. * * @var Image_Helper */ protected $image_helper; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The post helper. * * @var Post_Helper */ protected $post_helper; /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * The indexable repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * Class that finds all images in a content string and extracts them. * * @var Image_Content_Extractor */ private $image_content_extractor; /** * Indexable_Link_Builder constructor. * * @param SEO_Links_Repository $seo_links_repository The SEO links repository. * @param Url_Helper $url_helper The URL helper. * @param Post_Helper $post_helper The post helper. * @param Options_Helper $options_helper The options helper. * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( SEO_Links_Repository $seo_links_repository, Url_Helper $url_helper, Post_Helper $post_helper, Options_Helper $options_helper, Indexable_Helper $indexable_helper, Image_Content_Extractor $image_content_extractor ) { $this->seo_links_repository = $seo_links_repository; $this->url_helper = $url_helper; $this->post_helper = $post_helper; $this->options_helper = $options_helper; $this->indexable_helper = $indexable_helper; $this->image_content_extractor = $image_content_extractor; } /** * Sets the indexable repository. * * @required * * @param Indexable_Repository $indexable_repository The indexable repository. * @param Image_Helper $image_helper The image helper. * * @return void */ public function set_dependencies( Indexable_Repository $indexable_repository, Image_Helper $image_helper ) { $this->indexable_repository = $indexable_repository; $this->image_helper = $image_helper; } /** * Builds the links for a post. * * @param Indexable $indexable The indexable. * @param string $content The content. Expected to be unfiltered. * * @return SEO_Links[] The created SEO links. */ public function build( $indexable, $content ) { if ( ! $this->indexable_helper->should_index_indexable( $indexable ) ) { return []; } global $post; if ( $indexable->object_type === 'post' ) { $post_backup = $post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. $post = $this->post_helper->get_post( $indexable->object_id ); \setup_postdata( $post ); $content = \apply_filters( 'the_content', $content ); \wp_reset_postdata(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited -- To setup the post we need to do this explicitly. $post = $post_backup; } $content = \str_replace( ']]>', ']]>', $content ); $links = $this->gather_links( $content ); $images = $this->image_content_extractor->gather_images( $content ); if ( empty( $links ) && empty( $images ) ) { $indexable->link_count = 0; $this->update_related_indexables( $indexable, [] ); return []; } if ( ! empty( $images ) && ( $indexable->open_graph_image_source === 'first-content-image' || $indexable->twitter_image_source === 'first-content-image' ) ) { $this->update_first_content_image( $indexable, $images ); } $links = $this->create_links( $indexable, $links, $images ); $this->update_related_indexables( $indexable, $links ); $indexable->link_count = $this->get_internal_link_count( $links ); return $links; } /** * Deletes all SEO links for an indexable. * * @param Indexable $indexable The indexable. * * @return void */ public function delete( $indexable ) { $links = ( $this->seo_links_repository->find_all_by_indexable_id( $indexable->id ) ); $this->seo_links_repository->delete_all_by_indexable_id( $indexable->id ); $linked_indexable_ids = []; foreach ( $links as $link ) { if ( $link->target_indexable_id ) { $linked_indexable_ids[] = $link->target_indexable_id; } } $this->update_incoming_links_for_related_indexables( $linked_indexable_ids ); } /** * Fixes existing SEO links that are supposed to have a target indexable but don't, because of prior indexable * cleanup. * * @param Indexable $indexable The indexable to be the target of SEO Links. * * @return void */ public function patch_seo_links( Indexable $indexable ) { if ( ! empty( $indexable->id ) && ! empty( $indexable->object_id ) ) { $links = $this->seo_links_repository->find_all_by_target_post_id( $indexable->object_id ); $updated_indexable = false; foreach ( $links as $link ) { if ( \is_a( $link, SEO_Links::class ) && empty( $link->target_indexable_id ) ) { // Since that post ID exists in an SEO link but has no target_indexable_id, it's probably because of prior indexable cleanup. $this->seo_links_repository->update_target_indexable_id( $link->id, $indexable->id ); $updated_indexable = true; } } if ( $updated_indexable ) { $updated_indexable_id = [ $indexable->id ]; $this->update_incoming_links_for_related_indexables( $updated_indexable_id ); } } } /** * Gathers all links from content. * * @param string $content The content. * * @return string[] An array of urls. */ protected function gather_links( $content ) { if ( \strpos( $content, 'href' ) === false ) { // Nothing to do. return []; } $links = []; $regexp = '<a\s[^>]*href=("??)([^" >]*?)\1[^>]*>'; // Used modifiers iU to match case insensitive and make greedy quantifiers lazy. if ( \preg_match_all( "/$regexp/iU", $content, $matches, \PREG_SET_ORDER ) ) { foreach ( $matches as $match ) { $links[] = \trim( $match[2], "'" ); } } return $links; } /** * Creates link models from lists of URLs and image sources. * * @param Indexable $indexable The indexable. * @param string[] $links The link URLs. * @param int[] $images The image sources. * * @return SEO_Links[] The link models. */ protected function create_links( $indexable, $links, $images ) { $home_url = \wp_parse_url( \home_url() ); $current_url = \wp_parse_url( $indexable->permalink ); $links = \array_map( function ( $link ) use ( $home_url, $indexable ) { return $this->create_internal_link( $link, $home_url, $indexable ); }, $links ); // Filter out links to the same page with a fragment or query. $links = \array_filter( $links, function ( $link ) use ( $current_url ) { return $this->filter_link( $link, $current_url ); } ); $image_links = []; foreach ( $images as $image_url => $image_id ) { $image_links[] = $this->create_internal_link( $image_url, $home_url, $indexable, true, $image_id ); } return \array_merge( $links, $image_links ); } /** * Get the post ID based on the link's type and its target's permalink. * * @param string $type The type of link (either SEO_Links::TYPE_INTERNAL or SEO_Links::TYPE_INTERNAL_IMAGE). * @param string $permalink The permalink of the link's target. * * @return int The post ID. */ protected function get_post_id( $type, $permalink ) { if ( $type === SEO_Links::TYPE_INTERNAL ) { return \url_to_postid( $permalink ); } return $this->image_helper->get_attachment_by_url( $permalink ); } /** * Creates an internal link. * * @param string $url The url of the link. * @param array $home_url The home url, as parsed by wp_parse_url. * @param Indexable $indexable The indexable of the post containing the link. * @param bool $is_image Whether or not the link is an image. * @param int $image_id The ID of the internal image. * * @return SEO_Links The created link. */ protected function create_internal_link( $url, $home_url, $indexable, $is_image = false, $image_id = 0 ) { $parsed_url = \wp_parse_url( $url ); $link_type = $this->url_helper->get_link_type( $parsed_url, $home_url, $is_image ); /** * ORM representing a link in the SEO Links table. * * @var SEO_Links $model */ $model = $this->seo_links_repository->query()->create( [ 'url' => $url, 'type' => $link_type, 'indexable_id' => $indexable->id, 'post_id' => $indexable->object_id, ] ); $model->parsed_url = $parsed_url; if ( $model->type === SEO_Links::TYPE_INTERNAL ) { $permalink = $this->build_permalink( $url, $home_url ); return $this->enhance_link_from_indexable( $model, $permalink ); } if ( $model->type === SEO_Links::TYPE_INTERNAL_IMAGE ) { $permalink = $this->build_permalink( $url, $home_url ); /** The `wpseo_force_creating_and_using_attachment_indexables` filter is documented in indexable-link-builder.php */ if ( ! $this->options_helper->get( 'disable-attachment' ) || \apply_filters( 'wpseo_force_creating_and_using_attachment_indexables', false ) ) { $model = $this->enhance_link_from_indexable( $model, $permalink ); } else { $target_post_id = ( $image_id !== 0 ) ? $image_id : WPSEO_Image_Utils::get_attachment_by_url( $permalink ); if ( ! empty( $target_post_id ) ) { $model->target_post_id = $target_post_id; } } if ( $model->target_post_id ) { $file = \get_attached_file( $model->target_post_id ); if ( $file ) { if ( \file_exists( $file ) ) { $model->size = \filesize( $file ); } else { $model->size = null; } [ , $width, $height ] = \wp_get_attachment_image_src( $model->target_post_id, 'full' ); $model->width = $width; $model->height = $height; } else { $model->width = 0; $model->height = 0; $model->size = 0; } } } return $model; } /** * Enhances the link model with information from its indexable. * * @param SEO_Links $model The link's model. * @param string $permalink The link's permalink. * * @return SEO_Links The enhanced link model. */ protected function enhance_link_from_indexable( $model, $permalink ) { $target = $this->indexable_repository->find_by_permalink( $permalink ); if ( ! $target ) { // If target indexable cannot be found, create one based on the post's post ID. $post_id = $this->get_post_id( $model->type, $permalink ); if ( $post_id && $post_id !== 0 ) { $target = $this->indexable_repository->find_by_id_and_type( $post_id, 'post' ); } } if ( ! $target ) { return $model; } $model->target_indexable_id = $target->id; if ( $target->object_type === 'post' ) { $model->target_post_id = $target->object_id; } if ( $model->target_indexable_id ) { $model->language = $target->language; $model->region = $target->region; } return $model; } /** * Builds the link's permalink. * * @param string $url The url of the link. * @param array $home_url The home url, as parsed by wp_parse_url. * * @return string The link's permalink. */ protected function build_permalink( $url, $home_url ) { $permalink = $this->get_permalink( $url, $home_url ); if ( $this->url_helper->is_relative( $permalink ) ) { // Make sure we're checking against the absolute URL, and add a trailing slash if the site has a trailing slash in its permalink settings. $permalink = $this->url_helper->ensure_absolute_url( \user_trailingslashit( $permalink ) ); } return $permalink; } /** * Filters out links that point to the same page with a fragment or query. * * @param SEO_Links $link The link. * @param array $current_url The url of the page the link is on, as parsed by wp_parse_url. * * @return bool Whether or not the link should be filtered. */ protected function filter_link( SEO_Links $link, $current_url ) { $url = $link->parsed_url; // Always keep external links. if ( $link->type === SEO_Links::TYPE_EXTERNAL ) { return true; } // Always keep links with an empty path or pointing to other pages. if ( isset( $url['path'] ) ) { return empty( $url['path'] ) || $url['path'] !== $current_url['path']; } // Only keep links to the current page without a fragment or query. return ( ! isset( $url['fragment'] ) && ! isset( $url['query'] ) ); } /** * Updates the link counts for related indexables. * * @param Indexable $indexable The indexable. * @param SEO_Links[] $links The link models. * * @return void */ protected function update_related_indexables( $indexable, $links ) { // Old links were only stored by post id, so remove all old seo links for this post that have no indexable id. // This can be removed if we ever fully clear all seo links. if ( $indexable->object_type === 'post' ) { $this->seo_links_repository->delete_all_by_post_id_where_indexable_id_null( $indexable->object_id ); } $updated_indexable_ids = []; $old_links = $this->seo_links_repository->find_all_by_indexable_id( $indexable->id ); $links_to_remove = $this->links_diff( $old_links, $links ); $links_to_add = $this->links_diff( $links, $old_links ); if ( ! empty( $links_to_remove ) ) { $this->seo_links_repository->delete_many_by_id( \wp_list_pluck( $links_to_remove, 'id' ) ); } if ( ! empty( $links_to_add ) ) { $this->seo_links_repository->insert_many( $links_to_add ); } foreach ( $links_to_add as $link ) { if ( $link->target_indexable_id ) { $updated_indexable_ids[] = $link->target_indexable_id; } } foreach ( $links_to_remove as $link ) { if ( $link->target_indexable_id ) { $updated_indexable_ids[] = $link->target_indexable_id; } } $this->update_incoming_links_for_related_indexables( $updated_indexable_ids ); } /** * Creates a diff between two arrays of SEO links, based on urls. * * @param SEO_Links[] $links_a The array to compare. * @param SEO_Links[] $links_b The array to compare against. * * @return SEO_Links[] Links that are in $links_a, but not in $links_b. */ protected function links_diff( $links_a, $links_b ) { return \array_udiff( $links_a, $links_b, static function ( SEO_Links $link_a, SEO_Links $link_b ) { return \strcmp( $link_a->url, $link_b->url ); } ); } /** * Returns the number of internal links in an array of link models. * * @param SEO_Links[] $links The link models. * * @return int The number of internal links. */ protected function get_internal_link_count( $links ) { $internal_link_count = 0; foreach ( $links as $link ) { if ( $link->type === SEO_Links::TYPE_INTERNAL ) { ++$internal_link_count; } } return $internal_link_count; } /** * Returns a cleaned permalink for a given link. * * @param string $link The raw URL. * @param array $home_url The home URL, as parsed by wp_parse_url. * * @return string The cleaned permalink. */ protected function get_permalink( $link, $home_url ) { // Get rid of the #anchor. $url_split = \explode( '#', $link ); $link = $url_split[0]; // Get rid of URL ?query=string. $url_split = \explode( '?', $link ); $link = $url_split[0]; // Set the correct URL scheme. $link = \set_url_scheme( $link, $home_url['scheme'] ); // Add 'www.' if it is absent and should be there. if ( \strpos( $home_url['host'], 'www.' ) === 0 && \strpos( $link, '://www.' ) === false ) { $link = \str_replace( '://', '://www.', $link ); } // Strip 'www.' if it is present and shouldn't be. if ( \strpos( $home_url['host'], 'www.' ) !== 0 ) { $link = \str_replace( '://www.', '://', $link ); } return $link; } /** * Updates incoming link counts for related indexables. * * @param int[] $related_indexable_ids The IDs of all related indexables. * * @return void */ protected function update_incoming_links_for_related_indexables( $related_indexable_ids ) { if ( empty( $related_indexable_ids ) ) { return; } $counts = $this->seo_links_repository->get_incoming_link_counts_for_indexable_ids( $related_indexable_ids ); if ( \wp_cache_supports( 'flush_group' ) ) { \wp_cache_flush_group( 'orphaned_counts' ); } foreach ( $counts as $count ) { $this->indexable_repository->update_incoming_link_count( $count['target_indexable_id'], $count['incoming'] ); } } /** * Updates the image ids when the indexable images are marked as first content image. * * @param Indexable $indexable The indexable to change. * @param array<string|int> $images The image array. * * @return void */ public function update_first_content_image( Indexable $indexable, array $images ): void { $current_open_graph_image = $indexable->open_graph_image; $current_twitter_image = $indexable->twitter_image; $first_content_image_url = \key( $images ); $first_content_image_id = \current( $images ); if ( $indexable->open_graph_image_source === 'first-content-image' && $current_open_graph_image === $first_content_image_url && ! empty( $first_content_image_id ) ) { $indexable->open_graph_image_id = $first_content_image_id; } if ( $indexable->twitter_image_source === 'first-content-image' && $current_twitter_image === $first_content_image_url && ! empty( $first_content_image_id ) ) { $indexable->twitter_image_id = $first_content_image_id; } } } builders/indexable-author-builder.php 0000644 00000017117 15025657560 0013767 0 ustar 00 <?php namespace Yoast\WP\SEO\Builders; use Yoast\WP\SEO\Exceptions\Indexable\Author_Not_Built_Exception; use Yoast\WP\SEO\Helpers\Author_Archive_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Author Builder for the indexables. * * Formats the author meta to indexable format. */ class Indexable_Author_Builder { use Indexable_Social_Image_Trait; /** * The author archive helper. * * @var Author_Archive_Helper */ private $author_archive; /** * The latest version of the Indexable_Author_Builder. * * @var int */ protected $version; /** * Holds the options helper instance. * * @var Options_Helper */ protected $options_helper; /** * Holds the taxonomy helper instance. * * @var Post_Helper */ protected $post_helper; /** * Indexable_Author_Builder constructor. * * @param Author_Archive_Helper $author_archive The author archive helper. * @param Indexable_Builder_Versions $versions The Indexable version manager. * @param Options_Helper $options_helper The options helper. * @param Post_Helper $post_helper The post helper. */ public function __construct( Author_Archive_Helper $author_archive, Indexable_Builder_Versions $versions, Options_Helper $options_helper, Post_Helper $post_helper ) { $this->author_archive = $author_archive; $this->version = $versions->get_latest_version_for_type( 'user' ); $this->options_helper = $options_helper; $this->post_helper = $post_helper; } /** * Formats the data. * * @param int $user_id The user to retrieve the indexable for. * @param Indexable $indexable The indexable to format. * * @return Indexable The extended indexable. * * @throws Author_Not_Built_Exception When author is not built. */ public function build( $user_id, Indexable $indexable ) { $exception = $this->check_if_user_should_be_indexed( $user_id ); if ( $exception ) { throw $exception; } $meta_data = $this->get_meta_data( $user_id ); $indexable->object_id = $user_id; $indexable->object_type = 'user'; $indexable->permalink = \get_author_posts_url( $user_id ); $indexable->title = $meta_data['wpseo_title']; $indexable->description = $meta_data['wpseo_metadesc']; $indexable->is_cornerstone = false; $indexable->is_robots_noindex = ( $meta_data['wpseo_noindex_author'] === 'on' ); $indexable->is_robots_nofollow = null; $indexable->is_robots_noarchive = null; $indexable->is_robots_noimageindex = null; $indexable->is_robots_nosnippet = null; $indexable->is_public = ( $indexable->is_robots_noindex ) ? false : null; $indexable->has_public_posts = $this->author_archive->author_has_public_posts( $user_id ); $indexable->blog_id = \get_current_blog_id(); $this->reset_social_images( $indexable ); $this->handle_social_images( $indexable ); $timestamps = $this->get_object_timestamps( $user_id ); $indexable->object_published_at = $timestamps->published_at; $indexable->object_last_modified = $timestamps->last_modified; $indexable->version = $this->version; return $indexable; } /** * Retrieves the meta data for this indexable. * * @param int $user_id The user to retrieve the meta data for. * * @return array List of meta entries. */ protected function get_meta_data( $user_id ) { $keys = [ 'wpseo_title', 'wpseo_metadesc', 'wpseo_noindex_author', ]; $output = []; foreach ( $keys as $key ) { $output[ $key ] = $this->get_author_meta( $user_id, $key ); } return $output; } /** * Retrieves the author meta. * * @param int $user_id The user to retrieve the indexable for. * @param string $key The meta entry to retrieve. * * @return string|null The value of the meta field. */ protected function get_author_meta( $user_id, $key ) { $value = \get_the_author_meta( $key, $user_id ); if ( \is_string( $value ) && $value === '' ) { return null; } return $value; } /** * Finds an alternative image for the social image. * * @param Indexable $indexable The indexable. * * @return array|bool False when not found, array with data when found. */ protected function find_alternative_image( Indexable $indexable ) { $gravatar_image = \get_avatar_url( $indexable->object_id, [ 'size' => 500, 'scheme' => 'https', ] ); if ( $gravatar_image ) { return [ 'image' => $gravatar_image, 'source' => 'gravatar-image', ]; } return false; } /** * Returns the timestamps for a given author. * * @param int $author_id The author ID. * * @return object An object with last_modified and published_at timestamps. */ protected function get_object_timestamps( $author_id ) { global $wpdb; $post_statuses = $this->post_helper->get_public_post_statuses(); $replacements = []; $replacements[] = 'post_modified_gmt'; $replacements[] = 'post_date_gmt'; $replacements[] = $wpdb->posts; $replacements[] = 'post_status'; $replacements = \array_merge( $replacements, $post_statuses ); $replacements[] = 'post_password'; $replacements[] = 'post_author'; $replacements[] = $author_id; //phpcs:disable WordPress.DB.PreparedSQLPlaceholders -- %i placeholder is still not recognized. //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. return $wpdb->get_row( $wpdb->prepare( ' SELECT MAX(p.%i) AS last_modified, MIN(p.%i) AS published_at FROM %i AS p WHERE p.%i IN (' . \implode( ', ', \array_fill( 0, \count( $post_statuses ), '%s' ) ) . ") AND p.%i = '' AND p.%i = %d ", $replacements ) ); //phpcs:enable } /** * Checks if the user should be indexed. * Returns an exception with an appropriate message if not. * * @param string $user_id The user id. * * @return Author_Not_Built_Exception|null The exception if it should not be indexed, or `null` if it should. */ protected function check_if_user_should_be_indexed( $user_id ) { $exception = null; if ( $this->author_archive->are_disabled() ) { $exception = Author_Not_Built_Exception::author_archives_are_disabled( $user_id ); } // We will check if the author has public posts the WP way, instead of the indexable way, to make sure we get proper results even if SEO optimization is not run. // In case the user has no public posts, we check if the user should be indexed anyway. if ( $this->options_helper->get( 'noindex-author-noposts-wpseo', false ) === true && $this->author_archive->author_has_public_posts_wp( $user_id ) === false ) { $exception = Author_Not_Built_Exception::author_archives_are_not_indexed_for_users_without_posts( $user_id ); } /** * Filter: Include or exclude a user from being build and saved as an indexable. * Return an `Author_Not_Built_Exception` when the indexable should not be build, with an appropriate message telling why it should not be built. * Return `null` if the indexable should be build. * * @param Author_Not_Built_Exception|null $exception An exception if the indexable is not being built, `null` if the indexable should be built. * @param string $user_id The ID of the user that should or should not be excluded. */ return \apply_filters( 'wpseo_should_build_and_save_user_indexable', $exception, $user_id ); } } models/indexable-extension.php 0000644 00000001072 15025657560 0012520 0 ustar 00 <?php namespace Yoast\WP\SEO\Models; use Yoast\WP\Lib\Model; /** * Abstract class for indexable extensions. */ abstract class Indexable_Extension extends Model { /** * Holds the Indexable instance. * * @var Indexable|null */ protected $indexable = null; /** * Returns the indexable this extension belongs to. * * @return Indexable The indexable. */ public function indexable() { if ( $this->indexable === null ) { $this->indexable = $this->belongs_to( 'Indexable', 'indexable_id', 'id' )->find_one(); } return $this->indexable; } } models/indexable.php 0000644 00000012413 15025657560 0010507 0 ustar 00 <?php namespace Yoast\WP\SEO\Models; use Yoast\WP\Lib\Model; /** * Indexable table definition. * * @property int $id * @property int $object_id * @property string $object_type * @property string $object_sub_type * * @property int $author_id * @property int $post_parent * * @property string $created_at * @property string $updated_at * * @property string $permalink * @property string $permalink_hash * @property string $canonical * * @property bool $is_robots_noindex * @property bool $is_robots_nofollow * @property bool $is_robots_noarchive * @property bool $is_robots_noimageindex * @property bool $is_robots_nosnippet * * @property string $title * @property string $description * @property string $breadcrumb_title * * @property bool $is_cornerstone * * @property string $primary_focus_keyword * @property int $primary_focus_keyword_score * * @property int $readability_score * * @property int $inclusive_language_score * * @property int $link_count * @property int $incoming_link_count * @property int $number_of_pages * * @property string $open_graph_title * @property string $open_graph_description * @property string $open_graph_image * @property string $open_graph_image_id * @property string $open_graph_image_source * @property string $open_graph_image_meta * * @property string $twitter_title * @property string $twitter_description * @property string $twitter_image * @property string $twitter_image_id * @property string $twitter_image_source * @property string $twitter_card * * @property int $prominent_words_version * * @property bool $is_public * @property bool $is_protected * @property string $post_status * @property bool $has_public_posts * * @property int $blog_id * * @property string $language * @property string $region * * @property string $schema_page_type * @property string $schema_article_type * * @property bool $has_ancestors * * @property int $estimated_reading_time_minutes * * @property string $object_last_modified * @property string $object_published_at * * @property int $version */ class Indexable extends Model { /** * Holds the ancestors. * * @var Indexable[] */ public $ancestors = []; /** * Whether nor this model uses timestamps. * * @var bool */ protected $uses_timestamps = true; /** * Which columns contain boolean values. * * @var array */ protected $boolean_columns = [ 'is_robots_noindex', 'is_robots_nofollow', 'is_robots_noarchive', 'is_robots_noimageindex', 'is_robots_nosnippet', 'is_cornerstone', 'is_public', 'is_protected', 'has_public_posts', 'has_ancestors', ]; /** * Which columns contain int values. * * @var array */ protected $int_columns = [ 'id', 'object_id', 'author_id', 'post_parent', 'primary_focus_keyword_score', 'readability_score', 'inclusive_language_score', 'link_count', 'incoming_link_count', 'number_of_pages', 'prominent_words_version', 'blog_id', 'estimated_reading_time_minutes', 'version', ]; /** * The loaded indexable extensions. * * @var Indexable_Extension[] */ protected $loaded_extensions = []; /** * Returns an Indexable_Extension by its name. * * @param string $class_name The class name of the extension to load. * * @return Indexable_Extension|bool The extension. */ public function get_extension( $class_name ) { if ( ! $this->loaded_extensions[ $class_name ] ) { $this->loaded_extensions[ $class_name ] = $this->has_one( $class_name, 'indexable_id', 'id' )->find_one(); } return $this->loaded_extensions[ $class_name ]; } /** * Enhances the save method. * * @return bool True on success. */ public function save() { if ( $this->permalink ) { $this->sanitize_permalink(); $this->permalink_hash = \strlen( $this->permalink ) . ':' . \md5( $this->permalink ); } if ( \is_string( $this->primary_focus_keyword ) && \mb_strlen( $this->primary_focus_keyword ) > 191 ) { $this->primary_focus_keyword = \mb_substr( $this->primary_focus_keyword, 0, 191, 'UTF-8' ); } return parent::save(); } /** * Sanitizes the permalink. * * @return void */ protected function sanitize_permalink() { if ( $this->permalink === 'unindexed' ) { return; } $permalink_structure = \get_option( 'permalink_structure' ); $permalink_parts = \wp_parse_url( $this->permalink ); if ( ! isset( $permalink_parts['path'] ) ) { $permalink_parts['path'] = '/'; } if ( \substr( $permalink_structure, -1, 1 ) === '/' && \strpos( \substr( $permalink_parts['path'], -5 ), '.' ) === false ) { $permalink_parts['path'] = \trailingslashit( $permalink_parts['path'] ); } $permalink = ''; if ( isset( $permalink_parts['scheme'] ) ) { $permalink .= $permalink_parts['scheme'] . '://'; } if ( isset( $permalink_parts['host'] ) ) { $permalink .= $permalink_parts['host']; } if ( isset( $permalink_parts['port'] ) ) { $permalink .= ':' . $permalink_parts['port']; } if ( isset( $permalink_parts['path'] ) ) { $permalink .= $permalink_parts['path']; } if ( isset( $permalink_parts['query'] ) ) { $permalink .= '?' . $permalink_parts['query']; } // We never set the fragment as the fragment is intended to be client-only. $this->permalink = $permalink; } } models/indexable-hierarchy.php 0000644 00000001111 15025657560 0012454 0 ustar 00 <?php namespace Yoast\WP\SEO\Models; use Yoast\WP\Lib\Model; /** * Indexable Hierarchy model definition. * * @property int $indexable_id The ID of the indexable. * @property int $ancestor_id The ID of the indexable's ancestor. * @property int $depth The depth of the ancestry. 1 being a parent, 2 being a grandparent etc. * @property int $blog_id Blog ID. */ class Indexable_Hierarchy extends Model { /** * Which columns contain int values. * * @var array */ protected $int_columns = [ 'indexable_id', 'ancestor_id', 'depth', 'blog_id', ]; } models/seo-meta.php 0000644 00000001030 15025657560 0010257 0 ustar 00 <?php namespace Yoast\WP\SEO\Models; use Yoast\WP\Lib\Model; /** * Table definition for the SEO Meta table. * * @property int $object_id * @property int $internal_link_count * @property int $incoming_link_count */ class SEO_Meta extends Model { /** * Overwrites the default ID column name. * * @var string */ public static $id_column = 'object_id'; /** * Which columns contain int values. * * @var array */ protected $int_columns = [ 'object_id', 'internal_link_count', 'incoming_link_count', ]; } models/seo-links.php 0000644 00000002416 15025657560 0010462 0 ustar 00 <?php namespace Yoast\WP\SEO\Models; use Yoast\WP\Lib\Model; /** * Table definition for the SEO Links table. * * @property int $id * @property string $url * @property int $post_id * @property int $target_post_id * @property string $type * @property int $indexable_id * @property int $target_indexable_id * @property int $height * @property int $width * @property int $size * @property string $language * @property string $region */ class SEO_Links extends Model { /** * Indicates that the link is external. * * @var string */ public const TYPE_INTERNAL = 'internal'; /** * Indicates that the link is internal. * * @var string */ public const TYPE_EXTERNAL = 'external'; /** * Indicates the link is an internal image. * * @var string */ public const TYPE_INTERNAL_IMAGE = 'image-in'; /** * Indicates the link is an external image. * * @var string */ public const TYPE_EXTERNAL_IMAGE = 'image-ex'; /** * Holds the parsed URL. May not be set. * * @var array */ public $parsed_url; /** * Which columns contain int values. * * @var array */ protected $int_columns = [ 'id', 'post_id', 'target_post_id', 'indexable_id', 'target_indexable_id', 'height', 'width', 'size', ]; } models/primary-term.php 0000644 00000001234 15025657560 0011203 0 ustar 00 <?php namespace Yoast\WP\SEO\Models; use Yoast\WP\Lib\Model; /** * Primary Term model definition. * * @property int $id Identifier. * @property int $post_id Post ID. * @property int $term_id Term ID. * @property string $taxonomy Taxonomy. * @property int $blog_id Blog ID. * * @property string $created_at * @property string $updated_at */ class Primary_Term extends Model { /** * Whether nor this model uses timestamps. * * @var bool */ protected $uses_timestamps = true; /** * Which columns contain int values. * * @var array */ protected $int_columns = [ 'id', 'post_id', 'term_id', 'blog_id', ]; } conditionals/should-index-links-conditional.php 0000644 00000002026 15025657560 0016000 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Should_Index_Links_Conditional class. */ class Should_Index_Links_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ protected $options_helper; /** * Should_Index_Links_Conditional constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Returns `true` when the links on this website should be indexed. * * @return bool `true` when the links on this website should be indexed. */ public function is_met() { $should_index_links = $this->options_helper->get( 'enable_text_link_counter' ); /** * Filter: 'wpseo_should_index_links' - Allows disabling of Yoast's links indexation. * * @param bool $enable To disable the indexation, return false. */ return \apply_filters( 'wpseo_should_index_links', $should_index_links ); } } conditionals/user-can-manage-wpseo-options-conditional.php 0000644 00000000772 15025657560 0020054 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when the current user has the `wpseo_manage_options` capability. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class User_Can_Manage_Wpseo_Options_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return \current_user_can( 'wpseo_manage_options' ); } } conditionals/yoast-admin-and-dashboard-conditional.php 0000644 00000003170 15025657560 0017172 0 ustar 00 <?php // phpcs:ignore Yoast.Files.FileName.InvalidClassFileName -- Reason: this explicitly concerns the Yoast admin and dashboard. namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when in the admin dashboard, update or Yoast SEO pages. */ class Yoast_Admin_And_Dashboard_Conditional implements Conditional { /** * Returns `true` when on the admin dashboard, update or Yoast SEO pages. * * @return bool `true` when on the admin dashboard, update or Yoast SEO pages. */ public function is_met() { global $pagenow; // Bail out early if we're not on the front-end. if ( ! \is_admin() ) { return false; } // Do not output on plugin / theme upgrade pages or when WordPress is upgrading. if ( ( \defined( 'IFRAME_REQUEST' ) && \IFRAME_REQUEST ) || $this->on_upgrade_page() || \wp_installing() ) { return false; } if ( $pagenow === 'admin.php' && isset( $_GET['page'] ) && \strpos( $_GET['page'], 'wpseo' ) === 0 ) { return true; } $target_pages = [ 'index.php', 'plugins.php', 'update-core.php', 'options-permalink.php', ]; return \in_array( $pagenow, $target_pages, true ); } /** * Checks if we are on a theme or plugin upgrade page. * * @return bool Whether we are on a theme or plugin upgrade page. */ private function on_upgrade_page() { /* * IFRAME_REQUEST is not defined on these pages, * though these action pages do show when upgrading themes or plugins. */ $actions = [ 'do-theme-upgrade', 'do-plugin-upgrade', 'do-core-upgrade', 'do-core-reinstall' ]; return isset( $_GET['action'] ) && \in_array( $_GET['action'], $actions, true ); } } conditionals/migrations-conditional.php 0000644 00000001457 15025657560 0014442 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Config\Migration_Status; /** * Class for integrations that depend on having all migrations run. */ class Migrations_Conditional implements Conditional { /** * The migration status. * * @var Migration_Status */ protected $migration_status; /** * Migrations_Conditional constructor. * * @param Migration_Status $migration_status The migration status object. */ public function __construct( Migration_Status $migration_status ) { $this->migration_status = $migration_status; } /** * Returns `true` when all database migrations have been run. * * @return bool `true` when all database migrations have been run. */ public function is_met() { return $this->migration_status->is_version( 'free', \WPSEO_VERSION ); } } conditionals/deactivating-yoast-seo-conditional.php 0000644 00000001302 15025657560 0016636 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when on deactivating Yoast SEO. */ class Deactivating_Yoast_Seo_Conditional implements Conditional { /** * Returns whether this conditional is met. * * @return bool Whether the conditional is met. */ public function is_met() { // phpcs:ignore WordPress.Security.NonceVerification -- We can't verify nonce since this might run from any user. if ( isset( $_GET['action'] ) && \sanitize_text_field( \wp_unslash( $_GET['action'] ) ) === 'deactivate' && isset( $_GET['plugin'] ) && \sanitize_text_field( \wp_unslash( $_GET['plugin'] === 'wordpress-seo/wp-seo.php' ) ) ) { return true; } return false; } } conditionals/premium-active-conditional.php 0000644 00000000546 15025657560 0015213 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Class Premium_Active_Conditional. */ class Premium_Active_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return \YoastSEO()->helpers->product->is_premium(); } } conditionals/updated-importer-framework-conditional.php 0000644 00000000637 15025657560 0017545 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Feature flag conditional for the updated importer framework. */ class Updated_Importer_Framework_Conditional extends Feature_Flag_Conditional { /** * Returns the name of the updated importer framework feature flag. * * @return string The name of the feature flag. */ protected function get_feature_flag() { return 'UPDATED_IMPORTER_FRAMEWORK'; } } conditionals/admin/licenses-page-conditional.php 0000644 00000001212 15025657560 0016062 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Admin; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when current page is the tools page. */ class Licenses_Page_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { global $pagenow; if ( $pagenow !== 'admin.php' ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification -- This is not a form. if ( isset( $_GET['page'] ) && $_GET['page'] === 'wpseo_licenses' ) { return true; } return false; } } conditionals/admin/posts-overview-or-ajax-conditional.php 0000644 00000000746 15025657560 0017731 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Admin; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when on a post overview page or during an ajax request. */ class Posts_Overview_Or_Ajax_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { global $pagenow; return $pagenow === 'edit.php' || \wp_doing_ajax(); } } conditionals/admin/yoast-admin-conditional.php 0000644 00000002034 15025657560 0015573 0 ustar 00 <?php // phpcs:ignore Yoast.Files.FileName.InvalidClassFileName -- Reason: this explicitly concerns the Yoast admin. namespace Yoast\WP\SEO\Conditionals\Admin; use Yoast\WP\SEO\Conditionals\Conditional; use Yoast\WP\SEO\Helpers\Current_Page_Helper; /** * Conditional that is only met when on a Yoast SEO admin page. */ class Yoast_Admin_Conditional implements Conditional { /** * Holds the Current_Page_Helper. * * @var Current_Page_Helper */ private $current_page_helper; /** * Constructs the conditional. * * @param Current_Page_Helper $current_page_helper The current page helper. */ public function __construct( Current_Page_Helper $current_page_helper ) { $this->current_page_helper = $current_page_helper; } /** * Returns `true` when on the admin dashboard, update or Yoast SEO pages. * * @return bool `true` when on the admin dashboard, update or Yoast SEO pages. */ public function is_met() { if ( ! \is_admin() ) { return false; } return $this->current_page_helper->is_yoast_seo_page(); } } conditionals/admin/non-network-admin-conditional.php 0000644 00000000642 15025657560 0016720 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Admin; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when not on a network admin page. */ class Non_Network_Admin_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return ! \is_network_admin(); } } conditionals/admin/estimated-reading-time-conditional.php 0000644 00000003710 15025657560 0017672 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Admin; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only when we want the Estimated Reading Time. */ class Estimated_Reading_Time_Conditional implements Conditional { /** * The Post Conditional. * * @var Post_Conditional */ protected $post_conditional; /** * Constructs the Estimated Reading Time Conditional. * * @param Post_Conditional $post_conditional The post conditional. */ public function __construct( Post_Conditional $post_conditional ) { $this->post_conditional = $post_conditional; } /** * Returns whether this conditional is met. * * @return bool Whether the conditional is met. */ public function is_met() { // phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- Reason: Nonce verification should not be done in a conditional but rather in the classes using the conditional. // Check if we are in our Elementor ajax request (for saving). if ( \wp_doing_ajax() && isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are only strictly comparing the variable. $post_action = \wp_unslash( $_POST['action'] ); if ( $post_action === 'wpseo_elementor_save' ) { return true; } } if ( ! $this->post_conditional->is_met() ) { return false; } // We don't support Estimated Reading Time on the attachment post type. if ( isset( $_GET['post'] ) && \is_string( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are casting to an integer. $post_id = (int) \wp_unslash( $_GET['post'] ); if ( $post_id !== 0 && \get_post_type( $post_id ) === 'attachment' ) { return false; } } return true; // phpcs:enable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing } } conditionals/admin/post-conditional.php 0000644 00000001340 15025657560 0014332 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Admin; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when on a post edit or new post page. */ class Post_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { global $pagenow; // Current page is the creation of a new post (type, i.e. post, page, custom post or attachment). if ( $pagenow === 'post-new.php' ) { return true; } // Current page is the edit page of an existing post (type, i.e. post, page, custom post or attachment). if ( $pagenow === 'post.php' ) { return true; } return false; } } conditionals/admin/doing-post-quick-edit-save-conditional.php 0000644 00000001771 15025657560 0020431 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Admin; use Yoast\WP\SEO\Conditionals\Conditional; /** * Checks if the post is saved by inline-save. This is the case when doing quick edit. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded -- Base class can't be written shorter without abbreviating. */ class Doing_Post_Quick_Edit_Save_Conditional implements Conditional { /** * Checks if the current request is ajax and the action is inline-save. * * @return bool True when the quick edit action is executed. */ public function is_met() { if ( ! \wp_doing_ajax() ) { return false; } // Do the same nonce check as is done in wp_ajax_inline_save because we hook into that request. if ( ! \check_ajax_referer( 'inlineeditnonce', '_inline_edit', false ) ) { return false; } if ( ! isset( $_POST['action'] ) ) { return false; } $sanitized_action = \sanitize_text_field( \wp_unslash( $_POST['action'] ) ); return ( $sanitized_action === 'inline-save' ); } } conditionals/headless-rest-endpoints-enabled-conditional.php 0000644 00000001457 15025657560 0020422 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Conditional that is only met when the headless rest endpoints are enabled. */ class Headless_Rest_Endpoints_Enabled_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ private $options; /** * Headless_Rest_Endpoints_Enabled_Conditional constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns `true` whether the headless REST endpoints have been enabled. * * @return bool `true` when the headless REST endpoints have been enabled. */ public function is_met() { return $this->options->get( 'enable_headless_rest_endpoints' ); } } conditionals/wp-robots-conditional.php 0000644 00000000525 15025657560 0014215 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Class that checks if wp_robots exists. */ class WP_Robots_Conditional implements Conditional { /** * Checks if the wp_robots function exists. * * @return bool True when the wp_robots function exists. */ public function is_met() { return \function_exists( 'wp_robots' ); } } conditionals/premium-inactive-conditional.php 0000644 00000000611 15025657560 0015533 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Abstract class for creating conditionals based on feature flags. */ class Premium_Inactive_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return ! \YoastSEO()->helpers->product->is_premium(); } } conditionals/conditional-interface.php 0000644 00000000452 15025657560 0014220 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional interface, used to prevent integrations from loading. */ interface Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met(); } conditionals/xmlrpc-conditional.php 0000644 00000000667 15025657560 0013575 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is met when the current request is an XML-RPC request. */ class XMLRPC_Conditional implements Conditional { /** * Returns whether the current request is an XML-RPC request. * * @return bool `true` when the current request is an XML-RPC request, `false` if not. */ public function is_met() { return ( \defined( 'XMLRPC_REQUEST' ) && \XMLRPC_REQUEST ); } } conditionals/new-settings-ui-conditional.php 0000644 00000000543 15025657560 0015323 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Feature flag conditional for the new settings UI. */ class New_Settings_Ui_Conditional extends Feature_Flag_Conditional { /** * Returns the name of the feature flag. * * @return string The name of the feature flag. */ protected function get_feature_flag() { return 'NEW_SETTINGS_UI'; } } conditionals/no-conditionals-trait.php 0000644 00000000575 15025657560 0014206 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Trait for integrations that do not have any conditionals. */ trait No_Conditionals { /** * Returns an empty array, meaning no conditionals are required to load whatever uses this trait. * * @return array The conditionals that must be met to load this. */ public static function get_conditionals() { return []; } } conditionals/not-admin-ajax-conditional.php 0000644 00000000556 15025657560 0015074 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when not in a admin-ajax request. */ class Not_Admin_Ajax_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return ( ! \wp_doing_ajax() ); } } conditionals/wp-cron-enabled-conditional.php 0000644 00000000537 15025657560 0015241 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Class that checks if WP_CRON is enabled. */ class WP_CRON_Enabled_Conditional implements Conditional { /** * Checks if WP_CRON is enabled. * * @return bool True when WP_CRON is enabled. */ public function is_met() { return ! ( \defined( 'DISABLE_WP_CRON' ) && \DISABLE_WP_CRON ); } } conditionals/jetpack-conditional.php 0000644 00000000637 15025657560 0013706 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when Jetpack exists. */ class Jetpack_Conditional implements Conditional { /** * Returns `true` when the Jetpack plugin exists on this * WordPress installation. * * @return bool `true` when the Jetpack plugin exists on this WordPress installation. */ public function is_met() { return \class_exists( 'Jetpack' ); } } conditionals/web-stories-conditional.php 0000644 00000000670 15025657560 0014525 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when Web Stories are active. */ class Web_Stories_Conditional implements Conditional { /** * Returns `true` when the Web Stories plugins is installed and active. * * @return bool `true` when the Web Stories plugins is installed and active. */ public function is_met() { return \function_exists( '\Google\Web_Stories\get_plugin_instance' ); } } conditionals/wincher-automatically-track-conditional.php 0000644 00000001367 15025657560 0017675 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Conditional that is only met when the Wincher automatic tracking is enabled. */ class Wincher_Automatically_Track_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ private $options; /** * Wincher_Automatically_Track_Conditional constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns whether this conditional is met. * * @return bool Whether the conditional is met. */ public function is_met() { return $this->options->get( 'wincher_automatically_add_keyphrases' ); } } conditionals/check-required-version-conditional.php 0000644 00000000570 15025657560 0016637 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional for the CHECK_REQUIRED_VERSION feature flag. */ class Check_Required_Version_Conditional extends Feature_Flag_Conditional { /** * Returns the name of the feature flag. * * @return string The name of the feature flag. */ protected function get_feature_flag() { return 'CHECK_REQUIRED_VERSION'; } } conditionals/open-graph-conditional.php 0000644 00000001330 15025657560 0014314 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Conditional that is only met when the Open Graph feature is enabled. */ class Open_Graph_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ private $options; /** * Open_Graph_Conditional constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns `true` when the Open Graph feature is enabled. * * @return bool `true` when the Open Graph feature is enabled. */ public function is_met() { return $this->options->get( 'opengraph' ) === true; } } conditionals/user-can-publish-posts-and-pages-conditional.php 0000644 00000001036 15025657560 0020443 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when the current user has the `wpseo_manage_options` capability. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class User_Can_Publish_Posts_And_Pages_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return \current_user_can( 'publish_posts' ) && \current_user_can( 'publish_pages' ); } } conditionals/import-tool-selected-conditional.php 0000644 00000001050 15025657560 0016326 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when current page is not a specific tool's page. */ class Import_Tool_Selected_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We just check whether a URL parameter does not exist. return ( isset( $_GET['tool'] ) && $_GET['tool'] === 'import-export' ); } } conditionals/user-profile-conditional.php 0000644 00000001041 15025657560 0014667 0 ustar 00 <?php // phpcs:ignore Yoast.Files.FileName.InvalidClassFileName -- Reason: this explicitly concerns the Yoast tools page. namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when current page is the tools page. */ class User_Profile_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { global $pagenow; if ( $pagenow !== 'profile.php' ) { return false; } return true; } } conditionals/settings-conditional.php 0000644 00000001740 15025657560 0014121 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Class Settings_Conditional. */ class Settings_Conditional implements Conditional { /** * Holds User_Can_Manage_Wpseo_Options_Conditional. * * @var User_Can_Manage_Wpseo_Options_Conditional */ protected $user_can_manage_wpseo_options_conditional; /** * Constructs Settings_Conditional. * * @param User_Can_Manage_Wpseo_Options_Conditional $user_can_manage_wpseo_options_conditional The User_Can_Manage_Wpseo_Options_Conditional. */ public function __construct( User_Can_Manage_Wpseo_Options_Conditional $user_can_manage_wpseo_options_conditional ) { $this->user_can_manage_wpseo_options_conditional = $user_can_manage_wpseo_options_conditional; } /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { if ( ! $this->user_can_manage_wpseo_options_conditional->is_met() ) { return false; } return true; } } conditionals/user-edit-conditional.php 0000644 00000000751 15025657560 0014163 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when current page is the Edit User page or the User's profile page. */ class User_Edit_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { global $pagenow; if ( $pagenow !== 'profile.php' && $pagenow !== 'user-edit.php' ) { return false; } return true; } } conditionals/traits/admin-conditional-trait.php 0000644 00000000733 15025657560 0016001 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Traits; use Yoast\WP\SEO\Conditionals\Admin_Conditional; /** * Trait for all integration that rely on the Admin-conditional */ trait Admin_Conditional_Trait { /** * Returns an empty array, meaning no conditionals are required to load whatever uses this trait. * * @return array The conditionals that must be met to load this. */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } } conditionals/attachment-redirections-enabled-conditional.php 0000644 00000001605 15025657560 0020471 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Conditional that is only met when the 'Redirect attachment URLs to the attachment itself' setting is enabled. */ class Attachment_Redirections_Enabled_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ private $options; /** * Attachment_Redirections_Enabled_Conditional constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns whether the 'Redirect attachment URLs to the attachment itself' setting has been enabled. * * @return bool `true` when the 'Redirect attachment URLs to the attachment itself' setting has been enabled. */ public function is_met() { return $this->options->get( 'disable-attachment' ); } } conditionals/addon-installation-conditional.php 0000644 00000000666 15025657560 0016053 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Checks if the Addon_Installation constant is set. */ class Addon_Installation_Conditional extends Feature_Flag_Conditional { /** * Returns the name of the feature flag. * 'YOAST_SEO_' is automatically prepended to it and it will be uppercased. * * @return string the name of the feature flag. */ protected function get_feature_flag() { return 'ADDON_INSTALLATION'; } } conditionals/robots-txt-conditional.php 0000644 00000003312 15025657560 0014403 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when on the front end or Yoast file editor page. */ class Robots_Txt_Conditional implements Conditional { /** * Holds the Front_End_Conditional instance. * * @var Front_End_Conditional */ protected $front_end_conditional; /** * Constructs the class. * * @param Front_End_Conditional $front_end_conditional The front end conditional. */ public function __construct( Front_End_Conditional $front_end_conditional ) { $this->front_end_conditional = $front_end_conditional; } /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return $this->front_end_conditional->is_met() || $this->is_file_editor_page(); } /** * Returns whether the current page is the file editor page. * * This checks for two locations: * - Multisite network admin file editor page * - Single site file editor page (under tools) * * @return bool */ protected function is_file_editor_page() { global $pagenow; if ( $pagenow !== 'admin.php' ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification -- This is not a form. if ( isset( $_GET['page'] ) && $_GET['page'] === 'wpseo_files' && \is_multisite() && \is_network_admin() ) { return true; } // phpcs:ignore WordPress.Security.NonceVerification -- This is not a form. if ( ! ( isset( $_GET['page'] ) && $_GET['page'] === 'wpseo_tools' ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification -- This is not a form. if ( isset( $_GET['tool'] ) && $_GET['tool'] === 'file-editor' ) { return true; } return false; } } conditionals/woocommerce-conditional.php 0000644 00000000634 15025657560 0014601 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when WooCommerce is active. */ class WooCommerce_Conditional implements Conditional { /** * Returns `true` when the WooCommerce plugin is installed and activated. * * @return bool `true` when the WooCommerce plugin is installed and activated. */ public function is_met() { return \class_exists( 'WooCommerce' ); } } conditionals/third-party/wpml-conditional.php 0000644 00000000627 15025657560 0015512 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when WPML is active. */ class WPML_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return \defined( 'WPML_PLUGIN_FILE' ); } } conditionals/third-party/elementor-activated-conditional.php 0000644 00000000745 15025657560 0020470 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is met when the Elementor plugin is installed and activated. */ class Elementor_Activated_Conditional implements Conditional { /** * Checks if the Elementor plugins is installed and activated. * * @return bool `true` when the Elementor plugin is installed and activated. */ public function is_met() { return \defined( 'ELEMENTOR__FILE__' ); } } conditionals/third-party/translatepress-conditional.php 0000644 00000000710 15025657560 0017576 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when the TranslatePress plugin is active. */ class TranslatePress_Conditional implements Conditional { /** * Checks whether the TranslatePress plugin is active. * * @return bool Whether the TranslatePress plugin is active. */ public function is_met() { return \class_exists( 'TRP_Translate_Press' ); } } conditionals/third-party/polylang-conditional.php 0000644 00000000666 15025657560 0016363 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when the Polylang plugin is active. */ class Polylang_Conditional implements Conditional { /** * Checks whether the Polylang plugin is installed and active. * * @return bool Whether Polylang is installed and active. */ public function is_met() { return \defined( 'POLYLANG_FILE' ); } } conditionals/third-party/wpml-wpseo-conditional.php 0000644 00000001310 15025657560 0016633 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is met when the Yoast SEO Multilingual plugin, * a glue plugin developed by and for WPML, is active. */ class WPML_WPSEO_Conditional implements Conditional { /** * Path to the Yoast SEO Multilingual plugin file. * * @internal */ public const PATH_TO_WPML_WPSEO_PLUGIN_FILE = 'wp-seo-multilingual/plugin.php'; /** * Returns whether or not the Yoast SEO Multilingual plugin is active. * * @return bool Whether or not the Yoast SEO Multilingual plugin is active. */ public function is_met() { return \is_plugin_active( self::PATH_TO_WPML_WPSEO_PLUGIN_FILE ); } } conditionals/third-party/site-kit-conditional.php 0000644 00000001375 15025657560 0016265 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; /** * Conditional that is only met when the SiteKit plugin is active. */ class Site_Kit_Conditional implements Conditional { /** * Site Kit configuration * * @var Site_Kit $site_kit */ protected $site_kit; /** * The constructor. * * @param Site_Kit $site_kit The Site Kit configuration object. */ public function __construct( Site_Kit $site_kit ) { $this->site_kit = $site_kit; } /** * Checks whether the SiteKit plugin is active. * * @return bool Whether the SiteKit plugin is active. */ public function is_met() { return $this->site_kit->is_enabled(); } } conditionals/third-party/elementor-edit-conditional.php 0000644 00000004560 15025657560 0017450 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when on an Elementor edit page or when the current * request is an ajax request for saving our post meta data. */ class Elementor_Edit_Conditional implements Conditional { /** * Returns whether this conditional is met. * * @return bool Whether the conditional is met. */ public function is_met() { global $pagenow; // Editing a post/page in Elementor. if ( $pagenow === 'post.php' && $this->is_elementor_get_action() ) { return true; } // Request for us saving a post/page in Elementor (submits our form via AJAX). return \wp_doing_ajax() && $this->is_yoast_save_post_action(); } /** * Checks if the current request' GET action is 'elementor'. * * @return bool True when the GET action is 'elementor'. */ private function is_elementor_get_action(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( ! isset( $_GET['action'] ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( ! \is_string( $_GET['action'] ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, we are only strictly comparing. return \wp_unslash( $_GET['action'] ) === 'elementor'; } /** * Checks if the current request' POST action is 'wpseo_elementor_save'. * * @return bool True when the POST action is 'wpseo_elementor_save'. */ private function is_yoast_save_post_action(): bool { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( ! isset( $_POST['action'] ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( ! \is_string( $_POST['action'] ) ) { return false; } // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, we are only strictly comparing. return \wp_unslash( $_POST['action'] ) === 'wpseo_elementor_save'; } } conditionals/third-party/w3-total-cache-conditional.php 0000644 00000000747 15025657560 0017251 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals\Third_Party; use Yoast\WP\SEO\Conditionals\Conditional; /** * Conditional that is only met when in the admin. */ class W3_Total_Cache_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { if ( ! \defined( 'W3TC_DIR' ) ) { return false; } return \function_exists( 'w3tc_objectcache_flush' ); } } conditionals/wincher-token-conditional.php 0000644 00000001250 15025657560 0015032 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Config\Wincher_Client; /** * Conditional that is only met when the Wincher token is set. */ class Wincher_Token_Conditional implements Conditional { /** * The Wincher client. * * @var Wincher_Client */ private $client; /** * Wincher_Token_Conditional constructor. * * @param Wincher_Client $client The Wincher client. */ public function __construct( Wincher_Client $client ) { $this->client = $client; } /** * Returns whether this conditional is met. * * @return bool Whether the conditional is met. */ public function is_met() { return $this->client->has_valid_tokens(); } } conditionals/primary-category-conditional.php 0000644 00000003254 15025657560 0015561 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Current_Page_Helper; /** * Conditional that is only met when in frontend or page is a post overview or post add/edit form. */ class Primary_Category_Conditional implements Conditional { /** * The current page helper. * * @var Current_Page_Helper */ private $current_page; /** * Primary_Category_Conditional constructor. * * @param Current_Page_Helper $current_page The current page helper. */ public function __construct( Current_Page_Helper $current_page ) { $this->current_page = $current_page; } /** * Returns `true` when on the frontend, * or when on the post overview, post edit or new post admin page, * or when on additional admin pages, allowed by filter. * * @return bool `true` when on the frontend, or when on the post overview, * post edit, new post admin page or additional admin pages, allowed by filter. */ public function is_met() { if ( ! \is_admin() ) { return true; } $current_page = $this->current_page->get_current_admin_page(); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( $current_page === 'admin-ajax.php' && isset( $_POST['action'] ) && $_POST['action'] === 'wp-link-ajax' ) { return true; } /** * Filter: Adds the possibility to use primary category at additional admin pages. * * @param array $admin_pages List of additional admin pages. */ $additional_pages = \apply_filters( 'wpseo_primary_category_admin_pages', [] ); return \in_array( $current_page, \array_merge( [ 'edit.php', 'post.php', 'post-new.php' ], $additional_pages ), true ); } } conditionals/semrush-enabled-conditional.php 0000644 00000001343 15025657560 0015336 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Conditional that is only met when the SEMrush integration is enabled. */ class SEMrush_Enabled_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ private $options; /** * SEMrush_Enabled_Conditional constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return $this->options->get( 'semrush_integration_active', false ); } } conditionals/development-conditional.php 0000644 00000000601 15025657560 0014576 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use WPSEO_Utils; /** * Conditional that is only met when in development mode. */ class Development_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return WPSEO_Utils::is_development_mode(); } } conditionals/wincher-enabled-conditional.php 0000644 00000001343 15025657560 0015307 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Conditional that is only met when the Wincher integration is enabled. */ class Wincher_Enabled_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ private $options; /** * Wincher_Enabled_Conditional constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return $this->options->get( 'wincher_integration_active', false ); } } conditionals/feature-flag-conditional.php 0000644 00000001617 15025657560 0014626 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Abstract class for creating conditionals based on feature flags. */ abstract class Feature_Flag_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { $feature_flag = \strtoupper( $this->get_feature_flag() ); return \defined( 'YOAST_SEO_' . $feature_flag ) && \constant( 'YOAST_SEO_' . $feature_flag ) === true; } /** * Returns the name of the feature flag. * 'YOAST_SEO_' is automatically prepended to it and it will be uppercased. * * @return string the name of the feature flag. */ abstract protected function get_feature_flag(); /** * Returns the feature name. * * @return string the name of the feature flag. */ public function get_feature_name() { return $this->get_feature_flag(); } } conditionals/news-conditional.php 0000644 00000000550 15025657560 0013233 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when news SEO is activated. */ class News_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return \defined( 'WPSEO_NEWS_VERSION' ); } } conditionals/admin-conditional.php 0000644 00000000513 15025657560 0013346 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when in the admin. */ class Admin_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return \is_admin(); } } conditionals/text-formality-conditional.php 0000644 00000000661 15025657560 0015252 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Checks if the YOAST_SEO_TEXT_FORMALITY constant is set. */ class Text_Formality_Conditional extends Feature_Flag_Conditional { /** * Returns the name of the feature flag. * 'YOAST_SEO_' is automatically prepended to it and it will be uppercased. * * @return string the name of the feature flag. */ public function get_feature_flag() { return 'TEXT_FORMALITY'; } } conditionals/non-multisite-conditional.php 0000644 00000000564 15025657560 0015073 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when we aren't in a multisite setup. */ class Non_Multisite_Conditional implements Conditional { /** * Returns `true` when we aren't in a multisite setup. * * @return bool `true` when we aren't in a multisite setup. */ public function is_met() { return ! \is_multisite(); } } conditionals/get-request-conditional.php 0000644 00000000722 15025657560 0014525 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when the current request uses the GET method. */ class Get_Request_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { if ( isset( $_SERVER['REQUEST_METHOD'] ) && $_SERVER['REQUEST_METHOD'] === 'GET' ) { return true; } return false; } } conditionals/user-can-edit-users-conditional.php 0000644 00000000734 15025657560 0016062 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when the current user has the `edit_users` capability. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class User_Can_Edit_Users_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { return \current_user_can( 'edit_users' ); } } conditionals/wincher-conditional.php 0000644 00000000240 15025657560 0013712 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional for the Wincher integration. */ class Wincher_Conditional extends Non_Multisite_Conditional {} conditionals/google-site-kit-feature-conditional.php 0000644 00000001314 15025657560 0016712 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Conditional for the Google Site Kit feature. */ class Google_Site_Kit_Feature_Conditional implements Conditional { /** * The options helper. * * @var Options_Helper */ private $options; /** * The constructor. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Returns `true` when the Site Kit feature is enabled. * * @return bool `true` when the Site Kit feature is enabled. */ public function is_met() { return $this->options->get( 'google_site_kit_feature_enabled' ) === true; } } conditionals/yoast-tools-page-conditional.php 0000644 00000001176 15025657560 0015473 0 ustar 00 <?php // phpcs:ignore Yoast.Files.FileName.InvalidClassFileName -- Reason: this explicitly concerns the Yoast tools page. namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when current page is the tools page. */ class Yoast_Tools_Page_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { global $pagenow; if ( $pagenow !== 'admin.php' ) { return false; } if ( isset( $_GET['page'] ) && $_GET['page'] === 'wpseo_tools' ) { return true; } return false; } } conditionals/front-end-conditional.php 0000644 00000000512 15025657560 0014151 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when NOT in the admin. */ class Front_End_Conditional implements Conditional { /** * Returns `true` when NOT on an admin page. * * @return bool `true` when NOT on an admin page. */ public function is_met() { return ! \is_admin(); } } conditionals/no-tool-selected-conditional.php 0000644 00000000775 15025657560 0015445 0 ustar 00 <?php namespace Yoast\WP\SEO\Conditionals; /** * Conditional that is only met when current page is not a specific tool's page. */ class No_Tool_Selected_Conditional implements Conditional { /** * Returns whether or not this conditional is met. * * @return bool Whether or not the conditional is met. */ public function is_met() { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- We just check whether a URL parameter does not exist. return ! isset( $_GET['tool'] ); } } llms-txt/domain/file/llms-txt-permission-gate-interface.php 0000644 00000000700 15025657560 0020065 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Domain\File; /** * This interface is responsible for defining ways to make sure we can edit/regenerate the llms.txt file. */ interface Llms_Txt_Permission_Gate_Interface { /** * Checks if Yoast SEO manages the llms.txt. * * @return bool Checks if Yoast SEO manages the llms.txt. */ public function is_managed_by_yoast_seo(): bool; } llms-txt/domain/file/llms-file-system-interface.php 0000644 00000001571 15025657560 0016412 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Domain\File; /** * Interface to describe handeling the llms.txt file. */ interface Llms_File_System_Interface { /** * Method to set the llms.txt file content. * * @param string $content The content for the file. * * @return bool True on success, false on failure. */ public function set_file_content( string $content ): bool; /** * Method to remove the llms.txt file from the file system. * * @return bool True on success, false on failure. */ public function remove_file(): bool; /** * Gets the contents of the current llms.txt file. * * @return string */ public function get_file_contents(): string; /** * Checks if the llms.txt file exists. * * @return bool Whether the llms.txt file exists. */ public function file_exists(): bool; } llms-txt/domain/markdown/llms-txt-renderer.php 0000644 00000002227 15025657560 0015540 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Section_Interface; /** * The renderer of the LLMs.txt file. */ class Llms_Txt_Renderer { /** * The sections. * * @var Section_Interface[] */ private $sections; /** * Adds a section. * * @param Section_Interface $section The section to add. * * @return void */ public function add_section( Section_Interface $section ): void { $this->sections[] = $section; } /** * Returns the sections. * * @return Section_Interface[] */ public function get_sections(): array { return $this->sections; } /** * Renders the items of the bucket. * * @return string */ public function render(): string { if ( empty( $this->sections ) ) { return ''; } $rendered_sections = []; foreach ( $this->sections as $section ) { $section_content = $section->render(); if ( $section_content === '' ) { continue; } $rendered_sections[] = $section->get_prefix() . $section_content . \PHP_EOL; } return \implode( \PHP_EOL, $rendered_sections ); } } llms-txt/domain/markdown/items/link.php 0000644 00000002216 15025657560 0014224 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper; /** * Represents a link markdown item. */ class Link implements Item_Interface { /** * The link text. * * @var string */ private $text; /** * The anchor text. * * @var string */ private $anchor; /** * Class constructor. * * @param string $text The link text. * @param string $anchor The anchor text. */ public function __construct( string $text, string $anchor ) { $this->text = $text; $this->anchor = $anchor; } /** * Renders the link item. * * @return string */ public function render(): string { return "[$this->text]($this->anchor)"; } /** * Escapes the markdown content. * * @param param Markdown_Escaper $markdown_escaper The markdown escaper. * * @return void */ public function escape_markdown( Markdown_Escaper $markdown_escaper ): void { $this->text = $markdown_escaper->escape_markdown_content( $this->text ); $this->anchor = $markdown_escaper->escape_markdown_url( $this->anchor ); } } llms-txt/domain/markdown/items/item-interface.php 0000644 00000001060 15025657560 0016157 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper; /** * Represents a markdown item. */ interface Item_Interface { /** * Renders the item. * * @return string */ public function render(): string; /** * Escapes the markdown content. * * @param Markdown_Escaper $markdown_escaper The markdown escaper. * * @return void */ public function escape_markdown( Markdown_Escaper $markdown_escaper ): void; } llms-txt/domain/markdown/sections/description.php 0000644 00000002154 15025657560 0016321 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper; /** * Represents the description section. */ class Description implements Section_Interface { /** * The description. * * @var string */ private $description; /** * Class constructor. * * @param string $description The description. */ public function __construct( string $description ) { $this->description = $description; } /** * Returns the prefix of the description section. * * @return string */ public function get_prefix(): string { return '> '; } /** * Renders the description section. * * @return string */ public function render(): string { return $this->description; } /** * Escapes the markdown content. * * @param Markdown_Escaper $markdown_escaper The markdown escaper. * * @return void */ public function escape_markdown( Markdown_Escaper $markdown_escaper ): void { $this->description = $markdown_escaper->escape_markdown_content( $this->description ); } } llms-txt/domain/markdown/sections/link-list.php 0000644 00000003421 15025657560 0015702 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link; /** * Represents a link list markdown section. */ class Link_List implements Section_Interface { /** * The type of the links. * * @var string */ private $type; /** * The links. * * @var Link[] */ private $links = []; /** * Class constructor. * * @param string $type The type of the links. * @param Link[] $links The links. */ public function __construct( string $type, array $links ) { $this->type = $type; foreach ( $links as $link ) { $this->add_link( $link ); } } /** * Adds a link to the list. * * @param Link $link The link to add. * * @return void */ public function add_link( Link $link ): void { $this->links[] = $link; } /** * Returns the prefix of the link list section. * * @return string */ public function get_prefix(): string { return '## '; } /** * Renders the link item. * * @return string */ public function render(): string { if ( empty( $this->links ) ) { return ''; } $rendered_links = []; foreach ( $this->links as $link ) { $rendered_links[] = '- ' . $link->render(); } return $this->type . \PHP_EOL . \implode( \PHP_EOL, $rendered_links ); } /** * Escapes the markdown content. * * @param Markdown_Escaper $markdown_escaper The markdown escaper. * * @return void */ public function escape_markdown( Markdown_Escaper $markdown_escaper ): void { $this->type = $markdown_escaper->escape_markdown_content( $this->type ); foreach ( $this->links as $link ) { $link->escape_markdown( $markdown_escaper ); } } } llms-txt/domain/markdown/sections/section-interface.php 0000644 00000000615 15025657560 0017400 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Item_Interface; /** * Represents a section. */ interface Section_Interface extends Item_Interface { /** * Returns the prefix of the section. * * @return string */ public function get_prefix(): string; } llms-txt/domain/markdown/sections/intro.php 0000644 00000003570 15025657560 0015134 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link; /** * Represents the intro section. */ class Intro implements Section_Interface { /** * The intro content. * * @var string */ private $intro_content; /** * The intro links. * * @var Link[] */ private $intro_links = []; /** * Class constructor. * * @param string $intro_content The intro content. * @param Link[] $intro_links The intro links. */ public function __construct( string $intro_content, array $intro_links ) { $this->intro_content = $intro_content; foreach ( $intro_links as $link ) { $this->add_link( $link ); } } /** * Returns the prefix of the intro section. * * @return string */ public function get_prefix(): string { return ''; } /** * Adds a link to the intro section. * * @param Link $link The link to add. * * @return void */ public function add_link( Link $link ): void { $this->intro_links[] = $link; } /** * Returns the content of the intro section. * * @return string */ public function render(): string { if ( \count( $this->intro_links ) === 0 ) { return $this->intro_content; } $rendered_links = \array_map( static function ( $link ) { return $link->render(); }, $this->intro_links ); $this->intro_content = \sprintf( $this->intro_content, ...$rendered_links ); return $this->intro_content; } /** * Escapes the markdown content. * * @param Markdown_Escaper $markdown_escaper The markdown escaper. * * @return void */ public function escape_markdown( Markdown_Escaper $markdown_escaper ): void { foreach ( $this->intro_links as $link ) { $link->escape_markdown( $markdown_escaper ); } } } llms-txt/domain/markdown/sections/title.php 0000644 00000003016 15025657560 0015115 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper; /** * Represents the title section. */ class Title implements Section_Interface { /** * The site title. * * @var string */ private $site_title; /** * The site tagline. * * @var string */ private $site_tagline; /** * Class constructor. * * @param string $site_title The site title. * @param string $site_tagline The site tagline. */ public function __construct( string $site_title, string $site_tagline ) { $this->site_title = $site_title; $this->site_tagline = $site_tagline; } /** * Returns the prefix of the section. * * @return string */ public function get_prefix(): string { return '# '; } /** * Renders the title section. * * @return string */ public function render(): string { if ( $this->site_tagline === '' ) { return $this->site_title; } if ( $this->site_title === '' ) { return $this->site_tagline; } return "$this->site_title: $this->site_tagline"; } /** * Escapes the markdown content. * * @param Markdown_Escaper $markdown_escaper The markdown escaper. * * @return void */ public function escape_markdown( Markdown_Escaper $markdown_escaper ): void { $this->site_title = $markdown_escaper->escape_markdown_content( $this->site_title ); $this->site_tagline = $markdown_escaper->escape_markdown_content( $this->site_tagline ); } } llms-txt/application/markdown-builders/markdown-builder.php 0000644 00000005146 15025657560 0020264 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Escaper; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Llms_Txt_Renderer; /** * The builder of the markdown file. */ class Markdown_Builder { /** * The renderer of the LLMs.txt file. * * @var Llms_Txt_Renderer */ protected $llms_txt_renderer; /** * The intro builder. * * @var Intro_Builder */ protected $intro_builder; /** * The title builder. * * @var Title_Builder */ protected $title_builder; /** * The description builder. * * @var Description_Builder */ protected $description_builder; /** * The link lists builder. * * @var Link_Lists_Builder */ protected $link_lists_builder; /** * The markdown escaper. * * @var Markdown_Escaper */ protected $markdown_escaper; /** * The constructor. * * @param Llms_Txt_Renderer $llms_txt_renderer The renderer of the LLMs.txt file. * @param Intro_Builder $intro_builder The intro builder. * @param Title_Builder $title_builder The title builder. * @param Description_Builder $description_builder The description builder. * @param Link_Lists_Builder $link_lists_builder The link lists builder. * @param Markdown_Escaper $markdown_escaper The markdown escaper. */ public function __construct( Llms_Txt_Renderer $llms_txt_renderer, Intro_Builder $intro_builder, Title_Builder $title_builder, Description_Builder $description_builder, Link_Lists_Builder $link_lists_builder, Markdown_Escaper $markdown_escaper ) { $this->llms_txt_renderer = $llms_txt_renderer; $this->intro_builder = $intro_builder; $this->title_builder = $title_builder; $this->description_builder = $description_builder; $this->link_lists_builder = $link_lists_builder; $this->markdown_escaper = $markdown_escaper; } /** * Renders the markdown. * * @return string The rendered markdown. */ public function render(): string { $this->llms_txt_renderer->add_section( $this->intro_builder->build_intro() ); $this->llms_txt_renderer->add_section( $this->title_builder->build_title() ); $this->llms_txt_renderer->add_section( $this->description_builder->build_description() ); foreach ( $this->link_lists_builder->build_link_lists() as $link_list ) { $this->llms_txt_renderer->add_section( $link_list ); } foreach ( $this->llms_txt_renderer->get_sections() as $section ) { $section->escape_markdown( $this->markdown_escaper ); } return $this->llms_txt_renderer->render(); } } llms-txt/application/markdown-builders/link-lists-builder.php 0000644 00000002537 15025657560 0020534 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Link_List; use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Content_Types_Collector; use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Terms_Collector; /** * The builder of the link list sections. */ class Link_Lists_Builder { /** * The content types collector. * * @var Content_Types_Collector */ private $content_types_collector; /** * The terms collector. * * @var Terms_Collector */ private $terms_collector; /** * Constructs the class. * * @param Content_Types_Collector $content_types_collector The content types collector. * @param Terms_Collector $terms_collector The terms collector. */ public function __construct( Content_Types_Collector $content_types_collector, Terms_Collector $terms_collector ) { $this->content_types_collector = $content_types_collector; $this->terms_collector = $terms_collector; } /** * Builds the link list sections. * * @return Link_List[] The link list sections. */ public function build_link_lists(): array { return \array_merge( $this->content_types_collector->get_content_types_lists(), $this->terms_collector->get_terms_lists() ); } } llms-txt/application/markdown-builders/title-builder.php 0000644 00000001436 15025657560 0017561 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Title; use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Title_Adapter; /** * The builder of the title section. */ class Title_Builder { /** * The title adapter. * * @var Title_Adapter */ protected $title_adapter; /** * The constructor. * * @param Title_Adapter $title_adapter The title adapter. */ public function __construct( Title_Adapter $title_adapter ) { $this->title_adapter = $title_adapter; } /** * Builds the title section. * * @return Title The title section. */ public function build_title(): Title { return $this->title_adapter->get_title(); } } llms-txt/application/markdown-builders/intro-builder.php 0000644 00000003002 15025657560 0017562 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Intro; use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Sitemap_Link_Collector; /** * The builder of the intro section. */ class Intro_Builder { /** * The sitemap link collector. * * @var Sitemap_Link_Collector */ protected $sitemap_link_collector; /** * The constructor. * * @param Sitemap_Link_Collector $sitemap_link_collector The sitemap link collector. */ public function __construct( Sitemap_Link_Collector $sitemap_link_collector ) { $this->sitemap_link_collector = $sitemap_link_collector; } /** * Gets the plugin version that generated the llms.txt file. * * @return string The plugin version that generated the llms.txt file. */ protected function get_generator_version(): string { return 'Yoast SEO v' . \WPSEO_VERSION; } /** * Builds the intro section. * * @return Intro The intro section. */ public function build_intro(): Intro { $intro_content = \sprintf( 'Generated by %s, this is an llms.txt file, meant for consumption by LLMs.', $this->get_generator_version() ); $intro_links = []; $sitemap_link = $this->sitemap_link_collector->get_link(); if ( $sitemap_link !== null ) { $intro_links[] = $sitemap_link; $intro_content .= \PHP_EOL . \PHP_EOL . 'This is the %s of this website.'; } return new Intro( $intro_content, $intro_links ); } } llms-txt/application/markdown-builders/description-builder.php 0000644 00000001635 15025657560 0020764 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Description; use Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services\Description_Adapter; /** * The builder of the description section. */ class Description_Builder { /** * The description adapter. * * @var Description_Adapter */ protected $description_adapter; /** * Class constructor. * * @param Description_Adapter $description_adapter The description adapter. */ public function __construct( Description_Adapter $description_adapter ) { $this->description_adapter = $description_adapter; } /** * Builds the description section. * * @return Description The description section. */ public function build_description(): Description { return $this->description_adapter->get_description(); } } llms-txt/application/file/commands/populate-file-command-handler.php 0000644 00000005134 15025657560 0021677 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\File\Commands; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Llms_Txt\Application\Markdown_Builders\Markdown_Builder; use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter; use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_Llms_Txt_Permission_Gate; /** * Handles the population of the llms.txt. */ class Populate_File_Command_Handler { public const CONTENT_HASH_OPTION = 'wpseo_llms_txt_content_hash'; public const GENERATION_FAILURE_OPTION = 'wpseo_llms_txt_file_failure'; /** * The permission gate. * * @var WordPress_Llms_Txt_Permission_Gate $permission_gate */ private $permission_gate; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The file system adapter. * * @var WordPress_File_System_Adapter */ private $file_system_adapter; /** * The markdown builder. * * @var Markdown_Builder */ private $markdown_builder; /** * Constructor. * * @param Options_Helper $options_helper The options helper. * @param WordPress_File_System_Adapter $file_system_adapter The file system adapter. * @param Markdown_Builder $markdown_builder The markdown builder. * @param WordPress_Llms_Txt_Permission_Gate $permission_gate The editing permission checker. */ public function __construct( Options_Helper $options_helper, WordPress_File_System_Adapter $file_system_adapter, Markdown_Builder $markdown_builder, WordPress_Llms_Txt_Permission_Gate $permission_gate ) { $this->options_helper = $options_helper; $this->file_system_adapter = $file_system_adapter; $this->markdown_builder = $markdown_builder; $this->permission_gate = $permission_gate; } /** * Runs the command. * * @return void */ public function handle() { if ( $this->permission_gate->is_managed_by_yoast_seo() ) { $content = $this->markdown_builder->render(); $file_written = $this->file_system_adapter->set_file_content( $content ); if ( $file_written ) { // Maybe move this to a class if we need to handle this option more often. \update_option( self::CONTENT_HASH_OPTION, \md5( $content ) ); \delete_option( self::GENERATION_FAILURE_OPTION ); return; } \update_option( self::GENERATION_FAILURE_OPTION, 'filesystem_permissions' ); return; } \update_option( self::GENERATION_FAILURE_OPTION, 'not_managed_by_yoast_seo' ); } } llms-txt/application/file/commands/remove-file-command-handler.php 0000644 00000003463 15025657560 0021346 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\File\Commands; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_File_System_Adapter; use Yoast\WP\SEO\Llms_Txt\Infrastructure\File\WordPress_Llms_Txt_Permission_Gate; /** * Handles the removal of the llms.txt */ class Remove_File_Command_Handler { /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The file system adapter. * * @var WordPress_File_System_Adapter */ private $file_system_adapter; /** * The permission gate. * * @var WordPress_Llms_Txt_Permission_Gate $permission_gate */ private $permission_gate; /** * Constructor. * * @param Options_Helper $options_helper The options helper. * @param WordPress_File_System_Adapter $file_system_adapter The file system adapter. * @param WordPress_Llms_Txt_Permission_Gate $permission_gate The permission gate. */ public function __construct( Options_Helper $options_helper, WordPress_File_System_Adapter $file_system_adapter, WordPress_Llms_Txt_Permission_Gate $permission_gate ) { $this->options_helper = $options_helper; $this->file_system_adapter = $file_system_adapter; $this->permission_gate = $permission_gate; } /** * Runs the command. * * @return void */ public function handle() { if ( $this->permission_gate->is_managed_by_yoast_seo() ) { $file_removed = $this->file_system_adapter->remove_file(); if ( $file_removed ) { // Maybe move this to a class if we need to handle this option more often. \update_option( Populate_File_Command_Handler::CONTENT_HASH_OPTION, '' ); } } } } llms-txt/application/file/llms-txt-cron-scheduler.php 0000644 00000003437 15025657560 0017004 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\File; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Responsible for scheduling and unscheduling the cron. */ class Llms_Txt_Cron_Scheduler { /** * The name of the cron job. */ public const LLMS_TXT_POPULATION = 'wpseo_llms_txt_population'; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * Constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Schedules the llms txt population cron a week from now. * * @return void */ public function schedule_weekly_llms_txt_population(): void { if ( $this->options_helper->get( 'enable_llms_txt', false ) !== true ) { return; } if ( ! \wp_next_scheduled( self::LLMS_TXT_POPULATION ) ) { \wp_schedule_event( ( \time() + \WEEK_IN_SECONDS ), 'weekly', self::LLMS_TXT_POPULATION ); } } /** * Schedules the llms txt population cron 5 minutes from now. * * @return void */ public function schedule_quick_llms_txt_population(): void { if ( $this->options_helper->get( 'enable_llms_txt', false ) !== true ) { return; } if ( \wp_next_scheduled( self::LLMS_TXT_POPULATION ) ) { $this->unschedule_llms_txt_population(); } \wp_schedule_event( ( \time() + ( \MINUTE_IN_SECONDS * 5 ) ), 'weekly', self::LLMS_TXT_POPULATION ); } /** * Unschedules the llms txt population cron. * * @return void */ public function unschedule_llms_txt_population() { $scheduled = \wp_next_scheduled( self::LLMS_TXT_POPULATION ); if ( $scheduled ) { \wp_unschedule_event( $scheduled, self::LLMS_TXT_POPULATION ); } } } llms-txt/application/health-check/file-check.php 0000644 00000003637 15025657560 0015702 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\Health_Check; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Llms_Txt\User_Interface\Health_Check\File_Reports; use Yoast\WP\SEO\Services\Health_Check\Health_Check; /** * Fails when the llms.txt file fails to be generated. */ class File_Check extends Health_Check { /** * Runs the health check. * * @var File_Runner */ private $runner; /** * Generates WordPress-friendly health check results. * * @var File_Reports */ private $reports; /** * The Options_Helper instance. * * @var Options_Helper */ private $options_helper; /** * Constructor. * * @param File_Runner $runner The object that implements the actual health check. * @param File_Reports $reports The object that generates WordPress-friendly results. * @param Options_Helper $options_helper The options helper. */ public function __construct( File_Runner $runner, File_Reports $reports, Options_Helper $options_helper ) { $this->runner = $runner; $this->reports = $reports; $this->options_helper = $options_helper; $this->reports->set_test_identifier( $this->get_test_identifier() ); $this->set_runner( $this->runner ); } /** * Returns the WordPress-friendly health check result. * * @return string[] The WordPress-friendly health check result. */ protected function get_result() { if ( $this->runner->is_successful() ) { return $this->reports->get_success_result(); } return $this->reports->get_generation_failure_result( $this->runner->get_generation_failure_reason() ); } /** * Returns true when the llms.txt feature is disabled. * * @return bool Whether the health check should be excluded from the results. */ public function is_excluded() { return $this->options_helper->get( 'enable_llms_txt', false ) !== true; } } llms-txt/application/health-check/file-runner.php 0000644 00000002255 15025657560 0016131 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application\Health_Check; use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler; use Yoast\WP\SEO\Services\Health_Check\Runner_Interface; /** * Runs the File_Generation health check. */ class File_Runner implements Runner_Interface { /** * Is set to non-empty string when the llms.txt file failed to (re-)generate. * * @var bool */ private $generation_failure_reason = ''; /** * Runs the health check. * * @return void */ public function run() { $this->generation_failure_reason = \get_option( Populate_File_Command_Handler::GENERATION_FAILURE_OPTION, '' ); } /** * Returns true if there is no generation failure reason. * * @return bool The boolean indicating if the health check was succesful. */ public function is_successful() { return $this->generation_failure_reason === ''; } /** * Returns the generation failure reason. * * @return string The boolean indicating if the health check was succesful. */ public function get_generation_failure_reason(): string { return $this->generation_failure_reason; } } llms-txt/application/markdown-escaper.php 0000644 00000002152 15025657560 0014621 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Application; /** * The escaper of markdown. */ class Markdown_Escaper { /** * Escapes markdown text. * * @param string $text The markdown text to escape. * * @return string The escaped markdown text. */ public function escape_markdown_content( $text ) { // We have to decode the text first mostly because ampersands will be escaped below. $text = \html_entity_decode( $text, \ENT_QUOTES, 'UTF-8' ); // Define a regex pattern for all the special characters in markdown that we want to escape. $pattern = '/[-#*+`._[\]()!&<>_{}|]/'; $replacement = static function ( $matches ) { return '\\' . $matches[0]; }; return \preg_replace_callback( $pattern, $replacement, $text ); } /** * Escapes URLs in markdown. * * @param string $url The markdown URL to escape. * * @return string The escaped markdown URL. */ public function escape_markdown_url( $url ) { $escaped_url = \str_replace( [ ' ', '(', ')', '\\' ], [ '%20', '%28', '%29', '%5C' ], $url ); return $escaped_url; } } llms-txt/infrastructure/file/wordpress-llms-txt-permission-gate.php 0000644 00000003633 15025657560 0021776 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\File; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler; use Yoast\WP\SEO\Llms_Txt\Domain\File\Llms_Txt_Permission_Gate_Interface; /** * Handles checks to see if we manage the llms.txt file. */ class WordPress_Llms_Txt_Permission_Gate implements Llms_Txt_Permission_Gate_Interface { /** * The file system adapter. * * @var WordPress_File_System_Adapter */ private $file_system_adapter; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * Constructor. * * @param WordPress_File_System_Adapter $file_system_adapter The file system adapter. * @param Options_Helper $options_helper The options helper. */ public function __construct( WordPress_File_System_Adapter $file_system_adapter, Options_Helper $options_helper ) { $this->file_system_adapter = $file_system_adapter; $this->options_helper = $options_helper; } /** * Checks if Yoast SEO manages the llms.txt. * * @return bool Checks if Yoast SEO manages the llms.txt. */ public function is_managed_by_yoast_seo(): bool { $stored_hash = \get_option( Populate_File_Command_Handler::CONTENT_HASH_OPTION, '' ); // If the file does not exist yet, we always regenerate/create it. if ( ! $this->file_system_adapter->file_exists() ) { return true; } // This means the file is already there (maybe hand made or another plugin created it). And since we don't have a hash it's not ours. if ( $stored_hash === '' ) { return false; } $current_content = $this->file_system_adapter->get_file_contents(); // If you have a hash, we want to make sure it's the same. This check makes sure the file is not edited by the user. return \md5( $current_content ) === $stored_hash; } } llms-txt/infrastructure/file/wordpress-file-system-adapter.php 0000644 00000004371 15025657560 0020765 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\File; use Yoast\WP\SEO\Llms_Txt\Domain\File\Llms_File_System_Interface; /** * Adapter class for handling file system operations in a WordPress environment. */ class WordPress_File_System_Adapter implements Llms_File_System_Interface { /** * Creates a file and writes the specified content to it. * * @param string $content The content to write into the file. * * @return bool True on success, false on failure. */ public function set_file_content( string $content ): bool { if ( $this->is_file_system_available() ) { global $wp_filesystem; $result = $wp_filesystem->put_contents( $this->get_llms_file_path(), $content, \FS_CHMOD_FILE ); return $result; } return false; } /** * Removes the llms.txt from the filesystem. * * @return bool True on success, false on failure. */ public function remove_file(): bool { if ( $this->is_file_system_available() ) { global $wp_filesystem; $result = $wp_filesystem->delete( $this->get_llms_file_path() ); return $result; } return false; } /** * Gets the contents of the current llms.txt file. * * @return string The content of the file. */ public function get_file_contents(): string { if ( $this->is_file_system_available() ) { global $wp_filesystem; return $wp_filesystem->get_contents( $this->get_llms_file_path() ); } return ''; } /** * Checks if the llms.txt file exists. * * @return bool Whether the llms.txt file exists. */ public function file_exists(): bool { if ( $this->is_file_system_available() ) { global $wp_filesystem; return $wp_filesystem->exists( $this->get_llms_file_path() ); } return false; } /** * Checks if the file system is available. * * @return bool If the file system is available. */ private function is_file_system_available(): ?bool { if ( ! \function_exists( 'WP_Filesystem' ) ) { require_once \ABSPATH . 'wp-admin/includes/file.php'; } return \WP_Filesystem(); } /** * Creates the path to the llms.txt file. * * @return string */ private function get_llms_file_path(): string { return \trailingslashit( \get_home_path() ) . 'llms.txt'; } } llms-txt/infrastructure/markdown-services/title-adapter.php 0000644 00000001761 15025657560 0020343 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Title; use Yoast\WP\SEO\Services\Health_Check\Default_Tagline_Runner; /** * The adapter of the title. */ class Title_Adapter { /** * The default tagline runner. * * @var Default_Tagline_Runner */ private $default_tagline_runner; /** * Class constructor. * * @param Default_Tagline_Runner $default_tagline_runner The default tagline runner. */ public function __construct( Default_Tagline_Runner $default_tagline_runner ) { $this->default_tagline_runner = $default_tagline_runner; } /** * Gets the title. * * @return Title The title. */ public function get_title(): Title { $this->default_tagline_runner->run(); $tagline = ( $this->default_tagline_runner->is_successful() ? \get_bloginfo( 'description' ) : '' ); return new Title( \get_bloginfo( 'name' ), $tagline ); } } llms-txt/infrastructure/markdown-services/description-adapter.php 0000644 00000002131 15025657560 0021535 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Description; use Yoast\WP\SEO\Surfaces\Meta_Surface; /** * The adapter of the description. */ class Description_Adapter { /** * Holds the meta helper surface. * * @var Meta_Surface */ private $meta; /** * Class constructor. * * @param Meta_Surface $meta The meta surface. */ public function __construct( Meta_Surface $meta ) { $this->meta = $meta; } /** * Gets the description. * * @return Description The description. */ public function get_description(): Description { $meta_description = $this->meta->for_home_page()->meta_description; // In a lot of cases, the homepage's meta description falls back to the site's tagline. // But that is already used for the title section, so let's try to not have duplicate content. if ( $meta_description === \get_bloginfo( 'description' ) ) { return new Description( '' ); } return new Description( $meta_description ); } } llms-txt/infrastructure/markdown-services/content-types-collector.php 0000644 00000004324 15025657560 0022402 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Link_List; /** * The collector of content types. * * @TODO: This class could maybe be unified with Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector. */ class Content_Types_Collector { /** * The post type helper. * * @var Post_Type_Helper */ private $post_type_helper; /** * The constructor. * * @param Post_Type_Helper $post_type_helper The post type helper. */ public function __construct( Post_Type_Helper $post_type_helper ) { $this->post_type_helper = $post_type_helper; } /** * Returns the content types in a link list. * * @return Link_List[] The content types in a link list. */ public function get_content_types_lists(): array { $post_types = $this->post_type_helper->get_indexable_post_type_objects(); $link_list = []; foreach ( $post_types as $post_type_object ) { if ( $this->post_type_helper->is_indexable( $post_type_object->name ) === false ) { continue; } $posts = $this->get_relevant_posts( $post_type_object ); $post_links = new Link_List( $post_type_object->label, [] ); foreach ( $posts as $post ) { $post_link = new Link( $post->post_title, \get_permalink( $post->ID ) ); $post_links->add_link( $post_link ); } $link_list[] = $post_links; } return $link_list; } /** * Gets the posts that are relevant for the LLMs.txt. * * @param WP_Post_Type $post_type_object The post type object. * * @return WP_Post[] The posts that are relevant for the LLMs.txt. */ public function get_relevant_posts( $post_type_object ): array { $args = [ 'post_type' => $post_type_object->name, 'posts_per_page' => 5, 'post_status' => 'publish', 'orderby' => 'modified', 'order' => 'DESC', 'has_password' => false, ]; if ( $post_type_object->name === 'post' ) { $args['date_query'] = [ [ 'after' => '12 months ago', ], ]; } return \get_posts( $args ); } } llms-txt/infrastructure/markdown-services/sitemap-link-collector.php 0000644 00000001407 15025657560 0022162 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services; use WPSEO_Options; use WPSEO_Sitemaps_Router; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link; /** * The sitemap link collector. */ class Sitemap_Link_Collector { /** * Gets the link for the sitemap. * * @return Link The link for the sitemap. */ public function get_link(): ?Link { if ( WPSEO_Options::get( 'enable_xml_sitemap' ) ) { $sitemap_url = WPSEO_Sitemaps_Router::get_base_url( 'sitemap_index.xml' ); return new Link( 'sitemap', $sitemap_url ); } $sitemap_url = \get_sitemap_url( 'index' ); if ( $sitemap_url !== false ) { return new Link( 'sitemap', $sitemap_url ); } return null; } } llms-txt/infrastructure/markdown-services/terms-collector.php 0000644 00000002737 15025657560 0020726 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\Infrastructure\Markdown_Services; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Items\Link; use Yoast\WP\SEO\Llms_Txt\Domain\Markdown\Sections\Link_List; /** * The collector of terms. */ class Terms_Collector { /** * The taxonomy helper. * * @var Taxonomy_Helper */ private $taxonomy_helper; /** * The constructor. * * @param Taxonomy_Helper $taxonomy_helper The taxonomy helper. */ public function __construct( Taxonomy_Helper $taxonomy_helper ) { $this->taxonomy_helper = $taxonomy_helper; } /** * Returns the content types in a link list. * * @return Link_List[] The content types in a link list. */ public function get_terms_lists(): array { $taxonomies = $this->taxonomy_helper->get_indexable_taxonomy_objects(); $link_list = []; foreach ( $taxonomies as $taxonomy ) { if ( $this->taxonomy_helper->is_indexable( $taxonomy->name ) === false ) { continue; } $terms = \get_categories( [ 'taxonomy' => $taxonomy->name, 'number' => 5, 'orderby' => 'count', 'order' => 'DESC', ] ); $term_links = new Link_List( $taxonomy->label, [] ); foreach ( $terms as $term ) { $term_link = new Link( $term->name, \get_term_link( $term, $taxonomy->name ) ); $term_links->add_link( $term_link ); } $link_list[] = $term_links; } return $link_list; } } llms-txt/user-interface/enable-llms-txt-option-watcher.php 0000644 00000005302 15025657560 0017741 0 ustar 00 <?php namespace Yoast\WP\SEO\Llms_Txt\User_Interface; use Yoast\WP\SEO\Conditionals\Traits\Admin_Conditional_Trait; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler; use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler; use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler; /** * Watches and handles changes to the LLMS.txt enabled option. */ class Enable_Llms_Txt_Option_Watcher implements Integration_Interface { use Admin_Conditional_Trait; /** * The scheduler. * * @var Llms_Txt_Cron_Scheduler */ private $scheduler; /** * The remove file command handler. * * @var Remove_File_Command_Handler */ private $remove_file_command_handler; /** * The populate file command handler. * * @var Populate_File_Command_Handler */ private $populate_file_command_handler; /** * Constructor. * * @param Llms_Txt_Cron_Scheduler $scheduler The cron scheduler. * @param Remove_File_Command_Handler $remove_file_command_handler The remove file command handler. * @param Populate_File_Command_Handler $populate_file_command_handler The populate file command handler. */ public function __construct( Llms_Txt_Cron_Scheduler $scheduler, Remove_File_Command_Handler $remove_file_command_handler, Populate_File_Command_Handler $populate_file_command_handler ) { $this->scheduler = $scheduler; $this->remove_file_command_handler = $remove_file_command_handler; $this->populate_file_command_handler = $populate_file_command_handler; } /** * Initializes the integration. * * This is the place to register hooks and filters. * * @return void */ public function register_hooks() { \add_action( 'update_option_wpseo', [ $this, 'check_toggle_llms_txt' ], 10, 2 ); } /** * Checks if the LLMS.txt feature is toggled. * * @param array<string|int|bool|array<string|int|bool>> $old_value The old value of the option. * @param array<string|int|bool|array<string|int|bool>> $new_value The new value of the option. * * @return void */ public function check_toggle_llms_txt( $old_value, $new_value ): void { $option_name = 'enable_llms_txt'; if ( \array_key_exists( $option_name, $old_value ) && \array_key_exists( $option_name, $new_value ) && $old_value[ $option_name ] !== $new_value[ $option_name ] ) { if ( $new_value[ $option_name ] === true ) { $this->scheduler->schedule_weekly_llms_txt_population(); $this->populate_file_command_handler->handle(); } else { $this->scheduler->unschedule_llms_txt_population(); $this->remove_file_command_handler->handle(); } } } } llms-txt/user-interface/cleanup-llms-txt-on-deactivation.php 0000644 00000002767 15025657560 0020277 0 ustar 00 <?php namespace Yoast\WP\SEO\Llms_Txt\User_Interface; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler; use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler; /** * Trys to clean up the llms.txt file when the plugin is deactivated. */ class Cleanup_Llms_Txt_On_Deactivation implements Integration_Interface { use No_Conditionals; /** * The command handler. * * @var Remove_File_Command_Handler */ private $command_handler; /** * The cron scheduler. * * @var Llms_Txt_Cron_Scheduler */ private $cron_scheduler; /** * Constructor. * * @param Remove_File_Command_Handler $command_handler The command handler. * @param Llms_Txt_Cron_Scheduler $cron_scheduler The scheduler. */ public function __construct( Remove_File_Command_Handler $command_handler, Llms_Txt_Cron_Scheduler $cron_scheduler ) { $this->command_handler = $command_handler; $this->cron_scheduler = $cron_scheduler; } /** * Registers the unscheduling of the cron to the deactivation action. * * @return void */ public function register_hooks() { \add_action( 'wpseo_deactivate', [ $this, 'maybe_remove_llms_file' ] ); } /** * Call the command handler to remove the file. * * @return void */ public function maybe_remove_llms_file(): void { $this->command_handler->handle(); $this->cron_scheduler->unschedule_llms_txt_population(); } } llms-txt/user-interface/llms-txt-cron-callback-integration.php 0000644 00000005101 15025657560 0020563 0 ustar 00 <?php namespace Yoast\WP\SEO\Llms_Txt\User_Interface; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Populate_File_Command_Handler; use Yoast\WP\SEO\Llms_Txt\Application\File\Commands\Remove_File_Command_Handler; use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler; /** * Cron Callback integration. This handles the actual process of populating the llms.txt on a cron trigger. */ class Llms_Txt_Cron_Callback_Integration implements Integration_Interface { use No_Conditionals; /** * The remove file command handler. * * @var Remove_File_Command_Handler */ private $remove_file_command_handler; /** * The Create Populate Command Handler. * * @var Populate_File_Command_Handler */ private $populate_file_command_handler; /** * The options helper. * * @var Options_Helper */ private $options_helper; /** * The scheduler. * * @var Llms_Txt_Cron_Scheduler */ private $scheduler; /** * Constructor. * * @param Options_Helper $options_helper The options helper. * @param Llms_Txt_Cron_Scheduler $scheduler The scheduler. * @param Populate_File_Command_Handler $populate_file_command_handler The populate file command handler. * @param Remove_File_Command_Handler $remove_file_command_handler The remove file command handler. */ public function __construct( Options_Helper $options_helper, Llms_Txt_Cron_Scheduler $scheduler, Populate_File_Command_Handler $populate_file_command_handler, Remove_File_Command_Handler $remove_file_command_handler ) { $this->options_helper = $options_helper; $this->scheduler = $scheduler; $this->populate_file_command_handler = $populate_file_command_handler; $this->remove_file_command_handler = $remove_file_command_handler; } /** * Registers the hooks with WordPress. * * @return void */ public function register_hooks() { \add_action( Llms_Txt_Cron_Scheduler::LLMS_TXT_POPULATION, [ $this, 'populate_file', ] ); } /** * Populates and creates the file. * * @return void */ public function populate_file(): void { if ( ! \wp_doing_cron() ) { return; } if ( $this->options_helper->get( 'enable_llms_txt', false ) !== true ) { $this->scheduler->unschedule_llms_txt_population(); $this->remove_file_command_handler->handle(); return; } $this->populate_file_command_handler->handle(); } } llms-txt/user-interface/health-check/file-reports.php 0000644 00000007213 15025657560 0016726 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Llms_Txt\User_Interface\Health_Check; use Yoast\WP\SEO\Services\Health_Check\Report_Builder_Factory; use Yoast\WP\SEO\Services\Health_Check\Reports_Trait; /** * Presents a set of different messages for the File_Generation health check. */ class File_Reports { use Reports_Trait; /** * Constructor * * @param Report_Builder_Factory $report_builder_factory The factory for result builder objects. * This class uses the report builder to generate WordPress-friendly * health check results. */ public function __construct( Report_Builder_Factory $report_builder_factory ) { $this->report_builder_factory = $report_builder_factory; } /** * Returns the message for a successful health check. * * @return string[] The message as a WordPress site status report. */ public function get_success_result() { $label = \sprintf( /* translators: %s: Yoast SEO. */ \__( 'Your llms.txt file is auto-generated by %s', 'wordpress-seo' ), 'Yoast SEO', ); $description = \sprintf( /* translators: %s: Yoast SEO. */ \__( '%s keeps your llms.txt file up-to-date. This helps LLMs access and provide your site\'s information more easily.', 'wordpress-seo' ), 'Yoast SEO', ); return $this->get_report_builder() ->set_label( $label ) ->set_status_good() ->set_description( $description ) ->build(); } /** * Returns the message for a failed health check. In this case, when the llms.txt file couldn't be auto-generated. * * @param string $reason The reason why the llms.txt file couldn't be auto-generated. * * @return string[] The message as a WordPress site status report. */ public function get_generation_failure_result( $reason ) { switch ( $reason ) { case 'not_managed_by_yoast_seo': $title = \__( 'Your llms.txt file couldn\'t be auto-generated', 'wordpress-seo' ); $message = \sprintf( /* translators: 1,3,5: expand to opening paragraph tag, 2,4,6: expand to opening paragraph tag. */ \__( '%1$sYou have activated the Yoast llms.txt feature, but we couldn\'t generate an llms.txt file.%2$s%3$sIt looks like there is an llms.txt file already that wasn\'t created by Yoast, or the llms.txt file created by Yoast has been edited manually.%4$s%5$sWe don\'t want to overwrite this file\'s content, so if you want to let Yoast keep auto-generating the llms.txt file, you can manually delete the existing one. Otherwise, consider disabling the Yoast feature.%6$s', 'wordpress-seo' ), '<p>', '</p>', '<p>', '</p>', '<p>', '</p>' ); break; case 'filesystem_permissions': $title = \__( 'Your llms.txt file couldn\'t be auto-generated', 'wordpress-seo' ); $message = \sprintf( /* translators: 1,3: expand to opening paragraph tag, 2,4: expand to opening paragraph tag. */ \__( '%1$sYou have activated the Yoast llms.txt feature, but we couldn\'t generate an llms.txt file.%2$s%3$sIt looks like there aren\'t sufficient permissions on the web server\'s filesystem.%4$s', 'wordpress-seo' ), '<p>', '</p>', '<p>', '</p>' ); break; default: $title = \__( 'Your llms.txt file couldn\'t be auto-generated', 'wordpress-seo' ); $message = \__( 'You have activated the Yoast llms.txt feature, but we couldn\'t generate an llms.txt file, for unknown reasons.', 'wordpress-seo' ); break; } return $this->get_report_builder() ->set_label( $title ) ->set_status_recommended() ->set_description( $message ) ->build(); } } llms-txt/user-interface/schedule-population-on-activation-integration.php 0000644 00000002733 15025657560 0023053 0 ustar 00 <?php namespace Yoast\WP\SEO\Llms_Txt\User_Interface; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast\WP\SEO\Llms_Txt\Application\File\Llms_Txt_Cron_Scheduler; /** * Handles the cron when the plugin is activated. */ class Schedule_Population_On_Activation_Integration implements Integration_Interface { use No_Conditionals; /** * The options helper. * * @var Options_Helper $options_helper */ private $options_helper; /** * The scheduler. * * @var Llms_Txt_Cron_Scheduler $scheduler */ private $scheduler; /** * The constructor. * * @param Llms_Txt_Cron_Scheduler $scheduler The cron scheduler. * @param Options_Helper $options_helper The options helper. */ public function __construct( Llms_Txt_Cron_Scheduler $scheduler, Options_Helper $options_helper ) { $this->scheduler = $scheduler; $this->options_helper = $options_helper; } /** * Registers the scheduling of the cron to the activation action. * * @return void */ public function register_hooks() { \add_action( 'wpseo_activate', [ $this, 'schedule_llms_txt_population' ] ); } /** * Schedules the cron if the option is turned on. * * @return void */ public function schedule_llms_txt_population() { if ( $this->options_helper->get( 'enable_llms_txt', false ) === true ) { $this->scheduler->schedule_quick_llms_txt_population(); } } } loggers/logger.php 0000644 00000002057 15025657560 0010215 0 ustar 00 <?php namespace Yoast\WP\SEO\Loggers; use YoastSEO_Vendor\Psr\Log\LoggerInterface; use YoastSEO_Vendor\Psr\Log\LoggerTrait; use YoastSEO_Vendor\Psr\Log\NullLogger; /** * Our logger class. */ class Logger implements LoggerInterface { use LoggerTrait; /** * The logger object. * * @var LoggerInterface */ protected $wrapped_logger; /** * Logger constructor. */ public function __construct() { $this->wrapped_logger = new NullLogger(); /** * Gives the possibility to set override the logger interface. * * @param LoggerInterface $logger Instance of NullLogger. * * @return LoggerInterface The logger object. */ $this->wrapped_logger = \apply_filters( 'wpseo_logger', $this->wrapped_logger ); } /** * Logs with an arbitrary level. * * @param mixed $level The log level. * @param string $message The log message. * @param array $context The log context. * * @return void */ public function log( $level, $message, array $context = [] ) { $this->wrapped_logger->log( $level, $message, $context ); } } actions/importing/importing-indexation-action-interface.php 0000644 00000001445 15025657560 0020325 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Importing; /** * Interface definition of reindexing action for indexables. */ interface Importing_Indexation_Action_Interface { /** * Returns the total number of unindexed objects. * * @return int The total number of unindexed objects. */ public function get_total_unindexed(); /** * Indexes a number of objects. * * NOTE: ALWAYS use limits, this method is intended to be called multiple times over several requests. * * For indexing that requires JavaScript simply return the objects that should be indexed. * * @return array The reindexed objects. */ public function index(); /** * Returns the number of objects that will be indexed in a single indexing pass. * * @return int The limit. */ public function get_limit(); } actions/importing/abstract-aioseo-importing-action.php 0000644 00000015315 15025657560 0017306 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Importing; use Exception; use Yoast\WP\SEO\Helpers\Aioseo_Helper; use Yoast\WP\SEO\Helpers\Import_Cursor_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Sanitization_Helper; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service; /** * Importing action interface. */ abstract class Abstract_Aioseo_Importing_Action implements Importing_Action_Interface { /** * The plugin the class deals with. * * @var string */ public const PLUGIN = null; /** * The type the class deals with. * * @var string */ public const TYPE = null; /** * The AIOSEO helper. * * @var Aioseo_Helper */ protected $aioseo_helper; /** * The import cursor helper. * * @var Import_Cursor_Helper */ protected $import_cursor; /** * The options helper. * * @var Options_Helper */ protected $options; /** * The sanitization helper. * * @var Sanitization_Helper */ protected $sanitization; /** * The replacevar handler. * * @var Aioseo_Replacevar_Service */ protected $replacevar_handler; /** * The robots provider service. * * @var Aioseo_Robots_Provider_Service */ protected $robots_provider; /** * The robots transformer service. * * @var Aioseo_Robots_Transformer_Service */ protected $robots_transformer; /** * Abstract_Aioseo_Importing_Action constructor. * * @param Import_Cursor_Helper $import_cursor The import cursor helper. * @param Options_Helper $options The options helper. * @param Sanitization_Helper $sanitization The sanitization helper. * @param Aioseo_Replacevar_Service $replacevar_handler The replacevar handler. * @param Aioseo_Robots_Provider_Service $robots_provider The robots provider service. * @param Aioseo_Robots_Transformer_Service $robots_transformer The robots transfomer service. */ public function __construct( Import_Cursor_Helper $import_cursor, Options_Helper $options, Sanitization_Helper $sanitization, Aioseo_Replacevar_Service $replacevar_handler, Aioseo_Robots_Provider_Service $robots_provider, Aioseo_Robots_Transformer_Service $robots_transformer ) { $this->import_cursor = $import_cursor; $this->options = $options; $this->sanitization = $sanitization; $this->replacevar_handler = $replacevar_handler; $this->robots_provider = $robots_provider; $this->robots_transformer = $robots_transformer; } /** * Sets the AIOSEO helper. * * @required * * @param Aioseo_Helper $aioseo_helper The AIOSEO helper. * * @return void */ public function set_aioseo_helper( Aioseo_Helper $aioseo_helper ) { $this->aioseo_helper = $aioseo_helper; } /** * The name of the plugin we import from. * * @return string The plugin we import from. * * @throws Exception If the PLUGIN constant is not set in the child class. */ public function get_plugin() { $class = static::class; $plugin = $class::PLUGIN; if ( $plugin === null ) { throw new Exception( 'Importing action without explicit plugin' ); } return $plugin; } /** * The data type we import from the plugin. * * @return string The data type we import from the plugin. * * @throws Exception If the TYPE constant is not set in the child class. */ public function get_type() { $class = static::class; $type = $class::TYPE; if ( $type === null ) { throw new Exception( 'Importing action without explicit type' ); } return $type; } /** * Can the current action import the data from plugin $plugin of type $type? * * @param string|null $plugin The plugin to import from. * @param string|null $type The type of data to import. * * @return bool True if this action can handle the combination of Plugin and Type. * * @throws Exception If the TYPE constant is not set in the child class. */ public function is_compatible_with( $plugin = null, $type = null ) { if ( empty( $plugin ) && empty( $type ) ) { return true; } if ( $plugin === $this->get_plugin() && empty( $type ) ) { return true; } if ( empty( $plugin ) && $type === $this->get_type() ) { return true; } if ( $plugin === $this->get_plugin() && $type === $this->get_type() ) { return true; } return false; } /** * Gets the completed id (to be used as a key for the importing_completed option). * * @return string The completed id. */ public function get_completed_id() { return $this->get_cursor_id(); } /** * Returns the stored state of completedness. * * @return int The stored state of completedness. */ public function get_completed() { $completed_id = $this->get_completed_id(); $importers_completions = $this->options->get( 'importing_completed', [] ); return ( isset( $importers_completions[ $completed_id ] ) ) ? $importers_completions[ $completed_id ] : false; } /** * Stores the current state of completedness. * * @param bool $completed Whether the importer is completed. * * @return void */ public function set_completed( $completed ) { $completed_id = $this->get_completed_id(); $current_importers_completions = $this->options->get( 'importing_completed', [] ); $current_importers_completions[ $completed_id ] = $completed; $this->options->set( 'importing_completed', $current_importers_completions ); } /** * Returns whether the importing action is enabled. * * @return bool True by default unless a child class overrides it. */ public function is_enabled() { return true; } /** * Gets the cursor id. * * @return string The cursor id. */ protected function get_cursor_id() { return $this->get_plugin() . '_' . $this->get_type(); } /** * Minimally transforms data to be imported. * * @param string $meta_data The meta data to be imported. * * @return string The transformed meta data. */ public function simple_import( $meta_data ) { // Transform the replace vars into Yoast replace vars. $transformed_data = $this->replacevar_handler->transform( $meta_data ); return $this->sanitization->sanitize_text_field( \html_entity_decode( $transformed_data ) ); } /** * Transforms URL to be imported. * * @param string $meta_data The meta data to be imported. * * @return string The transformed URL. */ public function url_import( $meta_data ) { // We put null as the allowed protocols here, to have the WP default allowed protocols, see https://developer.wordpress.org/reference/functions/wp_allowed_protocols. return $this->sanitization->sanitize_url( $meta_data, null ); } } actions/importing/aioseo/aioseo-default-archive-settings-importing-action.php 0000644 00000005610 15025657560 0023660 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; /** * Importing action for AIOSEO default archive settings data. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Aioseo_Default_Archive_Settings_Importing_Action extends Abstract_Aioseo_Settings_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'default_archive_settings'; /** * The option_name of the AIOSEO option that contains the settings. */ public const SOURCE_OPTION_NAME = 'aioseo_options'; /** * The map of aioseo_options to yoast settings. * * @var array */ protected $aioseo_options_to_yoast_map = []; /** * The tab of the aioseo settings we're working with. * * @var string */ protected $settings_tab = 'archives'; /** * Builds the mapping that ties AOISEO option keys with Yoast ones and their data transformation method. * * @return void */ protected function build_mapping() { $this->aioseo_options_to_yoast_map = [ '/author/title' => [ 'yoast_name' => 'title-author-wpseo', 'transform_method' => 'simple_import', ], '/author/metaDescription' => [ 'yoast_name' => 'metadesc-author-wpseo', 'transform_method' => 'simple_import', ], '/date/title' => [ 'yoast_name' => 'title-archive-wpseo', 'transform_method' => 'simple_import', ], '/date/metaDescription' => [ 'yoast_name' => 'metadesc-archive-wpseo', 'transform_method' => 'simple_import', ], '/search/title' => [ 'yoast_name' => 'title-search-wpseo', 'transform_method' => 'simple_import', ], '/author/advanced/robotsMeta/noindex' => [ 'yoast_name' => 'noindex-author-wpseo', 'transform_method' => 'import_noindex', 'type' => 'archives', 'subtype' => 'author', 'option_name' => 'aioseo_options', ], '/date/advanced/robotsMeta/noindex' => [ 'yoast_name' => 'noindex-archive-wpseo', 'transform_method' => 'import_noindex', 'type' => 'archives', 'subtype' => 'date', 'option_name' => 'aioseo_options', ], ]; } /** * Returns a setting map of the robot setting for author archives. * * @return array The setting map of the robot setting for author archives. */ public function pluck_robot_setting_from_mapping() { $this->build_mapping(); foreach ( $this->aioseo_options_to_yoast_map as $setting ) { // Return the first archive setting map. if ( $setting['transform_method'] === 'import_noindex' && isset( $setting['subtype'] ) && $setting['subtype'] === 'author' ) { return $setting; } } return []; } } actions/importing/aioseo/aioseo-posttype-defaults-settings-importing-action.php 0000644 00000005540 15025657560 0024313 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; /** * Importing action for AIOSEO posttype defaults settings data. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Aioseo_Posttype_Defaults_Settings_Importing_Action extends Abstract_Aioseo_Settings_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'posttype_default_settings'; /** * The option_name of the AIOSEO option that contains the settings. */ public const SOURCE_OPTION_NAME = 'aioseo_options_dynamic'; /** * The map of aioseo_options to yoast settings. * * @var array */ protected $aioseo_options_to_yoast_map = []; /** * The tab of the aioseo settings we're working with. * * @var string */ protected $settings_tab = 'postTypes'; /** * Builds the mapping that ties AOISEO option keys with Yoast ones and their data transformation method. * * @return void */ protected function build_mapping() { $post_type_objects = \get_post_types( [ 'public' => true ], 'objects' ); foreach ( $post_type_objects as $pt ) { // Use all the custom post types that are public. $this->aioseo_options_to_yoast_map[ '/' . $pt->name . '/title' ] = [ 'yoast_name' => 'title-' . $pt->name, 'transform_method' => 'simple_import', ]; $this->aioseo_options_to_yoast_map[ '/' . $pt->name . '/metaDescription' ] = [ 'yoast_name' => 'metadesc-' . $pt->name, 'transform_method' => 'simple_import', ]; $this->aioseo_options_to_yoast_map[ '/' . $pt->name . '/advanced/showMetaBox' ] = [ 'yoast_name' => 'display-metabox-pt-' . $pt->name, 'transform_method' => 'simple_boolean_import', ]; $this->aioseo_options_to_yoast_map[ '/' . $pt->name . '/advanced/robotsMeta/noindex' ] = [ 'yoast_name' => 'noindex-' . $pt->name, 'transform_method' => 'import_noindex', 'type' => 'postTypes', 'subtype' => $pt->name, 'option_name' => 'aioseo_options_dynamic', ]; if ( $pt->name === 'attachment' ) { $this->aioseo_options_to_yoast_map['/attachment/redirectAttachmentUrls'] = [ 'yoast_name' => 'disable-attachment', 'transform_method' => 'import_redirect_attachment', ]; } } } /** * Transforms the redirect_attachment setting. * * @param string $redirect_attachment The redirect_attachment setting. * * @return bool The transformed redirect_attachment setting. */ public function import_redirect_attachment( $redirect_attachment ) { switch ( $redirect_attachment ) { case 'disabled': return false; case 'attachment': case 'attachment_parent': default: return true; } } } actions/importing/aioseo/aioseo-validate-data-action.php 0000644 00000021157 15025657560 0017455 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; use wpdb; use Yoast\WP\SEO\Actions\Importing\Abstract_Aioseo_Importing_Action; use Yoast\WP\SEO\Exceptions\Importing\Aioseo_Validation_Exception; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Importing action for validating AIOSEO data before the import occurs. */ class Aioseo_Validate_Data_Action extends Abstract_Aioseo_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'validate_data'; /** * The WordPress database instance. * * @var wpdb */ protected $wpdb; /** * The Post Importing action. * * @var Aioseo_Posts_Importing_Action */ protected $post_importing_action; /** * The settings importing actions. * * @var array */ protected $settings_importing_actions; /** * Class constructor. * * @param wpdb $wpdb The WordPress database instance. * @param Options_Helper $options The options helper. * @param Aioseo_Custom_Archive_Settings_Importing_Action $custom_archive_action The Custom Archive Settings importing action. * @param Aioseo_Default_Archive_Settings_Importing_Action $default_archive_action The Default Archive Settings importing action. * @param Aioseo_General_Settings_Importing_Action $general_settings_action The General Settings importing action. * @param Aioseo_Posttype_Defaults_Settings_Importing_Action $posttype_defaults_settings_action The Posttype Defaults Settings importing action. * @param Aioseo_Taxonomy_Settings_Importing_Action $taxonomy_settings_action The Taxonomy Settings importing action. * @param Aioseo_Posts_Importing_Action $post_importing_action The Post importing action. */ public function __construct( wpdb $wpdb, Options_Helper $options, Aioseo_Custom_Archive_Settings_Importing_Action $custom_archive_action, Aioseo_Default_Archive_Settings_Importing_Action $default_archive_action, Aioseo_General_Settings_Importing_Action $general_settings_action, Aioseo_Posttype_Defaults_Settings_Importing_Action $posttype_defaults_settings_action, Aioseo_Taxonomy_Settings_Importing_Action $taxonomy_settings_action, Aioseo_Posts_Importing_Action $post_importing_action ) { $this->wpdb = $wpdb; $this->options = $options; $this->post_importing_action = $post_importing_action; $this->settings_importing_actions = [ $custom_archive_action, $default_archive_action, $general_settings_action, $posttype_defaults_settings_action, $taxonomy_settings_action, ]; } /** * Just checks if the action has been completed in the past. * * @return int 1 if it hasn't been completed in the past, 0 if it has. */ public function get_total_unindexed() { return ( ! $this->get_completed() ) ? 1 : 0; } /** * Just checks if the action has been completed in the past. * * @param int $limit The maximum number of unimported objects to be returned. Not used, exists to comply with the interface. * * @return int 1 if it hasn't been completed in the past, 0 if it has. */ public function get_limited_unindexed_count( $limit ) { return ( ! $this->get_completed() ) ? 1 : 0; } /** * Validates AIOSEO data. * * @return array An array of validated data or false if aioseo data did not pass validation. * * @throws Aioseo_Validation_Exception If the validation fails. */ public function index() { if ( $this->get_completed() ) { return []; } $validated_aioseo_table = $this->validate_aioseo_table(); $validated_aioseo_settings = $this->validate_aioseo_settings(); $validated_robot_settings = $this->validate_robot_settings(); if ( $validated_aioseo_table === false || $validated_aioseo_settings === false || $validated_robot_settings === false ) { throw new Aioseo_Validation_Exception(); } $this->set_completed( true ); return [ 'validated_aioseo_table' => $validated_aioseo_table, 'validated_aioseo_settings' => $validated_aioseo_settings, 'validated_robot_settings' => $validated_robot_settings, ]; } /** * Validates the AIOSEO indexable table. * * @return bool Whether the AIOSEO table exists and has the structure we expect. */ public function validate_aioseo_table() { if ( ! $this->aioseo_helper->aioseo_exists() ) { return false; } $table = $this->aioseo_helper->get_table(); $needed_data = $this->post_importing_action->get_needed_data(); $aioseo_columns = $this->wpdb->get_col( "SHOW COLUMNS FROM {$table}", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. 0 ); return $needed_data === \array_intersect( $needed_data, $aioseo_columns ); } /** * Validates the AIOSEO settings from the options table. * * @return bool Whether the AIOSEO settings from the options table exist and have the structure we expect. */ public function validate_aioseo_settings() { foreach ( $this->settings_importing_actions as $settings_import_action ) { $aioseo_settings = \json_decode( \get_option( $settings_import_action->get_source_option_name(), '' ), true ); if ( ! $settings_import_action->isset_settings_tab( $aioseo_settings ) ) { return false; } } return true; } /** * Validates the AIOSEO robots settings from the options table. * * @return bool Whether the AIOSEO robots settings from the options table exist and have the structure we expect. */ public function validate_robot_settings() { if ( $this->validate_post_robot_settings() && $this->validate_default_robot_settings() ) { return true; } return false; } /** * Validates the post AIOSEO robots settings from the options table. * * @return bool Whether the post AIOSEO robots settings from the options table exist and have the structure we expect. */ public function validate_post_robot_settings() { $post_robot_mapping = $this->post_importing_action->enhance_mapping(); // We're gonna validate against posttype robot settings only for posts, assuming the robot settings stay the same for other post types. $post_robot_mapping['subtype'] = 'post'; // Let's get both the aioseo_options and the aioseo_options_dynamic options. $aioseo_global_settings = $this->aioseo_helper->get_global_option(); $aioseo_posts_settings = \json_decode( \get_option( $post_robot_mapping['option_name'], '' ), true ); $needed_robots_data = $this->post_importing_action->get_needed_robot_data(); \array_push( $needed_robots_data, 'default', 'noindex' ); foreach ( $needed_robots_data as $robot_setting ) { // Validate against global settings. if ( ! isset( $aioseo_global_settings['searchAppearance']['advanced']['globalRobotsMeta'][ $robot_setting ] ) ) { return false; } // Validate against posttype settings. if ( ! isset( $aioseo_posts_settings['searchAppearance'][ $post_robot_mapping['type'] ][ $post_robot_mapping['subtype'] ]['advanced']['robotsMeta'][ $robot_setting ] ) ) { return false; } } return true; } /** * Validates the default AIOSEO robots settings for search appearance settings from the options table. * * @return bool Whether the AIOSEO robots settings for search appearance settings from the options table exist and have the structure we expect. */ public function validate_default_robot_settings() { foreach ( $this->settings_importing_actions as $settings_import_action ) { $robot_setting_map = $settings_import_action->pluck_robot_setting_from_mapping(); // Some actions return empty robot settings, let's not validate against those. if ( ! empty( $robot_setting_map ) ) { $aioseo_settings = \json_decode( \get_option( $robot_setting_map['option_name'], '' ), true ); if ( ! isset( $aioseo_settings['searchAppearance'][ $robot_setting_map['type'] ][ $robot_setting_map['subtype'] ]['advanced']['robotsMeta']['default'] ) ) { return false; } } } return true; } /** * Used nowhere. Exists to comply with the interface. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_aioseo_cleanup_limit' - Allow filtering the number of validations during each action pass. * * @param int $limit The maximum number of validations. */ $limit = \apply_filters( 'wpseo_aioseo_validation_limit', 25 ); if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = 25; } return $limit; } } actions/importing/aioseo/aioseo-posts-importing-action.php 0000644 00000055143 15025657560 0020135 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; use wpdb; use Yoast\WP\SEO\Actions\Importing\Abstract_Aioseo_Importing_Action; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Import_Cursor_Helper; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\Indexable_To_Postmeta_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Sanitization_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Social_Images_Provider_Service; /** * Importing action for AIOSEO post data. */ class Aioseo_Posts_Importing_Action extends Abstract_Aioseo_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'posts'; /** * The map of aioseo to yoast meta. * * @var array<string, array<string, string|bool|array<string, string|bool>>> */ protected $aioseo_to_yoast_map = [ 'title' => [ 'yoast_name' => 'title', 'transform_method' => 'simple_import_post', ], 'description' => [ 'yoast_name' => 'description', 'transform_method' => 'simple_import_post', ], 'og_title' => [ 'yoast_name' => 'open_graph_title', 'transform_method' => 'simple_import_post', ], 'og_description' => [ 'yoast_name' => 'open_graph_description', 'transform_method' => 'simple_import_post', ], 'twitter_title' => [ 'yoast_name' => 'twitter_title', 'transform_method' => 'simple_import_post', 'twitter_import' => true, ], 'twitter_description' => [ 'yoast_name' => 'twitter_description', 'transform_method' => 'simple_import_post', 'twitter_import' => true, ], 'canonical_url' => [ 'yoast_name' => 'canonical', 'transform_method' => 'url_import_post', ], 'keyphrases' => [ 'yoast_name' => 'primary_focus_keyword', 'transform_method' => 'keyphrase_import', ], 'og_image_url' => [ 'yoast_name' => 'open_graph_image', 'social_image_import' => true, 'social_setting_prefix_aioseo' => 'og_', 'social_setting_prefix_yoast' => 'open_graph_', 'transform_method' => 'social_image_url_import', ], 'twitter_image_url' => [ 'yoast_name' => 'twitter_image', 'social_image_import' => true, 'social_setting_prefix_aioseo' => 'twitter_', 'social_setting_prefix_yoast' => 'twitter_', 'transform_method' => 'social_image_url_import', ], 'robots_noindex' => [ 'yoast_name' => 'is_robots_noindex', 'transform_method' => 'post_robots_noindex_import', 'robots_import' => true, ], 'robots_nofollow' => [ 'yoast_name' => 'is_robots_nofollow', 'transform_method' => 'post_general_robots_import', 'robots_import' => true, 'robot_type' => 'nofollow', ], 'robots_noarchive' => [ 'yoast_name' => 'is_robots_noarchive', 'transform_method' => 'post_general_robots_import', 'robots_import' => true, 'robot_type' => 'noarchive', ], 'robots_nosnippet' => [ 'yoast_name' => 'is_robots_nosnippet', 'transform_method' => 'post_general_robots_import', 'robots_import' => true, 'robot_type' => 'nosnippet', ], 'robots_noimageindex' => [ 'yoast_name' => 'is_robots_noimageindex', 'transform_method' => 'post_general_robots_import', 'robots_import' => true, 'robot_type' => 'noimageindex', ], ]; /** * Represents the indexables repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * The WordPress database instance. * * @var wpdb */ protected $wpdb; /** * The image helper. * * @var Image_Helper */ protected $image; /** * The indexable_to_postmeta helper. * * @var Indexable_To_Postmeta_Helper */ protected $indexable_to_postmeta; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The social images provider service. * * @var Aioseo_Social_Images_Provider_Service */ protected $social_images_provider; /** * Class constructor. * * @param Indexable_Repository $indexable_repository The indexables repository. * @param wpdb $wpdb The WordPress database instance. * @param Import_Cursor_Helper $import_cursor The import cursor helper. * @param Indexable_Helper $indexable_helper The indexable helper. * @param Indexable_To_Postmeta_Helper $indexable_to_postmeta The indexable_to_postmeta helper. * @param Options_Helper $options The options helper. * @param Image_Helper $image The image helper. * @param Sanitization_Helper $sanitization The sanitization helper. * @param Aioseo_Replacevar_Service $replacevar_handler The replacevar handler. * @param Aioseo_Robots_Provider_Service $robots_provider The robots provider service. * @param Aioseo_Robots_Transformer_Service $robots_transformer The robots transfomer service. * @param Aioseo_Social_Images_Provider_Service $social_images_provider The social images provider service. */ public function __construct( Indexable_Repository $indexable_repository, wpdb $wpdb, Import_Cursor_Helper $import_cursor, Indexable_Helper $indexable_helper, Indexable_To_Postmeta_Helper $indexable_to_postmeta, Options_Helper $options, Image_Helper $image, Sanitization_Helper $sanitization, Aioseo_Replacevar_Service $replacevar_handler, Aioseo_Robots_Provider_Service $robots_provider, Aioseo_Robots_Transformer_Service $robots_transformer, Aioseo_Social_Images_Provider_Service $social_images_provider ) { parent::__construct( $import_cursor, $options, $sanitization, $replacevar_handler, $robots_provider, $robots_transformer ); $this->indexable_repository = $indexable_repository; $this->wpdb = $wpdb; $this->image = $image; $this->indexable_helper = $indexable_helper; $this->indexable_to_postmeta = $indexable_to_postmeta; $this->social_images_provider = $social_images_provider; } // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: They are already prepared. /** * Returns the total number of unimported objects. * * @return int The total number of unimported objects. */ public function get_total_unindexed() { if ( ! $this->aioseo_helper->aioseo_exists() ) { return 0; } $limit = false; $just_detect = true; $indexables_to_create = $this->wpdb->get_col( $this->query( $limit, $just_detect ) ); $number_of_indexables_to_create = \count( $indexables_to_create ); $completed = $number_of_indexables_to_create === 0; $this->set_completed( $completed ); return $number_of_indexables_to_create; } /** * Returns the limited number of unimported objects. * * @param int $limit The maximum number of unimported objects to be returned. * * @return int|false The limited number of unindexed posts. False if the query fails. */ public function get_limited_unindexed_count( $limit ) { if ( ! $this->aioseo_helper->aioseo_exists() ) { return 0; } $just_detect = true; $indexables_to_create = $this->wpdb->get_col( $this->query( $limit, $just_detect ) ); $number_of_indexables_to_create = \count( $indexables_to_create ); $completed = $number_of_indexables_to_create === 0; $this->set_completed( $completed ); return $number_of_indexables_to_create; } /** * Imports AIOSEO meta data and creates the respective Yoast indexables and postmeta. * * @return Indexable[]|false An array of created indexables or false if aioseo data was not found. */ public function index() { if ( ! $this->aioseo_helper->aioseo_exists() ) { return false; } $limit = $this->get_limit(); $aioseo_indexables = $this->wpdb->get_results( $this->query( $limit ), \ARRAY_A ); $created_indexables = []; $completed = \count( $aioseo_indexables ) === 0; $this->set_completed( $completed ); // Let's build the list of fields to check their defaults, to identify whether we're gonna import AIOSEO data in the indexable or not. $check_defaults_fields = []; foreach ( $this->aioseo_to_yoast_map as $yoast_mapping ) { // We don't want to check all the imported fields. if ( ! \in_array( $yoast_mapping['yoast_name'], [ 'open_graph_image', 'twitter_image' ], true ) ) { $check_defaults_fields[] = $yoast_mapping['yoast_name']; } } $last_indexed_aioseo_id = 0; foreach ( $aioseo_indexables as $aioseo_indexable ) { $last_indexed_aioseo_id = $aioseo_indexable['id']; $indexable = $this->indexable_repository->find_by_id_and_type( $aioseo_indexable['post_id'], 'post' ); // Let's ensure that the current post id represents something that we want to index (eg. *not* shop_order). if ( ! \is_a( $indexable, 'Yoast\WP\SEO\Models\Indexable' ) ) { continue; } if ( $this->indexable_helper->check_if_default_indexable( $indexable, $check_defaults_fields ) ) { $indexable = $this->map( $indexable, $aioseo_indexable ); $this->indexable_helper->save_indexable( $indexable ); // To ensure that indexables can be rebuild after a reset, we have to store the data in the postmeta table too. $this->indexable_to_postmeta->map_to_postmeta( $indexable ); } $last_indexed_aioseo_id = $aioseo_indexable['id']; $created_indexables[] = $indexable; } $cursor_id = $this->get_cursor_id(); $this->import_cursor->set_cursor( $cursor_id, $last_indexed_aioseo_id ); return $created_indexables; } // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared /** * Maps AIOSEO meta data to Yoast meta data. * * @param Indexable $indexable The Yoast indexable. * @param array $aioseo_indexable The AIOSEO indexable. * * @return Indexable The created indexables. */ public function map( $indexable, $aioseo_indexable ) { foreach ( $this->aioseo_to_yoast_map as $aioseo_key => $yoast_mapping ) { // For robots import. if ( isset( $yoast_mapping['robots_import'] ) && $yoast_mapping['robots_import'] ) { $yoast_mapping['subtype'] = $indexable->object_sub_type; $indexable->{$yoast_mapping['yoast_name']} = $this->transform_import_data( $yoast_mapping['transform_method'], $aioseo_indexable, $aioseo_key, $yoast_mapping, $indexable ); continue; } // For social images, like open graph and twitter image. if ( isset( $yoast_mapping['social_image_import'] ) && $yoast_mapping['social_image_import'] ) { $image_url = $this->transform_import_data( $yoast_mapping['transform_method'], $aioseo_indexable, $aioseo_key, $yoast_mapping, $indexable ); // Update the indexable's social image only where there's actually a url to import, so as not to lose the social images that we came up with when we originally built the indexable. if ( ! empty( $image_url ) ) { $indexable->{$yoast_mapping['yoast_name']} = $image_url; $image_source_key = $yoast_mapping['social_setting_prefix_yoast'] . 'image_source'; $indexable->$image_source_key = 'imported'; $image_id_key = $yoast_mapping['social_setting_prefix_yoast'] . 'image_id'; $indexable->$image_id_key = $this->image->get_attachment_by_url( $image_url ); if ( $yoast_mapping['yoast_name'] === 'open_graph_image' ) { $indexable->open_graph_image_meta = null; } } continue; } // For twitter import, take the respective open graph data if the appropriate setting is enabled. if ( isset( $yoast_mapping['twitter_import'] ) && $yoast_mapping['twitter_import'] && $aioseo_indexable['twitter_use_og'] ) { $aioseo_indexable['twitter_title'] = $aioseo_indexable['og_title']; $aioseo_indexable['twitter_description'] = $aioseo_indexable['og_description']; } if ( ! empty( $aioseo_indexable[ $aioseo_key ] ) ) { $indexable->{$yoast_mapping['yoast_name']} = $this->transform_import_data( $yoast_mapping['transform_method'], $aioseo_indexable, $aioseo_key, $yoast_mapping, $indexable ); } } return $indexable; } /** * Transforms the data to be imported. * * @param string $transform_method The method that is going to be used for transforming the data. * @param array $aioseo_indexable The data of the AIOSEO indexable data that is being imported. * @param string $aioseo_key The name of the specific set of data that is going to be transformed. * @param array $yoast_mapping Extra details for the import of the specific data that is going to be transformed. * @param Indexable $indexable The Yoast indexable that we are going to import the transformed data into. * * @return string|bool|null The transformed data to be imported. */ protected function transform_import_data( $transform_method, $aioseo_indexable, $aioseo_key, $yoast_mapping, $indexable ) { return \call_user_func( [ $this, $transform_method ], $aioseo_indexable, $aioseo_key, $yoast_mapping, $indexable ); } /** * Returns the number of objects that will be imported in a single importing pass. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_aioseo_post_indexation_limit' - Allow filtering the number of posts indexed during each indexing pass. * * @param int $max_posts The maximum number of posts indexed. */ $limit = \apply_filters( 'wpseo_aioseo_post_indexation_limit', 25 ); if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = 25; } return $limit; } /** * Populates the needed data array based on which columns we use from the AIOSEO indexable table. * * @return array The needed data array that contains all the needed columns. */ public function get_needed_data() { $needed_data = \array_keys( $this->aioseo_to_yoast_map ); \array_push( $needed_data, 'id', 'post_id', 'robots_default', 'og_image_custom_url', 'og_image_type', 'twitter_image_custom_url', 'twitter_image_type', 'twitter_use_og' ); return $needed_data; } /** * Populates the needed robot data array to be used in validating against its structure. * * @return array The needed data array that contains all the needed columns. */ public function get_needed_robot_data() { $needed_robot_data = []; foreach ( $this->aioseo_to_yoast_map as $yoast_mapping ) { if ( isset( $yoast_mapping['robot_type'] ) ) { $needed_robot_data[] = $yoast_mapping['robot_type']; } } return $needed_robot_data; } /** * Creates a query for gathering AiOSEO data from the database. * * @param int|false $limit The maximum number of unimported objects to be returned. * False for "no limit". * @param bool $just_detect Whether we want to just detect if there are unimported objects. If false, we want to actually import them too. * * @return string The query to use for importing or counting the number of items to import. */ public function query( $limit = false, $just_detect = false ) { $table = $this->aioseo_helper->get_table(); $select_statement = 'id'; if ( ! $just_detect ) { // If we want to import too, we need the actual needed data from AIOSEO indexables. $needed_data = $this->get_needed_data(); $select_statement = \implode( ', ', $needed_data ); } $cursor_id = $this->get_cursor_id(); $cursor = $this->import_cursor->get_cursor( $cursor_id ); /** * Filter 'wpseo_aioseo_post_cursor' - Allow filtering the value of the aioseo post import cursor. * * @param int $import_cursor The value of the aioseo post import cursor. */ $cursor = \apply_filters( 'wpseo_aioseo_post_import_cursor', $cursor ); $replacements = [ $cursor ]; $limit_statement = ''; if ( ! empty( $limit ) ) { $replacements[] = $limit; $limit_statement = ' LIMIT %d'; } // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. return $this->wpdb->prepare( "SELECT {$select_statement} FROM {$table} WHERE id > %d ORDER BY id{$limit_statement}", $replacements ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } /** * Minimally transforms data to be imported. * * @param array $aioseo_data All of the AIOSEO data to be imported. * @param string $aioseo_key The AIOSEO key that contains the setting we're working with. * * @return string The transformed meta data. */ public function simple_import_post( $aioseo_data, $aioseo_key ) { return $this->simple_import( $aioseo_data[ $aioseo_key ] ); } /** * Transforms URL to be imported. * * @param array $aioseo_data All of the AIOSEO data to be imported. * @param string $aioseo_key The AIOSEO key that contains the setting we're working with. * * @return string The transformed URL. */ public function url_import_post( $aioseo_data, $aioseo_key ) { return $this->url_import( $aioseo_data[ $aioseo_key ] ); } /** * Plucks the keyphrase to be imported from the AIOSEO array of keyphrase meta data. * * @param array $aioseo_data All of the AIOSEO data to be imported. * @param string $aioseo_key The AIOSEO key that contains the setting we're working with, aka keyphrases. * * @return string|null The plucked keyphrase. */ public function keyphrase_import( $aioseo_data, $aioseo_key ) { $meta_data = \json_decode( $aioseo_data[ $aioseo_key ], true ); if ( ! isset( $meta_data['focus']['keyphrase'] ) ) { return null; } return $this->sanitization->sanitize_text_field( $meta_data['focus']['keyphrase'] ); } /** * Imports the post's noindex setting. * * @param bool $aioseo_robots_settings AIOSEO's set of robot settings for the post. * * @return bool|null The value of Yoast's noindex setting for the post. */ public function post_robots_noindex_import( $aioseo_robots_settings ) { // If robot settings defer to default settings, we have null in the is_robots_noindex field. if ( $aioseo_robots_settings['robots_default'] ) { return null; } return $aioseo_robots_settings['robots_noindex']; } /** * Imports the post's robots setting. * * @param bool $aioseo_robots_settings AIOSEO's set of robot settings for the post. * @param string $aioseo_key The AIOSEO key that contains the robot setting we're working with. * @param array $mapping The mapping of the setting we're working with. * * @return bool|null The value of Yoast's noindex setting for the post. */ public function post_general_robots_import( $aioseo_robots_settings, $aioseo_key, $mapping ) { $mapping = $this->enhance_mapping( $mapping ); if ( $aioseo_robots_settings['robots_default'] ) { // Let's first get the subtype's setting value and then transform it taking into consideration whether it defers to global defaults. $subtype_setting = $this->robots_provider->get_subtype_robot_setting( $mapping ); return $this->robots_transformer->transform_robot_setting( $mapping['robot_type'], $subtype_setting, $mapping ); } return $aioseo_robots_settings[ $aioseo_key ]; } /** * Enhances the mapping of the setting we're working with, with type and the option name, so that we can retrieve the settings for the object we're working with. * * @param array $mapping The mapping of the setting we're working with. * * @return array The enhanced mapping. */ public function enhance_mapping( $mapping = [] ) { $mapping['type'] = 'postTypes'; $mapping['option_name'] = 'aioseo_options_dynamic'; return $mapping; } /** * Imports the og and twitter image url. * * @param bool $aioseo_social_image_settings AIOSEO's set of social image settings for the post. * @param string $aioseo_key The AIOSEO key that contains the robot setting we're working with. * @param array $mapping The mapping of the setting we're working with. * @param Indexable $indexable The Yoast indexable we're importing into. * * @return bool|null The url of the social image we're importing, null if there's none. */ public function social_image_url_import( $aioseo_social_image_settings, $aioseo_key, $mapping, $indexable ) { if ( $mapping['social_setting_prefix_aioseo'] === 'twitter_' && $aioseo_social_image_settings['twitter_use_og'] ) { $mapping['social_setting_prefix_aioseo'] = 'og_'; } $social_setting = \rtrim( $mapping['social_setting_prefix_aioseo'], '_' ); $image_type = $aioseo_social_image_settings[ $mapping['social_setting_prefix_aioseo'] . 'image_type' ]; if ( $image_type === 'default' ) { $image_type = $this->social_images_provider->get_default_social_image_source( $social_setting ); } switch ( $image_type ) { case 'attach': $image_url = $this->social_images_provider->get_first_attached_image( $indexable->object_id ); break; case 'auto': if ( $this->social_images_provider->get_featured_image( $indexable->object_id ) ) { // If there's a featured image, lets not import it, as our indexable calculation has already set that as active social image. That way we achieve dynamicality. return null; } $image_url = $this->social_images_provider->get_auto_image( $indexable->object_id ); break; case 'content': $image_url = $this->social_images_provider->get_first_image_in_content( $indexable->object_id ); break; case 'custom_image': $image_url = $aioseo_social_image_settings[ $mapping['social_setting_prefix_aioseo'] . 'image_custom_url' ]; break; case 'featured': return null; // Our auto-calculation when the indexable was built/updated has taken care of it, so it's not needed to transfer any data now. case 'author': return null; case 'custom': return null; case 'default': $image_url = $this->social_images_provider->get_default_custom_social_image( $social_setting ); break; default: $image_url = $aioseo_social_image_settings[ $mapping['social_setting_prefix_aioseo'] . 'image_url' ]; break; } if ( empty( $image_url ) ) { $image_url = $this->social_images_provider->get_default_custom_social_image( $social_setting ); } if ( empty( $image_url ) ) { return null; } return $this->sanitization->sanitize_url( $image_url, null ); } } actions/importing/aioseo/aioseo-general-settings-importing-action.php 0000644 00000013752 15025657560 0022240 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Import_Cursor_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Sanitization_Helper; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service; /** * Importing action for AIOSEO general settings. */ class Aioseo_General_Settings_Importing_Action extends Abstract_Aioseo_Settings_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'general_settings'; /** * The option_name of the AIOSEO option that contains the settings. */ public const SOURCE_OPTION_NAME = 'aioseo_options'; /** * The map of aioseo_options to yoast settings. * * @var array */ protected $aioseo_options_to_yoast_map = []; /** * The tab of the aioseo settings we're working with. * * @var string */ protected $settings_tab = 'global'; /** * The image helper. * * @var Image_Helper */ protected $image; /** * Aioseo_General_Settings_Importing_Action constructor. * * @param Import_Cursor_Helper $import_cursor The import cursor helper. * @param Options_Helper $options The options helper. * @param Sanitization_Helper $sanitization The sanitization helper. * @param Image_Helper $image The image helper. * @param Aioseo_Replacevar_Service $replacevar_handler The replacevar handler. * @param Aioseo_Robots_Provider_Service $robots_provider The robots provider service. * @param Aioseo_Robots_Transformer_Service $robots_transformer The robots transfomer service. */ public function __construct( Import_Cursor_Helper $import_cursor, Options_Helper $options, Sanitization_Helper $sanitization, Image_Helper $image, Aioseo_Replacevar_Service $replacevar_handler, Aioseo_Robots_Provider_Service $robots_provider, Aioseo_Robots_Transformer_Service $robots_transformer ) { parent::__construct( $import_cursor, $options, $sanitization, $replacevar_handler, $robots_provider, $robots_transformer ); $this->image = $image; } /** * Builds the mapping that ties AOISEO option keys with Yoast ones and their data transformation method. * * @return void */ protected function build_mapping() { $this->aioseo_options_to_yoast_map = [ '/separator' => [ 'yoast_name' => 'separator', 'transform_method' => 'transform_separator', ], '/siteTitle' => [ 'yoast_name' => 'title-home-wpseo', 'transform_method' => 'simple_import', ], '/metaDescription' => [ 'yoast_name' => 'metadesc-home-wpseo', 'transform_method' => 'simple_import', ], '/schema/siteRepresents' => [ 'yoast_name' => 'company_or_person', 'transform_method' => 'transform_site_represents', ], '/schema/person' => [ 'yoast_name' => 'company_or_person_user_id', 'transform_method' => 'simple_import', ], '/schema/organizationName' => [ 'yoast_name' => 'company_name', 'transform_method' => 'simple_import', ], '/schema/organizationLogo' => [ 'yoast_name' => 'company_logo', 'transform_method' => 'import_company_logo', ], '/schema/personLogo' => [ 'yoast_name' => 'person_logo', 'transform_method' => 'import_person_logo', ], ]; } /** * Imports the organization logo while also accounting for the id of the log to be saved in the separate Yoast option. * * @param string $logo_url The company logo url coming from AIOSEO settings. * * @return string The transformed company logo url. */ public function import_company_logo( $logo_url ) { $logo_id = $this->image->get_attachment_by_url( $logo_url ); $this->options->set( 'company_logo_id', $logo_id ); $this->options->set( 'company_logo_meta', false ); $logo_meta = $this->image->get_attachment_meta_from_settings( 'company_logo' ); $this->options->set( 'company_logo_meta', $logo_meta ); return $this->url_import( $logo_url ); } /** * Imports the person logo while also accounting for the id of the log to be saved in the separate Yoast option. * * @param string $logo_url The person logo url coming from AIOSEO settings. * * @return string The transformed person logo url. */ public function import_person_logo( $logo_url ) { $logo_id = $this->image->get_attachment_by_url( $logo_url ); $this->options->set( 'person_logo_id', $logo_id ); $this->options->set( 'person_logo_meta', false ); $logo_meta = $this->image->get_attachment_meta_from_settings( 'person_logo' ); $this->options->set( 'person_logo_meta', $logo_meta ); return $this->url_import( $logo_url ); } /** * Transforms the site represents setting. * * @param string $site_represents The site represents setting. * * @return string The transformed site represents setting. */ public function transform_site_represents( $site_represents ) { switch ( $site_represents ) { case 'person': return 'person'; case 'organization': default: return 'company'; } } /** * Transforms the separator setting. * * @param string $separator The separator setting. * * @return string The transformed separator. */ public function transform_separator( $separator ) { switch ( $separator ) { case '-': return 'sc-dash'; case '–': return 'sc-ndash'; case '—': return 'sc-mdash'; case '»': return 'sc-raquo'; case '«': return 'sc-laquo'; case '>': return 'sc-gt'; case '•': return 'sc-bull'; case '|': return 'sc-pipe'; default: return 'sc-dash'; } } } actions/importing/aioseo/aioseo-custom-archive-settings-importing-action.php 0000644 00000007366 15025657560 0023560 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; use Yoast\WP\SEO\Helpers\Import_Cursor_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Helpers\Sanitization_Helper; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service; /** * Importing action for AIOSEO custom archive settings data. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Aioseo_Custom_Archive_Settings_Importing_Action extends Abstract_Aioseo_Settings_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'custom_archive_settings'; /** * The option_name of the AIOSEO option that contains the settings. */ public const SOURCE_OPTION_NAME = 'aioseo_options_dynamic'; /** * The map of aioseo_options to yoast settings. * * @var array */ protected $aioseo_options_to_yoast_map = []; /** * The tab of the aioseo settings we're working with. * * @var string */ protected $settings_tab = 'archives'; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type; /** * Aioseo_Custom_Archive_Settings_Importing_Action constructor. * * @param Import_Cursor_Helper $import_cursor The import cursor helper. * @param Options_Helper $options The options helper. * @param Sanitization_Helper $sanitization The sanitization helper. * @param Post_Type_Helper $post_type The post type helper. * @param Aioseo_Replacevar_Service $replacevar_handler The replacevar handler. * @param Aioseo_Robots_Provider_Service $robots_provider The robots provider service. * @param Aioseo_Robots_Transformer_Service $robots_transformer The robots transfomer service. */ public function __construct( Import_Cursor_Helper $import_cursor, Options_Helper $options, Sanitization_Helper $sanitization, Post_Type_Helper $post_type, Aioseo_Replacevar_Service $replacevar_handler, Aioseo_Robots_Provider_Service $robots_provider, Aioseo_Robots_Transformer_Service $robots_transformer ) { parent::__construct( $import_cursor, $options, $sanitization, $replacevar_handler, $robots_provider, $robots_transformer ); $this->post_type = $post_type; } /** * Builds the mapping that ties AOISEO option keys with Yoast ones and their data transformation method. * * @return void */ protected function build_mapping() { $post_type_objects = \get_post_types( [ 'public' => true ], 'objects' ); foreach ( $post_type_objects as $pt ) { // Use all the custom post types that have archives. if ( ! $pt->_builtin && $this->post_type->has_archive( $pt ) ) { $this->aioseo_options_to_yoast_map[ '/' . $pt->name . '/title' ] = [ 'yoast_name' => 'title-ptarchive-' . $pt->name, 'transform_method' => 'simple_import', ]; $this->aioseo_options_to_yoast_map[ '/' . $pt->name . '/metaDescription' ] = [ 'yoast_name' => 'metadesc-ptarchive-' . $pt->name, 'transform_method' => 'simple_import', ]; $this->aioseo_options_to_yoast_map[ '/' . $pt->name . '/advanced/robotsMeta/noindex' ] = [ 'yoast_name' => 'noindex-ptarchive-' . $pt->name, 'transform_method' => 'import_noindex', 'type' => 'archives', 'subtype' => $pt->name, 'option_name' => 'aioseo_options_dynamic', ]; } } } } actions/importing/aioseo/aioseo-taxonomy-settings-importing-action.php 0000644 00000007645 15025657560 0022505 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; /** * Importing action for AIOSEO taxonomies settings data. */ class Aioseo_Taxonomy_Settings_Importing_Action extends Abstract_Aioseo_Settings_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'taxonomy_settings'; /** * The option_name of the AIOSEO option that contains the settings. */ public const SOURCE_OPTION_NAME = 'aioseo_options_dynamic'; /** * The map of aioseo_options to yoast settings. * * @var array */ protected $aioseo_options_to_yoast_map = []; /** * The tab of the aioseo settings we're working with. * * @var string */ protected $settings_tab = 'taxonomies'; /** * Additional mapping between AiOSEO replace vars and Yoast replace vars. * * @see https://yoast.com/help/list-available-snippet-variables-yoast-seo/ * * @var array */ protected $replace_vars_edited_map = [ '#breadcrumb_404_error_format' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_archive_post_type_format' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_archive_post_type_name' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_author_display_name' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_author_first_name' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_blog_page_title' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_label' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_link' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_search_result_format' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_search_string' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_separator' => '', // Empty string, as AIOSEO shows nothing for that tag. '#breadcrumb_taxonomy_title' => '', // Empty string, as AIOSEO shows nothing for that tag. '#taxonomy_title' => '%%term_title%%', ]; /** * Builds the mapping that ties AOISEO option keys with Yoast ones and their data transformation method. * * @return void */ protected function build_mapping() { $taxonomy_objects = \get_taxonomies( [ 'public' => true ], 'object' ); foreach ( $taxonomy_objects as $tax ) { // Use all the public taxonomies. $this->aioseo_options_to_yoast_map[ '/' . $tax->name . '/title' ] = [ 'yoast_name' => 'title-tax-' . $tax->name, 'transform_method' => 'simple_import', ]; $this->aioseo_options_to_yoast_map[ '/' . $tax->name . '/metaDescription' ] = [ 'yoast_name' => 'metadesc-tax-' . $tax->name, 'transform_method' => 'simple_import', ]; $this->aioseo_options_to_yoast_map[ '/' . $tax->name . '/advanced/robotsMeta/noindex' ] = [ 'yoast_name' => 'noindex-tax-' . $tax->name, 'transform_method' => 'import_noindex', 'type' => 'taxonomies', 'subtype' => $tax->name, 'option_name' => 'aioseo_options_dynamic', ]; } } /** * Returns a setting map of the robot setting for post category taxonomies. * * @return array The setting map of the robot setting for post category taxonomies. */ public function pluck_robot_setting_from_mapping() { $this->build_mapping(); foreach ( $this->aioseo_options_to_yoast_map as $setting ) { // Return the first archive setting map. if ( $setting['transform_method'] === 'import_noindex' && isset( $setting['subtype'] ) && $setting['subtype'] === 'category' ) { return $setting; } } return []; } } actions/importing/aioseo/abstract-aioseo-settings-importing-action.php 0000644 00000024610 15025657560 0022421 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; use Exception; use Yoast\WP\SEO\Actions\Importing\Abstract_Aioseo_Importing_Action; use Yoast\WP\SEO\Helpers\Import_Helper; /** * Abstract class for importing AIOSEO settings. */ abstract class Abstract_Aioseo_Settings_Importing_Action extends Abstract_Aioseo_Importing_Action { /** * The plugin the class deals with. * * @var string */ public const PLUGIN = null; /** * The type the class deals with. * * @var string */ public const TYPE = null; /** * The option_name of the AIOSEO option that contains the settings. */ public const SOURCE_OPTION_NAME = null; /** * The map of aioseo_options to yoast settings. * * @var array */ protected $aioseo_options_to_yoast_map = []; /** * The tab of the aioseo settings we're working with, eg. taxonomies, posttypes. * * @var string */ protected $settings_tab = ''; /** * Additional mapping between AiOSEO replace vars and Yoast replace vars. * * @see https://yoast.com/help/list-available-snippet-variables-yoast-seo/ * * @var array */ protected $replace_vars_edited_map = []; /** * The import helper. * * @var Import_Helper */ protected $import_helper; /** * Builds the mapping that ties AOISEO option keys with Yoast ones and their data transformation method. * * @return void */ abstract protected function build_mapping(); /** * Sets the import helper. * * @required * * @param Import_Helper $import_helper The import helper. * * @return void */ public function set_import_helper( Import_Helper $import_helper ) { $this->import_helper = $import_helper; } /** * Retrieves the source option_name. * * @return string The source option_name. * * @throws Exception If the SOURCE_OPTION_NAME constant is not set in the child class. */ public function get_source_option_name() { $source_option_name = static::SOURCE_OPTION_NAME; if ( empty( $source_option_name ) ) { throw new Exception( 'Importing settings action without explicit source option_name' ); } return $source_option_name; } /** * Returns the total number of unimported objects. * * @return int The total number of unimported objects. */ public function get_total_unindexed() { return $this->get_unindexed_count(); } /** * Returns the limited number of unimported objects. * * @param int $limit The maximum number of unimported objects to be returned. * * @return int The limited number of unindexed posts. */ public function get_limited_unindexed_count( $limit ) { return $this->get_unindexed_count( $limit ); } /** * Returns the number of unimported objects (limited if limit is applied). * * @param int|null $limit The maximum number of unimported objects to be returned. * * @return int The number of unindexed posts. */ protected function get_unindexed_count( $limit = null ) { if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = null; } $settings_to_create = $this->query( $limit ); $number_of_settings_to_create = \count( $settings_to_create ); $completed = $number_of_settings_to_create === 0; $this->set_completed( $completed ); return $number_of_settings_to_create; } /** * Imports AIOSEO settings. * * @return array|false An array of the AIOSEO settings that were imported or false if aioseo data was not found. */ public function index() { $limit = $this->get_limit(); $aioseo_settings = $this->query( $limit ); $created_settings = []; $completed = \count( $aioseo_settings ) === 0; $this->set_completed( $completed ); // Prepare the setting keys mapping. $this->build_mapping(); // Prepare the replacement var mapping. foreach ( $this->replace_vars_edited_map as $aioseo_var => $yoast_var ) { $this->replacevar_handler->compose_map( $aioseo_var, $yoast_var ); } $last_imported_setting = ''; try { foreach ( $aioseo_settings as $setting => $setting_value ) { // Map and import the values of the setting we're working with (eg. post, book-category, etc.) to the respective Yoast option. $this->map( $setting_value, $setting ); // Save the type of the settings that were just imported, so that we can allow chunked imports. $last_imported_setting = $setting; $created_settings[] = $setting; } } finally { $cursor_id = $this->get_cursor_id(); $this->import_cursor->set_cursor( $cursor_id, $last_imported_setting ); } return $created_settings; } /** * Checks if the settings tab subsetting is set in the AIOSEO option. * * @param string $aioseo_settings The AIOSEO option. * * @return bool Whether the settings are set. */ public function isset_settings_tab( $aioseo_settings ) { return isset( $aioseo_settings['searchAppearance'][ $this->settings_tab ] ); } /** * Queries the database and retrieves unimported AiOSEO settings (in chunks if a limit is applied). * * @param int|null $limit The maximum number of unimported objects to be returned. * * @return array The (maybe chunked) unimported AiOSEO settings to import. */ protected function query( $limit = null ) { $aioseo_settings = \json_decode( \get_option( $this->get_source_option_name(), '' ), true ); if ( empty( $aioseo_settings ) ) { return []; } // We specifically want the setttings of the tab we're working with, eg. postTypes, taxonomies, etc. $settings_values = $aioseo_settings['searchAppearance'][ $this->settings_tab ]; if ( ! \is_array( $settings_values ) ) { return []; } $flattened_settings = $this->import_helper->flatten_settings( $settings_values ); return $this->get_unimported_chunk( $flattened_settings, $limit ); } /** * Retrieves (a chunk of, if limit is applied) the unimported AIOSEO settings. * To apply a chunk, we manipulate the cursor to the keys of the AIOSEO settings. * * @param array $importable_data All of the available AIOSEO settings. * @param int $limit The maximum number of unimported objects to be returned. * * @return array The (chunk of, if limit is applied)) unimported AIOSEO settings. */ protected function get_unimported_chunk( $importable_data, $limit ) { \ksort( $importable_data ); $cursor_id = $this->get_cursor_id(); $cursor = $this->import_cursor->get_cursor( $cursor_id, '' ); /** * Filter 'wpseo_aioseo_<identifier>_import_cursor' - Allow filtering the value of the aioseo settings import cursor. * * @param int $import_cursor The value of the aioseo posttype default settings import cursor. */ $cursor = \apply_filters( 'wpseo_aioseo_' . $this->get_type() . '_import_cursor', $cursor ); if ( $cursor === '' ) { return \array_slice( $importable_data, 0, $limit, true ); } // Let's find the position of the cursor in the alphabetically sorted importable data, so we can return only the unimported data. $keys = \array_flip( \array_keys( $importable_data ) ); // If the stored cursor now no longer exists in the data, we have no choice but to start over. $position = ( isset( $keys[ $cursor ] ) ) ? ( $keys[ $cursor ] + 1 ) : 0; return \array_slice( $importable_data, $position, $limit, true ); } /** * Returns the number of objects that will be imported in a single importing pass. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_aioseo_<identifier>_indexation_limit' - Allow filtering the number of settings imported during each importing pass. * * @param int $max_posts The maximum number of posts indexed. */ $limit = \apply_filters( 'wpseo_aioseo_' . $this->get_type() . '_indexation_limit', 25 ); if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = 25; } return $limit; } /** * Maps/imports AIOSEO settings into the respective Yoast settings. * * @param string|array $setting_value The value of the AIOSEO setting at hand. * @param string $setting The setting at hand, eg. post or movie-category, separator etc. * * @return void */ protected function map( $setting_value, $setting ) { $aioseo_options_to_yoast_map = $this->aioseo_options_to_yoast_map; if ( isset( $aioseo_options_to_yoast_map[ $setting ] ) ) { $this->import_single_setting( $setting, $setting_value, $aioseo_options_to_yoast_map[ $setting ] ); } } /** * Imports a single setting in the db after transforming it to adhere to Yoast conventions. * * @param string $setting The name of the setting. * @param string $setting_value The values of the setting. * @param array $setting_mapping The mapping of the setting to Yoast formats. * * @return void */ protected function import_single_setting( $setting, $setting_value, $setting_mapping ) { $yoast_key = $setting_mapping['yoast_name']; // Check if we're supposed to save the setting. if ( $this->options->get_default( 'wpseo_titles', $yoast_key ) !== null ) { // Then, do any needed data transfomation before actually saving the incoming data. $transformed_data = \call_user_func( [ $this, $setting_mapping['transform_method'] ], $setting_value, $setting_mapping ); $this->options->set( $yoast_key, $transformed_data ); } } /** * Minimally transforms boolean data to be imported. * * @param bool $meta_data The boolean meta data to be imported. * * @return bool The transformed boolean meta data. */ public function simple_boolean_import( $meta_data ) { return $meta_data; } /** * Imports the noindex setting, taking into consideration whether they defer to global defaults. * * @param bool $noindex The noindex of the type, without taking into consideration whether the type defers to global defaults. * @param array $mapping The mapping of the setting we're working with. * * @return bool The noindex setting. */ public function import_noindex( $noindex, $mapping ) { return $this->robots_transformer->transform_robot_setting( 'noindex', $noindex, $mapping ); } /** * Returns a setting map of the robot setting for one subset of post types/taxonomies/archives. * For custom archives, it returns an empty array because AIOSEO excludes some custom archives from this option structure, eg. WooCommerce's products and we don't want to raise a false alarm. * * @return array The setting map of the robot setting for one subset of post types/taxonomies/archives or an empty array. */ public function pluck_robot_setting_from_mapping() { return []; } } actions/importing/aioseo/aioseo-cleanup-action.php 0000644 00000011025 15025657560 0016375 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Given it's a very specific case. namespace Yoast\WP\SEO\Actions\Importing\Aioseo; use wpdb; use Yoast\WP\SEO\Actions\Importing\Abstract_Aioseo_Importing_Action; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Importing action for cleaning up AIOSEO data. */ class Aioseo_Cleanup_Action extends Abstract_Aioseo_Importing_Action { /** * The plugin of the action. */ public const PLUGIN = 'aioseo'; /** * The type of the action. */ public const TYPE = 'cleanup'; /** * The AIOSEO meta_keys to be cleaned up. * * @var array<string> */ protected $aioseo_postmeta_keys = [ '_aioseo_title', '_aioseo_description', '_aioseo_og_title', '_aioseo_og_description', '_aioseo_twitter_title', '_aioseo_twitter_description', ]; /** * The WordPress database instance. * * @var wpdb */ protected $wpdb; /** * Class constructor. * * @param wpdb $wpdb The WordPress database instance. * @param Options_Helper $options The options helper. */ public function __construct( wpdb $wpdb, Options_Helper $options ) { $this->wpdb = $wpdb; $this->options = $options; } /** * Retrieves the postmeta along with the db prefix. * * @return string The postmeta table name along with the db prefix. */ protected function get_postmeta_table() { return $this->wpdb->prefix . 'postmeta'; } /** * Just checks if the cleanup has been completed in the past. * * @return int The total number of unimported objects. */ public function get_total_unindexed() { if ( ! $this->aioseo_helper->aioseo_exists() ) { return 0; } return ( ! $this->get_completed() ) ? 1 : 0; } /** * Just checks if the cleanup has been completed in the past. * * @param int $limit The maximum number of unimported objects to be returned. * * @return int|false The limited number of unindexed posts. False if the query fails. */ public function get_limited_unindexed_count( $limit ) { if ( ! $this->aioseo_helper->aioseo_exists() ) { return 0; } return ( ! $this->get_completed() ) ? 1 : 0; } /** * Cleans up AIOSEO data. * * @return Indexable[]|false An array of created indexables or false if aioseo data was not found. */ public function index() { if ( $this->get_completed() ) { return []; } // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- Reason: There is no unescaped user input. $meta_data = $this->wpdb->query( $this->cleanup_postmeta_query() ); $aioseo_table_truncate_done = $this->wpdb->query( $this->truncate_query() ); // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared if ( $meta_data === false && $aioseo_table_truncate_done === false ) { return false; } $this->set_completed( true ); return [ 'metadata_cleanup' => $meta_data, 'indexables_cleanup' => $aioseo_table_truncate_done, ]; } /** * Creates a DELETE query string for deleting AIOSEO postmeta data. * * @return string The query to use for importing or counting the number of items to import. */ public function cleanup_postmeta_query() { $table = $this->get_postmeta_table(); $meta_keys_to_delete = $this->aioseo_postmeta_keys; // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Reason: There is no unescaped user input. return $this->wpdb->prepare( "DELETE FROM {$table} WHERE meta_key IN (" . \implode( ', ', \array_fill( 0, \count( $meta_keys_to_delete ), '%s' ) ) . ')', $meta_keys_to_delete ); // phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared } /** * Creates a TRUNCATE query string for emptying the AIOSEO indexable table, if it exists. * * @return string The query to use for importing or counting the number of items to import. */ public function truncate_query() { if ( ! $this->aioseo_helper->aioseo_exists() ) { // If the table doesn't exist, we need a string that will amount to a quick query that doesn't return false when ran. return 'SELECT 1'; } $table = $this->aioseo_helper->get_table(); return "TRUNCATE TABLE {$table}"; } /** * Used nowhere. Exists to comply with the interface. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_aioseo_cleanup_limit' - Allow filtering the number of posts indexed during each indexing pass. * * @param int $max_posts The maximum number of posts cleaned up. */ $limit = \apply_filters( 'wpseo_aioseo_cleanup_limit', 25 ); if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = 25; } return $limit; } } actions/importing/importing-action-interface.php 0000644 00000001614 15025657560 0016163 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Importing; use Yoast\WP\SEO\Actions\Indexing\Limited_Indexing_Action_Interface; interface Importing_Action_Interface extends Importing_Indexation_Action_Interface, Limited_Indexing_Action_Interface { /** * Returns the name of the plugin we import from. * * @return string The plugin name. */ public function get_plugin(); /** * Returns the type of data we import. * * @return string The type of data. */ public function get_type(); /** * Whether or not this action is capable of importing given a specific plugin and type. * * @param string|null $plugin The name of the plugin being imported. * @param string|null $type The component of the plugin being imported. * * @return bool True if the action can import the given plugin's data of the given type. */ public function is_compatible_with( $plugin = null, $type = null ); } actions/importing/deactivate-conflicting-plugins-action.php 0000644 00000010417 15025657560 0020303 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Importing; use Yoast\WP\SEO\Conditionals\Updated_Importer_Framework_Conditional; use Yoast\WP\SEO\Config\Conflicting_Plugins; use Yoast\WP\SEO\Helpers\Import_Cursor_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Sanitization_Helper; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Replacevar_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Provider_Service; use Yoast\WP\SEO\Services\Importing\Aioseo\Aioseo_Robots_Transformer_Service; use Yoast\WP\SEO\Services\Importing\Conflicting_Plugins_Service; /** * Deactivates plug-ins that cause conflicts with Yoast SEO. */ class Deactivate_Conflicting_Plugins_Action extends Abstract_Aioseo_Importing_Action { /** * The plugin the class deals with. * * @var string */ public const PLUGIN = 'conflicting-plugins'; /** * The type the class deals with. * * @var string */ public const TYPE = 'deactivation'; /** * The replacevar handler. * * @var Aioseo_Replacevar_Service */ protected $replacevar_handler; /** * Knows all plugins that might possibly conflict. * * @var Conflicting_Plugins_Service */ protected $conflicting_plugins; /** * The list of conflicting plugins * * @var array */ protected $detected_plugins; /** * Class constructor. * * @param Import_Cursor_Helper $import_cursor The import cursor helper. * @param Options_Helper $options The options helper. * @param Sanitization_Helper $sanitization The sanitization helper. * @param Aioseo_Replacevar_Service $replacevar_handler The replacevar handler. * @param Aioseo_Robots_Provider_Service $robots_provider The robots provider service. * @param Aioseo_Robots_Transformer_Service $robots_transformer The robots transfomer service. * @param Conflicting_Plugins_Service $conflicting_plugins_service The Conflicting plugins Service. */ public function __construct( Import_Cursor_Helper $import_cursor, Options_Helper $options, Sanitization_Helper $sanitization, Aioseo_Replacevar_Service $replacevar_handler, Aioseo_Robots_Provider_Service $robots_provider, Aioseo_Robots_Transformer_Service $robots_transformer, Conflicting_Plugins_Service $conflicting_plugins_service ) { parent::__construct( $import_cursor, $options, $sanitization, $replacevar_handler, $robots_provider, $robots_transformer ); $this->conflicting_plugins = $conflicting_plugins_service; $this->detected_plugins = []; } /** * Get the total number of conflicting plugins. * * @return int */ public function get_total_unindexed() { return \count( $this->get_detected_plugins() ); } /** * Returns whether the updated importer framework is enabled. * * @return bool True if the updated importer framework is enabled. */ public function is_enabled() { $updated_importer_framework_conditional = \YoastSEO()->classes->get( Updated_Importer_Framework_Conditional::class ); return $updated_importer_framework_conditional->is_met(); } /** * Deactivate conflicting plugins. * * @return array */ public function index() { $detected_plugins = $this->get_detected_plugins(); $this->conflicting_plugins->deactivate_conflicting_plugins( $detected_plugins ); // We need to conform to the interface, so we report that no indexables were created. return []; } /** * {@inheritDoc} */ public function get_limit() { return \count( Conflicting_Plugins::all_plugins() ); } /** * Returns the total number of unindexed objects up to a limit. * * @param int $limit The maximum. * * @return int The total number of unindexed objects. */ public function get_limited_unindexed_count( $limit ) { $count = \count( $this->get_detected_plugins() ); return ( $count <= $limit ) ? $count : $limit; } /** * Returns all detected plugins. * * @return array The detected plugins. */ protected function get_detected_plugins() { // The active plugins won't change much. We can reuse the result for the duration of the request. if ( \count( $this->detected_plugins ) < 1 ) { $this->detected_plugins = $this->conflicting_plugins->detect_conflicting_plugins(); } return $this->detected_plugins; } } actions/semrush/semrush-options-action.php 0000644 00000002140 15025657560 0015045 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\SEMrush; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Class SEMrush_Options_Action */ class SEMrush_Options_Action { /** * The Options_Helper instance. * * @var Options_Helper */ protected $options_helper; /** * SEMrush_Options_Action constructor. * * @param Options_Helper $options_helper The WPSEO options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Stores SEMrush country code in the WPSEO options. * * @param string $country_code The country code to store. * * @return object The response object. */ public function set_country_code( $country_code ) { // The country code has already been validated at this point. No need to do that again. $success = $this->options_helper->set( 'semrush_country_code', $country_code ); if ( $success ) { return (object) [ 'success' => true, 'status' => 200, ]; } return (object) [ 'success' => false, 'status' => 500, 'error' => 'Could not save option in the database', ]; } } actions/semrush/semrush-login-action.php 0000644 00000002360 15025657560 0014466 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\SEMrush; use Yoast\WP\SEO\Config\SEMrush_Client; use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception; /** * Class SEMrush_Login_Action */ class SEMrush_Login_Action { /** * The SEMrush_Client instance. * * @var SEMrush_Client */ protected $client; /** * SEMrush_Login_Action constructor. * * @param SEMrush_Client $client The API client. */ public function __construct( SEMrush_Client $client ) { $this->client = $client; } /** * Authenticates with SEMrush to request the necessary tokens. * * @param string $code The authentication code to use to request a token with. * * @return object The response object. */ public function authenticate( $code ) { // Code has already been validated at this point. No need to do that again. try { $tokens = $this->client->request_tokens( $code ); return (object) [ 'tokens' => $tokens->to_array(), 'status' => 200, ]; } catch ( Authentication_Failed_Exception $e ) { return $e->get_response(); } } /** * Performs the login request, if necessary. * * @return void */ public function login() { if ( $this->client->has_valid_tokens() ) { return; } // Prompt with login screen. } } actions/semrush/semrush-phrases-action.php 0000644 00000004441 15025657560 0015025 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\SEMrush; use Exception; use Yoast\WP\SEO\Config\SEMrush_Client; /** * Class SEMrush_Phrases_Action */ class SEMrush_Phrases_Action { /** * The transient cache key. */ public const TRANSIENT_CACHE_KEY = 'wpseo_semrush_related_keyphrases_%s_%s'; /** * The SEMrush keyphrase URL. * * @var string */ public const KEYPHRASES_URL = 'https://oauth.semrush.com/api/v1/keywords/phrase_fullsearch'; /** * The SEMrush_Client instance. * * @var SEMrush_Client */ protected $client; /** * SEMrush_Phrases_Action constructor. * * @param SEMrush_Client $client The API client. */ public function __construct( SEMrush_Client $client ) { $this->client = $client; } /** * Gets the related keyphrases and data based on the passed keyphrase and database country code. * * @param string $keyphrase The keyphrase to search for. * @param string $database The database's country code. * * @return object The response object. */ public function get_related_keyphrases( $keyphrase, $database ) { try { $transient_key = \sprintf( static::TRANSIENT_CACHE_KEY, $keyphrase, $database ); $transient = \get_transient( $transient_key ); if ( $transient !== false && isset( $transient['data']['columnNames'] ) && \count( $transient['data']['columnNames'] ) === 5 ) { return $this->to_result_object( $transient ); } $options = [ 'params' => [ 'phrase' => $keyphrase, 'database' => $database, 'export_columns' => 'Ph,Nq,Td,In,Kd', 'display_limit' => 10, 'display_offset' => 0, 'display_sort' => 'nq_desc', 'display_filter' => '%2B|Nq|Lt|1000', ], ]; $results = $this->client->get( self::KEYPHRASES_URL, $options ); \set_transient( $transient_key, $results, \DAY_IN_SECONDS ); return $this->to_result_object( $results ); } catch ( Exception $e ) { return (object) [ 'error' => $e->getMessage(), 'status' => $e->getCode(), ]; } } /** * Converts the passed dataset to an object. * * @param array $result The result dataset to convert to an object. * * @return object The result object. */ protected function to_result_object( $result ) { return (object) [ 'results' => $result['data'], 'status' => $result['status'], ]; } } actions/indexables/indexable-head-action.php 0000644 00000007417 15025657560 0015164 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexables; use Yoast\WP\SEO\Surfaces\Meta_Surface; use Yoast\WP\SEO\Surfaces\Values\Meta; /** * Get head action for indexables. */ class Indexable_Head_Action { /** * Caches the output. * * @var mixed */ protected $cache; /** * The meta surface. * * @var Meta_Surface */ private $meta_surface; /** * Indexable_Head_Action constructor. * * @param Meta_Surface $meta_surface The meta surface. */ public function __construct( Meta_Surface $meta_surface ) { $this->meta_surface = $meta_surface; } /** * Retrieves the head for a url. * * @param string $url The url to get the head for. * * @return object Object with head and status properties. */ public function for_url( $url ) { if ( $url === \trailingslashit( \get_home_url() ) ) { return $this->with_404_fallback( $this->with_cache( 'home_page' ) ); } return $this->with_404_fallback( $this->with_cache( 'url', $url ) ); } /** * Retrieves the head for a post. * * @param int $id The id. * * @return object Object with head and status properties. */ public function for_post( $id ) { return $this->with_404_fallback( $this->with_cache( 'post', $id ) ); } /** * Retrieves the head for a term. * * @param int $id The id. * * @return object Object with head and status properties. */ public function for_term( $id ) { return $this->with_404_fallback( $this->with_cache( 'term', $id ) ); } /** * Retrieves the head for an author. * * @param int $id The id. * * @return object Object with head and status properties. */ public function for_author( $id ) { return $this->with_404_fallback( $this->with_cache( 'author', $id ) ); } /** * Retrieves the head for a post type archive. * * @param int $type The id. * * @return object Object with head and status properties. */ public function for_post_type_archive( $type ) { return $this->with_404_fallback( $this->with_cache( 'post_type_archive', $type ) ); } /** * Retrieves the head for the posts page. * * @return object Object with head and status properties. */ public function for_posts_page() { return $this->with_404_fallback( $this->with_cache( 'posts_page' ) ); } /** * Retrieves the head for the 404 page. Always sets the status to 404. * * @return object Object with head and status properties. */ public function for_404() { $meta = $this->with_cache( '404' ); if ( ! $meta ) { return (object) [ 'html' => '', 'json' => [], 'status' => 404, ]; } $head = $meta->get_head(); return (object) [ 'html' => $head->html, 'json' => $head->json, 'status' => 404, ]; } /** * Retrieves the head for a successful page load. * * @param object $head The calculated Yoast head. * * @return object The presentations and status code 200. */ protected function for_200( $head ) { return (object) [ 'html' => $head->html, 'json' => $head->json, 'status' => 200, ]; } /** * Returns the head with 404 fallback * * @param Meta|false $meta The meta object. * * @return object The head response. */ protected function with_404_fallback( $meta ) { if ( $meta === false ) { return $this->for_404(); } else { return $this->for_200( $meta->get_head() ); } } /** * Retrieves a value from the meta surface cached. * * @param string $type The type of value to retrieve. * @param string $argument Optional. The argument for the value. * * @return Meta The meta object. */ protected function with_cache( $type, $argument = '' ) { if ( ! isset( $this->cache[ $type ][ $argument ] ) ) { $this->cache[ $type ][ $argument ] = \call_user_func( [ $this->meta_surface, "for_$type" ], $argument ); } return $this->cache[ $type ][ $argument ]; } } actions/indexing/limited-indexing-action-interface.php 0000644 00000000763 15025657560 0017206 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; /** * Interface definition of a reindexing action for indexables that have a limited unindexed count. */ interface Limited_Indexing_Action_Interface { /** * Returns a limited number of unindexed posts. * * @param int $limit Limit the maximum number of unindexed posts that are counted. * * @return int|false The limited number of unindexed posts. False if the query fails. */ public function get_limited_unindexed_count( $limit ); } actions/indexing/indexation-action-interface.php 0000644 00000001432 15025657560 0016110 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; /** * Interface definition of reindexing action for indexables. */ interface Indexation_Action_Interface { /** * Returns the total number of unindexed objects. * * @return int The total number of unindexed objects. */ public function get_total_unindexed(); /** * Indexes a number of objects. * * NOTE: ALWAYS use limits, this method is intended to be called multiple times over several requests. * * For indexing that requires JavaScript simply return the objects that should be indexed. * * @return array The reindexed objects. */ public function index(); /** * Returns the number of objects that will be indexed in a single indexing pass. * * @return int The limit. */ public function get_limit(); } actions/indexing/indexing-prepare-action.php 0000644 00000001261 15025657560 0015251 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use Yoast\WP\SEO\Helpers\Indexing_Helper; /** * Class Indexing_Prepare_Action. * * Action for preparing the indexing routine. */ class Indexing_Prepare_Action { /** * The indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * Action for preparing the indexing routine. * * @param Indexing_Helper $indexing_helper The indexing helper. */ public function __construct( Indexing_Helper $indexing_helper ) { $this->indexing_helper = $indexing_helper; } /** * Prepares the indexing routine. * * @return void */ public function prepare() { $this->indexing_helper->prepare(); } } actions/indexing/indexable-indexing-complete-action.php 0000644 00000001316 15025657560 0017355 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use Yoast\WP\SEO\Helpers\Indexable_Helper; /** * Indexing action to call when the indexable indexing process is completed. */ class Indexable_Indexing_Complete_Action { /** * The options helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * Indexable_Indexing_Complete_Action constructor. * * @param Indexable_Helper $indexable_helper The indexable helper. */ public function __construct( Indexable_Helper $indexable_helper ) { $this->indexable_helper = $indexable_helper; } /** * Wraps up the indexing process. * * @return void */ public function complete() { $this->indexable_helper->finish_indexing(); } } actions/indexing/indexable-post-type-archive-indexation-action.php 0000644 00000014026 15025657560 0021467 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use Yoast\WP\SEO\Builders\Indexable_Builder; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Reindexing action for post type archive indexables. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Indexable_Post_Type_Archive_Indexation_Action implements Indexation_Action_Interface, Limited_Indexing_Action_Interface { /** * The transient cache key. */ public const UNINDEXED_COUNT_TRANSIENT = 'wpseo_total_unindexed_post_type_archives'; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type; /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The indexable builder. * * @var Indexable_Builder */ protected $builder; /** * The current version of the post type archive indexable builder. * * @var int */ protected $version; /** * Indexation_Post_Type_Archive_Action constructor. * * @param Indexable_Repository $repository The indexable repository. * @param Indexable_Builder $builder The indexable builder. * @param Post_Type_Helper $post_type The post type helper. * @param Indexable_Builder_Versions $versions The current versions of all indexable builders. */ public function __construct( Indexable_Repository $repository, Indexable_Builder $builder, Post_Type_Helper $post_type, Indexable_Builder_Versions $versions ) { $this->repository = $repository; $this->builder = $builder; $this->post_type = $post_type; $this->version = $versions->get_latest_version_for_type( 'post-type-archive' ); } /** * Returns the total number of unindexed post type archives. * * @param int|false $limit Limit the number of counted objects. * False for "no limit". * * @return int The total number of unindexed post type archives. */ public function get_total_unindexed( $limit = false ) { $transient = \get_transient( static::UNINDEXED_COUNT_TRANSIENT ); if ( $transient !== false ) { return (int) $transient; } \set_transient( static::UNINDEXED_COUNT_TRANSIENT, 0, \DAY_IN_SECONDS ); $result = \count( $this->get_unindexed_post_type_archives( $limit ) ); \set_transient( static::UNINDEXED_COUNT_TRANSIENT, $result, \DAY_IN_SECONDS ); /** * Action: 'wpseo_indexables_unindexed_calculated' - sets an option to timestamp when there are no unindexed indexables left. * * @internal */ \do_action( 'wpseo_indexables_unindexed_calculated', static::UNINDEXED_COUNT_TRANSIENT, $result ); return $result; } /** * Creates indexables for post type archives. * * @return Indexable[] The created indexables. */ public function index() { $unindexed_post_type_archives = $this->get_unindexed_post_type_archives( $this->get_limit() ); $indexables = []; foreach ( $unindexed_post_type_archives as $post_type_archive ) { $indexables[] = $this->builder->build_for_post_type_archive( $post_type_archive ); } if ( \count( $indexables ) > 0 ) { \delete_transient( static::UNINDEXED_COUNT_TRANSIENT ); } return $indexables; } /** * Returns the number of post type archives that will be indexed in a single indexing pass. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_post_type_archive_indexation_limit' - Allow filtering the number of posts indexed during each indexing pass. * * @param int $limit The maximum number of posts indexed. */ $limit = \apply_filters( 'wpseo_post_type_archive_indexation_limit', 25 ); if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = 25; } return $limit; } /** * Retrieves the list of post types for which no indexable for its archive page has been made yet. * * @param int|false $limit Limit the number of retrieved indexables to this number. * * @return array The list of post types for which no indexable for its archive page has been made yet. */ protected function get_unindexed_post_type_archives( $limit = false ) { $post_types_with_archive_pages = $this->get_post_types_with_archive_pages(); $indexed_post_types = $this->get_indexed_post_type_archives(); $unindexed_post_types = \array_diff( $post_types_with_archive_pages, $indexed_post_types ); if ( $limit ) { return \array_slice( $unindexed_post_types, 0, $limit ); } return $unindexed_post_types; } /** * Returns the names of all the post types that have archive pages. * * @return array The list of names of all post types that have archive pages. */ protected function get_post_types_with_archive_pages() { // We only want to index archive pages of public post types that have them. $post_types_with_archive = $this->post_type->get_indexable_post_archives(); // We only need the post type names, not the objects. $post_types = []; foreach ( $post_types_with_archive as $post_type_with_archive ) { $post_types[] = $post_type_with_archive->name; } return $post_types; } /** * Retrieves the list of post type names for which an archive indexable exists. * * @return array The list of names of post types with unindexed archive pages. */ protected function get_indexed_post_type_archives() { $results = $this->repository->query() ->select( 'object_sub_type' ) ->where( 'object_type', 'post-type-archive' ) ->where_equal( 'version', $this->version ) ->find_array(); if ( $results === false ) { return []; } $callback = static function ( $result ) { return $result['object_sub_type']; }; return \array_map( $callback, $results ); } /** * Returns a limited number of unindexed posts. * * @param int $limit Limit the maximum number of unindexed posts that are counted. * * @return int|false The limited number of unindexed posts. False if the query fails. */ public function get_limited_unindexed_count( $limit ) { return $this->get_total_unindexed( $limit ); } } actions/indexing/abstract-indexing-action.php 0000644 00000006173 15025657560 0015425 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; /** * Base class of indexing actions. */ abstract class Abstract_Indexing_Action implements Indexation_Action_Interface, Limited_Indexing_Action_Interface { /** * The transient name. * * This is a trick to force derived classes to define a transient themselves. * * @var string */ public const UNINDEXED_COUNT_TRANSIENT = null; /** * The transient cache key for limited counts. * * @var string */ public const UNINDEXED_LIMITED_COUNT_TRANSIENT = self::UNINDEXED_COUNT_TRANSIENT . '_limited'; /** * Builds a query for selecting the ID's of unindexed posts. * * @param bool $limit The maximum number of post IDs to return. * * @return string The prepared query string. */ abstract protected function get_select_query( $limit ); /** * Builds a query for counting the number of unindexed posts. * * @return string The prepared query string. */ abstract protected function get_count_query(); /** * Returns a limited number of unindexed posts. * * @param int $limit Limit the maximum number of unindexed posts that are counted. * * @return int The limited number of unindexed posts. 0 if the query fails. */ public function get_limited_unindexed_count( $limit ) { $transient = \get_transient( static::UNINDEXED_LIMITED_COUNT_TRANSIENT ); if ( $transient !== false ) { return (int) $transient; } \set_transient( static::UNINDEXED_LIMITED_COUNT_TRANSIENT, 0, ( \MINUTE_IN_SECONDS * 15 ) ); $query = $this->get_select_query( $limit ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Function get_count_query returns a prepared query. $unindexed_object_ids = ( $query === '' ) ? [] : $this->wpdb->get_col( $query ); $count = (int) \count( $unindexed_object_ids ); \set_transient( static::UNINDEXED_LIMITED_COUNT_TRANSIENT, $count, ( \MINUTE_IN_SECONDS * 15 ) ); return $count; } /** * Returns the total number of unindexed posts. * * @return int|false The total number of unindexed posts. False if the query fails. */ public function get_total_unindexed() { $transient = \get_transient( static::UNINDEXED_COUNT_TRANSIENT ); if ( $transient !== false ) { return (int) $transient; } // Store transient before doing the query so multiple requests won't make multiple queries. // Only store this for 15 minutes to ensure that if the query doesn't complete a wrong count is not kept too long. \set_transient( static::UNINDEXED_COUNT_TRANSIENT, 0, ( \MINUTE_IN_SECONDS * 15 ) ); $query = $this->get_count_query(); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Function get_count_query returns a prepared query. $count = ( $query === '' ) ? 0 : $this->wpdb->get_var( $query ); if ( $count === null ) { return false; } \set_transient( static::UNINDEXED_COUNT_TRANSIENT, $count, \DAY_IN_SECONDS ); /** * Action: 'wpseo_indexables_unindexed_calculated' - sets an option to timestamp when there are no unindexed indexables left. * * @internal */ \do_action( 'wpseo_indexables_unindexed_calculated', static::UNINDEXED_COUNT_TRANSIENT, $count ); return (int) $count; } } actions/indexing/indexable-term-indexation-action.php 0000644 00000012163 15025657560 0017053 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use wpdb; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Reindexing action for term indexables. */ class Indexable_Term_Indexation_Action extends Abstract_Indexing_Action { /** * The transient cache key. */ public const UNINDEXED_COUNT_TRANSIENT = 'wpseo_total_unindexed_terms'; /** * The transient cache key for limited counts. * * @var string */ public const UNINDEXED_LIMITED_COUNT_TRANSIENT = self::UNINDEXED_COUNT_TRANSIENT . '_limited'; /** * The post type helper. * * @var Taxonomy_Helper */ protected $taxonomy; /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The WordPress database instance. * * @var wpdb */ protected $wpdb; /** * The latest version of the Indexable term builder * * @var int */ protected $version; /** * Indexable_Term_Indexation_Action constructor * * @param Taxonomy_Helper $taxonomy The taxonomy helper. * @param Indexable_Repository $repository The indexable repository. * @param wpdb $wpdb The WordPress database instance. * @param Indexable_Builder_Versions $builder_versions The latest versions of all indexable builders. */ public function __construct( Taxonomy_Helper $taxonomy, Indexable_Repository $repository, wpdb $wpdb, Indexable_Builder_Versions $builder_versions ) { $this->taxonomy = $taxonomy; $this->repository = $repository; $this->wpdb = $wpdb; $this->version = $builder_versions->get_latest_version_for_type( 'term' ); } /** * Creates indexables for unindexed terms. * * @return Indexable[] The created indexables. */ public function index() { $query = $this->get_select_query( $this->get_limit() ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Function get_select_query returns a prepared query. $term_ids = ( $query === '' ) ? [] : $this->wpdb->get_col( $query ); $indexables = []; foreach ( $term_ids as $term_id ) { $indexables[] = $this->repository->find_by_id_and_type( (int) $term_id, 'term' ); } if ( \count( $indexables ) > 0 ) { \delete_transient( static::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( static::UNINDEXED_LIMITED_COUNT_TRANSIENT ); } return $indexables; } /** * Returns the number of terms that will be indexed in a single indexing pass. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_term_indexation_limit' - Allow filtering the number of terms indexed during each indexing pass. * * @param int $limit The maximum number of terms indexed. */ $limit = \apply_filters( 'wpseo_term_indexation_limit', 25 ); if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = 25; } return $limit; } /** * Builds a query for counting the number of unindexed terms. * * @return string The prepared query string. */ protected function get_count_query() { $indexable_table = Model::get_table_name( 'Indexable' ); $taxonomy_table = $this->wpdb->term_taxonomy; $public_taxonomies = $this->taxonomy->get_indexable_taxonomies(); if ( empty( $public_taxonomies ) ) { return ''; } $taxonomies_placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) ); $replacements = [ $this->version ]; \array_push( $replacements, ...$public_taxonomies ); // Warning: If this query is changed, makes sure to update the query in get_count_query as well. return $this->wpdb->prepare( " SELECT COUNT(term_id) FROM {$taxonomy_table} AS T LEFT JOIN $indexable_table AS I ON T.term_id = I.object_id AND I.object_type = 'term' AND I.version = %d WHERE I.object_id IS NULL AND taxonomy IN ($taxonomies_placeholders)", $replacements ); } /** * Builds a query for selecting the ID's of unindexed terms. * * @param bool $limit The maximum number of term IDs to return. * * @return string The prepared query string. */ protected function get_select_query( $limit = false ) { $indexable_table = Model::get_table_name( 'Indexable' ); $taxonomy_table = $this->wpdb->term_taxonomy; $public_taxonomies = $this->taxonomy->get_indexable_taxonomies(); if ( empty( $public_taxonomies ) ) { return ''; } $placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) ); $replacements = [ $this->version ]; \array_push( $replacements, ...$public_taxonomies ); $limit_query = ''; if ( $limit ) { $limit_query = 'LIMIT %d'; $replacements[] = $limit; } // Warning: If this query is changed, makes sure to update the query in get_count_query as well. return $this->wpdb->prepare( " SELECT term_id FROM {$taxonomy_table} AS T LEFT JOIN $indexable_table AS I ON T.term_id = I.object_id AND I.object_type = 'term' AND I.version = %d WHERE I.object_id IS NULL AND taxonomy IN ($placeholders) $limit_query", $replacements ); } } actions/indexing/indexing-complete-action.php 0000644 00000001227 15025657560 0015425 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use Yoast\WP\SEO\Helpers\Indexing_Helper; /** * Indexing action to call when the indexing is completed. */ class Indexing_Complete_Action { /** * The indexing helper. * * @var Indexing_Helper */ protected $indexing_helper; /** * Indexing_Complete_Action constructor. * * @param Indexing_Helper $indexing_helper The indexing helper. */ public function __construct( Indexing_Helper $indexing_helper ) { $this->indexing_helper = $indexing_helper; } /** * Wraps up the indexing process. * * @return void */ public function complete() { $this->indexing_helper->complete(); } } actions/indexing/abstract-link-indexing-action.php 0000644 00000006166 15025657560 0016362 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use wpdb; use Yoast\WP\SEO\Builders\Indexable_Link_Builder; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Models\SEO_Links; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Reindexing action for link indexables. */ abstract class Abstract_Link_Indexing_Action extends Abstract_Indexing_Action { /** * The link builder. * * @var Indexable_Link_Builder */ protected $link_builder; /** * The indexable helper. * * @var Indexable_Helper */ protected $indexable_helper; /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The WordPress database instance. * * @var wpdb */ protected $wpdb; /** * Indexable_Post_Indexing_Action constructor * * @param Indexable_Link_Builder $link_builder The indexable link builder. * @param Indexable_Helper $indexable_helper The indexable repository. * @param Indexable_Repository $repository The indexable repository. * @param wpdb $wpdb The WordPress database instance. */ public function __construct( Indexable_Link_Builder $link_builder, Indexable_Helper $indexable_helper, Indexable_Repository $repository, wpdb $wpdb ) { $this->link_builder = $link_builder; $this->indexable_helper = $indexable_helper; $this->repository = $repository; $this->wpdb = $wpdb; } /** * Builds links for indexables which haven't had their links indexed yet. * * @return SEO_Links[] The created SEO links. */ public function index() { $objects = $this->get_objects(); $indexables = []; foreach ( $objects as $object ) { $indexable = $this->repository->find_by_id_and_type( $object->id, $object->type ); if ( $indexable ) { $this->link_builder->build( $indexable, $object->content ); $this->indexable_helper->save_indexable( $indexable ); $indexables[] = $indexable; } } if ( \count( $indexables ) > 0 ) { \delete_transient( static::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( static::UNINDEXED_LIMITED_COUNT_TRANSIENT ); } return $indexables; } /** * In the case of term-links and post-links we want to use the total unindexed count, because using * the limited unindexed count actually leads to worse performance. * * @param int|bool $limit Unused. * * @return int The total number of unindexed links. */ public function get_limited_unindexed_count( $limit = false ) { return $this->get_total_unindexed(); } /** * Returns the number of texts that will be indexed in a single link indexing pass. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_link_indexing_limit' - Allow filtering the number of texts indexed during each link indexing pass. * * @param int $limit The maximum number of texts indexed. */ return \apply_filters( 'wpseo_link_indexing_limit', 5 ); } /** * Returns objects to be indexed. * * @return array Objects to be indexed, should be an array of objects with object_id, object_type and content. */ abstract protected function get_objects(); } actions/indexing/post-link-indexing-action.php 0000644 00000007532 15025657560 0015542 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Helpers\Post_Type_Helper; /** * Reindexing action for post link indexables. */ class Post_Link_Indexing_Action extends Abstract_Link_Indexing_Action { /** * The transient name. * * @var string */ public const UNINDEXED_COUNT_TRANSIENT = 'wpseo_unindexed_post_link_count'; /** * The transient cache key for limited counts. * * @var string */ public const UNINDEXED_LIMITED_COUNT_TRANSIENT = self::UNINDEXED_COUNT_TRANSIENT . '_limited'; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * Sets the required helper. * * @required * * @param Post_Type_Helper $post_type_helper The post type helper. * * @return void */ public function set_helper( Post_Type_Helper $post_type_helper ) { $this->post_type_helper = $post_type_helper; } /** * Returns objects to be indexed. * * @return array Objects to be indexed. */ protected function get_objects() { $query = $this->get_select_query( $this->get_limit() ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Function get_select_query returns a prepared query. $posts = $this->wpdb->get_results( $query ); return \array_map( static function ( $post ) { return (object) [ 'id' => (int) $post->ID, 'type' => 'post', 'content' => $post->post_content, ]; }, $posts ); } /** * Builds a query for counting the number of unindexed post links. * * @return string The prepared query string. */ protected function get_count_query() { $public_post_types = $this->post_type_helper->get_indexable_post_types(); $indexable_table = Model::get_table_name( 'Indexable' ); $links_table = Model::get_table_name( 'SEO_Links' ); // Warning: If this query is changed, makes sure to update the query in get_select_query as well. return $this->wpdb->prepare( "SELECT COUNT(P.ID) FROM {$this->wpdb->posts} AS P LEFT JOIN $indexable_table AS I ON P.ID = I.object_id AND I.link_count IS NOT NULL AND I.object_type = 'post' LEFT JOIN $links_table AS L ON L.post_id = P.ID AND L.target_indexable_id IS NULL AND L.type = 'internal' AND L.target_post_id IS NOT NULL AND L.target_post_id != 0 WHERE ( I.object_id IS NULL OR L.post_id IS NOT NULL ) AND P.post_status = 'publish' AND P.post_type IN (" . \implode( ', ', \array_fill( 0, \count( $public_post_types ), '%s' ) ) . ')', $public_post_types ); } /** * Builds a query for selecting the ID's of unindexed post links. * * @param int|false $limit The maximum number of post link IDs to return. * * @return string The prepared query string. */ protected function get_select_query( $limit = false ) { $public_post_types = $this->post_type_helper->get_indexable_post_types(); $indexable_table = Model::get_table_name( 'Indexable' ); $links_table = Model::get_table_name( 'SEO_Links' ); $replacements = $public_post_types; $limit_query = ''; if ( $limit ) { $limit_query = 'LIMIT %d'; $replacements[] = $limit; } // Warning: If this query is changed, makes sure to update the query in get_count_query as well. return $this->wpdb->prepare( " SELECT P.ID, P.post_content FROM {$this->wpdb->posts} AS P LEFT JOIN $indexable_table AS I ON P.ID = I.object_id AND I.link_count IS NOT NULL AND I.object_type = 'post' LEFT JOIN $links_table AS L ON L.post_id = P.ID AND L.target_indexable_id IS NULL AND L.type = 'internal' AND L.target_post_id IS NOT NULL AND L.target_post_id != 0 WHERE ( I.object_id IS NULL OR L.post_id IS NOT NULL ) AND P.post_status = 'publish' AND P.post_type IN (" . \implode( ', ', \array_fill( 0, \count( $public_post_types ), '%s' ) ) . ") $limit_query", $replacements ); } } actions/indexing/term-link-indexing-action.php 0000644 00000006677 15025657560 0015535 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Helpers\Taxonomy_Helper; /** * Reindexing action for term link indexables. */ class Term_Link_Indexing_Action extends Abstract_Link_Indexing_Action { /** * The transient name. * * @var string */ public const UNINDEXED_COUNT_TRANSIENT = 'wpseo_unindexed_term_link_count'; /** * The transient cache key for limited counts. * * @var string */ public const UNINDEXED_LIMITED_COUNT_TRANSIENT = self::UNINDEXED_COUNT_TRANSIENT . '_limited'; /** * The post type helper. * * @var Taxonomy_Helper */ protected $taxonomy_helper; /** * Sets the required helper. * * @required * * @param Taxonomy_Helper $taxonomy_helper The taxonomy helper. * * @return void */ public function set_helper( Taxonomy_Helper $taxonomy_helper ) { $this->taxonomy_helper = $taxonomy_helper; } /** * Returns objects to be indexed. * * @return array Objects to be indexed. */ protected function get_objects() { $query = $this->get_select_query( $this->get_limit() ); if ( $query === '' ) { return []; } // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Function get_select_query returns a prepared query. $terms = $this->wpdb->get_results( $query ); return \array_map( static function ( $term ) { return (object) [ 'id' => (int) $term->term_id, 'type' => 'term', 'content' => $term->description, ]; }, $terms ); } /** * Builds a query for counting the number of unindexed term links. * * @return string The prepared query string. */ protected function get_count_query() { $public_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies(); if ( empty( $public_taxonomies ) ) { return ''; } $placeholders = \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) ); $indexable_table = Model::get_table_name( 'Indexable' ); // Warning: If this query is changed, makes sure to update the query in get_select_query as well. return $this->wpdb->prepare( " SELECT COUNT(T.term_id) FROM {$this->wpdb->term_taxonomy} AS T LEFT JOIN $indexable_table AS I ON T.term_id = I.object_id AND I.object_type = 'term' AND I.link_count IS NOT NULL WHERE I.object_id IS NULL AND T.taxonomy IN ($placeholders)", $public_taxonomies ); } /** * Builds a query for selecting the ID's of unindexed term links. * * @param int|false $limit The maximum number of term link IDs to return. * * @return string The prepared query string. */ protected function get_select_query( $limit = false ) { $public_taxonomies = $this->taxonomy_helper->get_indexable_taxonomies(); if ( empty( $public_taxonomies ) ) { return ''; } $indexable_table = Model::get_table_name( 'Indexable' ); $replacements = $public_taxonomies; $limit_query = ''; if ( $limit ) { $limit_query = 'LIMIT %d'; $replacements[] = $limit; } // Warning: If this query is changed, makes sure to update the query in get_count_query as well. return $this->wpdb->prepare( " SELECT T.term_id, T.description FROM {$this->wpdb->term_taxonomy} AS T LEFT JOIN $indexable_table AS I ON T.term_id = I.object_id AND I.object_type = 'term' AND I.link_count IS NOT NULL WHERE I.object_id IS NULL AND T.taxonomy IN (" . \implode( ', ', \array_fill( 0, \count( $public_taxonomies ), '%s' ) ) . ") $limit_query", $replacements ); } } actions/indexing/indexable-general-indexation-action.php 0000644 00000007601 15025657560 0017522 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * General reindexing action for indexables. */ class Indexable_General_Indexation_Action implements Indexation_Action_Interface, Limited_Indexing_Action_Interface { /** * The transient cache key. */ public const UNINDEXED_COUNT_TRANSIENT = 'wpseo_total_unindexed_general_items'; /** * Represents the indexables repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * Indexable_General_Indexation_Action constructor. * * @param Indexable_Repository $indexable_repository The indexables repository. */ public function __construct( Indexable_Repository $indexable_repository ) { $this->indexable_repository = $indexable_repository; } /** * Returns the total number of unindexed objects. * * @return int The total number of unindexed objects. */ public function get_total_unindexed() { $transient = \get_transient( static::UNINDEXED_COUNT_TRANSIENT ); if ( $transient !== false ) { return (int) $transient; } $indexables_to_create = $this->query(); $result = \count( $indexables_to_create ); \set_transient( static::UNINDEXED_COUNT_TRANSIENT, $result, \DAY_IN_SECONDS ); /** * Action: 'wpseo_indexables_unindexed_calculated' - sets an option to timestamp when there are no unindexed indexables left. * * @internal */ \do_action( 'wpseo_indexables_unindexed_calculated', static::UNINDEXED_COUNT_TRANSIENT, $result ); return $result; } /** * Returns a limited number of unindexed posts. * * @param int $limit Limit the maximum number of unindexed posts that are counted. * * @return int|false The limited number of unindexed posts. False if the query fails. */ public function get_limited_unindexed_count( $limit ) { return $this->get_total_unindexed(); } /** * Creates indexables for unindexed system pages, the date archive, and the homepage. * * @return Indexable[] The created indexables. */ public function index() { $indexables = []; $indexables_to_create = $this->query(); if ( isset( $indexables_to_create['404'] ) ) { $indexables[] = $this->indexable_repository->find_for_system_page( '404' ); } if ( isset( $indexables_to_create['search'] ) ) { $indexables[] = $this->indexable_repository->find_for_system_page( 'search-result' ); } if ( isset( $indexables_to_create['date_archive'] ) ) { $indexables[] = $this->indexable_repository->find_for_date_archive(); } if ( isset( $indexables_to_create['home_page'] ) ) { $indexables[] = $this->indexable_repository->find_for_home_page(); } \set_transient( static::UNINDEXED_COUNT_TRANSIENT, 0, \DAY_IN_SECONDS ); return $indexables; } /** * Returns the number of objects that will be indexed in a single indexing pass. * * @return int The limit. */ public function get_limit() { // This matches the maximum number of indexables created by this action. return 4; } /** * Check which indexables already exist and return the values of the ones to create. * * @return array The indexable types to create. */ private function query() { $indexables_to_create = []; if ( ! $this->indexable_repository->find_for_system_page( '404', false ) ) { $indexables_to_create['404'] = true; } if ( ! $this->indexable_repository->find_for_system_page( 'search-result', false ) ) { $indexables_to_create['search'] = true; } if ( ! $this->indexable_repository->find_for_date_archive( false ) ) { $indexables_to_create['date_archive'] = true; } $need_home_page_indexable = ( (int) \get_option( 'page_on_front' ) === 0 && \get_option( 'show_on_front' ) === 'posts' ); if ( $need_home_page_indexable && ! $this->indexable_repository->find_for_home_page( false ) ) { $indexables_to_create['home_page'] = true; } return $indexables_to_create; } } actions/indexing/indexable-post-indexation-action.php 0000644 00000013313 15025657560 0017067 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Indexing; use wpdb; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Helpers\Post_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Values\Indexables\Indexable_Builder_Versions; /** * Reindexing action for post indexables. */ class Indexable_Post_Indexation_Action extends Abstract_Indexing_Action { /** * The transient cache key. * * @var string */ public const UNINDEXED_COUNT_TRANSIENT = 'wpseo_total_unindexed_posts'; /** * The transient cache key for limited counts. * * @var string */ public const UNINDEXED_LIMITED_COUNT_TRANSIENT = self::UNINDEXED_COUNT_TRANSIENT . '_limited'; /** * The post type helper. * * @var Post_Type_Helper */ protected $post_type_helper; /** * The post helper. * * @var Post_Helper */ protected $post_helper; /** * The indexable repository. * * @var Indexable_Repository */ protected $repository; /** * The WordPress database instance. * * @var wpdb */ protected $wpdb; /** * The latest version of Post Indexables. * * @var int */ protected $version; /** * Indexable_Post_Indexing_Action constructor * * @param Post_Type_Helper $post_type_helper The post type helper. * @param Indexable_Repository $repository The indexable repository. * @param wpdb $wpdb The WordPress database instance. * @param Indexable_Builder_Versions $builder_versions The latest versions for each Indexable type. * @param Post_Helper $post_helper The post helper. */ public function __construct( Post_Type_Helper $post_type_helper, Indexable_Repository $repository, wpdb $wpdb, Indexable_Builder_Versions $builder_versions, Post_Helper $post_helper ) { $this->post_type_helper = $post_type_helper; $this->repository = $repository; $this->wpdb = $wpdb; $this->version = $builder_versions->get_latest_version_for_type( 'post' ); $this->post_helper = $post_helper; } /** * Creates indexables for unindexed posts. * * @return Indexable[] The created indexables. */ public function index() { $query = $this->get_select_query( $this->get_limit() ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Function get_select_query returns a prepared query. $post_ids = $this->wpdb->get_col( $query ); $indexables = []; foreach ( $post_ids as $post_id ) { $indexables[] = $this->repository->find_by_id_and_type( (int) $post_id, 'post' ); } if ( \count( $indexables ) > 0 ) { \delete_transient( static::UNINDEXED_COUNT_TRANSIENT ); \delete_transient( static::UNINDEXED_LIMITED_COUNT_TRANSIENT ); } return $indexables; } /** * Returns the number of posts that will be indexed in a single indexing pass. * * @return int The limit. */ public function get_limit() { /** * Filter 'wpseo_post_indexation_limit' - Allow filtering the amount of posts indexed during each indexing pass. * * @param int $limit The maximum number of posts indexed. */ $limit = \apply_filters( 'wpseo_post_indexation_limit', 25 ); if ( ! \is_int( $limit ) || $limit < 1 ) { $limit = 25; } return $limit; } /** * Builds a query for counting the number of unindexed posts. * * @return string The prepared query string. */ protected function get_count_query() { $indexable_table = Model::get_table_name( 'Indexable' ); $post_types = $this->post_type_helper->get_indexable_post_types(); $excluded_post_statuses = $this->post_helper->get_excluded_post_statuses(); $replacements = \array_merge( $post_types, $excluded_post_statuses ); $replacements[] = $this->version; // Warning: If this query is changed, makes sure to update the query in get_select_query as well. // @phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber return $this->wpdb->prepare( " SELECT COUNT(P.ID) FROM {$this->wpdb->posts} AS P WHERE P.post_type IN (" . \implode( ', ', \array_fill( 0, \count( $post_types ), '%s' ) ) . ') AND P.post_status NOT IN (' . \implode( ', ', \array_fill( 0, \count( $excluded_post_statuses ), '%s' ) ) . ") AND P.ID not in ( SELECT I.object_id from $indexable_table as I WHERE I.object_type = 'post' AND I.version = %d )", $replacements ); } /** * Builds a query for selecting the ID's of unindexed posts. * * @param bool $limit The maximum number of post IDs to return. * * @return string The prepared query string. */ protected function get_select_query( $limit = false ) { $indexable_table = Model::get_table_name( 'Indexable' ); $post_types = $this->post_type_helper->get_indexable_post_types(); $excluded_post_statuses = $this->post_helper->get_excluded_post_statuses(); $replacements = \array_merge( $post_types, $excluded_post_statuses ); $replacements[] = $this->version; $limit_query = ''; if ( $limit ) { $limit_query = 'LIMIT %d'; $replacements[] = $limit; } // Warning: If this query is changed, makes sure to update the query in get_count_query as well. // @phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber return $this->wpdb->prepare( " SELECT P.ID FROM {$this->wpdb->posts} AS P WHERE P.post_type IN (" . \implode( ', ', \array_fill( 0, \count( $post_types ), '%s' ) ) . ') AND P.post_status NOT IN (' . \implode( ', ', \array_fill( 0, \count( $excluded_post_statuses ), '%s' ) ) . ") AND P.ID not in ( SELECT I.object_id from $indexable_table as I WHERE I.object_type = 'post' AND I.version = %d ) $limit_query", $replacements ); } } actions/configuration/first-time-configuration-action.php 0000644 00000021142 15025657560 0020002 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Configuration; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Social_Profiles_Helper; /** * Class First_Time_Configuration_Action. */ class First_Time_Configuration_Action { /** * The fields for the site representation payload. */ public const SITE_REPRESENTATION_FIELDS = [ 'company_or_person', 'company_name', 'website_name', 'company_logo', 'company_logo_id', 'person_logo', 'person_logo_id', 'company_or_person_user_id', 'description', ]; /** * The Options_Helper instance. * * @var Options_Helper */ protected $options_helper; /** * The Social_Profiles_Helper instance. * * @var Social_Profiles_Helper */ protected $social_profiles_helper; /** * First_Time_Configuration_Action constructor. * * @param Options_Helper $options_helper The WPSEO options helper. * @param Social_Profiles_Helper $social_profiles_helper The social profiles helper. */ public function __construct( Options_Helper $options_helper, Social_Profiles_Helper $social_profiles_helper ) { $this->options_helper = $options_helper; $this->social_profiles_helper = $social_profiles_helper; } /** * Stores the values for the site representation. * * @param array $params The values to store. * * @return object The response object. */ public function set_site_representation( $params ) { $failures = []; $old_values = $this->get_old_values( self::SITE_REPRESENTATION_FIELDS ); foreach ( self::SITE_REPRESENTATION_FIELDS as $field_name ) { if ( isset( $params[ $field_name ] ) ) { $result = $this->options_helper->set( $field_name, $params[ $field_name ] ); if ( ! $result ) { $failures[] = $field_name; } } } // Delete cached logos in the db. $this->options_helper->set( 'company_logo_meta', false ); $this->options_helper->set( 'person_logo_meta', false ); /** * Action: 'wpseo_post_update_site_representation' - Allows for Hiive event tracking. * * @param array $params The new values of the options. * @param array $old_values The old values of the options. * @param array $failures The options that failed to be saved. * * @internal */ \do_action( 'wpseo_ftc_post_update_site_representation', $params, $old_values, $failures ); if ( \count( $failures ) === 0 ) { return (object) [ 'success' => true, 'status' => 200, ]; } return (object) [ 'success' => false, 'status' => 500, 'error' => 'Could not save some options in the database', 'failures' => $failures, ]; } /** * Stores the values for the social profiles. * * @param array $params The values to store. * * @return object The response object. */ public function set_social_profiles( $params ) { $old_values = $this->get_old_values( \array_keys( $this->social_profiles_helper->get_organization_social_profile_fields() ) ); $failures = $this->social_profiles_helper->set_organization_social_profiles( $params ); /** * Action: 'wpseo_post_update_social_profiles' - Allows for Hiive event tracking. * * @param array $params The new values of the options. * @param array $old_values The old values of the options. * @param array $failures The options that failed to be saved. * * @internal */ \do_action( 'wpseo_ftc_post_update_social_profiles', $params, $old_values, $failures ); if ( empty( $failures ) ) { return (object) [ 'success' => true, 'status' => 200, ]; } return (object) [ 'success' => false, 'status' => 200, 'error' => 'Could not save some options in the database', 'failures' => $failures, ]; } /** * Stores the values for the social profiles. * * @param array $params The values to store. * * @return object The response object. */ public function set_person_social_profiles( $params ) { $social_profiles = \array_filter( $params, static function ( $key ) { return $key !== 'user_id'; }, \ARRAY_FILTER_USE_KEY ); $failures = $this->social_profiles_helper->set_person_social_profiles( $params['user_id'], $social_profiles ); if ( \count( $failures ) === 0 ) { return (object) [ 'success' => true, 'status' => 200, ]; } return (object) [ 'success' => false, 'status' => 200, 'error' => 'Could not save some options in the database', 'failures' => $failures, ]; } /** * Gets the values for the social profiles. * * @param int $user_id The person ID. * * @return object The response object. */ public function get_person_social_profiles( $user_id ) { return (object) [ 'success' => true, 'status' => 200, 'social_profiles' => $this->social_profiles_helper->get_person_social_profiles( $user_id ), ]; } /** * Stores the values to enable/disable tracking. * * @param array $params The values to store. * * @return object The response object. */ public function set_enable_tracking( $params ) { $success = true; $option_value = $this->options_helper->get( 'tracking' ); if ( $option_value !== $params['tracking'] ) { $this->options_helper->set( 'toggled_tracking', true ); $success = $this->options_helper->set( 'tracking', $params['tracking'] ); } /** * Action: 'wpseo_post_update_enable_tracking' - Allows for Hiive event tracking. * * @param array $new_value The new value. * @param array $old_value The old value. * @param bool $failure Whether the option failed to be stored. * * @internal */ // $success is negated to be aligned with the other two actions which pass $failures. \do_action( 'wpseo_ftc_post_update_enable_tracking', $params['tracking'], $option_value, ! $success ); if ( $success ) { return (object) [ 'success' => true, 'status' => 200, ]; } return (object) [ 'success' => false, 'status' => 500, 'error' => 'Could not save the option in the database', ]; } /** * Checks if the current user has the capability a specific user. * * @param int $user_id The id of the user to be edited. * * @return object The response object. */ public function check_capability( $user_id ) { if ( $this->can_edit_profile( $user_id ) ) { return (object) [ 'success' => true, 'status' => 200, ]; } return (object) [ 'success' => false, 'status' => 403, ]; } /** * Stores the first time configuration state. * * @param array $params The values to store. * * @return object The response object. */ public function save_configuration_state( $params ) { // If the finishedSteps param is not present in the REST request, it's a malformed request. if ( ! isset( $params['finishedSteps'] ) ) { return (object) [ 'success' => false, 'status' => 400, 'error' => 'Bad request', ]; } // Sanitize input. $finished_steps = \array_map( '\sanitize_text_field', \wp_unslash( $params['finishedSteps'] ) ); $success = $this->options_helper->set( 'configuration_finished_steps', $finished_steps ); if ( ! $success ) { return (object) [ 'success' => false, 'status' => 500, 'error' => 'Could not save the option in the database', ]; } // If all the five steps of the configuration have been completed, set first_time_install option to false. if ( \count( $params['finishedSteps'] ) === 3 ) { $this->options_helper->set( 'first_time_install', false ); } return (object) [ 'success' => true, 'status' => 200, ]; } /** * Gets the first time configuration state. * * @return object The response object. */ public function get_configuration_state() { $configuration_option = $this->options_helper->get( 'configuration_finished_steps' ); if ( $configuration_option !== null ) { return (object) [ 'success' => true, 'status' => 200, 'data' => $configuration_option, ]; } return (object) [ 'success' => false, 'status' => 500, 'error' => 'Could not get data from the database', ]; } /** * Checks if the current user has the capability to edit a specific user. * * @param int $person_id The id of the person to edit. * * @return bool */ private function can_edit_profile( $person_id ) { return \current_user_can( 'edit_user', $person_id ); } /** * Gets the old values for the given fields. * * @param array $fields_names The fields to get the old values for. * * @return array The old values. */ private function get_old_values( array $fields_names ): array { $old_values = []; foreach ( $fields_names as $field_name ) { $old_values[ $field_name ] = $this->options_helper->get( $field_name ); } return $old_values; } } actions/addon-installation/addon-install-action.php 0000644 00000007562 15025657560 0016532 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Addon_Installation; use Plugin_Upgrader; use WP_Error; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Already_Installed_Exception; use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Installation_Error_Exception; use Yoast\WP\SEO\Exceptions\Addon_Installation\User_Cannot_Install_Plugins_Exception; use Yoast\WP\SEO\Helpers\Require_File_Helper; /** * Represents the endpoint for downloading and installing a zip-file from MyYoast. */ class Addon_Install_Action { /** * The addon manager. * * @var WPSEO_Addon_Manager */ protected $addon_manager; /** * The require file helper. * * @var Require_File_Helper */ protected $require_file_helper; /** * Addon_Activate_Action constructor. * * @param WPSEO_Addon_Manager $addon_manager The addon manager. * @param Require_File_Helper $require_file_helper A helper that can require files. */ public function __construct( WPSEO_Addon_Manager $addon_manager, Require_File_Helper $require_file_helper ) { $this->addon_manager = $addon_manager; $this->require_file_helper = $require_file_helper; } /** * Installs the plugin based on the given slug. * * @param string $plugin_slug The plugin slug to install. * @param string $download_url The plugin download URL. * * @return bool True when install is successful. * * @throws Addon_Already_Installed_Exception When the addon is already installed. * @throws Addon_Installation_Error_Exception When the installation encounters an error. * @throws User_Cannot_Install_Plugins_Exception When the user does not have the permissions to install plugins. */ public function install_addon( $plugin_slug, $download_url ) { if ( ! \current_user_can( 'install_plugins' ) ) { throw new User_Cannot_Install_Plugins_Exception( $plugin_slug ); } if ( $this->is_installed( $plugin_slug ) ) { throw new Addon_Already_Installed_Exception( $plugin_slug ); } $this->load_wordpress_classes(); $install_result = $this->install( $download_url ); if ( \is_wp_error( $install_result ) ) { throw new Addon_Installation_Error_Exception( $install_result->get_error_message() ); } return $install_result; } /** * Requires the files needed from WordPress itself. * * @codeCoverageIgnore * * @return void */ protected function load_wordpress_classes() { if ( ! \class_exists( 'WP_Upgrader' ) ) { $this->require_file_helper->require_file_once( \ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); } if ( ! \class_exists( 'Plugin_Upgrader' ) ) { $this->require_file_helper->require_file_once( \ABSPATH . 'wp-admin/includes/class-plugin-upgrader.php' ); } if ( ! \class_exists( 'WP_Upgrader_Skin' ) ) { $this->require_file_helper->require_file_once( \ABSPATH . 'wp-admin/includes/class-wp-upgrader-skin.php' ); } if ( ! \function_exists( 'get_plugin_data' ) ) { $this->require_file_helper->require_file_once( \ABSPATH . 'wp-admin/includes/plugin.php' ); } if ( ! \function_exists( 'request_filesystem_credentials' ) ) { $this->require_file_helper->require_file_once( \ABSPATH . 'wp-admin/includes/file.php' ); } } /** * Checks is a plugin is installed. * * @param string $plugin_slug The plugin to check. * * @return bool True when plugin is installed. */ protected function is_installed( $plugin_slug ) { return $this->addon_manager->get_plugin_file( $plugin_slug ) !== false; } /** * Runs the installation by using the WordPress installation routine. * * @codeCoverageIgnore Contains WordPress specific logic. * * @param string $plugin_download The url to the download. * * @return bool|WP_Error True when success, WP_Error when something went wrong. */ protected function install( $plugin_download ) { $plugin_upgrader = new Plugin_Upgrader(); return $plugin_upgrader->install( $plugin_download ); } } actions/addon-installation/addon-activate-action.php 0000644 00000004522 15025657560 0016655 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Addon_Installation; use WPSEO_Addon_Manager; use Yoast\WP\SEO\Exceptions\Addon_Installation\Addon_Activation_Error_Exception; use Yoast\WP\SEO\Exceptions\Addon_Installation\User_Cannot_Activate_Plugins_Exception; use Yoast\WP\SEO\Helpers\Require_File_Helper; /** * Represents the endpoint for activating a specific Yoast Plugin on WordPress. */ class Addon_Activate_Action { /** * The addon manager. * * @var WPSEO_Addon_Manager */ protected $addon_manager; /** * The require file helper. * * @var Require_File_Helper */ protected $require_file_helper; /** * Addon_Activate_Action constructor. * * @param WPSEO_Addon_Manager $addon_manager The addon manager. * @param Require_File_Helper $require_file_helper A file helper. */ public function __construct( WPSEO_Addon_Manager $addon_manager, Require_File_Helper $require_file_helper ) { $this->addon_manager = $addon_manager; $this->require_file_helper = $require_file_helper; } /** * Activates the plugin based on the given plugin file. * * @param string $plugin_slug The plugin slug to get download url for. * * @return bool True when activation is successful. * * @throws Addon_Activation_Error_Exception Exception when the activation encounters an error. * @throws User_Cannot_Activate_Plugins_Exception Exception when the user is not allowed to activate. */ public function activate_addon( $plugin_slug ) { if ( ! \current_user_can( 'activate_plugins' ) ) { throw new User_Cannot_Activate_Plugins_Exception(); } if ( $this->addon_manager->is_installed( $plugin_slug ) ) { return true; } $this->load_wordpress_classes(); $plugin_file = $this->addon_manager->get_plugin_file( $plugin_slug ); $activation_result = \activate_plugin( $plugin_file ); if ( $activation_result !== null && \is_wp_error( $activation_result ) ) { throw new Addon_Activation_Error_Exception( $activation_result->get_error_message() ); } return true; } /** * Requires the files needed from WordPress itself. * * @codeCoverageIgnore Only loads a WordPress file. * * @return void */ protected function load_wordpress_classes() { if ( ! \function_exists( 'get_plugins' ) ) { $this->require_file_helper->require_file_once( \ABSPATH . 'wp-admin/includes/plugin.php' ); } } } actions/integrations-action.php 0000644 00000002366 15025657560 0012720 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Class Integrations_Action. */ class Integrations_Action { /** * The Options_Helper instance. * * @var Options_Helper */ protected $options_helper; /** * Integrations_Action constructor. * * @param Options_Helper $options_helper The WPSEO options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Sets an integration state. * * @param string $integration_name The name of the integration to activate/deactivate. * @param bool $value The value to store. * * @return object The response object. */ public function set_integration_active( $integration_name, $value ) { $option_name = $integration_name . '_integration_active'; $success = true; $option_value = $this->options_helper->get( $option_name ); if ( $option_value !== $value ) { $success = $this->options_helper->set( $option_name, $value ); } if ( $success ) { return (object) [ 'success' => true, 'status' => 200, ]; } return (object) [ 'success' => false, 'status' => 500, 'error' => 'Could not save the option in the database', ]; } } actions/wincher/wincher-login-action.php 0000644 00000003336 15025657560 0014414 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Wincher; use Yoast\WP\SEO\Config\Wincher_Client; use Yoast\WP\SEO\Exceptions\OAuth\Authentication_Failed_Exception; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Class Wincher_Login_Action */ class Wincher_Login_Action { /** * The Wincher_Client instance. * * @var Wincher_Client */ protected $client; /** * The Options_Helper instance. * * @var Options_Helper */ protected $options_helper; /** * Wincher_Login_Action constructor. * * @param Wincher_Client $client The API client. * @param Options_Helper $options_helper The options helper. */ public function __construct( Wincher_Client $client, Options_Helper $options_helper ) { $this->client = $client; $this->options_helper = $options_helper; } /** * Returns the authorization URL. * * @return object The response object. */ public function get_authorization_url() { return (object) [ 'status' => 200, 'url' => $this->client->get_authorization_url(), ]; } /** * Authenticates with Wincher to request the necessary tokens. * * @param string $code The authentication code to use to request a token with. * @param string $website_id The website id associated with the code. * * @return object The response object. */ public function authenticate( $code, $website_id ) { // Code has already been validated at this point. No need to do that again. try { $tokens = $this->client->request_tokens( $code ); $this->options_helper->set( 'wincher_website_id', $website_id ); return (object) [ 'tokens' => $tokens->to_array(), 'status' => 200, ]; } catch ( Authentication_Failed_Exception $e ) { return $e->get_response(); } } } actions/wincher/wincher-account-action.php 0000644 00000004744 15025657560 0014744 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Wincher; use Exception; use Yoast\WP\SEO\Config\Wincher_Client; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Class Wincher_Account_Action */ class Wincher_Account_Action { public const ACCOUNT_URL = 'https://api.wincher.com/beta/account'; public const UPGRADE_CAMPAIGN_URL = 'https://api.wincher.com/v1/yoast/upgrade-campaign'; /** * The Wincher_Client instance. * * @var Wincher_Client */ protected $client; /** * The Options_Helper instance. * * @var Options_Helper */ protected $options_helper; /** * Wincher_Account_Action constructor. * * @param Wincher_Client $client The API client. * @param Options_Helper $options_helper The options helper. */ public function __construct( Wincher_Client $client, Options_Helper $options_helper ) { $this->client = $client; $this->options_helper = $options_helper; } /** * Checks the account limit for tracking keyphrases. * * @return object The response object. */ public function check_limit() { // Code has already been validated at this point. No need to do that again. try { $results = $this->client->get( self::ACCOUNT_URL ); $usage = $results['limits']['keywords']['usage']; $limit = $results['limits']['keywords']['limit']; $history = $results['limits']['history_days']; return (object) [ 'canTrack' => ( $limit === null || $usage < $limit ), 'limit' => $limit, 'usage' => $usage, 'historyDays' => $history, 'status' => 200, ]; } catch ( Exception $e ) { return (object) [ 'status' => $e->getCode(), 'error' => $e->getMessage(), ]; } } /** * Gets the upgrade campaign. * * @return object The response object. */ public function get_upgrade_campaign() { try { $result = $this->client->get( self::UPGRADE_CAMPAIGN_URL ); $type = ( $result['type'] ?? null ); $months = ( $result['months'] ?? null ); $discount = ( $result['value'] ?? null ); // We display upgrade discount only if it's a rate discount and positive months/discount. if ( $type === 'RATE' && $months && $discount ) { return (object) [ 'discount' => $discount, 'months' => $months, 'status' => 200, ]; } return (object) [ 'discount' => null, 'months' => null, 'status' => 200, ]; } catch ( Exception $e ) { return (object) [ 'status' => $e->getCode(), 'error' => $e->getMessage(), ]; } } } actions/wincher/wincher-keyphrases-action.php 0000644 00000022224 15025657560 0015457 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions\Wincher; use Exception; use WP_Post; use WPSEO_Utils; use Yoast\WP\SEO\Config\Wincher_Client; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Class Wincher_Keyphrases_Action */ class Wincher_Keyphrases_Action { /** * The Wincher keyphrase URL for bulk addition. * * @var string */ public const KEYPHRASES_ADD_URL = 'https://api.wincher.com/beta/websites/%s/keywords/bulk'; /** * The Wincher tracked keyphrase retrieval URL. * * @var string */ public const KEYPHRASES_URL = 'https://api.wincher.com/beta/yoast/%s'; /** * The Wincher delete tracked keyphrase URL. * * @var string */ public const KEYPHRASE_DELETE_URL = 'https://api.wincher.com/beta/websites/%s/keywords/%s'; /** * The Wincher_Client instance. * * @var Wincher_Client */ protected $client; /** * The Options_Helper instance. * * @var Options_Helper */ protected $options_helper; /** * The Indexable_Repository instance. * * @var Indexable_Repository */ protected $indexable_repository; /** * Wincher_Keyphrases_Action constructor. * * @param Wincher_Client $client The API client. * @param Options_Helper $options_helper The options helper. * @param Indexable_Repository $indexable_repository The indexables repository. */ public function __construct( Wincher_Client $client, Options_Helper $options_helper, Indexable_Repository $indexable_repository ) { $this->client = $client; $this->options_helper = $options_helper; $this->indexable_repository = $indexable_repository; } /** * Sends the tracking API request for one or more keyphrases. * * @param string|array $keyphrases One or more keyphrases that should be tracked. * @param Object $limits The limits API call response data. * * @return Object The reponse object. */ public function track_keyphrases( $keyphrases, $limits ) { try { $endpoint = \sprintf( self::KEYPHRASES_ADD_URL, $this->options_helper->get( 'wincher_website_id' ) ); // Enforce arrrays to ensure a consistent way of preparing the request. if ( ! \is_array( $keyphrases ) ) { $keyphrases = [ $keyphrases ]; } // Calculate if the user would exceed their limit. // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- To ensure JS code style, this can be ignored. if ( ! $limits->canTrack || $this->would_exceed_limits( $keyphrases, $limits ) ) { $response = [ 'limit' => $limits->limit, 'error' => 'Account limit exceeded', 'status' => 400, ]; return $this->to_result_object( $response ); } $formatted_keyphrases = \array_values( \array_map( static function ( $keyphrase ) { return [ 'keyword' => $keyphrase, 'groups' => [], ]; }, $keyphrases ) ); $results = $this->client->post( $endpoint, WPSEO_Utils::format_json_encode( $formatted_keyphrases ) ); if ( ! \array_key_exists( 'data', $results ) ) { return $this->to_result_object( $results ); } // The endpoint returns a lot of stuff that we don't want/need. $results['data'] = \array_map( static function ( $keyphrase ) { return [ 'id' => $keyphrase['id'], 'keyword' => $keyphrase['keyword'], ]; }, $results['data'] ); $results['data'] = \array_combine( \array_column( $results['data'], 'keyword' ), \array_values( $results['data'] ) ); return $this->to_result_object( $results ); } catch ( Exception $e ) { return (object) [ 'error' => $e->getMessage(), 'status' => $e->getCode(), ]; } } /** * Sends an untrack request for the passed keyword ID. * * @param int $keyphrase_id The ID of the keyphrase to untrack. * * @return object The response object. */ public function untrack_keyphrase( $keyphrase_id ) { try { $endpoint = \sprintf( self::KEYPHRASE_DELETE_URL, $this->options_helper->get( 'wincher_website_id' ), $keyphrase_id ); $this->client->delete( $endpoint ); return (object) [ 'status' => 200, ]; } catch ( Exception $e ) { return (object) [ 'error' => $e->getMessage(), 'status' => $e->getCode(), ]; } } /** * Gets the keyphrase data for the passed keyphrases. * Retrieves all available data if no keyphrases are provided. * * @param array|null $used_keyphrases The currently used keyphrases. Optional. * @param string|null $permalink The current permalink. Optional. * @param string|null $start_at The position start date. Optional. * * @return object The keyphrase chart data. */ public function get_tracked_keyphrases( $used_keyphrases = null, $permalink = null, $start_at = null ) { try { if ( $used_keyphrases === null ) { $used_keyphrases = $this->collect_all_keyphrases(); } // If we still have no keyphrases the API will return an error, so // don't even bother sending a request. if ( empty( $used_keyphrases ) ) { return $this->to_result_object( [ 'data' => [], 'status' => 200, ] ); } $endpoint = \sprintf( self::KEYPHRASES_URL, $this->options_helper->get( 'wincher_website_id' ) ); $results = $this->client->post( $endpoint, WPSEO_Utils::format_json_encode( [ 'keywords' => $used_keyphrases, 'url' => $permalink, 'start_at' => $start_at, ] ), [ 'timeout' => 60, ] ); if ( ! \array_key_exists( 'data', $results ) ) { return $this->to_result_object( $results ); } $results['data'] = $this->filter_results_by_used_keyphrases( $results['data'], $used_keyphrases ); // Extract the positional data and assign it to the keyphrase. $results['data'] = \array_combine( \array_column( $results['data'], 'keyword' ), \array_values( $results['data'] ) ); return $this->to_result_object( $results ); } catch ( Exception $e ) { return (object) [ 'error' => $e->getMessage(), 'status' => $e->getCode(), ]; } } /** * Collects the keyphrases associated with the post. * * @param WP_Post $post The post object. * * @return array The keyphrases. */ public function collect_keyphrases_from_post( $post ) { $keyphrases = []; $primary_keyphrase = $this->indexable_repository ->query() ->select( 'primary_focus_keyword' ) ->where( 'object_id', $post->ID ) ->find_one(); if ( $primary_keyphrase ) { $keyphrases[] = $primary_keyphrase->primary_focus_keyword; } /** * Filters the keyphrases collected by the Wincher integration from the post. * * @param array $keyphrases The keyphrases array. * @param int $post_id The ID of the post. */ return \apply_filters( 'wpseo_wincher_keyphrases_from_post', $keyphrases, $post->ID ); } /** * Collects all keyphrases known to Yoast. * * @return array */ protected function collect_all_keyphrases() { // Collect primary keyphrases first. $keyphrases = \array_column( $this->indexable_repository ->query() ->select( 'primary_focus_keyword' ) ->where_not_null( 'primary_focus_keyword' ) ->where( 'object_type', 'post' ) ->where_not_equal( 'post_status', 'trash' ) ->distinct() ->find_array(), 'primary_focus_keyword' ); /** * Filters the keyphrases collected by the Wincher integration from all the posts. * * @param array $keyphrases The keyphrases array. */ $keyphrases = \apply_filters( 'wpseo_wincher_all_keyphrases', $keyphrases ); // Filter out empty entries. return \array_filter( $keyphrases ); } /** * Filters the results based on the passed keyphrases. * * @param array $results The results to filter. * @param array $used_keyphrases The used keyphrases. * * @return array The filtered results. */ protected function filter_results_by_used_keyphrases( $results, $used_keyphrases ) { return \array_filter( $results, static function ( $result ) use ( $used_keyphrases ) { return \in_array( $result['keyword'], \array_map( 'strtolower', $used_keyphrases ), true ); } ); } /** * Determines whether the amount of keyphrases would mean the user exceeds their account limits. * * @param string|array $keyphrases The keyphrases to be added. * @param object $limits The current account limits. * * @return bool Whether the limit is exceeded. */ protected function would_exceed_limits( $keyphrases, $limits ) { if ( ! \is_array( $keyphrases ) ) { $keyphrases = [ $keyphrases ]; } if ( $limits->limit === null ) { return false; } return ( \count( $keyphrases ) + $limits->usage ) > $limits->limit; } /** * Converts the passed dataset to an object. * * @param array $result The result dataset to convert to an object. * * @return object The result object. */ protected function to_result_object( $result ) { if ( \array_key_exists( 'data', $result ) ) { $result['results'] = (object) $result['data']; unset( $result['data'] ); } if ( \array_key_exists( 'message', $result ) ) { $result['error'] = $result['message']; unset( $result['message'] ); } return (object) $result; } } actions/alert-dismissal-action.php 0000644 00000012576 15025657560 0013313 0 ustar 00 <?php namespace Yoast\WP\SEO\Actions; use Yoast\WP\SEO\Helpers\User_Helper; /** * Class Alert_Dismissal_Action. */ class Alert_Dismissal_Action { public const USER_META_KEY = '_yoast_alerts_dismissed'; /** * Holds the user helper instance. * * @var User_Helper */ protected $user; /** * Constructs Alert_Dismissal_Action. * * @param User_Helper $user User helper. */ public function __construct( User_Helper $user ) { $this->user = $user; } /** * Dismisses an alert. * * @param string $alert_identifier Alert identifier. * * @return bool Whether the dismiss was successful or not. */ public function dismiss( $alert_identifier ) { $user_id = $this->user->get_current_user_id(); if ( $user_id === 0 ) { return false; } if ( $this->is_allowed( $alert_identifier ) === false ) { return false; } $dismissed_alerts = $this->get_dismissed_alerts( $user_id ); if ( $dismissed_alerts === false ) { return false; } if ( \array_key_exists( $alert_identifier, $dismissed_alerts ) === true ) { // The alert is already dismissed. return true; } // Add this alert to the dismissed alerts. $dismissed_alerts[ $alert_identifier ] = true; // Save. return $this->user->update_meta( $user_id, static::USER_META_KEY, $dismissed_alerts ) !== false; } /** * Resets an alert. * * @param string $alert_identifier Alert identifier. * * @return bool Whether the reset was successful or not. */ public function reset( $alert_identifier ) { $user_id = $this->user->get_current_user_id(); if ( $user_id === 0 ) { return false; } if ( $this->is_allowed( $alert_identifier ) === false ) { return false; } $dismissed_alerts = $this->get_dismissed_alerts( $user_id ); if ( $dismissed_alerts === false ) { return false; } $amount_of_dismissed_alerts = \count( $dismissed_alerts ); if ( $amount_of_dismissed_alerts === 0 ) { // No alerts: nothing to reset. return true; } if ( \array_key_exists( $alert_identifier, $dismissed_alerts ) === false ) { // Alert not found: nothing to reset. return true; } if ( $amount_of_dismissed_alerts === 1 ) { // The 1 remaining dismissed alert is the alert to reset: delete the alerts user meta row. return $this->user->delete_meta( $user_id, static::USER_META_KEY, $dismissed_alerts ); } // Remove this alert from the dismissed alerts. unset( $dismissed_alerts[ $alert_identifier ] ); // Save. return $this->user->update_meta( $user_id, static::USER_META_KEY, $dismissed_alerts ) !== false; } /** * Returns if an alert is dismissed or not. * * @param string $alert_identifier Alert identifier. * * @return bool Whether the alert has been dismissed. */ public function is_dismissed( $alert_identifier ) { $user_id = $this->user->get_current_user_id(); if ( $user_id === 0 ) { return false; } if ( $this->is_allowed( $alert_identifier ) === false ) { return false; } $dismissed_alerts = $this->get_dismissed_alerts( $user_id ); if ( $dismissed_alerts === false ) { return false; } return \array_key_exists( $alert_identifier, $dismissed_alerts ); } /** * Returns an object with all alerts dismissed by current user. * * @return array|false An array with the keys of all Alerts that have been dismissed * by the current user or `false`. */ public function all_dismissed() { $user_id = $this->user->get_current_user_id(); if ( $user_id === 0 ) { return false; } $dismissed_alerts = $this->get_dismissed_alerts( $user_id ); if ( $dismissed_alerts === false ) { return false; } return $dismissed_alerts; } /** * Returns if an alert is allowed or not. * * @param string $alert_identifier Alert identifier. * * @return bool Whether the alert is allowed. */ public function is_allowed( $alert_identifier ) { return \in_array( $alert_identifier, $this->get_allowed_dismissable_alerts(), true ); } /** * Retrieves the dismissed alerts. * * @param int $user_id User ID. * * @return string[]|false The dismissed alerts. False for an invalid $user_id. */ protected function get_dismissed_alerts( $user_id ) { $dismissed_alerts = $this->user->get_meta( $user_id, static::USER_META_KEY, true ); if ( $dismissed_alerts === false ) { // Invalid user ID. return false; } if ( $dismissed_alerts === '' ) { /* * When no database row exists yet, an empty string is returned because of the `single` parameter. * We do want a single result returned, but the default should be an empty array instead. */ return []; } return $dismissed_alerts; } /** * Retrieves the allowed dismissable alerts. * * @return string[] The allowed dismissable alerts. */ protected function get_allowed_dismissable_alerts() { /** * Filter: 'wpseo_allowed_dismissable_alerts' - List of allowed dismissable alerts. * * @param string[] $allowed_dismissable_alerts Allowed dismissable alerts list. */ $allowed_dismissable_alerts = \apply_filters( 'wpseo_allowed_dismissable_alerts', [] ); if ( \is_array( $allowed_dismissable_alerts ) === false ) { return []; } // Only allow strings. $allowed_dismissable_alerts = \array_filter( $allowed_dismissable_alerts, 'is_string' ); // Filter unique and reorder indices. $allowed_dismissable_alerts = \array_values( \array_unique( $allowed_dismissable_alerts ) ); return $allowed_dismissable_alerts; } } exceptions/importing/aioseo-validation-exception.php 0000644 00000000653 15025657560 0017070 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Importing; use Exception; /** * Class Aioseo_Validation_Exception */ class Aioseo_Validation_Exception extends Exception { /** * Exception that is thrown whenever validation of the * AIOSEO data structure has failed. */ public function __construct() { parent::__construct( \esc_html__( 'The validation of the AIOSEO data structure has failed.', 'wordpress-seo' ) ); } } exceptions/missing-method.php 0000644 00000001271 15025657560 0012401 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions; use Exception; /** * Exception to use when a method does not exist. */ class Missing_Method extends Exception { /** * Creates exception for a method that does not exist in a class. * * @param string $method The method that does not exist. * @param string $class_name The class name. * * @return static Instance of the exception. */ public static function for_class( $method, $class_name ) { return new static( \sprintf( /* translators: %1$s expands to the method name. %2$s expands to the class name */ \__( 'Method %1$s() does not exist in class %2$s', 'wordpress-seo' ), $method, $class_name ) ); } } exceptions/addon-installation/user-cannot-activate-plugins-exception.php 0000644 00000000276 15025657560 0022751 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Addon_Installation; use Exception; /** * Class User_Cannot_Activate_Plugins */ class User_Cannot_Activate_Plugins_Exception extends Exception {} exceptions/addon-installation/addon-installation-error-exception.php 0000644 00000000266 15025657560 0022150 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Addon_Installation; use Exception; /** * Class Addon_Installation_Error */ class Addon_Installation_Error_Exception extends Exception {} exceptions/addon-installation/user-cannot-install-plugins-exception.php 0000644 00000000306 15025657560 0022611 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Addon_Installation; use Exception; /** * Class User_Cannot_Install_Plugins_Exception */ class User_Cannot_Install_Plugins_Exception extends Exception {} exceptions/addon-installation/addon-activation-error-exception.php 0000644 00000000274 15025657560 0021607 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Addon_Installation; use Exception; /** * Class Addon_Activation_Error_Exception */ class Addon_Activation_Error_Exception extends Exception {} exceptions/addon-installation/addon-already-installed-exception.php 0000644 00000000276 15025657560 0021717 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Addon_Installation; use Exception; /** * Class Addon_Already_Installed_Exception */ class Addon_Already_Installed_Exception extends Exception {} exceptions/oauth/authentication-failed-exception.php 0000644 00000001340 15025657560 0017024 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\OAuth; use Exception; /** * Class Authentication_Failed_Exception */ class Authentication_Failed_Exception extends Exception { /** * Authentication_Failed_Exception constructor. * * @param Exception $original_exception The original exception. */ public function __construct( Exception $original_exception ) { parent::__construct( 'Authentication failed', 401, $original_exception ); } /** * Returns a formatted response object. * * @return object The response object. */ public function get_response() { return (object) [ 'tokens' => [], 'error' => $this->getMessage() . ': ' . $this->getPrevious()->getMessage(), 'status' => $this->getCode(), ]; } } exceptions/oauth/tokens/failed-storage-exception.php 0000644 00000001116 15025657560 0016755 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\OAuth\Tokens; use Exception; /** * Class Failed_Storage_Exception */ class Failed_Storage_Exception extends Exception { public const DEFAULT_MESSAGE = 'Token storing failed. Please try again.'; /** * Failed_Storage_Exception constructor. * * @param string $reason The reason why token storage failed. Optional. */ public function __construct( $reason = '' ) { $message = ( $reason ) ? \sprintf( 'Token storing failed. Reason: %s. Please try again', $reason ) : self::DEFAULT_MESSAGE; parent::__construct( $message, 500 ); } } exceptions/oauth/tokens/empty-property-exception.php 0000644 00000000665 15025657560 0017117 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\OAuth\Tokens; use Exception; /** * Class Empty_Property_Exception */ class Empty_Property_Exception extends Exception { /** * Empty_Property_Exception constructor. * * @param string $property The property that is empty. */ public function __construct( $property ) { parent::__construct( \sprintf( 'Token creation failed. Property `%s` cannot be empty.', $property ), 400 ); } } exceptions/oauth/tokens/empty-token-exception.php 0000644 00000000474 15025657560 0016351 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\OAuth\Tokens; use Exception; /** * Class Empty_Token_Exception */ class Empty_Token_Exception extends Exception { /** * Empty_Token_Exception constructor. */ public function __construct() { parent::__construct( 'Token usage failed. Token is empty.', 400 ); } } exceptions/indexable/term-not-found-exception.php 0000644 00000000703 15025657560 0016256 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Exception that is thrown whenever a term could not be found * in the context of the indexables. */ class Term_Not_Found_Exception extends Source_Exception { /** * Exception that is thrown whenever a term could not be found * in the context of the indexables. */ public function __construct() { parent::__construct( \__( 'The term could not be found.', 'wordpress-seo' ) ); } } exceptions/indexable/not-built-exception.php 0000644 00000001040 15025657560 0015310 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Class Not_Built_Exception */ class Not_Built_Exception extends Indexable_Exception { /** * Creates an exception that should be thrown when an indexable * was not built because of an invalid object id. * * @param int $object_id The invalid object id. * * @return Not_Built_Exception The exception. */ public static function invalid_object_id( $object_id ) { return new self( "Indexable was not built because it had an invalid object id of $object_id." ); } } exceptions/indexable/invalid-term-exception.php 0000644 00000001353 15025657560 0015775 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Exception that can be thrown whenever a term is considered invalid by WordPress * within the context of the indexables. */ class Invalid_Term_Exception extends Source_Exception { /** * Exception that can be thrown whenever a term is considered invalid by WordPress * within the context of the indexables. * * @param string $reason The reason given by WordPress why the term is invalid. */ public function __construct( $reason ) { parent::__construct( \sprintf( /* translators: %s is the reason given by WordPress. */ \esc_html__( 'The term is considered invalid. The following reason was given by WordPress: %s', 'wordpress-seo' ), $reason ) ); } } exceptions/indexable/post-type-not-built-exception.php 0000644 00000001232 15025657560 0017255 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Exception that is thrown whenever a post type could not be built * in the context of the indexables. */ class Post_Type_Not_Built_Exception extends Not_Built_Exception { /** * Throws an exception if the post is not indexable. * * @param string $post_type The post type. * * @return Post_Type_Not_Built_Exception */ public static function because_not_indexable( $post_type ) { /* translators: %s: expands to the post type */ return new self( \sprintf( \__( 'The post type %s could not be indexed because it does not meet indexing requirements.', 'wordpress-seo' ), $post_type ) ); } } exceptions/indexable/post-not-found-exception.php 0000644 00000000703 15025657560 0016274 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Exception that is thrown whenever a post could not be found * in the context of the indexables. */ class Post_Not_Found_Exception extends Source_Exception { /** * Exception that is thrown whenever a post could not be found * in the context of the indexables. */ public function __construct() { parent::__construct( \__( 'The post could not be found.', 'wordpress-seo' ) ); } } exceptions/indexable/author-not-built-exception.php 0000644 00000003141 15025657560 0016614 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * For when an author indexable is not being built. */ class Author_Not_Built_Exception extends Not_Built_Exception { /** * Named constructor for creating an Author_Not_Built_Exception * when author archives are disabled for users without posts. * * @param string $user_id The user id. * * @return Author_Not_Built_Exception The exception. */ public static function author_archives_are_not_indexed_for_users_without_posts( $user_id ) { return new self( 'Indexable for author with id ' . $user_id . ' is not being built, since author archives are not indexed for users without posts.' ); } /** * Named constructor for creating an Author_Not_Built_Exception * when author archives are disabled. * * @param string $user_id The user id. * * @return Author_Not_Built_Exception The exception. */ public static function author_archives_are_disabled( $user_id ) { return new self( 'Indexable for author with id ' . $user_id . ' is not being built, since author archives are disabled.' ); } /** * Named constructor for creating an Author_Not_Build_Exception * when an author is excluded because of the `'wpseo_should_build_and_save_user_indexable'` filter. * * @param string $user_id The user id. * * @return Author_Not_Built_Exception The exception. */ public static function author_not_built_because_of_filter( $user_id ) { return new self( 'Indexable for author with id ' . $user_id . ' is not being built, since it is excluded because of the \'wpseo_should_build_and_save_user_indexable\' filter.' ); } } exceptions/indexable/term-not-built-exception.php 0000644 00000001147 15025657560 0016265 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Exception that is thrown whenever a term could not be built * in the context of the indexables. */ class Term_Not_Built_Exception extends Not_Built_Exception { /** * Throws an exception if the term is not indexable. * * @param int $term_id ID of the term. * * @return Term_Not_Built_Exception */ public static function because_not_indexable( $term_id ) { /* translators: %s: expands to the term id */ return new self( \sprintf( \__( 'The term %s could not be built because it\'s not indexable.', 'wordpress-seo' ), $term_id ) ); } } exceptions/indexable/source-exception.php 0000644 00000000231 15025657560 0014674 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Class Indexable_Source_Exception */ class Source_Exception extends Indexable_Exception { } exceptions/indexable/post-not-built-exception.php 0000644 00000002056 15025657560 0016303 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; /** * Exception that is thrown whenever a post could not be built * in the context of the indexables. */ class Post_Not_Built_Exception extends Not_Built_Exception { /** * Throws an exception if the post is not indexable. * * @param int $post_id ID of the post. * * @return Post_Not_Built_Exception */ public static function because_not_indexable( $post_id ) { /* translators: %s: expands to the post id */ return new self( \sprintf( \__( 'The post %s could not be indexed because it does not meet indexing requirements.', 'wordpress-seo' ), $post_id ) ); } /** * Throws an exception if the post type is excluded from indexing. * * @param int $post_id ID of the post. * * @return Post_Not_Built_Exception */ public static function because_post_type_excluded( $post_id ) { /* translators: %s: expands to the post id */ return new self( \sprintf( \__( 'The post %s could not be indexed because it\'s post type is excluded from indexing.', 'wordpress-seo' ), $post_id ) ); } } exceptions/indexable/indexable-exception.php 0000644 00000000244 15025657560 0015333 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions\Indexable; use Exception; /** * Class Indexable_Exception */ abstract class Indexable_Exception extends Exception { } exceptions/forbidden-property-mutation-exception.php 0000644 00000002232 15025657560 0017120 0 ustar 00 <?php namespace Yoast\WP\SEO\Exceptions; use RuntimeException; /** * Exception for attempting a mutation on properties that are made readonly through magic getters and setters. */ class Forbidden_Property_Mutation_Exception extends RuntimeException { /** * Creates a Forbidden_Property_Mutation_Exception exception when an attempt is made * to assign a value to an immutable property. * * @param string $property_name The name of the immutable property. * * @return Forbidden_Property_Mutation_Exception The exception. */ public static function cannot_set_because_property_is_immutable( $property_name ) { return new self( \sprintf( 'Setting property $%s is not supported.', $property_name ) ); } /** * Creates a Forbidden_Property_Mutation_Exception exception when an attempt is made to unset an immutable property. * * @param string $property_name The name of the immutable property. * * @return Forbidden_Property_Mutation_Exception The exception. */ public static function cannot_unset_because_property_is_immutable( $property_name ) { return new self( \sprintf( 'Unsetting property $%s is not supported.', $property_name ) ); } } analytics/domain/missing-indexable-bucket.php 0000644 00000001731 15025657560 0015405 0 ustar 00 <?php namespace Yoast\WP\SEO\Analytics\Domain; /** * A collection domain object. */ class Missing_Indexable_Bucket { /** * All the missing indexable count objects. * * @var array<Missing_Indexable_Count> */ private $missing_indexable_counts; /** * The constructor. */ public function __construct() { $this->missing_indexable_counts = []; } /** * Adds a missing indexable count object to this bucket. * * @param Missing_Indexable_Count $missing_indexable_count The missing indexable count object. * * @return void */ public function add_missing_indexable_count( Missing_Indexable_Count $missing_indexable_count ): void { $this->missing_indexable_counts[] = $missing_indexable_count; } /** * Returns the array representation of all indexable counts. * * @return array */ public function to_array() { return \array_map( static function ( $item ) { return $item->to_array(); }, $this->missing_indexable_counts ); } } analytics/domain/to-be-cleaned-indexable-count.php 0000644 00000002231 15025657560 0016202 0 ustar 00 <?php namespace Yoast\WP\SEO\Analytics\Domain; /** * The to be cleaned indexable domain object. */ class To_Be_Cleaned_Indexable_Count { /** * The cleanup task that is represented by this. * * @var string */ private $cleanup_name; /** * The amount of missing indexables. * * @var int */ private $count; /** * The constructor. * * @param string $cleanup_name The indexable type that is represented by this. * @param int $count The amount of missing indexables. */ public function __construct( $cleanup_name, $count ) { $this->cleanup_name = $cleanup_name; $this->count = $count; } /** * Returns an array representation of the data. * * @return array Returns both values in an array format. */ public function to_array() { return [ 'cleanup_name' => $this->get_cleanup_name(), 'count' => $this->get_count(), ]; } /** * Gets the name. * * @return string */ public function get_cleanup_name() { return $this->cleanup_name; } /** * Gets the count. * * @return int Returns the amount of missing indexables. */ public function get_count() { return $this->count; } } analytics/domain/to-be-cleaned-indexable-bucket.php 0000644 00000002062 15025657560 0016331 0 ustar 00 <?php namespace Yoast\WP\SEO\Analytics\Domain; /** * A collection domain object. */ class To_Be_Cleaned_Indexable_Bucket { /** * All the to be cleaned indexable count objects. * * @var array<To_Be_Cleaned_Indexable_Count> */ private $to_be_cleaned_indexable_counts; /** * The constructor. */ public function __construct() { $this->to_be_cleaned_indexable_counts = []; } /** * Adds a 'to be cleaned' indexable count object to this bucket. * * @param To_Be_Cleaned_Indexable_Count $to_be_cleaned_indexable_counts The to be cleaned indexable count object. * * @return void */ public function add_to_be_cleaned_indexable_count( To_Be_Cleaned_Indexable_Count $to_be_cleaned_indexable_counts ) { $this->to_be_cleaned_indexable_counts[] = $to_be_cleaned_indexable_counts; } /** * Returns the array representation of all indexable counts. * * @return array */ public function to_array() { return \array_map( static function ( $item ) { return $item->to_array(); }, $this->to_be_cleaned_indexable_counts ); } } analytics/domain/missing-indexable-count.php 0000644 00000002336 15025657560 0015262 0 ustar 00 <?php namespace Yoast\WP\SEO\Analytics\Domain; /** * Domain object that holds indexable count information. */ class Missing_Indexable_Count { /** * The indexable type that is represented by this. * * @var string */ private $indexable_type; /** * The amount of missing indexables. * * @var int */ private $count; /** * The constructor. * * @param string $indexable_type The indexable type that is represented by this. * @param int $count The amount of missing indexables. */ public function __construct( $indexable_type, $count ) { $this->indexable_type = $indexable_type; $this->count = $count; } /** * Returns an array representation of the data. * * @return array Returns both values in an array format. */ public function to_array() { return [ 'indexable_type' => $this->get_indexable_type(), 'count' => $this->get_count(), ]; } /** * Gets the indexable type. * * @return string Returns the indexable type. */ public function get_indexable_type() { return $this->indexable_type; } /** * Gets the count. * * @return int Returns the amount of missing indexables. */ public function get_count() { return $this->count; } } analytics/application/to-be-cleaned-indexables-collector.php 0000644 00000010124 15025657560 0020257 0 ustar 00 <?php namespace Yoast\WP\SEO\Analytics\Application; use WPSEO_Collection; use Yoast\WP\SEO\Analytics\Domain\To_Be_Cleaned_Indexable_Bucket; use Yoast\WP\SEO\Analytics\Domain\To_Be_Cleaned_Indexable_Count; use Yoast\WP\SEO\Repositories\Indexable_Cleanup_Repository; /** * Collects data about to-be-cleaned indexables. * * @makePublic */ class To_Be_Cleaned_Indexables_Collector implements WPSEO_Collection { /** * The cleanup query repository. * * @var Indexable_Cleanup_Repository */ private $indexable_cleanup_repository; /** * The constructor. * * @param Indexable_Cleanup_Repository $indexable_cleanup_repository The Indexable cleanup repository. */ public function __construct( Indexable_Cleanup_Repository $indexable_cleanup_repository ) { $this->indexable_cleanup_repository = $indexable_cleanup_repository; } /** * Gets the data for the collector. * * @return array */ public function get() { $to_be_cleaned_indexable_bucket = new To_Be_Cleaned_Indexable_Bucket(); $cleanup_tasks = [ 'indexables_with_post_object_type_and_shop_order_object_sub_type' => $this->indexable_cleanup_repository->count_indexables_with_object_type_and_object_sub_type( 'post', 'shop_order' ), 'indexables_with_auto-draft_post_status' => $this->indexable_cleanup_repository->count_indexables_with_post_status( 'auto-draft' ), 'indexables_for_non_publicly_viewable_post' => $this->indexable_cleanup_repository->count_indexables_for_non_publicly_viewable_post(), 'indexables_for_non_publicly_viewable_taxonomies' => $this->indexable_cleanup_repository->count_indexables_for_non_publicly_viewable_taxonomies(), 'indexables_for_non_publicly_viewable_post_type_archive_pages' => $this->indexable_cleanup_repository->count_indexables_for_non_publicly_post_type_archive_pages(), 'indexables_for_authors_archive_disabled' => $this->indexable_cleanup_repository->count_indexables_for_authors_archive_disabled(), 'indexables_for_authors_without_archive' => $this->indexable_cleanup_repository->count_indexables_for_authors_without_archive(), 'indexables_for_object_type_and_source_table_users' => $this->indexable_cleanup_repository->count_indexables_for_orphaned_users(), 'indexables_for_object_type_and_source_table_posts' => $this->indexable_cleanup_repository->count_indexables_for_object_type_and_source_table( 'posts', 'ID', 'post' ), 'indexables_for_object_type_and_source_table_terms' => $this->indexable_cleanup_repository->count_indexables_for_object_type_and_source_table( 'terms', 'term_id', 'term' ), 'orphaned_from_table_indexable_hierarchy' => $this->indexable_cleanup_repository->count_orphaned_from_table( 'Indexable_Hierarchy', 'indexable_id' ), 'orphaned_from_table_indexable_id' => $this->indexable_cleanup_repository->count_orphaned_from_table( 'SEO_Links', 'indexable_id' ), 'orphaned_from_table_target_indexable_id' => $this->indexable_cleanup_repository->count_orphaned_from_table( 'SEO_Links', 'target_indexable_id' ), ]; foreach ( $cleanup_tasks as $name => $count ) { if ( $count !== null ) { $count_object = new To_Be_Cleaned_Indexable_Count( $name, $count ); $to_be_cleaned_indexable_bucket->add_to_be_cleaned_indexable_count( $count_object ); } } $this->add_additional_counts( $to_be_cleaned_indexable_bucket ); return [ 'to_be_cleaned_indexables' => $to_be_cleaned_indexable_bucket->to_array() ]; } /** * Allows additional tasks to be added via the 'wpseo_add_cleanup_counts_to_indexable_bucket' action. * * @param To_Be_Cleaned_Indexable_Bucket $to_be_cleaned_indexable_bucket The current bucket with data. * * @return void */ private function add_additional_counts( $to_be_cleaned_indexable_bucket ) { /** * Action: Adds the possibility to add additional to be cleaned objects. * * @internal * @param To_Be_Cleaned_Indexable_Bucket $bucket An indexable cleanup bucket. New values are instances of To_Be_Cleaned_Indexable_Count. */ \do_action( 'wpseo_add_cleanup_counts_to_indexable_bucket', $to_be_cleaned_indexable_bucket ); } } analytics/application/missing-indexables-collector.php 0000644 00000004521 15025657560 0017335 0 ustar 00 <?php namespace Yoast\WP\SEO\Analytics\Application; use WPSEO_Collection; use Yoast\WP\SEO\Actions\Indexing\Indexation_Action_Interface; use Yoast\WP\SEO\Analytics\Domain\Missing_Indexable_Bucket; use Yoast\WP\SEO\Analytics\Domain\Missing_Indexable_Count; /** * Manages the collection of the missing indexable data. * * @makePublic */ class Missing_Indexables_Collector implements WPSEO_Collection { /** * All the indexation actions. * * @var array<Indexation_Action_Interface> */ private $indexation_actions; /** * The collector constructor. * * @param Indexation_Action_Interface ...$indexation_actions All the Indexation actions. */ public function __construct( Indexation_Action_Interface ...$indexation_actions ) { $this->indexation_actions = $indexation_actions; $this->add_additional_indexing_actions(); } /** * Gets the data for the tracking collector. * * @return array The list of missing indexables. */ public function get() { $missing_indexable_bucket = new Missing_Indexable_Bucket(); foreach ( $this->indexation_actions as $indexation_action ) { $missing_indexable_count = new Missing_Indexable_Count( \get_class( $indexation_action ), $indexation_action->get_total_unindexed() ); $missing_indexable_bucket->add_missing_indexable_count( $missing_indexable_count ); } return [ 'missing_indexables' => $missing_indexable_bucket->to_array() ]; } /** * Adds additional indexing actions to count from the 'wpseo_indexable_collector_add_indexation_actions' filter. * * @return void */ private function add_additional_indexing_actions() { /** * Filter: Adds the possibility to add additional indexation actions to be included in the count routine. * * @internal * @param Indexation_Action_Interface $actions This filter expects a list of Indexation_Action_Interface instances * and expects only Indexation_Action_Interface implementations to be * added to the list. */ $indexing_actions = (array) \apply_filters( 'wpseo_indexable_collector_add_indexation_actions', $this->indexation_actions ); $this->indexation_actions = \array_filter( $indexing_actions, static function ( $indexing_action ) { return \is_a( $indexing_action, Indexation_Action_Interface::class ); } ); } } analytics/user-interface/last-completed-indexation-integration.php 0000644 00000003334 15025657560 0021574 0 ustar 00 <?php namespace Yoast\WP\SEO\Analytics\User_Interface; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Handles setting a timestamp when the indexation of a specific indexation action is completed. */ class Last_Completed_Indexation_Integration implements Integration_Interface { use No_Conditionals; /** * The options helper. * * @var Options_Helper The options helper. */ private $options_helper; /** * The constructor. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Registers action hook to maybe save an option. * * @return void */ public function register_hooks(): void { \add_action( 'wpseo_indexables_unindexed_calculated', [ $this, 'maybe_set_indexables_unindexed_calculated', ], 10, 2 ); } /** * Saves a timestamp option when there are no unindexed indexables. * * @param string $indexable_name The name of the indexable that is being checked. * @param int $count The amount of missing indexables. * * @return void */ public function maybe_set_indexables_unindexed_calculated( string $indexable_name, int $count ): void { if ( $count === 0 ) { $no_index = $this->options_helper->get( 'last_known_no_unindexed', [] ); $no_index[ $indexable_name ] = \time(); \remove_action( 'update_option_wpseo', [ 'WPSEO_Utils', 'clear_cache' ] ); $this->options_helper->set( 'last_known_no_unindexed', $no_index ); \add_action( 'update_option_wpseo', [ 'WPSEO_Utils', 'clear_cache' ] ); } } } dashboard/domain/score-results/score-result.php 0000644 00000003064 15025657560 0015732 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results; /** * This class describes a score result. */ class Score_Result { /** * The list of the current scores of the score result. * * @var Current_Scores_List */ private $current_scores_list; /** * The time the query took to get the score results. * * @var float */ private $query_time; /** * Whether cache was used to get the score results. * * @var bool */ private $is_cached_used; /** * The constructor. * * @param Current_Scores_List $current_scores_list The list of the current scores of the score result. * @param float $query_time The time the query took to get the score results. * @param bool $is_cached_used Whether cache was used to get the score results. */ public function __construct( Current_Scores_List $current_scores_list, float $query_time, bool $is_cached_used ) { $this->current_scores_list = $current_scores_list; $this->query_time = $query_time; $this->is_cached_used = $is_cached_used; } /** * Return this object represented by a key value array. * * @return array<string, array<array<string, string|int|array<string, string>>>|float|bool> Returns the name and if the feature is enabled. */ public function to_array(): array { return [ 'scores' => $this->current_scores_list->to_array(), 'queryTime' => $this->query_time, 'cacheUsed' => $this->is_cached_used, ]; } } dashboard/domain/score-results/current-score.php 0000644 00000004103 15025657560 0016071 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results; /** * This class describes a current score. */ class Current_Score { /** * The name of the current score. * * @var string */ private $name; /** * The amount of the current score. * * @var string */ private $amount; /** * The ids of the current score. * * @var string|null */ private $ids; /** * The links of the current score. * * @var array<string, string>|null */ private $links; /** * The constructor. * * @param string $name The name of the current score. * @param int $amount The amount of the current score. * @param string|null $ids The ids of the current score. * @param array<string, string>|null $links The links of the current score. */ public function __construct( string $name, int $amount, ?string $ids = null, ?array $links = null ) { $this->name = $name; $this->amount = $amount; $this->ids = $ids; $this->links = $links; } /** * Gets name of the current score. * * @return string The name of the current score. */ public function get_name(): string { return $this->name; } /** * Gets the amount of the current score. * * @return int The amount of the current score. */ public function get_amount(): int { return $this->amount; } /** * Gets the ids of the current score. * * @return string|null The ids of the current score. */ public function get_ids(): ?string { return $this->ids; } /** * Gets the links of the current score in the expected key value representation. * * @return array<string, string> The links of the current score in the expected key value representation. */ public function get_links_to_array(): ?array { $links = []; if ( $this->links === null ) { return $links; } foreach ( $this->links as $key => $link ) { if ( $link === null ) { continue; } $links[ $key ] = $link; } return $links; } } dashboard/domain/score-results/current-scores-list.php 0000644 00000002535 15025657560 0017234 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results; /** * This class describes a list of current scores. */ class Current_Scores_List { /** * The current scores. * * @var Current_Score[] */ private $current_scores = []; /** * Adds a current score to the list. * * @param Current_Score $current_score The current score to add. * @param int $position The position to add the current score. * * @return void */ public function add( Current_Score $current_score, int $position ): void { $this->current_scores[ $position ] = $current_score; } /** * Parses the current score list to the expected key value representation. * * @return array<array<string, string|int|array<string, string>>> The score list presented as the expected key value representation. */ public function to_array(): array { $array = []; \ksort( $this->current_scores ); foreach ( $this->current_scores as $key => $current_score ) { $array[] = [ 'name' => $current_score->get_name(), 'amount' => $current_score->get_amount(), 'links' => $current_score->get_links_to_array(), ]; if ( $current_score->get_ids() !== null ) { $array[ $key ]['ids'] = $current_score->get_ids(); } } return $array; } } dashboard/domain/score-results/score-results-not-found-exception.php 0000644 00000000663 15025657560 0022022 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Score_Results; use Exception; /** * Exception for when score results are not found. */ class Score_Results_Not_Found_Exception extends Exception { /** * Constructor of the exception. */ public function __construct() { parent::__construct( 'Score results not found', 500 ); } } dashboard/domain/score-groups/score-groups-interface.php 0000644 00000002034 15025657560 0017503 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups; /** * This interface describes a score group implementation. */ interface Score_Groups_Interface { /** * Gets the name of the score group. * * @return string */ public function get_name(): string; /** * Gets the key of the score group that is used when filtering on the posts page. * * @return string */ public function get_filter_key(): string; /** * Gets the value of the score group that is used when filtering on the posts page. * * @return string */ public function get_filter_value(): string; /** * Gets the minimum score of the score group. * * @return int|null */ public function get_min_score(): ?int; /** * Gets the maximum score of the score group. * * @return int|null */ public function get_max_score(): ?int; /** * Gets the position of the score group. * * @return int */ public function get_position(): int; } dashboard/domain/score-groups/readability-score-groups/good-readability-score-group.php 0000644 00000002653 15025657560 0025525 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups; /** * This class describes a good readability score group. */ class Good_Readability_Score_Group extends Abstract_Readability_Score_Group { /** * Gets the name of the readability score group. * * @return string The name of the readability score group. */ public function get_name(): string { return 'good'; } /** * Gets the value of the readability score group that is used when filtering on the posts page. * * @return string The name of the readability score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'good'; } /** * Gets the position of the readability score group. * * @return int The position of the readability score group. */ public function get_position(): int { return 0; } /** * Gets the minimum score of the readability score group. * * @return int|null The minimum score of the readability score group. */ public function get_min_score(): ?int { return 71; } /** * Gets the maximum score of the readability score group. * * @return int|null The maximum score of the readability score group. */ public function get_max_score(): ?int { return 100; } } dashboard/domain/score-groups/readability-score-groups/abstract-readability-score-group.php 0000644 00000001437 15025657560 0026377 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Abstract_Score_Group; /** * Abstract class for a readability score group. */ abstract class Abstract_Readability_Score_Group extends Abstract_Score_Group implements Readability_Score_Groups_Interface { /** * Gets the key of the readability score group that is used when filtering on the posts page. * * @return string The name of the readability score group that is used when filtering on the posts page. */ public function get_filter_key(): string { return 'readability_filter'; } } dashboard/domain/score-groups/readability-score-groups/readability-score-groups-interface.php 0000644 00000000563 15025657560 0026716 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface; /** * This interface describes a readability score group implementation. */ interface Readability_Score_Groups_Interface extends Score_Groups_Interface { } dashboard/domain/score-groups/readability-score-groups/no-readability-score-group.php 0000644 00000002664 15025657560 0025213 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups; /** * This class describes a missing readability score group. */ class No_Readability_Score_Group extends Abstract_Readability_Score_Group { /** * Gets the name of the readability score group. * * @return string The name of the readability score group. */ public function get_name(): string { return 'notAnalyzed'; } /** * Gets the value of the readability score group that is used when filtering on the posts page. * * @return string The name of the readability score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'na'; } /** * Gets the position of the readability score group. * * @return int The position of the readability score group. */ public function get_position(): int { return 3; } /** * Gets the minimum score of the readability score group. * * @return int|null The minimum score of the readability score group. */ public function get_min_score(): ?int { return null; } /** * Gets the maximum score of the readability score group. * * @return int|null The maximum score of the readability score group. */ public function get_max_score(): ?int { return null; } } dashboard/domain/score-groups/readability-score-groups/bad-readability-score-group.php 0000644 00000002645 15025657560 0025324 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups; /** * This class describes a bad readability score group. */ class Bad_Readability_Score_Group extends Abstract_Readability_Score_Group { /** * Gets the name of the readability score group. * * @return string The name of the readability score group. */ public function get_name(): string { return 'bad'; } /** * Gets the value of the readability score group that is used when filtering on the posts page. * * @return string The name of the readability score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'bad'; } /** * Gets the position of the readability score group. * * @return int The position of the readability score group. */ public function get_position(): int { return 2; } /** * Gets the minimum score of the readability score group. * * @return int|null The minimum score of the readability score group. */ public function get_min_score(): ?int { return 1; } /** * Gets the maximum score of the readability score group. * * @return int|null The maximum score of the readability score group. */ public function get_max_score(): ?int { return 40; } } dashboard/domain/score-groups/readability-score-groups/ok-readability-score-group.php 0000644 00000002647 15025657560 0025211 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups; /** * This class describes an OK readability score group. */ class Ok_Readability_Score_Group extends Abstract_Readability_Score_Group { /** * Gets the name of the readability score group. * * @return string The the name of the readability score group. */ public function get_name(): string { return 'ok'; } /** * Gets the value of the readability score group that is used when filtering on the posts page. * * @return string The name of the readability score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'ok'; } /** * Gets the position of the readability score group. * * @return int The position of the readability score group. */ public function get_position(): int { return 1; } /** * Gets the minimum score of the readability score group. * * @return int|null The minimum score of the readability score group. */ public function get_min_score(): ?int { return 41; } /** * Gets the maximum score of the readability score group. * * @return int|null The maximum score of the readability score group. */ public function get_max_score(): ?int { return 70; } } dashboard/domain/score-groups/seo-score-groups/bad-seo-score-group.php 0000644 00000002465 15025657560 0022116 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups; /** * This class describes a bad SEO score group. */ class Bad_SEO_Score_Group extends Abstract_SEO_Score_Group { /** * Gets the name of the SEO score group. * * @return string The name of the SEO score group. */ public function get_name(): string { return 'bad'; } /** * Gets the value of the SEO score group that is used when filtering on the posts page. * * @return string The name of the SEO score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'bad'; } /** * Gets the position of the SEO score group. * * @return int The position of the SEO score group. */ public function get_position(): int { return 2; } /** * Gets the minimum score of the SEO score group. * * @return int|null The minimum score of the SEO score group. */ public function get_min_score(): ?int { return 1; } /** * Gets the maximum score of the SEO score group. * * @return int|null The maximum score of the SEO score group. */ public function get_max_score(): ?int { return 40; } } dashboard/domain/score-groups/seo-score-groups/seo-score-groups-interface.php 0000644 00000000533 15025657560 0023505 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface; /** * This interface describes an SEO score group implementation. */ interface SEO_Score_Groups_Interface extends Score_Groups_Interface {} dashboard/domain/score-groups/seo-score-groups/good-seo-score-group.php 0000644 00000002473 15025657560 0022317 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups; /** * This class describes a good SEO score group. */ class Good_SEO_Score_Group extends Abstract_SEO_Score_Group { /** * Gets the name of the SEO score group. * * @return string The name of the SEO score group. */ public function get_name(): string { return 'good'; } /** * Gets the value of the SEO score group that is used when filtering on the posts page. * * @return string The name of the SEO score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'good'; } /** * Gets the position of the SEO score group. * * @return int The position of the SEO score group. */ public function get_position(): int { return 0; } /** * Gets the minimum score of the SEO score group. * * @return int|null The minimum score of the SEO score group. */ public function get_min_score(): ?int { return 71; } /** * Gets the maximum score of the SEO score group. * * @return int|null The maximum score of the SEO score group. */ public function get_max_score(): ?int { return 100; } } dashboard/domain/score-groups/seo-score-groups/abstract-seo-score-group.php 0000644 00000001350 15025657560 0023163 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Abstract_Score_Group; /** * Abstract class for an SEO score group. */ abstract class Abstract_SEO_Score_Group extends Abstract_Score_Group implements SEO_Score_Groups_Interface { /** * Gets the key of the SEO score group that is used when filtering on the posts page. * * @return string The name of the SEO score group that is used when filtering on the posts page. */ public function get_filter_key(): string { return 'seo_filter'; } } dashboard/domain/score-groups/seo-score-groups/no-seo-score-group.php 0000644 00000002504 15025657560 0021776 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups; /** * This class describes a missing SEO score group. */ class No_SEO_Score_Group extends Abstract_SEO_Score_Group { /** * Gets the name of the SEO score group. * * @return string The name of the SEO score group. */ public function get_name(): string { return 'notAnalyzed'; } /** * Gets the value of the SEO score group that is used when filtering on the posts page. * * @return string The name of the SEO score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'na'; } /** * Gets the position of the SEO score group. * * @return int The position of the SEO score group. */ public function get_position(): int { return 3; } /** * Gets the minimum score of the SEO score group. * * @return int|null The minimum score of the SEO score group. */ public function get_min_score(): ?int { return null; } /** * Gets the maximum score of the SEO score group. * * @return int|null The maximum score of the SEO score group. */ public function get_max_score(): ?int { return null; } } dashboard/domain/score-groups/seo-score-groups/ok-seo-score-group.php 0000644 00000002467 15025657560 0022003 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups; /** * This class describes an OK SEO score group. */ class Ok_SEO_Score_Group extends Abstract_SEO_Score_Group { /** * Gets the name of the SEO score group. * * @return string The the name of the SEO score group. */ public function get_name(): string { return 'ok'; } /** * Gets the value of the SEO score group that is used when filtering on the posts page. * * @return string The name of the SEO score group that is used when filtering on the posts page. */ public function get_filter_value(): string { return 'ok'; } /** * Gets the position of the SEO score group. * * @return int The position of the SEO score group. */ public function get_position(): int { return 1; } /** * Gets the minimum score of the SEO score group. * * @return int|null The minimum score of the SEO score group. */ public function get_min_score(): ?int { return 41; } /** * Gets the maximum score of the SEO score group. * * @return int|null The maximum score of the SEO score group. */ public function get_max_score(): ?int { return 70; } } dashboard/domain/score-groups/abstract-score-group.php 0000644 00000001563 15025657560 0017171 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Score_Groups; /** * Abstract class for a score group. */ abstract class Abstract_Score_Group implements Score_Groups_Interface { /** * The name of the score group. * * @var string */ private $name; /** * The key of the score group that is used when filtering on the posts page. * * @var string */ private $filter_key; /** * The value of the score group that is used when filtering on the posts page. * * @var string */ private $filter_value; /** * The min score of the score group. * * @var int */ private $min_score; /** * The max score of the score group. * * @var int */ private $max_score; /** * The position of the score group. * * @var int */ private $position; } dashboard/domain/content-types/content-types-list.php 0000644 00000002570 15025657560 0017073 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Content_Types; /** * This class describes a list of content types. */ class Content_Types_List { /** * The content types. * * @var array<Content_Type> */ private $content_types = []; /** * Adds a content type to the list. * * @param Content_Type $content_type The content type to add. * * @return void */ public function add( Content_Type $content_type ): void { $this->content_types[ $content_type->get_name() ] = $content_type; } /** * Returns the content types in the list. * * @return array<Content_Type> The content types in the list. */ public function get(): array { return $this->content_types; } /** * Parses the content type list to the expected key value representation. * * @return array<array<string, array<string, array<string, array<string, string|null>>>>> The content type list presented as the expected key value representation. */ public function to_array(): array { $array = []; foreach ( $this->content_types as $content_type ) { $array[] = [ 'name' => $content_type->get_name(), 'label' => $content_type->get_label(), 'taxonomy' => ( $content_type->get_taxonomy() ) ? $content_type->get_taxonomy()->to_array() : null, ]; } return $array; } } dashboard/domain/content-types/content-type.php 0000644 00000003364 15025657560 0015741 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Content_Types; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; /** * This class describes a Content Type. */ class Content_Type { /** * The name of the content type. * * @var string */ private $name; /** * The label of the content type. * * @var string */ private $label; /** * The taxonomy that filters the content type. * * @var Taxonomy */ private $taxonomy; /** * The constructor. * * @param string $name The name of the content type. * @param string $label The label of the content type. * @param Taxonomy|null $taxonomy The taxonomy that filters the content type. */ public function __construct( string $name, string $label, ?Taxonomy $taxonomy = null ) { $this->name = $name; $this->label = $label; $this->taxonomy = $taxonomy; } /** * Gets name of the content type. * * @return string The name of the content type. */ public function get_name(): string { return $this->name; } /** * Gets label of the content type. * * @return string The label of the content type. */ public function get_label(): string { return $this->label; } /** * Gets the taxonomy that filters the content type. * * @return Taxonomy|null The taxonomy that filters the content type. */ public function get_taxonomy(): ?Taxonomy { return $this->taxonomy; } /** * Sets the taxonomy that filters the content type. * * @param Taxonomy|null $taxonomy The taxonomy that filters the content type. * * @return void */ public function set_taxonomy( ?Taxonomy $taxonomy ): void { $this->taxonomy = $taxonomy; } } dashboard/domain/endpoint/endpoint-list.php 0000644 00000001501 15025657560 0015074 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Endpoint; /** * List of endpoints. */ class Endpoint_List { /** * Holds the endpoints. * * @var array<Endpoint_Interface> */ private $endpoints = []; /** * Adds an endpoint to the list. * * @param Endpoint_Interface $endpoint An endpoint. * * @return void */ public function add_endpoint( Endpoint_Interface $endpoint ): void { $this->endpoints[] = $endpoint; } /** * Converts the list to an array. * * @return array<string, string> The array of endpoints. */ public function to_array(): array { $result = []; foreach ( $this->endpoints as $endpoint ) { $result[ $endpoint->get_name() ] = $endpoint->get_url(); } return $result; } } dashboard/domain/endpoint/endpoint-interface.php 0000644 00000001056 15025657560 0016066 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Endpoint; interface Endpoint_Interface { /** * Gets the name. * * @return string */ public function get_name(): string; /** * Gets the namespace. * * @return string */ public function get_namespace(): string; /** * Gets the route. * * @return string */ public function get_route(): string; /** * Gets the URL. * * @return string */ public function get_url(): string; } dashboard/domain/analytics-4/invalid-request-exception.php 0000644 00000001044 15025657560 0017725 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Analytics_4; use Exception; /** * Exception for when an Analytics 4 request is invalid. */ class Invalid_Request_Exception extends Exception { /** * Constructor of the exception. * * @param string $error_message The error message of the request. */ public function __construct( $error_message ) { parent::__construct( 'The Analytics 4 request is invalid: ' . $error_message, 400 ); } } dashboard/domain/analytics-4/failed-request-exception.php 0000644 00000001214 15025657560 0017522 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Analytics_4; use Exception; /** * Exception for when an Analytics 4 request fails. */ class Failed_Request_Exception extends Exception { /** * Constructor of the exception. * * @param string $error_message The error message of the request. * @param int $error_status_code The error status code of the request. */ public function __construct( $error_message, $error_status_code ) { parent::__construct( 'The Analytics 4 request failed: ' . $error_message, $error_status_code ); } } dashboard/domain/analytics-4/unexpected-response-exception.php 0000644 00000000767 15025657560 0020624 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Analytics_4; use Exception; /** * Exception for when an Analytics 4 request returns with an unexpected response. */ class Unexpected_Response_Exception extends Exception { /** * Constructor of the exception. */ public function __construct() { parent::__construct( 'The response from Google Site Kit did not have an expected format.', 400 ); } } dashboard/domain/search-rankings/top-page-data.php 0000644 00000003501 15025657560 0016167 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Search_Rankings; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface; /** * Domain object that represents a single Top Page Data record. */ class Top_Page_Data implements Data_Interface { /** * The search ranking data for the top page. * * @var Search_Ranking_Data */ private $search_ranking_data; /** * The SEO score group the top page belongs to. * * @var SEO_Score_Groups_Interface */ private $seo_score_group; /** * The edit link of the top page. * * @var string */ private $edit_link; /** * The constructor. * * @param Search_Ranking_Data $search_ranking_data The search ranking data for the top page. * @param SEO_Score_Groups_Interface $seo_score_group The SEO score group the top page belongs to. * @param string $edit_link The edit link of the top page. */ public function __construct( Search_Ranking_Data $search_ranking_data, SEO_Score_Groups_Interface $seo_score_group, ?string $edit_link = null ) { $this->search_ranking_data = $search_ranking_data; $this->seo_score_group = $seo_score_group; $this->edit_link = $edit_link; } /** * The array representation of this domain object. * * @return array<string|float|int|string[]> */ public function to_array(): array { $top_page_data = $this->search_ranking_data->to_array(); $top_page_data['seoScore'] = $this->seo_score_group->get_name(); $top_page_data['links'] = []; if ( $this->edit_link !== null ) { $top_page_data['links']['edit'] = $this->edit_link; } return $top_page_data; } } dashboard/domain/search-rankings/comparison-search-ranking-data.php 0000644 00000005153 15025657560 0021524 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Search_Rankings; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface; /** * Domain object that represents a Comparison Search Ranking record. */ class Comparison_Search_Ranking_Data implements Data_Interface { /** * The current search ranking data. * * @var Search_Ranking_Data[] */ private $current_search_ranking_data = []; /** * The previous search ranking data. * * @var Search_Ranking_Data[] */ private $previous_search_ranking_data = []; /** * Sets the current search ranking data. * * @param Search_Ranking_Data $current_search_ranking_data The current search ranking data. * * @return void */ public function add_current_traffic_data( Search_Ranking_Data $current_search_ranking_data ): void { \array_push( $this->current_search_ranking_data, $current_search_ranking_data ); } /** * Sets the previous search ranking data. * * @param Search_Ranking_Data $previous_search_ranking_data The previous search ranking data. * * @return void */ public function add_previous_traffic_data( Search_Ranking_Data $previous_search_ranking_data ): void { \array_push( $this->previous_search_ranking_data, $previous_search_ranking_data ); } /** * The array representation of this domain object. * * @return array<array<string, int>> */ public function to_array(): array { return [ 'current' => $this->parse_data( $this->current_search_ranking_data ), 'previous' => $this->parse_data( $this->previous_search_ranking_data ), ]; } /** * Parses search ranking data into the expected format. * * @param Search_Ranking_Data[] $search_ranking_data The search ranking data to be parsed. * * @return array<string, int> The parsed data */ private function parse_data( array $search_ranking_data ): array { $parsed_data = [ 'total_clicks' => 0, 'total_impressions' => 0, ]; $weighted_postion = 0; foreach ( $search_ranking_data as $search_ranking ) { $parsed_data['total_clicks'] += $search_ranking->get_clicks(); $parsed_data['total_impressions'] += $search_ranking->get_impressions(); $weighted_postion += ( $search_ranking->get_position() * $search_ranking->get_impressions() ); } if ( $parsed_data['total_impressions'] !== 0 ) { $parsed_data['average_ctr'] = ( $parsed_data['total_clicks'] / $parsed_data['total_impressions'] ); $parsed_data['average_position'] = ( $weighted_postion / $parsed_data['total_impressions'] ); } return $parsed_data; } } dashboard/domain/search-rankings/search-ranking-data.php 0000644 00000004675 15025657560 0017364 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Search_Rankings; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface; /** * Domain object that represents a single Search Ranking Data record. */ class Search_Ranking_Data implements Data_Interface { /** * The amount of clicks a `subject` gets. * * @var int */ private $clicks; /** * The click-through rate a `subject` gets. * * @var float */ private $ctr; /** * The amount of impressions a `subject` gets. * * @var int */ private $impressions; /** * The average position for the given `subject`. * * @var float */ private $position; /** * In the context of this domain object subject can represent a `URI` or a `search term` * * @var string */ private $subject; /** * The constructor. * * @param int $clicks The clicks. * @param float $ctr The ctr. * @param int $impressions The impressions. * @param float $position The position. * @param string $subject The subject of the data. */ public function __construct( int $clicks, float $ctr, int $impressions, float $position, string $subject ) { $this->clicks = $clicks; $this->ctr = $ctr; $this->impressions = $impressions; $this->position = $position; $this->subject = $subject; } /** * The array representation of this domain object. * * @return array<string|float|int|string[]> */ public function to_array(): array { return [ 'clicks' => $this->clicks, 'ctr' => $this->ctr, 'impressions' => $this->impressions, 'position' => $this->position, 'subject' => $this->subject, ]; } /** * Gets the clicks. * * @return string The clicks. */ public function get_clicks(): string { return $this->clicks; } /** * Gets the click-through rate. * * @return string The click-through rate. */ public function get_ctr(): string { return $this->ctr; } /** * Gets the impressions. * * @return string The impressions. */ public function get_impressions(): string { return $this->impressions; } /** * Gets the position. * * @return string The position. */ public function get_position(): string { return $this->position; } /** * Gets the subject. * * @return string The subject. */ public function get_subject(): string { return $this->subject; } } dashboard/domain/taxonomies/taxonomy.php 0000644 00000002522 15025657560 0014533 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Taxonomies; /** * This class describes a Taxonomy. */ class Taxonomy { /** * The name of the taxonomy. * * @var string */ private $name; /** * The label of the taxonomy. * * @var string */ private $label; /** * The REST URL of the taxonomy. * * @var string */ private $rest_url; /** * The constructor. * * @param string $name The name of the taxonomy. * @param string $label The label of the taxonomy. * @param string $rest_url The REST URL of the taxonomy. */ public function __construct( string $name, string $label, string $rest_url ) { $this->name = $name; $this->label = $label; $this->rest_url = $rest_url; } /** * Returns the name of the taxonomy. * * @return string The name of the taxonomy. */ public function get_name(): string { return $this->name; } /** * Parses the taxonomy to the expected key value representation. * * @return array<string, array<string, string>> The taxonomy presented as the expected key value representation. */ public function to_array(): array { return [ 'name' => $this->name, 'label' => $this->label, 'links' => [ 'search' => $this->rest_url, ], ]; } } dashboard/domain/search-console/failed-request-exception.php 0000644 00000001224 15025657560 0020300 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Search_Console; use Exception; /** * Exception for when a search console request fails. */ class Failed_Request_Exception extends Exception { /** * Constructor of the exception. * * @param string $error_message The error message of the request. * @param int $error_status_code The error status code of the request. */ public function __construct( $error_message, $error_status_code ) { parent::__construct( 'The Search Console request failed: ' . $error_message, $error_status_code ); } } dashboard/domain/search-console/unexpected-response-exception.php 0000644 00000000774 15025657560 0021377 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Search_Console; use Exception; /** * Exception for when a Search Console request returns with an unexpected response. */ class Unexpected_Response_Exception extends Exception { /** * Constructor of the exception. */ public function __construct() { parent::__construct( 'The response from Google Site Kit did not have an expected format.', 400 ); } } dashboard/domain/data-provider/data-interface.php 0000644 00000000527 15025657560 0016102 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider; /** * The interface to describe the data domain. */ interface Data_Interface { /** * A to array method. * * @return array<string> */ public function to_array(): array; } dashboard/domain/data-provider/parameters.php 0000644 00000004743 15025657560 0015402 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider; /** * Object representation of the request parameters. */ abstract class Parameters { /** * The start date. * * @var string */ private $start_date; /** * The end date. * * @var string */ private $end_date; /** * The amount of results. * * @var int */ private $limit = 0; /** * The compare start date. * * @var string */ private $compare_start_date; /** * The compare end date. * * @var string */ private $compare_end_date; /** * Getter for the start date. * * @return string */ public function get_start_date(): string { return $this->start_date; } /** * Getter for the end date. * The date format should be Y-M-D. * * @return string */ public function get_end_date(): string { return $this->end_date; } /** * Getter for the result limit. * * @return int */ public function get_limit(): int { return $this->limit; } /** * Getter for the compare start date. * * @return string */ public function get_compare_start_date(): ?string { return $this->compare_start_date; } /** * Getter for the compare end date. * The date format should be Y-M-D. * * @return string */ public function get_compare_end_date(): ?string { return $this->compare_end_date; } /** * The start date setter. * * @param string $start_date The start date. * * @return void */ public function set_start_date( string $start_date ): void { $this->start_date = $start_date; } /** * The end date setter. * * @param string $end_date The end date. * * @return void */ public function set_end_date( string $end_date ): void { $this->end_date = $end_date; } /** * The result limit. * * @param int $limit The result limit. * @return void */ public function set_limit( int $limit ): void { $this->limit = $limit; } /** * The compare start date setter. * * @param string $compare_start_date The compare start date. * * @return void */ public function set_compare_start_date( string $compare_start_date ): void { $this->compare_start_date = $compare_start_date; } /** * The compare end date setter. * * @param string $compare_end_date The compare end date. * * @return void */ public function set_compare_end_date( string $compare_end_date ): void { $this->compare_end_date = $compare_end_date; } } dashboard/domain/data-provider/data-container.php 0000644 00000002045 15025657560 0016121 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider; /** * The data container. */ class Data_Container { /** * All the data points. * * @var array<Data_Interface> */ private $data_container; /** * The constructor */ public function __construct() { $this->data_container = []; } /** * Method to add data. * * @param Data_Interface $data The data. * * @return void */ public function add_data( Data_Interface $data ) { $this->data_container[] = $data; } /** * Method to get all the data points. * * @return Data_Interface[] All the data points. */ public function get_data(): array { return $this->data_container; } /** * Converts the data points into an array. * * @return array<string, string> The array of the data points. */ public function to_array(): array { $result = []; foreach ( $this->data_container as $data ) { $result[] = $data->to_array(); } return $result; } } dashboard/domain/data-provider/dashboard-repository-interface.php 0000644 00000001033 15025657560 0021326 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Data_Provider; /** * Interface describing the way to get data for a specific data provider. */ interface Dashboard_Repository_Interface { /** * Method to get dashboard related data from a provider. * * @param Parameters $parameters The parameter to get the dashboard data for. * * @return Data_Container */ public function get_data( Parameters $parameters ): Data_Container; } dashboard/domain/filter-pairs/filter-pairs-interface.php 0000644 00000000770 15025657560 0017432 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Filter_Pairs; /** * This interface describes a Filter Pair implementation. */ interface Filter_Pairs_Interface { /** * Gets the filtering taxonomy. * * @return string */ public function get_filtering_taxonomy(): string; /** * Gets the filtered content type. * * @return string */ public function get_filtered_content_type(): string; } dashboard/domain/filter-pairs/product-category-filter-pair.php 0000644 00000001203 15025657560 0020572 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Filter_Pairs; /** * This class describes the product category filter pair. */ class Product_Category_Filter_Pair implements Filter_Pairs_Interface { /** * Gets the filtering taxonomy. * * @return string The filtering taxonomy. */ public function get_filtering_taxonomy(): string { return 'product_cat'; } /** * Gets the filtered content type. * * @return string The filtered content type. */ public function get_filtered_content_type(): string { return 'product'; } } dashboard/domain/traffic/comparison-traffic-data.php 0000644 00000003627 15025657560 0016611 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Traffic; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface; /** * Domain object that represents a single Comparison Traffic record. */ class Comparison_Traffic_Data implements Data_Interface { public const CURRENT_PERIOD_KEY = 'current'; public const PREVIOUS_PERIOD_KEY = 'previous'; /** * The current traffic data. * * @var Traffic_Data */ private $current_traffic_data; /** * The previous traffic data. * * @var Traffic_Data */ private $previous_traffic_data; /** * The constructor. * * @param Traffic_Data $current_traffic_data The current traffic data. * @param Traffic_Data $previous_traffic_data The previous traffic data. */ public function __construct( ?Traffic_Data $current_traffic_data = null, ?Traffic_Data $previous_traffic_data = null ) { $this->current_traffic_data = $current_traffic_data; $this->previous_traffic_data = $previous_traffic_data; } /** * Sets the current traffic data. * * @param Traffic_Data $current_traffic_data The current traffic data. * * @return void */ public function set_current_traffic_data( Traffic_Data $current_traffic_data ): void { $this->current_traffic_data = $current_traffic_data; } /** * Sets the previous traffic data. * * @param Traffic_Data $previous_traffic_data The previous traffic data. * * @return void */ public function set_previous_traffic_data( Traffic_Data $previous_traffic_data ): void { $this->previous_traffic_data = $previous_traffic_data; } /** * The array representation of this domain object. * * @return array<string|float|int|string[]> */ public function to_array(): array { return [ 'current' => $this->current_traffic_data->to_array(), 'previous' => $this->previous_traffic_data->to_array(), ]; } } dashboard/domain/traffic/traffic-data.php 0000644 00000002331 15025657560 0014430 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Traffic; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface; /** * Domain object that represents a single Traffic record. */ class Traffic_Data implements Data_Interface { /** * The sessions, if any. * * @var int|null */ private $sessions; /** * The total users, if any. * * @var int|null */ private $total_users; /** * The array representation of this domain object. * * @return array<string, int> */ public function to_array(): array { $result = []; if ( $this->sessions !== null ) { $result['sessions'] = $this->sessions; } if ( $this->total_users !== null ) { $result['total_users'] = $this->total_users; } return $result; } /** * Sets the sessions. * * @param int $sessions The sessions. * * @return void */ public function set_sessions( int $sessions ): void { $this->sessions = $sessions; } /** * Sets the total users. * * @param int $total_users The total users. * * @return void */ public function set_total_users( int $total_users ): void { $this->total_users = $total_users; } } dashboard/domain/traffic/daily-traffic-data.php 0000644 00000002202 15025657560 0015525 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Traffic; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Interface; /** * Domain object that represents a single Daily Traffic record. */ class Daily_Traffic_Data implements Data_Interface { /** * The date of the traffic data, in YYYYMMDD format. * * @var string */ private $date; /** * The traffic data for the date. * * @var Traffic_Data */ private $traffic_data; /** * The constructor. * * @param string $date The date of the traffic data, in YYYYMMDD format. * @param Traffic_Data $traffic_data The traffic data for the date. */ public function __construct( string $date, Traffic_Data $traffic_data ) { $this->date = $date; $this->traffic_data = $traffic_data; } /** * The array representation of this domain object. * * @return array<string, string|int> */ public function to_array(): array { $result = []; $result['date'] = $this->date; return \array_merge( $result, $this->traffic_data->to_array() ); } } dashboard/domain/time-based-seo-metrics/repository-not-found-exception.php 0000644 00000000714 15025657560 0023054 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Time_Based_SEO_Metrics; use Exception; /** * Exception for when the repository for the given widget are not found. */ class Repository_Not_Found_Exception extends Exception { /** * Constructor of the exception. */ public function __construct() { parent::__construct( 'Repository not found', 404 ); } } dashboard/domain/time-based-seo-metrics/data-source-not-available-exception.php 0000644 00000001146 15025657560 0023651 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics; use Exception; /** * Exception for when the integration is not yet onboarded. */ class Data_Source_Not_Available_Exception extends Exception { /** * Constructor of the exception. * * @param string $data_source_name The name of the data source that is not found. */ public function __construct( $data_source_name ) { parent::__construct( "$data_source_name is not available yet. Not all prerequisites have been met.", 400 ); } } dashboard/application/tracking/setup-steps-tracking.php 0000644 00000005353 15025657560 0017426 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Tracking; use Yoast\WP\SEO\Dashboard\Infrastructure\Tracking\Setup_Steps_Tracking_Repository_Interface; /** * Tracks the setup steps. */ class Setup_Steps_Tracking { /** * The setup steps tracking repository. * * @var Setup_Steps_Tracking_Repository_Interface */ private $setup_steps_tracking_repository; /** * Constructs the class. * * @param Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository The setup steps tracking repository. */ public function __construct( Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository ) { $this->setup_steps_tracking_repository = $setup_steps_tracking_repository; } /** * If the Site Kit setup widget has been loaded. * * @return string "yes" on "no". */ public function get_setup_widget_loaded(): string { return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'setup_widget_loaded' ); } /** * Gets the stage of the first interaction. * * @return string The stage name. */ public function get_first_interaction_stage(): string { return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'first_interaction_stage' ); } /** * Gets the stage of the last interaction. * * @return string The stage name. */ public function get_last_interaction_stage(): string { return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'last_interaction_stage' ); } /** * If the setup widget has been temporarily dismissed. * * @return string "yes" on "no". */ public function get_setup_widget_temporarily_dismissed(): string { return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'setup_widget_temporarily_dismissed' ); } /** * If the setup widget has been permanently dismissed. * * @return string "yes" on "no". */ public function get_setup_widget_permanently_dismissed(): string { return $this->setup_steps_tracking_repository->get_setup_steps_tracking_element( 'setup_widget_permanently_dismissed' ); } /** * Return this object represented by a key value array. * * @return array<string> The tracking data */ public function to_array(): array { return [ 'setupWidgetLoaded' => $this->get_setup_widget_loaded(), 'firstInteractionStage' => $this->get_first_interaction_stage(), 'lastInteractionStage' => $this->get_last_interaction_stage(), 'setupWidgetTemporarilyDismissed' => $this->get_setup_widget_temporarily_dismissed(), 'setupWidgetPermanentlyDismissed' => $this->get_setup_widget_permanently_dismissed(), ]; } } dashboard/application/score-results/seo-score-results/seo-score-results-repository.php 0000644 00000002211 15025657560 0025541 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Application\Score_Results\SEO_Score_Results; use Yoast\WP\SEO\Dashboard\Application\Score_Results\Abstract_Score_Results_Repository; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results\Cached_SEO_Score_Results_Collector; /** * The repository to get SEO score results. */ class SEO_Score_Results_Repository extends Abstract_Score_Results_Repository { /** * The constructor. * * @param Cached_SEO_Score_Results_Collector $seo_score_results_collector The cached SEO score results collector. * @param SEO_Score_Groups_Interface ...$seo_score_groups All SEO score groups. */ public function __construct( Cached_SEO_Score_Results_Collector $seo_score_results_collector, SEO_Score_Groups_Interface ...$seo_score_groups ) { $this->score_results_collector = $seo_score_results_collector; $this->score_groups = $seo_score_groups; } } dashboard/application/score-results/abstract-score-results-repository.php 0000644 00000005271 15025657560 0023171 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Application\Score_Results; use Exception; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Result; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface; /** * The abstract score results repository. */ abstract class Abstract_Score_Results_Repository { /** * The score results collector. * * @var Score_Results_Collector_Interface */ protected $score_results_collector; /** * The current scores repository. * * @var Current_Scores_Repository */ protected $current_scores_repository; /** * All score groups. * * @var Score_Groups_Interface[] */ protected $score_groups; /** * Sets the repositories. * * @required * * @param Current_Scores_Repository $current_scores_repository The current scores repository. * * @return void */ public function set_repositories( Current_Scores_Repository $current_scores_repository ) { $this->current_scores_repository = $current_scores_repository; } /** * Returns the score results for a content type. * * @param Content_Type $content_type The content type. * @param Taxonomy|null $taxonomy The taxonomy of the term we're filtering for. * @param int|null $term_id The ID of the term we're filtering for. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<array<string, string|int|array<string, string>>> The scores. * * @throws Exception When getting score results from the infrastructure fails. */ public function get_score_results( Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id, ?bool $is_troubleshooting ): array { $score_results = $this->score_results_collector->get_score_results( $this->score_groups, $content_type, $term_id, $is_troubleshooting ); if ( $is_troubleshooting === true ) { $score_results['score_ids'] = clone $score_results['scores']; foreach ( $score_results['scores'] as &$score ) { $score = ( $score !== null ) ? \count( \explode( ',', $score ) ) : 0; } } $current_scores_list = $this->current_scores_repository->get_current_scores( $this->score_groups, $score_results, $content_type, $taxonomy, $term_id ); $score_result_object = new Score_Result( $current_scores_list, $score_results['query_time'], $score_results['cache_used'] ); return $score_result_object->to_array(); } } dashboard/application/score-results/current-scores-repository.php 0000644 00000006223 15025657560 0021532 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Application\Score_Results; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Current_Score; use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Current_Scores_List; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Groups\Score_Group_Link_Collector; /** * The current scores repository. */ class Current_Scores_Repository { /** * The score group link collector. * * @var Score_Group_Link_Collector */ protected $score_group_link_collector; /** * The constructor. * * @param Score_Group_Link_Collector $score_group_link_collector The score group link collector. */ public function __construct( Score_Group_Link_Collector $score_group_link_collector ) { $this->score_group_link_collector = $score_group_link_collector; } /** * Returns the current results. * * @param Score_Groups_Interface[] $score_groups The score groups. * @param array<string, string> $score_results The score results. * @param Content_Type $content_type The content type. * @param Taxonomy|null $taxonomy The taxonomy of the term we're filtering for. * @param int|null $term_id The ID of the term we're filtering for. * * @return array<array<string, string|int|array<string, string>>> The current results. */ public function get_current_scores( array $score_groups, array $score_results, Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id ): Current_Scores_List { $current_scores_list = new Current_Scores_List(); foreach ( $score_groups as $score_group ) { $score_name = $score_group->get_name(); $current_score_links = $this->get_current_score_links( $score_group, $content_type, $taxonomy, $term_id ); $score_amount = (int) $score_results['scores']->$score_name; $score_ids = ( isset( $score_results['score_ids'] ) ) ? $score_results['score_ids']->$score_name : null; $current_score = new Current_Score( $score_name, $score_amount, $score_ids, $current_score_links ); $current_scores_list->add( $current_score, $score_group->get_position() ); } return $current_scores_list; } /** * Returns the links for the current scores of a score group. * * @param Score_Groups_Interface $score_group The scoure group. * @param Content_Type $content_type The content type. * @param Taxonomy|null $taxonomy The taxonomy of the term we're filtering for. * @param int|null $term_id The ID of the term we're filtering for. * * @return array<string, string> The current score links. */ protected function get_current_score_links( Score_Groups_Interface $score_group, Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id ): array { return [ 'view' => $this->score_group_link_collector->get_view_link( $score_group, $content_type, $taxonomy, $term_id ), ]; } } application/score-results/readability-score-results/readability-score-results-repository.php 0000644 00000002441 15025657560 0030675 0 ustar 00 dashboard <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Application\Score_Results\Readability_Score_Results; use Yoast\WP\SEO\Dashboard\Application\Score_Results\Abstract_Score_Results_Repository; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Readability_Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results\Cached_Readability_Score_Results_Collector; /** * The repository to get readability score results. */ class Readability_Score_Results_Repository extends Abstract_Score_Results_Repository { /** * The constructor. * * @param Cached_Readability_Score_Results_Collector $readability_score_results_collector The cached readability score results collector. * @param Readability_Score_Groups_Interface ...$readability_score_groups All readability score groups. */ public function __construct( Cached_Readability_Score_Results_Collector $readability_score_results_collector, Readability_Score_Groups_Interface ...$readability_score_groups ) { $this->score_results_collector = $readability_score_results_collector; $this->score_groups = $readability_score_groups; } } dashboard/application/endpoints/endpoints-repository.php 0000644 00000001725 15025657560 0017752 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Endpoints; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_List; /** * Repository for endpoints. */ class Endpoints_Repository { /** * Holds the endpoints. * * @var array<Endpoint_Interface> */ private $endpoints; /** * Constructs the repository. * * @param Endpoint_Interface ...$endpoints The endpoints to add to the repository. */ public function __construct( Endpoint_Interface ...$endpoints ) { $this->endpoints = $endpoints; } /** * Creates a list with all endpoints. * * @return Endpoint_List The list with all endpoints. */ public function get_all_endpoints(): Endpoint_List { $list = new Endpoint_List(); foreach ( $this->endpoints as $endpoint ) { $list->add_endpoint( $endpoint ); } return $list; } } dashboard/application/configuration/dashboard-configuration.php 0000644 00000012772 15025657560 0021176 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Application\Configuration; use Yoast\WP\SEO\Dashboard\Application\Content_Types\Content_Types_Repository; use Yoast\WP\SEO\Dashboard\Application\Endpoints\Endpoints_Repository; use Yoast\WP\SEO\Dashboard\Application\Tracking\Setup_Steps_Tracking; use Yoast\WP\SEO\Dashboard\Infrastructure\Browser_Cache\Browser_Cache_Configuration; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Dashboard\Infrastructure\Nonces\Nonce_Repository; use Yoast\WP\SEO\Editors\Application\Analysis_Features\Enabled_Analysis_Features_Repository; use Yoast\WP\SEO\Editors\Framework\Keyphrase_Analysis; use Yoast\WP\SEO\Editors\Framework\Readability_Analysis; use Yoast\WP\SEO\Helpers\Indexable_Helper; use Yoast\WP\SEO\Helpers\User_Helper; /** * Responsible for the dashboard configuration. */ class Dashboard_Configuration { /** * The content types repository. * * @var Content_Types_Repository */ private $content_types_repository; /** * The indexable helper. * * @var Indexable_Helper */ private $indexable_helper; /** * The user helper. * * @var User_Helper */ private $user_helper; /** * The repository. * * @var Enabled_Analysis_Features_Repository */ private $enabled_analysis_features_repository; /** * The endpoints repository. * * @var Endpoints_Repository */ private $endpoints_repository; /** * The nonce repository. * * @var Nonce_Repository */ private $nonce_repository; /** * The Site Kit integration data. * * @var Site_Kit */ private $site_kit_integration_data; /** * The setup steps tracking data. * * @var Setup_Steps_Tracking */ private $setup_steps_tracking; /** * The browser cache configuration. * * @var Browser_Cache_Configuration */ private $browser_cache_configuration; /** * The constructor. * * @param Content_Types_Repository $content_types_repository The content types repository. * @param Indexable_Helper $indexable_helper The indexable helper * repository. * @param User_Helper $user_helper The user helper. * @param Enabled_Analysis_Features_Repository $enabled_analysis_features_repository The analysis feature. * repository. * @param Endpoints_Repository $endpoints_repository The endpoints repository. * @param Nonce_Repository $nonce_repository The nonce repository. * @param Site_Kit $site_kit_integration_data The Site Kit integration data. * @param Setup_Steps_Tracking $setup_steps_tracking The setup steps tracking data. * @param Browser_Cache_Configuration $browser_cache_configuration The browser cache configuration. */ public function __construct( Content_Types_Repository $content_types_repository, Indexable_Helper $indexable_helper, User_Helper $user_helper, Enabled_Analysis_Features_Repository $enabled_analysis_features_repository, Endpoints_Repository $endpoints_repository, Nonce_Repository $nonce_repository, Site_Kit $site_kit_integration_data, Setup_Steps_Tracking $setup_steps_tracking, Browser_Cache_Configuration $browser_cache_configuration ) { $this->content_types_repository = $content_types_repository; $this->indexable_helper = $indexable_helper; $this->user_helper = $user_helper; $this->enabled_analysis_features_repository = $enabled_analysis_features_repository; $this->endpoints_repository = $endpoints_repository; $this->nonce_repository = $nonce_repository; $this->site_kit_integration_data = $site_kit_integration_data; $this->setup_steps_tracking = $setup_steps_tracking; $this->browser_cache_configuration = $browser_cache_configuration; } /** * Returns a configuration * * @return array<string, array<string>|array<string, string|array<string, array<string, int>>>> */ public function get_configuration(): array { $configuration = [ 'contentTypes' => $this->content_types_repository->get_content_types(), 'indexablesEnabled' => $this->indexable_helper->should_index_indexables(), 'displayName' => $this->user_helper->get_current_user_display_name(), 'enabledAnalysisFeatures' => $this->enabled_analysis_features_repository->get_features_by_keys( [ Readability_Analysis::NAME, Keyphrase_Analysis::NAME, ] )->to_array(), 'endpoints' => $this->endpoints_repository->get_all_endpoints()->to_array(), 'nonce' => $this->nonce_repository->get_rest_nonce(), 'setupStepsTracking' => $this->setup_steps_tracking->to_array(), ]; $site_kit_integration_data = $this->site_kit_integration_data->to_array(); if ( ! empty( $site_kit_integration_data ) ) { $configuration ['siteKitConfiguration'] = $site_kit_integration_data; } $browser_cache_configuration = $this->browser_cache_configuration->get_configuration(); if ( ! empty( $browser_cache_configuration ) ) { $configuration ['browserCache'] = $browser_cache_configuration; } return $configuration; } } dashboard/application/score-groups/seo-score-groups/seo-score-groups-repository.php 0000644 00000003050 15025657560 0025015 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Application\Score_Groups\SEO_Score_Groups; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface; /** * The repository to get SEO score groups. */ class SEO_Score_Groups_Repository { /** * All SEO score groups. * * @var SEO_Score_Groups_Interface[] */ private $seo_score_groups; /** * The constructor. * * @param SEO_Score_Groups_Interface ...$seo_score_groups All SEO score groups. */ public function __construct( SEO_Score_Groups_Interface ...$seo_score_groups ) { $this->seo_score_groups = $seo_score_groups; } /** * Returns the SEO score group that a SEO score belongs to. * * @param int $seo_score The SEO score to be assigned into a group. * * @return SEO_Score_Groups_Interface The SEO score group that the SEO score belongs to. */ public function get_seo_score_group( ?int $seo_score ): SEO_Score_Groups_Interface { if ( $seo_score === null || $seo_score === 0 ) { return new No_SEO_Score_Group(); } foreach ( $this->seo_score_groups as $seo_score_group ) { if ( $seo_score_group->get_max_score() === null ) { continue; } if ( $seo_score >= $seo_score_group->get_min_score() && $seo_score <= $seo_score_group->get_max_score() ) { return $seo_score_group; } } return new No_SEO_Score_Group(); } } dashboard/application/content-types/content-types-repository.php 0000644 00000003132 15025657560 0021366 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Application\Content_Types; use Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository; use Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector; /** * The repository to get content types. */ class Content_Types_Repository { /** * The post type helper. * * @var Content_Types_Collector */ protected $content_types_collector; /** * The taxonomies repository. * * @var Taxonomies_Repository */ private $taxonomies_repository; /** * The constructor. * * @param Content_Types_Collector $content_types_collector The post type helper. * @param Taxonomies_Repository $taxonomies_repository The taxonomies repository. */ public function __construct( Content_Types_Collector $content_types_collector, Taxonomies_Repository $taxonomies_repository ) { $this->content_types_collector = $content_types_collector; $this->taxonomies_repository = $taxonomies_repository; } /** * Returns the content types array. * * @return array<array<string, array<string, array<string, array<string, string|null>>>>> The content types array. */ public function get_content_types(): array { $content_types_list = $this->content_types_collector->get_content_types(); foreach ( $content_types_list->get() as $content_type ) { $content_type_taxonomy = $this->taxonomies_repository->get_content_type_taxonomy( $content_type->get_name() ); $content_type->set_taxonomy( $content_type_taxonomy ); } return $content_types_list->to_array(); } } dashboard/application/search-rankings/top-query-repository.php 0000644 00000004013 15025657560 0020761 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; /** * The data provider for top query data. */ class Top_Query_Repository implements Dashboard_Repository_Interface { /** * The adapter. * * @var Site_Kit_Search_Console_Adapter */ private $site_kit_search_console_adapter; /** * The site kit configuration object. * * @var Site_Kit */ private $site_kit_configuration; /** * The constructor. * * @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter. * @param Site_Kit $site_kit_configuration The site kit configuration object. */ public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter, Site_Kit $site_kit_configuration ) { $this->site_kit_search_console_adapter = $site_kit_search_console_adapter; $this->site_kit_configuration = $site_kit_configuration; } /** * Gets the top queries' data. * * @param Parameters $parameters The parameter to use for getting the top queries. * * @return Data_Container * * @throws Data_Source_Not_Available_Exception When this repository is used without the needed prerequisites ready. */ public function get_data( Parameters $parameters ): Data_Container { if ( ! $this->site_kit_configuration->is_onboarded() ) { throw new Data_Source_Not_Available_Exception( 'Top queries repository' ); } return $this->site_kit_search_console_adapter->get_data( $parameters ); } } dashboard/application/search-rankings/search-ranking-compare-repository.php 0000644 00000004127 15025657560 0023342 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; /** * The data provider for comparing search ranking related data. */ class Search_Ranking_Compare_Repository implements Dashboard_Repository_Interface { /** * The adapter. * * @var Site_Kit_Search_Console_Adapter */ private $site_kit_search_console_adapter; /** * The site kit configuration object. * * @var Site_Kit */ private $site_kit_configuration; /** * The constructor. * * @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter. * @param Site_Kit $site_kit_configuration The site kit configuration object. */ public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter, Site_Kit $site_kit_configuration ) { $this->site_kit_search_console_adapter = $site_kit_search_console_adapter; $this->site_kit_configuration = $site_kit_configuration; } /** * Gets the comparing search ranking data. * * @param Parameters $parameters The parameter to use for getting the comparing search ranking data. * * @return Data_Container * * @throws Data_Source_Not_Available_Exception When getting the comparing search ranking data fails. */ public function get_data( Parameters $parameters ): Data_Container { if ( ! $this->site_kit_configuration->is_onboarded() ) { throw new Data_Source_Not_Available_Exception( 'Comparison search ranking repository' ); } return $this->site_kit_search_console_adapter->get_comparison_data( $parameters ); } } dashboard/application/search-rankings/top-page-repository.php 0000644 00000005173 15025657560 0020540 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Search_Rankings; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Indexables\Top_Page_Indexable_Collector; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Site_Kit_Search_Console_Adapter; /** * The data provider for top page data. */ class Top_Page_Repository implements Dashboard_Repository_Interface { /** * The adapter. * * @var Site_Kit_Search_Console_Adapter */ private $site_kit_search_console_adapter; /** * The top page indexable collector. * * @var Top_Page_Indexable_Collector */ private $top_page_indexable_collector; /** * The site kit configuration object. * * @var Site_Kit */ private $site_kit_configuration; /** * The constructor. * * @param Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter The adapter. * @param Top_Page_Indexable_Collector $top_page_indexable_collector The top page indexable collector. * @param Site_Kit $site_kit_configuration The site kit configuration object. */ public function __construct( Site_Kit_Search_Console_Adapter $site_kit_search_console_adapter, Top_Page_Indexable_Collector $top_page_indexable_collector, Site_Kit $site_kit_configuration ) { $this->site_kit_search_console_adapter = $site_kit_search_console_adapter; $this->top_page_indexable_collector = $top_page_indexable_collector; $this->site_kit_configuration = $site_kit_configuration; } /** * Gets the top pages' data. * * @param Parameters $parameters The parameter to use for getting the top pages. * * @return Data_Container * * @throws Data_Source_Not_Available_Exception When this repository is used without the needed prerequisites ready. */ public function get_data( Parameters $parameters ): Data_Container { if ( ! $this->site_kit_configuration->is_onboarded() ) { throw new Data_Source_Not_Available_Exception( 'Top page repository' ); } $top_pages_search_ranking_data = $this->site_kit_search_console_adapter->get_data( $parameters ); $top_pages_full_data = $this->top_page_indexable_collector->get_data( $top_pages_search_ranking_data ); return $top_pages_full_data; } } dashboard/application/taxonomies/taxonomies-repository.php 0000644 00000003727 15025657560 0020324 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Application\Taxonomies; use Yoast\WP\SEO\Dashboard\Application\Filter_Pairs\Filter_Pairs_Repository; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; use Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies\Taxonomies_Collector; /** * The repository to get taxonomies. */ class Taxonomies_Repository { /** * The taxonomies collector. * * @var Taxonomies_Collector */ private $taxonomies_collector; /** * The filter pairs repository. * * @var Filter_Pairs_Repository */ private $filter_pairs_repository; /** * The constructor. * * @param Taxonomies_Collector $taxonomies_collector The taxonomies collector. * @param Filter_Pairs_Repository $filter_pairs_repository The filter pairs repository. */ public function __construct( Taxonomies_Collector $taxonomies_collector, Filter_Pairs_Repository $filter_pairs_repository ) { $this->taxonomies_collector = $taxonomies_collector; $this->filter_pairs_repository = $filter_pairs_repository; } /** * Returns the object of the filtering taxonomy of a content type. * * @param string $content_type The content type that the taxonomy filters. * * @return Taxonomy|null The filtering taxonomy of the content type. */ public function get_content_type_taxonomy( string $content_type ) { // First we check if there's a filter that overrides the filtering taxonomy for this content type. $taxonomy = $this->taxonomies_collector->get_custom_filtering_taxonomy( $content_type ); if ( $taxonomy ) { return $taxonomy; } // Then we check if there is a filter explicitly made for this content type. $taxonomy = $this->filter_pairs_repository->get_taxonomy( $content_type ); if ( $taxonomy ) { return $taxonomy; } // If everything else returned empty, we can always try the fallback taxonomy. return $this->taxonomies_collector->get_fallback_taxonomy( $content_type ); } } dashboard/application/filter-pairs/filter-pairs-repository.php 0000644 00000003053 15025657560 0020742 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Application\Filter_Pairs; use Yoast\WP\SEO\Dashboard\Domain\Filter_Pairs\Filter_Pairs_Interface; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; use Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies\Taxonomies_Collector; /** * The repository to get hardcoded filter pairs. */ class Filter_Pairs_Repository { /** * The taxonomies collector. * * @var Taxonomies_Collector */ private $taxonomies_collector; /** * All filter pairs. * * @var Filter_Pairs_Interface[] */ private $filter_pairs; /** * The constructor. * * @param Taxonomies_Collector $taxonomies_collector The taxonomies collector. * @param Filter_Pairs_Interface ...$filter_pairs All filter pairs. */ public function __construct( Taxonomies_Collector $taxonomies_collector, Filter_Pairs_Interface ...$filter_pairs ) { $this->taxonomies_collector = $taxonomies_collector; $this->filter_pairs = $filter_pairs; } /** * Returns a taxonomy based on a content type, by looking into hardcoded filter pairs. * * @param string $content_type The content type. * * @return Taxonomy|null The taxonomy filter. */ public function get_taxonomy( string $content_type ): ?Taxonomy { foreach ( $this->filter_pairs as $filter_pair ) { if ( $filter_pair->get_filtered_content_type() === $content_type ) { return $this->taxonomies_collector->get_taxonomy( $filter_pair->get_filtering_taxonomy(), $content_type ); } } return null; } } dashboard/application/traffic/organic-sessions-daily-repository.php 0000644 00000004122 15025657560 0021742 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Traffic; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Site_Kit_Analytics_4_Adapter; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; /** * The data provider for daily organic sessions data. */ class Organic_Sessions_Daily_Repository implements Dashboard_Repository_Interface { /** * The adapter. * * @var Site_Kit_Analytics_4_Adapter */ private $site_kit_analytics_4_adapter; /** * The site kit configuration object. * * @var Site_Kit */ private $site_kit_configuration; /** * The constructor. * * @param Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter The adapter. * @param Site_Kit $site_kit_configuration The site kit configuration object. */ public function __construct( Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter, Site_Kit $site_kit_configuration ) { $this->site_kit_analytics_4_adapter = $site_kit_analytics_4_adapter; $this->site_kit_configuration = $site_kit_configuration; } /** * Gets daily organic sessions' data. * * @param Parameters $parameters The parameter to use for getting the daily organic sessions' data. * * @return Data_Container * * @throws Data_Source_Not_Available_Exception When this repository is used without the needed prerequisites ready. */ public function get_data( Parameters $parameters ): Data_Container { if ( ! $this->site_kit_configuration->is_onboarded() || ! $this->site_kit_configuration->is_ga_connected() ) { throw new Data_Source_Not_Available_Exception( 'Daily organic sessions repository' ); } return $this->site_kit_analytics_4_adapter->get_daily_data( $parameters ); } } dashboard/application/traffic/organic-sessions-compare-repository.php 0000644 00000004142 15025657560 0022270 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Application\Traffic; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Dashboard_Repository_Interface; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Domain\Time_Based_Seo_Metrics\Data_Source_Not_Available_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Site_Kit_Analytics_4_Adapter; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; /** * The data provider for comparison organic sessions data. */ class Organic_Sessions_Compare_Repository implements Dashboard_Repository_Interface { /** * The adapter. * * @var Site_Kit_Analytics_4_Adapter */ private $site_kit_analytics_4_adapter; /** * The site kit configuration object. * * @var Site_Kit */ private $site_kit_configuration; /** * The constructor. * * @param Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter The adapter. * @param Site_Kit $site_kit_configuration The site kit configuration object. */ public function __construct( Site_Kit_Analytics_4_Adapter $site_kit_analytics_4_adapter, Site_Kit $site_kit_configuration ) { $this->site_kit_analytics_4_adapter = $site_kit_analytics_4_adapter; $this->site_kit_configuration = $site_kit_configuration; } /** * Gets comparison organic sessions' data. * * @param Parameters $parameters The parameter to use for getting the comparison organic sessions' data. * * @return Data_Container * * @throws Data_Source_Not_Available_Exception When getting the comparison organic sessions' data fails. */ public function get_data( Parameters $parameters ): Data_Container { if ( ! $this->site_kit_configuration->is_onboarded() || ! $this->site_kit_configuration->is_ga_connected() ) { throw new Data_Source_Not_Available_Exception( 'Comparison organic sessions repository' ); } return $this->site_kit_analytics_4_adapter->get_comparison_data( $parameters ); } } dashboard/infrastructure/integrations/site-kit.php 0000644 00000026342 15025657560 0016545 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Integrations; use Google\Site_Kit\Core\REST_API\REST_Routes; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface as Configuration_Repository; use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository_Interface; use Yoast\WP\SEO\Dashboard\Infrastructure\Connection\Site_Kit_Is_Connected_Call; use Yoast\WP\SEO\Dashboard\User_Interface\Setup\Setup_Url_Interceptor; /** * Describes if the Site kit integration is enabled and configured. */ class Site_Kit { private const SITE_KIT_FILE = 'google-site-kit/google-site-kit.php'; /** * The Site Kit feature conditional. * * @var Google_Site_Kit_Feature_Conditional */ protected $site_kit_feature_conditional; /** * The Site Kit consent repository. * * @var Site_Kit_Consent_Repository_Interface */ private $site_kit_consent_repository; /** * The Site Kit consent repository. * * @var Configuration_Repository */ private $permanently_dismissed_site_kit_configuration_repository; /** * The call wrapper. * * @var Site_Kit_Is_Connected_Call $site_kit_is_connected_call */ private $site_kit_is_connected_call; /** * The search console module data. * * @var array<string, bool> $search_console_module */ private $search_console_module = [ 'can_view' => null, ]; /** * The analytics module data. * * @var array<string, bool> $ga_module */ private $ga_module = [ 'can_view' => null, 'connected' => null, ]; /** * The constructor. * * @param Site_Kit_Consent_Repository_Interface $site_kit_consent_repository The Site Kit consent repository. * @param Configuration_Repository $configuration_repository The Site Kit permanently dismissed * configuration repository. * @param Site_Kit_Is_Connected_Call $site_kit_is_connected_call The api call to check if the site is * connected. * @param Google_Site_Kit_Feature_Conditional $site_kit_feature_conditional The Site Kit feature conditional. */ public function __construct( Site_Kit_Consent_Repository_Interface $site_kit_consent_repository, Configuration_Repository $configuration_repository, Site_Kit_Is_Connected_Call $site_kit_is_connected_call, Google_Site_Kit_Feature_Conditional $site_kit_feature_conditional ) { $this->site_kit_consent_repository = $site_kit_consent_repository; $this->permanently_dismissed_site_kit_configuration_repository = $configuration_repository; $this->site_kit_is_connected_call = $site_kit_is_connected_call; $this->site_kit_feature_conditional = $site_kit_feature_conditional; } /** * If the integration is activated. * * @return bool If the integration is activated. */ public function is_enabled(): bool { return \is_plugin_active( self::SITE_KIT_FILE ); } /** * If the Google site kit setup has been completed. * * @return bool If the Google site kit setup has been completed. */ private function is_setup_completed(): bool { return $this->site_kit_is_connected_call->is_setup_completed(); } /** * If consent has been granted. * * @return bool If consent has been granted. */ private function is_connected(): bool { return $this->site_kit_consent_repository->is_consent_granted(); } /** * If Google Analytics is connected. * * @return bool If Google Analytics is connected. */ public function is_ga_connected(): bool { if ( $this->ga_module['connected'] !== null ) { return $this->ga_module['connected']; } return $this->site_kit_is_connected_call->is_ga_connected(); } /** * If the Site Kit plugin is installed. This is needed since we cannot check with `is_plugin_active` in rest * requests. `Plugin.php` is only loaded on admin pages. * * @return bool If the Site Kit plugin is installed. */ private function is_site_kit_installed(): bool { return \class_exists( 'Google\Site_Kit\Plugin' ); } /** * If the entire onboarding has been completed. * * @return bool If the entire onboarding has been completed. */ public function is_onboarded(): bool { // @TODO: Consider replacing the `is_setup_completed()` check with a `can_read_data( $module )` check (and possibly rename the method to something more genric eg. is_ready() ). return ( $this->is_site_kit_installed() && $this->is_setup_completed() && $this->is_connected() ); } /** * Checks if current user can view dashboard data for a module * * @param array<array|null> $module The module. * * @return bool If the user can read the data. */ private function can_read_data( array $module ): bool { return ( ! \is_null( $module['can_view'] ) ? $module['can_view'] : false ); } /** * Return this object represented by a key value array. * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_array(): array { if ( ! $this->site_kit_feature_conditional->is_met() ) { return []; } if ( $this->is_enabled() ) { $this->parse_site_kit_data(); } return [ 'installUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_install_url() ), 'activateUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_activate_url() ), 'setupUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_setup_url() ), 'updateUrl' => \self_admin_url( 'update.php?page=' . Setup_Url_Interceptor::PAGE . '&redirect_setup_url=' ) . \rawurlencode( $this->get_update_url() ), 'dashboardUrl' => \self_admin_url( 'admin.php?page=googlesitekit-dashboard' ), 'isAnalyticsConnected' => $this->is_ga_connected(), 'isFeatureEnabled' => true, 'isSetupWidgetDismissed' => $this->permanently_dismissed_site_kit_configuration_repository->is_site_kit_configuration_dismissed(), 'capabilities' => [ 'installPlugins' => \current_user_can( 'install_plugins' ), 'viewSearchConsoleData' => $this->can_read_data( $this->search_console_module ), 'viewAnalyticsData' => $this->can_read_data( $this->ga_module ), ], 'connectionStepsStatuses' => [ 'isInstalled' => \file_exists( \WP_PLUGIN_DIR . '/' . self::SITE_KIT_FILE ), 'isActive' => $this->is_enabled(), 'isSetupCompleted' => $this->can_read_data( $this->search_console_module ) || $this->can_read_data( $this->ga_module ), 'isConsentGranted' => $this->is_connected(), ], 'isVersionSupported' => \defined( 'GOOGLESITEKIT_VERSION' ) ? \version_compare( \GOOGLESITEKIT_VERSION, '1.148.0', '>=' ) : false, // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. 'isRedirectedFromSiteKit' => isset( $_GET['redirected_from_site_kit'] ), ]; } /** * Return this object represented by a key value array. This is not used yet. * * @codeCoverageIgnore * * @return array<string, bool> Returns the name and if the feature is enabled. */ public function to_legacy_array(): array { return $this->to_array(); } /** * Parses the Site Kit configuration data. * * @return void */ public function parse_site_kit_data(): void { $paths = $this->get_preload_paths(); $preloaded = $this->get_preloaded_data( $paths ); if ( empty( $preloaded ) ) { return; } $modules_data = ! empty( $preloaded[ $paths['modules'] ]['body'] ) ? $preloaded[ $paths['modules'] ]['body'] : []; $modules_permissions = ! empty( $preloaded[ $paths['permissions'] ]['body'] ) ? $preloaded[ $paths['permissions'] ]['body'] : []; $can_view_dashboard = ( $modules_permissions['googlesitekit_view_authenticated_dashboard'] ?? false ); foreach ( $modules_data as $module ) { $slug = $module['slug']; // We have to also check if the module is recoverable, because if we rely on the module being shared, we have to make also sure the module owner is still connected. $is_recoverable = ( $module['recoverable'] ?? null ); if ( $slug === 'analytics-4' ) { $can_read_shared_module_data = ( $modules_permissions['googlesitekit_read_shared_module_data::["analytics-4"]'] ?? false ); $this->ga_module['can_view'] = $can_view_dashboard || ( $can_read_shared_module_data && ! $is_recoverable ); $this->ga_module['connected'] = ( $module['connected'] ?? false ); } if ( $slug === 'search-console' ) { $can_read_shared_module_data = ( $modules_permissions['googlesitekit_read_shared_module_data::["search-console"]'] ?? false ); $this->search_console_module['can_view'] = $can_view_dashboard || ( $can_read_shared_module_data && ! $is_recoverable ); } } } /** * Holds the parsed preload paths for preloading some Site Kit API data. * * @return string[] */ public function get_preload_paths(): array { $rest_root = ( \class_exists( REST_Routes::class ) ) ? REST_Routes::REST_ROOT : ''; return [ 'permissions' => '/' . $rest_root . '/core/user/data/permissions', 'modules' => '/' . $rest_root . '/core/modules/data/list', ]; } /** * Runs the given paths through the `rest_preload_api_request` method. * * @param string[] $paths The paths to add to `rest_preload_api_request`. * * @return array<array|null> The array with all the now filled in preloaded data. */ public function get_preloaded_data( array $paths ): array { $preload_paths = \apply_filters( 'googlesitekit_apifetch_preload_paths', [] ); $actual_paths = \array_intersect( $paths, $preload_paths ); return \array_reduce( \array_unique( $actual_paths ), 'rest_preload_api_request', [] ); } /** * Creates a valid activation URL for the Site Kit plugin. * * @return string */ public function get_activate_url(): string { return \html_entity_decode( \wp_nonce_url( \self_admin_url( 'plugins.php?action=activate&plugin=' . self::SITE_KIT_FILE ), 'activate-plugin_' . self::SITE_KIT_FILE ) ); } /** * Creates a valid install URL for the Site Kit plugin. * * @return string */ public function get_install_url(): string { return \html_entity_decode( \wp_nonce_url( \self_admin_url( 'update.php?action=install-plugin&plugin=google-site-kit' ), 'install-plugin_google-site-kit' ) ); } /** * Creates a valid update URL for the Site Kit plugin. * * @return string */ public function get_update_url(): string { return \html_entity_decode( \wp_nonce_url( \self_admin_url( 'update.php?action=upgrade-plugin&plugin=' . self::SITE_KIT_FILE ), 'upgrade-plugin_' . self::SITE_KIT_FILE ) ); } /** * Creates a valid setup URL for the Site Kit plugin. * * @return string */ public function get_setup_url(): string { return \self_admin_url( 'admin.php?page=googlesitekit-splash' ); } } dashboard/infrastructure/tracking/setup-steps-tracking-repository-interface.php 0000644 00000002043 15025657560 0024347 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Tracking; /** * Interface for the Site Kit Usage Tracking Repository. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ interface Setup_Steps_Tracking_Repository_Interface { /** * Sets an option inside the Site Kit usage options array.. * * @param string $element_name The name of the array element to set. * @param string $element_value The value of the array element to set. * * @return bool False when the update failed, true when the update succeeded. */ public function set_setup_steps_tracking_element( string $element_name, string $element_value ): bool; /** * Gets an option inside the Site Kit usage options array.. * * @param string $element_name The name of the array element to get. * * @return string The value if present, empty string if not. */ public function get_setup_steps_tracking_element( string $element_name ): string; } dashboard/infrastructure/tracking/setup-steps-tracking-repository.php 0000644 00000003112 15025657560 0022407 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Tracking; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Stores and retrieves data about Site Kit usage. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Setup_Steps_Tracking_Repository implements Setup_Steps_Tracking_Repository_Interface { /** * Holds the Options_Helper instance. * * @var Options_Helper */ private $options_helper; /** * Constructs the class. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Sets an option inside the Site Kit usage options array. * * @param string $element_name The name of the option to set. * @param string $element_value The value of the option to set. * * @return bool False when the update failed, true when the update succeeded. */ public function set_setup_steps_tracking_element( string $element_name, string $element_value ): bool { return $this->options_helper->set( 'site_kit_tracking_' . $element_name, $element_value ); } /** * Gets an option inside the Site Kit usage options array. * * @param string $element_name The name of the option to get. * * @return string The value if present, empty string if not. */ public function get_setup_steps_tracking_element( string $element_name ): string { return $this->options_helper->get( 'site_kit_tracking_' . $element_name, '' ); } } dashboard/infrastructure/score-results/seo-score-results/cached-seo-score-results-collector.php 0000644 00000005511 15025657560 0027300 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results; use WPSEO_Utils; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface; /** * The caching decorator to get readability score results. */ class Cached_SEO_Score_Results_Collector implements Score_Results_Collector_Interface { public const SEO_SCORES_TRANSIENT = 'wpseo_seo_scores'; /** * The actual collector implementation. * * @var SEO_Score_Results_Collector */ private $seo_score_results_collector; /** * The constructor. * * @param SEO_Score_Results_Collector $seo_score_results_collector The collector implementation to use. */ public function __construct( SEO_Score_Results_Collector $seo_score_results_collector ) { $this->seo_score_results_collector = $seo_score_results_collector; } /** * Retrieves the SEO score results for a content type. * Based on caching returns either the result or gets it from the collector. * * @param SEO_Score_Groups_Interface[] $score_groups All SEO score groups. * @param Content_Type $content_type The content type. * @param int|null $term_id The ID of the term we're filtering for. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<string, object|bool|float> The SEO score results for a content type. * * @throws Score_Results_Not_Found_Exception When the query of getting score results fails. */ public function get_score_results( array $score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting ) { $content_type_name = $content_type->get_name(); $transient_name = self::SEO_SCORES_TRANSIENT . '_' . $content_type_name . ( ( $term_id === null ) ? '' : '_' . $term_id ); $results = []; $transient = \get_transient( $transient_name ); if ( $is_troubleshooting !== true && $transient !== false ) { $results['scores'] = \json_decode( $transient, false ); $results['cache_used'] = true; $results['query_time'] = 0; return $results; } $results = $this->seo_score_results_collector->get_score_results( $score_groups, $content_type, $term_id, $is_troubleshooting ); $results['cache_used'] = false; if ( $is_troubleshooting !== true ) { \set_transient( $transient_name, WPSEO_Utils::format_json_encode( $results['scores'] ), \MINUTE_IN_SECONDS ); } return $results; } } dashboard/infrastructure/score-results/seo-score-results/seo-score-results-collector.php 0000644 00000012666 15025657560 0026104 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\SEO_Score_Results; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface; /** * Getting SEO score results from the indexable database table. */ class SEO_Score_Results_Collector implements Score_Results_Collector_Interface { /** * Retrieves the SEO score results for a content type. * * @param SEO_Score_Groups_Interface[] $seo_score_groups All SEO score groups. * @param Content_Type $content_type The content type. * @param int|null $term_id The ID of the term we're filtering for. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<string, object|bool|float> The SEO score results for a content type. * * @throws Score_Results_Not_Found_Exception When the query of getting score results fails. */ public function get_score_results( array $seo_score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting ): array { global $wpdb; $results = []; $content_type_name = $content_type->get_name(); $select = $this->build_select( $seo_score_groups, $is_troubleshooting ); $replacements = \array_merge( \array_values( $select['replacements'] ), [ Model::get_table_name( 'Indexable' ), $content_type_name, ] ); if ( $term_id === null ) { //phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements. //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders. $query = $wpdb->prepare( " SELECT {$select['fields']} FROM %i AS I WHERE ( I.post_status = 'publish' OR I.post_status IS NULL ) AND I.object_type = 'post' AND I.object_sub_type = %s AND ( I.is_robots_noindex IS NULL OR I.is_robots_noindex <> 1 )", $replacements ); //phpcs:enable } else { $replacements[] = $wpdb->term_relationships; $replacements[] = $term_id; //phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements. //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders. $query = $wpdb->prepare( " SELECT {$select['fields']} FROM %i AS I WHERE ( I.post_status = 'publish' OR I.post_status IS NULL ) AND I.object_type IN ('post') AND I.object_sub_type = %s AND ( I.is_robots_noindex IS NULL OR I.is_robots_noindex <> 1 ) AND I.object_id IN ( SELECT object_id FROM %i WHERE term_taxonomy_id = %d )", $replacements ); //phpcs:enable } $start_time = \microtime( true ); //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared above. //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. $current_scores = $wpdb->get_row( $query ); //phpcs:enable if ( $current_scores === null ) { throw new Score_Results_Not_Found_Exception(); } $end_time = \microtime( true ); $results['scores'] = $current_scores; $results['query_time'] = ( $end_time - $start_time ); return $results; } /** * Builds the select statement for the SEO scores query. * * @param SEO_Score_Groups_Interface[] $seo_score_groups All SEO score groups. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<string, string> The select statement for the SEO scores query. */ private function build_select( array $seo_score_groups, ?bool $is_troubleshooting ): array { $select_fields = []; $select_replacements = []; // When we don't troubleshoot, we're interested in the amount of posts in a group, when we troubleshoot we want to gather the actual IDs. $select_operation = ( $is_troubleshooting === true ) ? 'GROUP_CONCAT' : 'COUNT'; $selected_info = ( $is_troubleshooting === true ) ? 'I.object_id' : '1'; foreach ( $seo_score_groups as $seo_score_group ) { $min = $seo_score_group->get_min_score(); $max = $seo_score_group->get_max_score(); $name = $seo_score_group->get_name(); if ( $min === null || $max === null ) { $select_fields[] = "{$select_operation}(CASE WHEN I.primary_focus_keyword_score = 0 OR I.primary_focus_keyword_score IS NULL THEN {$selected_info} END) AS %i"; $select_replacements[] = $name; } else { $select_fields[] = "{$select_operation}(CASE WHEN I.primary_focus_keyword_score >= %d AND I.primary_focus_keyword_score <= %d THEN {$selected_info} END) AS %i"; $select_replacements[] = $min; $select_replacements[] = $max; $select_replacements[] = $name; } } $select_fields = \implode( ', ', $select_fields ); return [ 'fields' => $select_fields, 'replacements' => $select_replacements, ]; } } dashboard/infrastructure/score-results/score-results-collector-interface.php 0000644 00000002026 15025657560 0023645 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface; /** * The interface of score result collectors. */ interface Score_Results_Collector_Interface { /** * Retrieves the score results for a content type. * * @param Score_Groups_Interface[] $score_groups All score groups. * @param Content_Type $content_type The content type. * @param int|null $term_id The ID of the term we're filtering for. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<string, string> The score results for a content type. */ public function get_score_results( array $score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting ); } score-results/readability-score-results/cached-readability-score-results-collector.php 0000644 00000006102 15025657560 0032424 0 ustar 00 dashboard/infrastructure <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results; use WPSEO_Utils; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Readability_Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface; /** * The caching decorator to get readability score results. */ class Cached_Readability_Score_Results_Collector implements Score_Results_Collector_Interface { public const READABILITY_SCORES_TRANSIENT = 'wpseo_readability_scores'; /** * The actual collector implementation. * * @var Readability_Score_Results_Collector */ private $readability_score_results_collector; /** * The constructor. * * @param Readability_Score_Results_Collector $readability_score_results_collector The collector implementation to * use. */ public function __construct( Readability_Score_Results_Collector $readability_score_results_collector ) { $this->readability_score_results_collector = $readability_score_results_collector; } /** * Retrieves readability score results for a content type. * Based on caching returns either the result or gets it from the collector. * * @param Readability_Score_Groups_Interface[] $score_groups All readability score groups. * @param Content_Type $content_type The content type. * @param int|null $term_id The ID of the term we're filtering for. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<string, object|bool|float> The readability score results for a content type. * * @throws Score_Results_Not_Found_Exception When the query of getting score results fails. */ public function get_score_results( array $score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting ) { $content_type_name = $content_type->get_name(); $transient_name = self::READABILITY_SCORES_TRANSIENT . '_' . $content_type_name . ( ( $term_id === null ) ? '' : '_' . $term_id ); $results = []; $transient = \get_transient( $transient_name ); if ( $is_troubleshooting !== true && $transient !== false ) { $results['scores'] = \json_decode( $transient, false ); $results['cache_used'] = true; $results['query_time'] = 0; return $results; } $results = $this->readability_score_results_collector->get_score_results( $score_groups, $content_type, $term_id, $is_troubleshooting ); $results['cache_used'] = false; if ( $is_troubleshooting !== true ) { \set_transient( $transient_name, WPSEO_Utils::format_json_encode( $results['scores'] ), \MINUTE_IN_SECONDS ); } return $results; } } infrastructure/score-results/readability-score-results/readability-score-results-collector.php 0000644 00000013205 15025657560 0031221 0 ustar 00 dashboard <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Readability_Score_Results; use Yoast\WP\Lib\Model; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Readability_Score_Groups\Readability_Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Domain\Score_Results\Score_Results_Not_Found_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Score_Results\Score_Results_Collector_Interface; /** * Getting readability score results from the indexable database table. */ class Readability_Score_Results_Collector implements Score_Results_Collector_Interface { /** * Retrieves readability score results for a content type. * * @param Readability_Score_Groups_Interface[] $readability_score_groups All readability score groups. * @param Content_Type $content_type The content type. * @param int|null $term_id The ID of the term we're filtering for. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<string, object|bool|float> The readability score results for a content type. * * @throws Score_Results_Not_Found_Exception When the query of getting score results fails. */ public function get_score_results( array $readability_score_groups, Content_Type $content_type, ?int $term_id, ?bool $is_troubleshooting ) { global $wpdb; $results = []; $content_type_name = $content_type->get_name(); $select = $this->build_select( $readability_score_groups, $is_troubleshooting ); $replacements = \array_merge( \array_values( $select['replacements'] ), [ Model::get_table_name( 'Indexable' ), $content_type_name, ] ); if ( $term_id === null ) { //phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements. //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders. $query = $wpdb->prepare( " SELECT {$select['fields']} FROM %i AS I WHERE ( I.post_status = 'publish' OR I.post_status IS NULL ) AND I.object_type = 'post' AND I.object_sub_type = %s", $replacements ); //phpcs:enable } else { $replacements[] = $wpdb->term_relationships; $replacements[] = $term_id; //phpcs:disable WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber -- $replacements is an array with the correct replacements. //phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $select['fields'] is an array of simple strings with placeholders. $query = $wpdb->prepare( " SELECT {$select['fields']} FROM %i AS I WHERE ( I.post_status = 'publish' OR I.post_status IS NULL ) AND I.object_type = 'post' AND I.object_sub_type = %s AND I.object_id IN ( SELECT object_id FROM %i WHERE term_taxonomy_id = %d )", $replacements ); //phpcs:enable } $start_time = \microtime( true ); //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared -- $query is prepared above. //phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery -- Reason: Most performant way. //phpcs:disable WordPress.DB.DirectDatabaseQuery.NoCaching -- Reason: No relevant caches. $current_scores = $wpdb->get_row( $query ); //phpcs:enable if ( $current_scores === null ) { throw new Score_Results_Not_Found_Exception(); } $end_time = \microtime( true ); $results['scores'] = $current_scores; $results['query_time'] = ( $end_time - $start_time ); return $results; } /** * Builds the select statement for the readability scores query. * * @param Readability_Score_Groups_Interface[] $readability_score_groups All readability score groups. * @param bool|null $is_troubleshooting Whether we're in troubleshooting mode. * * @return array<string, string> The select statement for the readability scores query. */ private function build_select( array $readability_score_groups, ?bool $is_troubleshooting ): array { $select_fields = []; $select_replacements = []; // When we don't troubleshoot, we're interested in the amount of posts in a group, when we troubleshoot we want to gather the actual IDs. $select_operation = ( $is_troubleshooting === true ) ? 'GROUP_CONCAT' : 'COUNT'; $selected_info = ( $is_troubleshooting === true ) ? 'I.object_id' : '1'; foreach ( $readability_score_groups as $readability_score_group ) { $min = $readability_score_group->get_min_score(); $max = $readability_score_group->get_max_score(); $name = $readability_score_group->get_name(); if ( $min === null && $max === null ) { $select_fields[] = "{$select_operation}(CASE WHEN I.readability_score = 0 AND I.estimated_reading_time_minutes IS NULL THEN {$selected_info} END) AS %i"; $select_replacements[] = $name; } else { $needs_ert = ( $min === 1 ) ? ' OR (I.readability_score = 0 AND I.estimated_reading_time_minutes IS NOT NULL)' : ''; $select_fields[] = "{$select_operation}(CASE WHEN ( I.readability_score >= %d AND I.readability_score <= %d ){$needs_ert} THEN {$selected_info} END) AS %i"; $select_replacements[] = $min; $select_replacements[] = $max; $select_replacements[] = $name; } } $select_fields = \implode( ', ', $select_fields ); return [ 'fields' => $select_fields, 'replacements' => $select_replacements, ]; } } dashboard/infrastructure/endpoints/seo-scores-endpoint.php 0000644 00000002162 15025657560 0020203 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints; use Exception; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Abstract_Scores_Route; use Yoast\WP\SEO\Dashboard\User_Interface\Scores\SEO_Scores_Route; /** * Represents the SEO scores endpoint. */ class SEO_Scores_Endpoint implements Endpoint_Interface { /** * Gets the name. * * @return string */ public function get_name(): string { return 'seoScores'; } /** * Gets the namespace. * * @return string */ public function get_namespace(): string { return Abstract_Scores_Route::ROUTE_NAMESPACE; } /** * Gets the route. * * @return string * * @throws Exception If the route prefix is not overwritten this throws. */ public function get_route(): string { return SEO_Scores_Route::get_route_prefix(); } /** * Gets the URL. * * @return string */ public function get_url(): string { return \rest_url( $this->get_namespace() . $this->get_route() ); } } dashboard/infrastructure/endpoints/site-kit-configuration-dismissal-endpoint.php 0000644 00000002232 15025657560 0024503 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints; use Exception; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; use Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Configuration_Dismissal_Route; /** * Represents the readability scores endpoint. */ class Site_Kit_Configuration_Dismissal_Endpoint implements Endpoint_Interface { /** * Gets the name. * * @return string */ public function get_name(): string { return 'siteKitConfigurationDismissal'; } /** * Gets the namespace. * * @return string */ public function get_namespace(): string { return Site_Kit_Configuration_Dismissal_Route::ROUTE_NAMESPACE; } /** * Gets the route. * * @return string * * @throws Exception If the route prefix is not overwritten this throws. */ public function get_route(): string { return Site_Kit_Configuration_Dismissal_Route::ROUTE_PREFIX; } /** * Gets the URL. * * @return string */ public function get_url(): string { return \rest_url( $this->get_namespace() . $this->get_route() ); } } dashboard/infrastructure/endpoints/setup-steps-tracking-endpoint.php 0000644 00000002130 15025657560 0022210 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints; use Exception; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; use Yoast\WP\SEO\Dashboard\User_Interface\Tracking\Setup_Steps_Tracking_Route; /** * Represents the setup steps tracking endpoint. */ class Setup_Steps_Tracking_Endpoint implements Endpoint_Interface { /** * Gets the name. * * @return string */ public function get_name(): string { return 'setupStepsTracking'; } /** * Gets the namespace. * * @return string */ public function get_namespace(): string { return Setup_Steps_Tracking_Route::ROUTE_NAMESPACE; } /** * Gets the route. * * @throws Exception If the route prefix is not overwritten this throws. * @return string */ public function get_route(): string { return Setup_Steps_Tracking_Route::ROUTE_PREFIX; } /** * Gets the URL. * * @return string */ public function get_url(): string { return \rest_url( $this->get_namespace() . $this->get_route() ); } } dashboard/infrastructure/endpoints/time-based-seo-metrics-endpoint.php 0000644 00000002066 15025657560 0022366 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; use Yoast\WP\SEO\Dashboard\User_Interface\Time_Based_SEO_Metrics\Time_Based_SEO_Metrics_Route; /** * Represents the time based SEO metrics endpoint. */ class Time_Based_SEO_Metrics_Endpoint implements Endpoint_Interface { /** * Gets the name. * * @return string */ public function get_name(): string { return 'timeBasedSeoMetrics'; } /** * Gets the namespace. * * @return string */ public function get_namespace(): string { return Time_Based_SEO_Metrics_Route::ROUTE_NAMESPACE; } /** * Gets the route. * * @return string */ public function get_route(): string { return Time_Based_SEO_Metrics_Route::ROUTE_NAME; } /** * Gets the URL. * * @return string */ public function get_url(): string { return \rest_url( $this->get_namespace() . $this->get_route() ); } } dashboard/infrastructure/endpoints/readability-scores-endpoint.php 0000644 00000002232 15025657560 0021704 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints; use Exception; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Abstract_Scores_Route; use Yoast\WP\SEO\Dashboard\User_Interface\Scores\Readability_Scores_Route; /** * Represents the readability scores endpoint. */ class Readability_Scores_Endpoint implements Endpoint_Interface { /** * Gets the name. * * @return string */ public function get_name(): string { return 'readabilityScores'; } /** * Gets the namespace. * * @return string */ public function get_namespace(): string { return Abstract_Scores_Route::ROUTE_NAMESPACE; } /** * Gets the route. * * @return string * * @throws Exception If the route prefix is not overwritten this throws. */ public function get_route(): string { return Readability_Scores_Route::get_route_prefix(); } /** * Gets the URL. * * @return string */ public function get_url(): string { return \rest_url( $this->get_namespace() . $this->get_route() ); } } dashboard/infrastructure/endpoints/site-kit-consent-management-endpoint.php 0000644 00000002212 15025657560 0023427 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Endpoints; use Exception; use Yoast\WP\SEO\Dashboard\Domain\Endpoint\Endpoint_Interface; use Yoast\WP\SEO\Dashboard\User_Interface\Configuration\Site_Kit_Consent_Management_Route; /** * Represents the Site Kit consent management endpoint. */ class Site_Kit_Consent_Management_Endpoint implements Endpoint_Interface { /** * Gets the name. * * @return string */ public function get_name(): string { return 'siteKitConsentManagement'; } /** * Gets the namespace. * * @return string */ public function get_namespace(): string { return Site_Kit_Consent_Management_Route::ROUTE_NAMESPACE; } /** * Gets the route. * * @return string * * @throws Exception If the route prefix is not overwritten this throws. */ public function get_route(): string { return Site_Kit_Consent_Management_Route::ROUTE_PREFIX; } /** * Gets the URL. * * @return string */ public function get_url(): string { return \rest_url( $this->get_namespace() . $this->get_route() ); } } dashboard/infrastructure/nonces/nonce-repository.php 0000644 00000000607 15025657560 0017106 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Nonces; /** * Repository for WP nonces. */ class Nonce_Repository { /** * Creates the nonce for a WP REST request. * * @return string */ public function get_rest_nonce(): string { return \wp_create_nonce( 'wp_rest' ); } } dashboard/infrastructure/indexables/top-page-indexable-collector.php 0000644 00000007126 15025657560 0022054 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Indexables; use Yoast\WP\SEO\Dashboard\Application\Score_Groups\SEO_Score_Groups\SEO_Score_Groups_Repository; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\SEO_Score_Groups\No_SEO_Score_Group; use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Top_Page_Data; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * The indexable collector that gets SEO scores from the indexables of top pages. */ class Top_Page_Indexable_Collector { /** * The indexable repository. * * @var Indexable_Repository */ private $indexable_repository; /** * The SEO score groups repository. * * @var SEO_Score_Groups_Repository */ private $seo_score_groups_repository; /** * The constructor. * * @param Indexable_Repository $indexable_repository The indexable repository. * @param SEO_Score_Groups_Repository $seo_score_groups_repository The SEO score groups repository. */ public function __construct( Indexable_Repository $indexable_repository, SEO_Score_Groups_Repository $seo_score_groups_repository ) { $this->indexable_repository = $indexable_repository; $this->seo_score_groups_repository = $seo_score_groups_repository; } /** * Gets full data for top pages. * * @param Data_Container $top_pages The top pages. * * @return Data_Container Data about SEO scores of top pages. */ public function get_data( Data_Container $top_pages ): Data_Container { $top_page_data_container = new Data_Container(); foreach ( $top_pages->get_data() as $top_page ) { $url = $top_page->get_subject(); $indexable = $this->get_top_page_indexable( $url ); if ( $indexable instanceof Indexable ) { $seo_score_group = $this->seo_score_groups_repository->get_seo_score_group( $indexable->primary_focus_keyword_score ); $edit_link = $this->get_top_page_edit_link( $indexable ); $top_page_data_container->add_data( new Top_Page_Data( $top_page, $seo_score_group, $edit_link ) ); continue; } $seo_score_group = new No_SEO_Score_Group(); $top_page_data_container->add_data( new Top_Page_Data( $top_page, $seo_score_group ) ); } return $top_page_data_container; } /** * Gets indexable for a top page URL. * * @param string $url The URL of the top page. * * @return bool|Indexable The indexable of the top page URL or false if there is none. */ protected function get_top_page_indexable( string $url ) { // First check if the URL is the static homepage. if ( \trailingslashit( $url ) === \trailingslashit( \get_home_url() ) && \get_option( 'show_on_front' ) === 'page' ) { return $this->indexable_repository->find_by_id_and_type( \get_option( 'page_on_front' ), 'post', false ); } return $this->indexable_repository->find_by_permalink( $url ); } /** * Gets edit links from a top page's indexable. * * @param Indexable $indexable The top page's indexable. * * @return string|null The edit link for the top page. */ protected function get_top_page_edit_link( Indexable $indexable ): ?string { if ( $indexable->object_type === 'post' && \current_user_can( 'edit_post', $indexable->object_id ) ) { return \get_edit_post_link( $indexable->object_id, '&' ); } if ( $indexable->object_type === 'term' && \current_user_can( 'edit_term', $indexable->object_id ) ) { return \get_edit_term_link( $indexable->object_id ); } return null; } } dashboard/infrastructure/configuration/site-kit-consent-repository.php 0000644 00000002457 15025657560 0022573 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Stores and retrieves the Site Kit consent status. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Site_Kit_Consent_Repository implements Site_Kit_Consent_Repository_Interface { /** * Holds the Options_Helper instance. * * @var Options_Helper */ private $options_helper; /** * Constructs the class. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Sets the Site Kit consent value. * * @param bool $consent The consent status. * * @return bool False when the update failed, true when the update succeeded. */ public function set_site_kit_consent( bool $consent ): bool { return $this->options_helper->set( 'site_kit_connected', $consent ); } /** * Checks if consent has ben given for Site Kit. * * * * @return bool True when consent has been given, false when it is not. */ public function is_consent_granted(): bool { return $this->options_helper->get( 'site_kit_connected', false ); } } dashboard/infrastructure/configuration/permanently-dismissed-site-kit-configuration-repository.php 0000644 00000003011 15025657560 0030272 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration; use Yoast\WP\SEO\Helpers\Options_Helper; /** * Stores and retrieves whether the Site Kit configuration is permanently dismissed. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Permanently_Dismissed_Site_Kit_Configuration_Repository implements Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface { /** * Holds the Options_Helper instance. * * @var Options_Helper */ private $options_helper; /** * Constructs the class. * * @param Options_Helper $options_helper The options helper. */ public function __construct( Options_Helper $options_helper ) { $this->options_helper = $options_helper; } /** * Sets the Site Kit dismissal status. * * @param bool $is_dismissed The dismissal status. * * @return bool False when the update failed, true when the update succeeded. */ public function set_site_kit_configuration_dismissal( bool $is_dismissed ): bool { return $this->options_helper->set( 'site_kit_configuration_permanently_dismissed', $is_dismissed ); } /** * Checks if the Site Kit configuration is dismissed permanently. * * * * @return bool True when the configuration is dismissed, false when it is not. */ public function is_site_kit_configuration_dismissed(): bool { return $this->options_helper->get( 'site_kit_configuration_permanently_dismissed', false ); } } dashboard/infrastructure/configuration/site-kit-consent-repository-interface.php 0000644 00000001403 15025657560 0024517 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration; /** * Interface for theSite Kit Consent Repository. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ interface Site_Kit_Consent_Repository_Interface { /** * Sets the Site Kit consent. * * @param bool $consent The consent value. * * @return bool False when the update failed, true when the update succeeded. */ public function set_site_kit_consent( bool $consent ): bool; /** * Returns the Site Kit consent status. * * * * @return bool True when the consent has been granted, false when it is not. */ public function is_consent_granted(): bool; } infrastructure/configuration/permanently-dismissed-site-kit-configuration-repository-interface.php 0000644 00000001635 15025657560 0032163 0 ustar 00 dashboard <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Configuration; /** * Interface for the Permanently Dismissed Site Kit configuration Repository. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ interface Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface { /** * Sets the Site Kit configuration dismissal status. * * @param bool $is_dismissed The dismissal status. * * @return bool False when the update failed, true when the update succeeded. */ public function set_site_kit_configuration_dismissal( bool $is_dismissed ): bool; /** * Checks if the Site Kit configuration is dismissed permanently. * * * * @return bool True when the configuration is dismissed, false when it is not. */ public function is_site_kit_configuration_dismissed(): bool; } dashboard/infrastructure/score-groups/score-group-link-collector.php 0000644 00000003233 15025657560 0022114 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Score_Groups; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Score_Groups\Score_Groups_Interface; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; /** * Getting links for score groups. */ class Score_Group_Link_Collector { /** * Builds the view link of the score group. * * @param Score_Groups_Interface $score_group The score group. * @param Content_Type $content_type The content type. * @param Taxonomy|null $taxonomy The taxonomy of the term we might be filtering. * @param int|null $term_id The ID of the term we might be filtering. * * @return string|null The view link of the score. */ public function get_view_link( Score_Groups_Interface $score_group, Content_Type $content_type, ?Taxonomy $taxonomy, ?int $term_id ): ?string { $posts_page = \admin_url( 'edit.php' ); $args = [ 'post_status' => 'publish', 'post_type' => $content_type->get_name(), $score_group->get_filter_key() => $score_group->get_filter_value(), ]; if ( $taxonomy === null || $term_id === null ) { return \add_query_arg( $args, $posts_page ); } $taxonomy_object = \get_taxonomy( $taxonomy->get_name() ); $query_var = $taxonomy_object->query_var; if ( ! $query_var ) { return null; } $term = \get_term( $term_id ); $args[ $query_var ] = $term->slug; return \add_query_arg( $args, $posts_page ); } } dashboard/infrastructure/content-types/content-types-collector.php 0000644 00000002462 15025657560 0021717 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Types_List; use Yoast\WP\SEO\Helpers\Post_Type_Helper; /** * Class that collects post types and relevant information. */ class Content_Types_Collector { /** * The post type helper. * * @var Post_Type_Helper */ private $post_type_helper; /** * The constructor. * * @param Post_Type_Helper $post_type_helper The post type helper. */ public function __construct( Post_Type_Helper $post_type_helper ) { $this->post_type_helper = $post_type_helper; } /** * Returns the content types in a list. * * @return Content_Types_List The content types in a list. */ public function get_content_types(): Content_Types_List { $content_types_list = new Content_Types_List(); $post_types = $this->post_type_helper->get_indexable_post_type_objects(); foreach ( $post_types as $post_type_object ) { if ( $post_type_object->show_ui === false ) { continue; } $content_type = new Content_Type( $post_type_object->name, $post_type_object->label ); $content_types_list->add( $content_type ); } return $content_types_list; } } dashboard/infrastructure/analytics-4/analytics-4-parameters.php 0000644 00000005575 15025657560 0020734 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; /** * Domain object to add Analytics 4 specific data to the parameters. */ class Analytics_4_Parameters extends Parameters { /** * The dimensions to query. * * @var array<array<string, string>> $dimensions */ private $dimensions = []; /** * The dimensions filters. * * @var array<string, array<string>> $dimension_filters */ private $dimension_filters = []; /** * The metrics. * * @var array<array<string, string>> $metrics */ private $metrics = []; /** * The order by. * * @var array<array<string, array<string, string>>> $order_by */ private $order_by = []; /** * Sets the dimensions. * * @link https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/Dimension * * @param array<string> $dimensions The dimensions. * * @return void */ public function set_dimensions( array $dimensions ): void { foreach ( $dimensions as $dimension ) { $this->dimensions[] = [ 'name' => $dimension ]; } } /** * Getter for the dimensions. * * @return array<array<string, string>> */ public function get_dimensions(): array { return $this->dimensions; } /** * Sets the dimension filters. * * @param array<string, array<string>> $dimension_filters The dimension filters. * * @return void */ public function set_dimension_filters( array $dimension_filters ): void { $this->dimension_filters = $dimension_filters; } /** * Getter for the dimension filters. * * @return array<string, array<string>> */ public function get_dimension_filters(): array { return $this->dimension_filters; } /** * Sets the metrics. * * @link https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/Metric * * @param array<string> $metrics The metrics. * * @return void */ public function set_metrics( array $metrics ): void { foreach ( $metrics as $metric ) { $this->metrics[] = [ 'name' => $metric ]; } } /** * Getter for the metrics. * * @return array<array<string, string>> */ public function get_metrics(): array { return $this->metrics; } /** * Sets the order by. * * @link https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/OrderBy * * @param string $key The key to order by. * @param string $name The name to order by. * * @return void */ public function set_order_by( string $key, string $name ): void { $order_by = [ [ $key => [ $key . 'Name' => $name, ], ], ]; $this->order_by = $order_by; } /** * Getter for the order by. * * @return array<array<string, array<string, string>>> */ public function get_order_by(): array { return $this->order_by; } } dashboard/infrastructure/analytics-4/site-kit-analytics-4-adapter.php 0000644 00000024707 15025657560 0021736 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4; use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\Row; use Google\Site_Kit_Dependencies\Google\Service\AnalyticsData\RunReportResponse; use WP_REST_Response; use Yoast\WP\SEO\Dashboard\Domain\Analytics_4\Failed_Request_Exception; use Yoast\WP\SEO\Dashboard\Domain\Analytics_4\Invalid_Request_Exception; use Yoast\WP\SEO\Dashboard\Domain\Analytics_4\Unexpected_Response_Exception; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Traffic\Comparison_Traffic_Data; use Yoast\WP\SEO\Dashboard\Domain\Traffic\Daily_Traffic_Data; use Yoast\WP\SEO\Dashboard\Domain\Traffic\Traffic_Data; /** * The site API adapter to make calls to the Analytics 4 API, via the Site_Kit plugin. */ class Site_Kit_Analytics_4_Adapter { /** * Holds the api call class. * * @var Site_Kit_Analytics_4_Api_Call $site_kit_analytics_4_api_call */ private $site_kit_search_console_api_call; /** * The register method that sets the instance in the adapter. * * @param Site_Kit_Analytics_4_Api_Call $site_kit_analytics_4_api_call The api call class. * * @return void */ public function __construct( Site_Kit_Analytics_4_Api_Call $site_kit_analytics_4_api_call ) { $this->site_kit_search_console_api_call = $site_kit_analytics_4_api_call; } /** * The wrapper method to do a comparison Site Kit API request for Analytics. * * @param Analytics_4_Parameters $parameters The parameters. * * @return Data_Container The Site Kit API response. * * @throws Failed_Request_Exception When the request responds with an error from Site Kit. * @throws Unexpected_Response_Exception When the request responds with an unexpected format. * @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters. */ public function get_comparison_data( Analytics_4_Parameters $parameters ): Data_Container { $api_parameters = $this->build_parameters( $parameters ); $response = $this->site_kit_search_console_api_call->do_request( $api_parameters ); $this->validate_response( $response ); return $this->parse_comparison_response( $response->get_data() ); } /** * The wrapper method to do a daily Site Kit API request for Analytics. * * @param Analytics_4_Parameters $parameters The parameters. * * @return Data_Container The Site Kit API response. * * @throws Failed_Request_Exception When the request responds with an error from Site Kit. * @throws Unexpected_Response_Exception When the request responds with an unexpected format. * @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters. */ public function get_daily_data( Analytics_4_Parameters $parameters ): Data_Container { $api_parameters = $this->build_parameters( $parameters ); $response = $this->site_kit_search_console_api_call->do_request( $api_parameters ); $this->validate_response( $response ); return $this->parse_daily_response( $response->get_data() ); } /** * Builds the parameters to be used in the Site Kit API request. * * @param Analytics_4_Parameters $parameters The parameters. * * @return array<string, array<string, string>> The Site Kit API parameters. */ private function build_parameters( Analytics_4_Parameters $parameters ): array { $api_parameters = [ 'slug' => 'analytics-4', 'datapoint' => 'report', 'startDate' => $parameters->get_start_date(), 'endDate' => $parameters->get_end_date(), ]; if ( ! empty( $parameters->get_dimension_filters() ) ) { $api_parameters['dimensionFilters'] = $parameters->get_dimension_filters(); } if ( ! empty( $parameters->get_dimensions() ) ) { $api_parameters['dimensions'] = $parameters->get_dimensions(); } if ( ! empty( $parameters->get_metrics() ) ) { $api_parameters['metrics'] = $parameters->get_metrics(); } if ( ! empty( $parameters->get_order_by() ) ) { $api_parameters['orderby'] = $parameters->get_order_by(); } if ( ! empty( $parameters->get_compare_start_date() && ! empty( $parameters->get_compare_end_date() ) ) ) { $api_parameters['compareStartDate'] = $parameters->get_compare_start_date(); $api_parameters['compareEndDate'] = $parameters->get_compare_end_date(); } return $api_parameters; } /** * Parses a response for a Site Kit API request that requests daily data for Analytics 4. * * @param RunReportResponse $response The response to parse. * * @return Data_Container The parsed response. * * @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters. */ private function parse_daily_response( RunReportResponse $response ): Data_Container { if ( ! $this->is_daily_request( $response ) ) { throw new Invalid_Request_Exception( 'Unexpected parameters for the request' ); } $data_container = new Data_Container(); foreach ( $response->getRows() as $daily_traffic ) { $traffic_data = new Traffic_Data(); foreach ( $response->getMetricHeaders() as $key => $metric ) { // As per https://developers.google.com/analytics/devguides/reporting/data/v1/basics#read_the_response, // the order of the columns is consistent in the request, header, and rows. // So we can use the key of the header to get the correct metric value from the row. $metric_value = $daily_traffic->getMetricValues()[ $key ]->getValue(); if ( $metric->getName() === 'sessions' ) { $traffic_data->set_sessions( (int) $metric_value ); } elseif ( $metric->getName() === 'totalUsers' ) { $traffic_data->set_total_users( (int) $metric_value ); } } // Since we're here, we know that the first dimension is date, so we know that dimensionValues[0]->value is a date. $data_container->add_data( new Daily_Traffic_Data( $daily_traffic->getDimensionValues()[0]->getValue(), $traffic_data ) ); } return $data_container; } /** * Parses a response for a Site Kit API request for Analytics 4 that compares data ranges. * * @param RunReportResponse $response The response to parse. * * @return Data_Container The parsed response. * * @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters. */ private function parse_comparison_response( RunReportResponse $response ): Data_Container { if ( ! $this->is_comparison_request( $response ) ) { throw new Invalid_Request_Exception( 'Unexpected parameters for the request' ); } $data_container = new Data_Container(); $comparison_traffic_data = new Comparison_Traffic_Data(); // First row is the current date range's data, second row is the previous date range's data. foreach ( $response->getRows() as $date_range_row ) { $traffic_data = new Traffic_Data(); // Loop through all the metrics of the date range. foreach ( $response->getMetricHeaders() as $key => $metric ) { // As per https://developers.google.com/analytics/devguides/reporting/data/v1/basics#read_the_response, // the order of the columns is consistent in the request, header, and rows. // So we can use the key of the header to get the correct metric value from the row. $metric_value = $date_range_row->getMetricValues()[ $key ]->getValue(); if ( $metric->getName() === 'sessions' ) { $traffic_data->set_sessions( (int) $metric_value ); } elseif ( $metric->getName() === 'totalUsers' ) { $traffic_data->set_total_users( (int) $metric_value ); } } $period = $this->get_period( $date_range_row ); if ( $period === Comparison_Traffic_Data::CURRENT_PERIOD_KEY ) { $comparison_traffic_data->set_current_traffic_data( $traffic_data ); } elseif ( $period === Comparison_Traffic_Data::PREVIOUS_PERIOD_KEY ) { $comparison_traffic_data->set_previous_traffic_data( $traffic_data ); } } $data_container->add_data( $comparison_traffic_data ); return $data_container; } /** * Parses the response row and returns whether it's about the current period or the previous period. * * @see https://developers.google.com/analytics/devguides/reporting/data/v1/rest/v1beta/DateRange * * @param Row $date_range_row The response row. * * @return string The key associated with the current or the previous period. * * @throws Invalid_Request_Exception When the request is invalid due to unexpected parameters. */ private function get_period( Row $date_range_row ): string { foreach ( $date_range_row->getDimensionValues() as $dimension_value ) { if ( $dimension_value->getValue() === 'date_range_0' ) { return Comparison_Traffic_Data::CURRENT_PERIOD_KEY; } elseif ( $dimension_value->getValue() === 'date_range_1' ) { return Comparison_Traffic_Data::PREVIOUS_PERIOD_KEY; } } throw new Invalid_Request_Exception( 'Unexpected date range names' ); } /** * Checks the response of the request to detect if it's a comparison request. * * @param RunReportResponse $response The response. * * @return bool Whether it's a comparison request. */ private function is_comparison_request( RunReportResponse $response ): bool { return \count( $response->getDimensionHeaders() ) === 1 && $response->getDimensionHeaders()[0]->getName() === 'dateRange'; } /** * Checks the response of the request to detect if it's a daily request. * * @param RunReportResponse $response The response. * * @return bool Whether it's a daily request. */ private function is_daily_request( RunReportResponse $response ): bool { return \count( $response->getDimensionHeaders() ) === 1 && $response->getDimensionHeaders()[0]->getName() === 'date'; } /** * Validates the response coming from Google Analytics. * * @param WP_REST_Response $response The response we want to validate. * * @return void * * @throws Failed_Request_Exception When the request responds with an error from Site Kit. * @throws Unexpected_Response_Exception When the request responds with an unexpected format. */ private function validate_response( WP_REST_Response $response ): void { if ( $response->is_error() ) { $error_data = $response->as_error()->get_error_data(); $error_status_code = ( $error_data['status'] ?? 500 ); throw new Failed_Request_Exception( \wp_kses_post( $response->as_error()->get_error_message() ), (int) $error_status_code ); } if ( ! \is_a( $response->get_data(), RunReportResponse::class ) ) { throw new Unexpected_Response_Exception(); } } } dashboard/infrastructure/analytics-4/site-kit-analytics-4-api-call.php 0000644 00000002031 15025657560 0021762 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_REST_Request; use WP_REST_Response; /** * Class that hold the code to do the REST call to the Site Kit api. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Site_Kit_Analytics_4_Api_Call { /** * The Analytics 4 API route path. */ private const ANALYTICS_DATA_REPORT_ROUTE = '/modules/analytics-4/data/report'; /** * Runs the internal REST api call. * * @param array<string, array<string, string>> $api_parameters The api parameters. * * @return WP_REST_Response */ public function do_request( array $api_parameters ): WP_REST_Response { $request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . self::ANALYTICS_DATA_REPORT_ROUTE ); $request->set_query_params( $api_parameters ); return \rest_do_request( $request ); } } dashboard/infrastructure/browser-cache/browser-cache-configuration.php 0000644 00000004301 15025657560 0022412 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Infrastructure\Browser_Cache; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; /** * Responsible for the browser cache configuration. */ class Browser_Cache_Configuration { /** * The Site Kit conditional. * * @var Google_Site_Kit_Feature_Conditional */ private $google_site_kit_feature_conditional; /** * The constructor. * * @param Google_Site_Kit_Feature_Conditional $google_site_kit_feature_conditional The Site Kit conditional. */ public function __construct( Google_Site_Kit_Feature_Conditional $google_site_kit_feature_conditional ) { $this->google_site_kit_feature_conditional = $google_site_kit_feature_conditional; } /** * Gets the Time To Live for each widget's cache. * * @return array<string, array<string, int>> The cache TTL for each widget. */ private function get_widgets_cache_ttl() { return [ 'topPages' => [ 'ttl' => ( 1 * \MINUTE_IN_SECONDS ), ], 'topQueries' => [ 'ttl' => ( 1 * \HOUR_IN_SECONDS ), ], 'searchRankingCompare' => [ 'ttl' => ( 1 * \HOUR_IN_SECONDS ), ], 'organicSessions' => [ 'ttl' => ( 1 * \HOUR_IN_SECONDS ), ], ]; } /** * Gets the prefix for the client side cache key. * * Cache key is scoped to user session and blog_id to isolate the * cache between users and sites (in multisite). * * @return string */ private function get_storage_prefix() { $current_user = \wp_get_current_user(); $auth_cookie = \wp_parse_auth_cookie(); $blog_id = \get_current_blog_id(); $session_token = isset( $auth_cookie['token'] ) ? $auth_cookie['token'] : ''; return \wp_hash( $current_user->user_login . '|' . $session_token . '|' . $blog_id ); } /** * Returns the browser cache configuration. * * @return array<string, string|array<string, array<string, int>>> */ public function get_configuration(): array { if ( ! $this->google_site_kit_feature_conditional->is_met() ) { return []; } return [ 'storagePrefix' => $this->get_storage_prefix(), 'yoastVersion' => \WPSEO_VERSION, 'widgetsCacheTtl' => $this->get_widgets_cache_ttl(), ]; } } dashboard/infrastructure/taxonomies/taxonomy-validator.php 0000644 00000001460 15025657560 0020327 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies; use WP_Taxonomy; /** * Class that validates taxonomies. */ class Taxonomy_Validator { /** * Returns whether the taxonomy in question is valid and associated with a given content type. * * @param WP_Taxonomy|false|null $taxonomy The taxonomy to check. * @param string $content_type The name of the content type to check. * * @return bool Whether the taxonomy in question is valid. */ public function is_valid_taxonomy( $taxonomy, string $content_type ): bool { return \is_a( $taxonomy, 'WP_Taxonomy' ) && $taxonomy->public && $taxonomy->show_in_rest && \in_array( $taxonomy->name, \get_object_taxonomies( $content_type ), true ); } } dashboard/infrastructure/taxonomies/taxonomies-collector.php 0000644 00000006341 15025657560 0020643 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong namespace Yoast\WP\SEO\Dashboard\Infrastructure\Taxonomies; use WP_Taxonomy; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; /** * Class that collects taxonomies and relevant information. */ class Taxonomies_Collector { /** * The taxonomy validator. * * @var Taxonomy_Validator */ private $taxonomy_validator; /** * The constructor. * * @param Taxonomy_Validator $taxonomy_validator The taxonomy validator. */ public function __construct( Taxonomy_Validator $taxonomy_validator ) { $this->taxonomy_validator = $taxonomy_validator; } /** * Returns a custom pair of taxonomy/content type, that's been given by users via hooks. * * @param string $content_type The content type we're hooking for. * * @return Taxonomy|null The hooked filtering taxonomy. */ public function get_custom_filtering_taxonomy( string $content_type ) { /** * Filter: 'wpseo_{$content_type}_filtering_taxonomy' - Allows overriding which taxonomy filters the content type. * * @internal * * @param string $filtering_taxonomy The taxonomy that filters the content type. */ $filtering_taxonomy = \apply_filters( "wpseo_{$content_type}_filtering_taxonomy", '' ); if ( $filtering_taxonomy !== '' ) { $taxonomy = $this->get_taxonomy( $filtering_taxonomy, $content_type ); if ( $taxonomy ) { return $taxonomy; } \_doing_it_wrong( 'Filter: \'wpseo_{$content_type}_filtering_taxonomy\'', 'The `wpseo_{$content_type}_filtering_taxonomy` filter should return a public taxonomy, available in REST API, that is associated with that content type.', 'YoastSEO v24.1' ); } return null; } /** * Returns the fallback, WP-native category taxonomy, if it's associated with the specific content type. * * @param string $content_type The content type. * * @return Taxonomy|null The taxonomy object for the category taxonomy. */ public function get_fallback_taxonomy( string $content_type ): ?Taxonomy { return $this->get_taxonomy( 'category', $content_type ); } /** * Returns the taxonomy object that filters a specific content type. * * @param string $taxonomy_name The name of the taxonomy we're going to build the object for. * @param string $content_type The content type that the taxonomy object is filtering. * * @return Taxonomy|null The taxonomy object. */ public function get_taxonomy( string $taxonomy_name, string $content_type ): ?Taxonomy { $taxonomy = \get_taxonomy( $taxonomy_name ); if ( $this->taxonomy_validator->is_valid_taxonomy( $taxonomy, $content_type ) ) { return new Taxonomy( $taxonomy->name, $taxonomy->label, $this->get_taxonomy_rest_url( $taxonomy ) ); } return null; } /** * Builds the REST API URL for the taxonomy. * * @param WP_Taxonomy $taxonomy The taxonomy we want to build the REST API URL for. * * @return string The REST API URL for the taxonomy. */ protected function get_taxonomy_rest_url( WP_Taxonomy $taxonomy ): string { $rest_base = ( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $rest_namespace = ( $taxonomy->rest_namespace ) ? $taxonomy->rest_namespace : 'wp/v2'; return \rest_url( "{$rest_namespace}/{$rest_base}" ); } } dashboard/infrastructure/connection/site-kit-is-connected-call.php 0000644 00000002675 15025657560 0021463 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Connection; use Google\Site_Kit\Core\REST_API\REST_Routes; use WP_REST_Request; /** * Class that hold the code to do the REST call to the Site Kit api. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Site_Kit_Is_Connected_Call { /** * Runs the internal REST api call. * * @return bool */ public function is_setup_completed(): bool { if ( ! \class_exists( REST_Routes::class ) ) { return false; } $request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/site/data/connection' ); $response = \rest_do_request( $request ); if ( $response->is_error() ) { return false; } return $response->get_data()['setupCompleted']; } /** * Runs the internal REST api call. * * @return bool */ public function is_ga_connected(): bool { if ( ! \class_exists( REST_Routes::class ) ) { return false; } $request = new WP_REST_Request( 'GET', '/' . REST_Routes::REST_ROOT . '/core/modules/data/list' ); $response = \rest_do_request( $request ); if ( $response->is_error() ) { return false; } $connected = false; foreach ( $response->get_data() as $module ) { if ( $module['slug'] === 'analytics-4' ) { $connected = $module['connected']; } } return $connected; } } dashboard/infrastructure/search-console/site-kit-search-console-api-call.php 0000644 00000002023 15025657560 0023315 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console; use WP_REST_Request; use WP_REST_Response; /** * Class that hold the code to do the REST call to the Site Kit api. * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Site_Kit_Search_Console_Api_Call { /** * The search analytics API route path. */ private const SEARCH_CONSOLE_DATA_SEARCH_ANALYTICS_ROUTE = '/google-site-kit/v1/modules/search-console/data/searchanalytics'; /** * Runs the internal REST api call. * * @param array<string, array<string, string>> $api_parameters The api parameters. * * @return WP_REST_Response */ public function do_request( array $api_parameters ): WP_REST_Response { $request = new WP_REST_Request( 'GET', self::SEARCH_CONSOLE_DATA_SEARCH_ANALYTICS_ROUTE ); $request->set_query_params( $api_parameters ); return \rest_do_request( $request ); } } dashboard/infrastructure/search-console/site-kit-search-console-adapter.php 0000644 00000015665 15025657560 0023273 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong // phpcs:disable Yoast.NamingConventions.NamespaceName.MaxExceeded namespace Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console; use Google\Site_Kit_Dependencies\Google\Service\SearchConsole\ApiDataRow; use WP_REST_Response; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Data_Container; use Yoast\WP\SEO\Dashboard\Domain\Search_Console\Failed_Request_Exception; use Yoast\WP\SEO\Dashboard\Domain\Search_Console\Unexpected_Response_Exception; use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Comparison_Search_Ranking_Data; use Yoast\WP\SEO\Dashboard\Domain\Search_Rankings\Search_Ranking_Data; /** * The site API adapter to make calls to the Search Console API, via the Site_Kit plugin. */ class Site_Kit_Search_Console_Adapter { /** * Holds the api call class. * * @var Site_Kit_Search_Console_Api_Call $site_kit_search_console_api_call */ private $site_kit_search_console_api_call; /** * The constructor. * * @param Site_Kit_Search_Console_Api_Call $site_kit_search_console_api_call The api call class. */ public function __construct( Site_Kit_Search_Console_Api_Call $site_kit_search_console_api_call ) { $this->site_kit_search_console_api_call = $site_kit_search_console_api_call; } /** * The wrapper method to do a Site Kit API request for Search Console. * * @param Search_Console_Parameters $parameters The parameters. * * @throws Failed_Request_Exception When the request responds with an error from Site Kit. * @throws Unexpected_Response_Exception When the request responds with an unexpected format. * @return Data_Container The Site Kit API response. */ public function get_data( Search_Console_Parameters $parameters ): Data_Container { $api_parameters = $this->build_parameters( $parameters ); $response = $this->site_kit_search_console_api_call->do_request( $api_parameters ); $this->validate_response( $response ); return $this->parse_response( $response->get_data() ); } /** * The wrapper method to do a comparison Site Kit API request for Search Console. * * @param Search_Console_Parameters $parameters The parameters. * * @throws Failed_Request_Exception When the request responds with an error from Site Kit. * @throws Unexpected_Response_Exception When the request responds with an unexpected format. * @return Data_Container The Site Kit API response. */ public function get_comparison_data( Search_Console_Parameters $parameters ): Data_Container { $api_parameters = $this->build_parameters( $parameters ); // Since we're doing a comparison request, we need to increase the date range to the start of the previous period. We'll later split the data into two periods. $api_parameters['startDate'] = $parameters->get_compare_start_date(); $response = $this->site_kit_search_console_api_call->do_request( $api_parameters ); $this->validate_response( $response ); return $this->parse_comparison_response( $response->get_data(), $parameters->get_compare_end_date() ); } /** * Builds the parameters to be used in the Site Kit API request. * * @param Search_Console_Parameters $parameters The parameters. * * @return array<string, array<string, string>> The Site Kit API parameters. */ private function build_parameters( Search_Console_Parameters $parameters ): array { $api_parameters = [ 'startDate' => $parameters->get_start_date(), 'endDate' => $parameters->get_end_date(), 'dimensions' => $parameters->get_dimensions(), ]; if ( $parameters->get_limit() !== 0 ) { $api_parameters['limit'] = $parameters->get_limit(); } return $api_parameters; } /** * Parses a response for a comparison Site Kit API request for Search Analytics. * * @param ApiDataRow[] $response The response to parse. * @param string $compare_end_date The compare end date. * * @throws Unexpected_Response_Exception When the comparison request responds with an unexpected format. * @return Data_Container The parsed comparison Site Kit API response. */ private function parse_comparison_response( array $response, ?string $compare_end_date ): Data_Container { $data_container = new Data_Container(); $comparison_search_ranking_data = new Comparison_Search_Ranking_Data(); foreach ( $response as $ranking_date ) { if ( ! \is_a( $ranking_date, ApiDataRow::class ) ) { throw new Unexpected_Response_Exception(); } $ranking_data = new Search_Ranking_Data( $ranking_date->getClicks(), $ranking_date->getCtr(), $ranking_date->getImpressions(), $ranking_date->getPosition(), $ranking_date->getKeys()[0] ); // Now split the data into two periods. if ( $ranking_date->getKeys()[0] <= $compare_end_date ) { $comparison_search_ranking_data->add_previous_traffic_data( $ranking_data ); } else { $comparison_search_ranking_data->add_current_traffic_data( $ranking_data ); } } $data_container->add_data( $comparison_search_ranking_data ); return $data_container; } /** * Parses a response for a Site Kit API request for Search Analytics. * * @param ApiDataRow[] $response The response to parse. * * @throws Unexpected_Response_Exception When the request responds with an unexpected format. * @return Data_Container The parsed Site Kit API response. */ private function parse_response( array $response ): Data_Container { $search_ranking_data_container = new Data_Container(); foreach ( $response as $ranking ) { if ( ! \is_a( $ranking, ApiDataRow::class ) ) { throw new Unexpected_Response_Exception(); } /** * Filter: 'wpseo_transform_dashboard_subject_for_testing' - Allows overriding subjects like URLs for the dashboard, to facilitate testing in local environments. * * @param string $url The subject to be transformed. * * @internal */ $subject = \apply_filters( 'wpseo_transform_dashboard_subject_for_testing', $ranking->getKeys()[0] ); $search_ranking_data_container->add_data( new Search_Ranking_Data( $ranking->getClicks(), $ranking->getCtr(), $ranking->getImpressions(), $ranking->getPosition(), $subject ) ); } return $search_ranking_data_container; } /** * Validates the response coming from Search Console. * * @param WP_REST_Response $response The response we want to validate. * * @return void. * * @throws Failed_Request_Exception When the request responds with an error from Site Kit. * @throws Unexpected_Response_Exception When the request responds with an unexpected format. */ private function validate_response( WP_REST_Response $response ): void { if ( $response->is_error() ) { $error_data = $response->as_error()->get_error_data(); $error_status_code = ( $error_data['status'] ?? 500 ); throw new Failed_Request_Exception( \wp_kses_post( $response->as_error() ->get_error_message() ), (int) $error_status_code ); } if ( ! \is_array( $response->get_data() ) ) { throw new Unexpected_Response_Exception(); } } } dashboard/infrastructure/search-console/search-console-parameters.php 0000644 00000001463 15025657560 0022256 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; /** * Domain object to add search console specific data to the parameters. */ class Search_Console_Parameters extends Parameters { /** * The search dimensions to query. * * @var string[] */ private $dimensions; /** * Sets the dimension parameter. * * @param array<string> $dimensions The dimensions. * * @return void */ public function set_dimensions( array $dimensions ): void { $this->dimensions = $dimensions; } /** * Getter for the dimensions. * * @return string[] */ public function get_dimensions(): array { return $this->dimensions; } } dashboard/user-interface/setup/setup-flow-interceptor.php 0000644 00000010463 15025657560 0017740 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Setup; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Redirect_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Setup flow interceptor class. */ class Setup_Flow_Interceptor implements Integration_Interface { /** * The page name of the Site Kit Setup finished page. */ private const GOOGLE_SITE_KIT_SEARCH_CONSOLE_SETUP_FINISHED_PAGE = 'googlesitekit-splash'; private const GOOGLE_SITE_KIT_ANALYTICS_SETUP_FINISHED_PAGE = 'googlesitekit-dashboard'; /** * The Site Kit configuration object. * * @var Site_Kit $site_kit_configuration */ private $site_kit_configuration; /** * Holds the Current_Page_Helper. * * @var Current_Page_Helper */ private $current_page_helper; /** * Holds the redirect helper. * * @var Redirect_Helper $redirect_helper */ private $redirect_helper; /** * The constructor. * * @param Current_Page_Helper $current_page_helper The current page helper. * @param Redirect_Helper $redirect_helper The redirect helper to abstract away the actual redirecting. * @param Site_Kit $site_kit_configuration The Site Kit configuration object. */ public function __construct( Current_Page_Helper $current_page_helper, Redirect_Helper $redirect_helper, Site_Kit $site_kit_configuration ) { $this->current_page_helper = $current_page_helper; $this->redirect_helper = $redirect_helper; $this->site_kit_configuration = $site_kit_configuration; } /** * Registers our redirect back to our dashboard all the way at the end of the admin init to make sure everything from the Site Kit callback can be finished. * * @return void */ public function register_hooks() { \add_action( 'admin_init', [ $this, 'intercept_site_kit_setup_flow' ], 999 ); } /** * The conditions for this integration to load. * * @return string[] The conditionals. */ public static function get_conditionals() { return [ Google_Site_Kit_Feature_Conditional::class, Admin_Conditional::class ]; } /** * Checks if we should intercept the final page from the Site Kit flow. * * @return void */ public function intercept_site_kit_setup_flow() { if ( \get_transient( Setup_Url_Interceptor::SITE_KIT_SETUP_TRANSIENT ) === '1' && $this->is_site_kit_setup_completed_page() ) { \delete_transient( Setup_Url_Interceptor::SITE_KIT_SETUP_TRANSIENT ); $this->redirect_helper->do_safe_redirect( \self_admin_url( 'admin.php?page=wpseo_dashboard&redirected_from_site_kit' ), 302, 'Yoast SEO' ); } } /** * Checks if we are on the site kit setup completed page. * * @return bool */ private function is_site_kit_setup_completed_page(): bool { $current_page = $this->current_page_helper->get_current_yoast_seo_page(); $on_search_console_setup_page = $current_page === self::GOOGLE_SITE_KIT_SEARCH_CONSOLE_SETUP_FINISHED_PAGE; $on_analytics_setup_page = $current_page === self::GOOGLE_SITE_KIT_ANALYTICS_SETUP_FINISHED_PAGE; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $authentication_success_notification = isset( $_GET['notification'] ) && \sanitize_text_field( \wp_unslash( $_GET['notification'] ) ) === 'authentication_success'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. $analytics_4_slug = isset( $_GET['slug'] ) && \sanitize_text_field( \wp_unslash( $_GET['slug'] ) ) === 'analytics-4'; /** * This checks two scenarios * 1. The user only wants Search Console. In this case just checking if you are on the thank-you page from Site Kit is enough. * 2. The user also wants analytics. So we need to check another page and also check if the analytics 4 connection is finalized. */ return ( $on_search_console_setup_page && $authentication_success_notification ) || ( $on_analytics_setup_page && $authentication_success_notification && $analytics_4_slug && $this->site_kit_configuration->is_ga_connected() ); } } dashboard/user-interface/setup/setup-url-interceptor.php 0000644 00000010145 15025657560 0017570 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Setup; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Integrations\Site_Kit; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Redirect_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Setup url Interceptor class. */ class Setup_Url_Interceptor implements Integration_Interface { /** * The page url where this check lives. */ public const PAGE = 'wpseo_page_site_kit_set_up'; /** * The name of the transient. */ public const SITE_KIT_SETUP_TRANSIENT = 'wpseo_site_kit_set_up_transient'; /** * Holds the Current_Page_Helper. * * @var Current_Page_Helper $current_page_helper */ private $current_page_helper; /** * Holds the redirect helper. * * @var Redirect_Helper $redirect_helper */ private $redirect_helper; /** * The Site Kit configuration object. * * @var Site_Kit $site_kit_configuration */ private $site_kit_configuration; /** * The constructor. * * @param Current_Page_Helper $current_page_helper The current page helper. * @param Site_Kit $site_kit_configuration The Site Kit configuration object. * @param Redirect_Helper $redirect_helper The redirect helper to abstract away the actual redirecting. */ public function __construct( Current_Page_Helper $current_page_helper, Site_Kit $site_kit_configuration, Redirect_Helper $redirect_helper ) { $this->current_page_helper = $current_page_helper; $this->redirect_helper = $redirect_helper; $this->site_kit_configuration = $site_kit_configuration; } /** * The conditions for this integration to load. * * @return string[] The conditionals. */ public static function get_conditionals() { return [ Google_Site_Kit_Feature_Conditional::class, Admin_Conditional::class ]; } /** * Registers the interceptor code to the admin_init function. * * @return void */ public function register_hooks() { \add_filter( 'admin_menu', [ $this, 'add_redirect_page' ] ); \add_action( 'admin_init', [ $this, 'intercept_site_kit_setup_url_redirect' ], 1 ); } /** * Adds a dummy page. * * We need to register this in between page. * * @param array<array<string>> $pages The pages. * * @return array<array<string>> The pages. */ public function add_redirect_page( $pages ) { \add_submenu_page( '', '', '', 'wpseo_manage_options', self::PAGE ); return $pages; } /** * Checks if we are trying to reach a site kit setup url and sets the needed transient in between. * * @return void */ public function intercept_site_kit_setup_url_redirect() { $allowed_setup_links = [ $this->site_kit_configuration->get_install_url(), $this->site_kit_configuration->get_activate_url(), $this->site_kit_configuration->get_setup_url(), $this->site_kit_configuration->get_update_url(), ]; // Are we on the in-between page? if ( $this->current_page_helper->get_current_yoast_seo_page() === self::PAGE ) { // Check if parameter is there and is valid. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['redirect_setup_url'] ) && \in_array( \wp_unslash( $_GET['redirect_setup_url'] ), $allowed_setup_links, true ) ) { // We overwrite the transient for each time this redirect is hit to keep refreshing the time. \set_transient( self::SITE_KIT_SETUP_TRANSIENT, 1, ( \MINUTE_IN_SECONDS * 15 ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: Only allowed pre verified links can end up here. $redirect_url = \wp_unslash( $_GET['redirect_setup_url'] ); $this->redirect_helper->do_safe_redirect( $redirect_url, 302, 'Yoast SEO' ); } else { $this->redirect_helper->do_safe_redirect( \self_admin_url( 'admin.php?page=wpseo_dashboard' ), 302, 'Yoast SEO' ); } } } } dashboard/user-interface/tracking/setup-steps-tracking-route.php 0000644 00000012246 15025657560 0021172 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Tracking; use Exception; use WP_Error; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Tracking\Setup_Steps_Tracking_Repository_Interface; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; /** * Registers a route to keep track of the Site Kit usage. * * @makePublic * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Setup_Steps_Tracking_Route implements Route_Interface { /** * The namespace for this route. * * @var string */ public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE; /** * The prefix for this route. * * @var string */ public const ROUTE_PREFIX = '/setup_steps_tracking'; /** * Holds the repository instance. * * @var Setup_Steps_Tracking_Repository_Interface */ private $setup_steps_tracking_repository; /** * Holds the capability helper instance. * * @var Capability_Helper */ private $capability_helper; /** * Returns the needed conditionals. * * @return array<string> The conditionals that must be met to load this. */ public static function get_conditionals(): array { return [ Google_Site_Kit_Feature_Conditional::class ]; } /** * Constructs the class. * * @param Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository The repository. * @param Capability_Helper $capability_helper The capability helper. */ public function __construct( Setup_Steps_Tracking_Repository_Interface $setup_steps_tracking_repository, Capability_Helper $capability_helper ) { $this->setup_steps_tracking_repository = $setup_steps_tracking_repository; $this->capability_helper = $capability_helper; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { \register_rest_route( self::ROUTE_NAMESPACE, self::ROUTE_PREFIX, [ [ 'methods' => 'POST', 'callback' => [ $this, 'track_setup_steps' ], 'permission_callback' => [ $this, 'check_capabilities' ], 'args' => [ 'setup_widget_loaded' => [ 'required' => false, 'type' => 'string', 'enum' => [ 'yes', 'no' ], ], 'first_interaction_stage' => [ 'required' => false, 'type' => 'string', 'enum' => [ 'install', 'activate', 'setup', 'grantConsent', 'successfullyConnected' ], ], 'last_interaction_stage' => [ 'required' => false, 'type' => 'string', 'enum' => [ 'install', 'activate', 'setup', 'grantConsent', 'successfullyConnected' ], ], 'setup_widget_temporarily_dismissed' => [ 'required' => false, 'type' => 'string', 'enum' => [ 'yes', 'no' ], ], 'setup_widget_permanently_dismissed' => [ 'required' => false, 'type' => 'string', 'enum' => [ 'yes', 'no' ], ], ], ], ] ); } /** * Stores tracking information. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response|WP_Error The success or failure response. */ public function track_setup_steps( WP_REST_Request $request ) { $data = [ 'setup_widget_loaded' => $request->get_param( 'setupWidgetLoaded' ), 'first_interaction_stage' => $request->get_param( 'firstInteractionStage' ), 'last_interaction_stage' => $request->get_param( 'lastInteractionStage' ), 'setup_widget_temporarily_dismissed' => $request->get_param( 'setupWidgetTemporarilyDismissed' ), 'setup_widget_permanently_dismissed' => $request->get_param( 'setupWidgetPermanentlyDismissed' ), ]; // Filter out null values from the data array. $data = \array_filter( $data, static function ( $value ) { return $value !== null; } ); // Check if all values are null then return an error that no valid params were passed. if ( empty( $data ) ) { return new WP_Error( 'wpseo_set_site_kit_usage_tracking', \__( 'No valid parameters were passed.', 'wordpress-seo' ), [ 'status' => 400 ] ); } $result = true; foreach ( $data as $key => $value ) { try { $result = $this->setup_steps_tracking_repository->set_setup_steps_tracking_element( $key, $value ); } catch ( Exception $exception ) { return new WP_Error( 'wpseo_set_site_kit_usage_tracking', $exception->getMessage(), (object) [] ); } if ( ! $result ) { break; } } return new WP_REST_Response( [ 'success' => $result, ], ( $result ) ? 200 : 400 ); } /** * Checks if the current user has the required capabilities. * * @return bool */ public function check_capabilities() { return $this->capability_helper->current_user_can( 'wpseo_manage_options' ); } } dashboard/user-interface/configuration/site-kit-consent-management-route.php 0000644 00000007024 15025657560 0023453 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Configuration; use Exception; use WP_Error; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Site_Kit_Consent_Repository_Interface; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; /** * Registers a route to set whether the Site Kit configuration is permanently dismissed. * * @makePublic * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Site_Kit_Consent_Management_Route implements Route_Interface { /** * The namespace for this route. * * @var string */ public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE; /** * The prefix for this route. * * @var string */ public const ROUTE_PREFIX = '/site_kit_manage_consent'; /** * Holds the repository instance. * * @var Site_Kit_Consent_Repository_Interface */ private $site_kit_consent_repository; /** * Holds the capabilit helper instance. * * @var Capability_Helper */ private $capability_helper; /** * The needed conditionals. * * @return array<string> */ public static function get_conditionals() { // This cannot have the Admin Conditional since it also needs to run in Rest requests. return [ Google_Site_Kit_Feature_Conditional::class, Site_Kit_Conditional::class ]; } /** * Constructs the class. * * @param Site_Kit_Consent_Repository_Interface $site_kit_consent_repository The repository. * @param Capability_Helper $capability_helper The capability helper. */ public function __construct( Site_Kit_Consent_Repository_Interface $site_kit_consent_repository, Capability_Helper $capability_helper ) { $this->site_kit_consent_repository = $site_kit_consent_repository; $this->capability_helper = $capability_helper; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { \register_rest_route( self::ROUTE_NAMESPACE, self::ROUTE_PREFIX, [ [ 'methods' => 'POST', 'callback' => [ $this, 'set_site_kit_consent' ], 'permission_callback' => [ $this, 'check_capabilities' ], 'args' => [ 'consent' => [ 'required' => true, 'type' => 'bool', 'sanitize_callback' => 'rest_sanitize_boolean', ], ], ], ] ); } /** * Sets whether the Site Kit configuration is permanently dismissed. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response|WP_Error The success or failure response. */ public function set_site_kit_consent( WP_REST_Request $request ) { $consent = $request->get_param( 'consent' ); try { $result = $this->site_kit_consent_repository->set_site_kit_consent( $consent ); } catch ( Exception $exception ) { return new WP_Error( 'wpseo_set_site_kit_consent_error', $exception->getMessage(), (object) [] ); } return new WP_REST_Response( [ 'success' => $result, ], ( $result ) ? 200 : 400 ); } /** * Checks if the current user has the required capabilities. * * @return bool */ public function check_capabilities() { return $this->capability_helper->current_user_can( 'wpseo_manage_options' ); } } dashboard/user-interface/configuration/site-kit-capabilities-integration.php 0000644 00000003414 15025657560 0023505 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Configuration; use Google\Site_Kit\Core\Permissions\Permissions; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional; use Yoast\WP\SEO\Integrations\Integration_Interface; /** * Enables the needed Site Kit capabilities for the SEO manager role. */ class Site_Kit_Capabilities_Integration implements Integration_Interface { /** * Registers needed filters. * * @return void */ public function register_hooks() { \add_filter( 'user_has_cap', [ $this, 'enable_site_kit_capabilities' ], 10, 2 ); } /** * The needed conditionals. * * @return array<string> */ public static function get_conditionals() { // This cannot have the Admin Conditional since it also needs to run in Rest requests. return [ Google_Site_Kit_Feature_Conditional::class, Site_Kit_Conditional::class ]; } /** * Checks if the Site Kit capabilities need to be enabled for a manager. * * @param array<string> $all_caps All the current capabilities of the current user. * @param array<string> $cap_to_check The capability to check against. * * @return array<string> */ public function enable_site_kit_capabilities( $all_caps, $cap_to_check ) { if ( ! isset( $cap_to_check[0] ) || ! \class_exists( Permissions::class ) ) { return $all_caps; } $user = \wp_get_current_user(); $caps_to_check = [ Permissions::SETUP, Permissions::VIEW_DASHBOARD, ]; if ( \in_array( $cap_to_check[0], $caps_to_check, true ) && \in_array( 'wpseo_manager', $user->roles, true ) ) { $all_caps[ $cap_to_check[0] ] = true; } return $all_caps; } } dashboard/user-interface/configuration/site-kit-configuration-dismissal-route.php 0000644 00000007700 15025657560 0024526 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Configuration; use Exception; use WP_Error; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Dashboard\Infrastructure\Configuration\Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; /** * Registers a route to set whether the Site Kit configuration is permanently dismissed. * * @makePublic * * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Site_Kit_Configuration_Dismissal_Route implements Route_Interface { /** * The namespace for this route. * * @var string */ public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE; /** * The prefix for this route. * * @var string */ public const ROUTE_PREFIX = '/site_kit_configuration_permanent_dismissal'; /** * Holds the introductions collector instance. * * @var Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface */ private $permanently_dismissed_site_kit_configuration_repository; /** * Holds the capabilit helper instance. * * @var Capability_Helper */ private $capability_helper; /** * The needed conditionals. * * @return array<string> */ public static function get_conditionals() { // This cannot have the Admin Conditional since it also needs to run in Rest requests. return [ Google_Site_Kit_Feature_Conditional::class ]; } /** * Constructs the class. * * @param Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface $permanently_dismissed_site_kit_configuration_repository The repository. * @param Capability_Helper $capability_helper The capability helper. */ public function __construct( Permanently_Dismissed_Site_Kit_Configuration_Repository_Interface $permanently_dismissed_site_kit_configuration_repository, Capability_Helper $capability_helper ) { $this->permanently_dismissed_site_kit_configuration_repository = $permanently_dismissed_site_kit_configuration_repository; $this->capability_helper = $capability_helper; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { \register_rest_route( self::ROUTE_NAMESPACE, self::ROUTE_PREFIX, [ [ 'methods' => 'POST', 'callback' => [ $this, 'set_site_kit_configuration_permanent_dismissal' ], 'permission_callback' => [ $this, 'check_capabilities' ], 'args' => [ 'is_dismissed' => [ 'required' => true, 'type' => 'bool', 'sanitize_callback' => 'rest_sanitize_boolean', ], ], ], ] ); } /** * Sets whether the Site Kit configuration is permanently dismissed. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response|WP_Error The success or failure response. */ public function set_site_kit_configuration_permanent_dismissal( WP_REST_Request $request ) { $is_dismissed = $request->get_param( 'is_dismissed' ); try { $result = $this->permanently_dismissed_site_kit_configuration_repository->set_site_kit_configuration_dismissal( $is_dismissed ); } catch ( Exception $exception ) { return new WP_Error( 'wpseo_set_site_kit_configuration_permanent_dismissal_error', $exception->getMessage(), (object) [] ); } return new WP_REST_Response( [ 'success' => $result, ], ( $result ) ? 200 : 400 ); } /** * Checks if the current user has the required capabilities. * * @return bool */ public function check_capabilities() { return $this->capability_helper->current_user_can( 'wpseo_manage_options' ); } } dashboard/user-interface/time-based-seo-metrics/time-based-seo-metrics-route.php 0000644 00000024526 15025657560 0024004 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Time_Based_SEO_Metrics; use DateTime; use DateTimeZone; use Exception; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\Google_Site_Kit_Feature_Conditional; use Yoast\WP\SEO\Conditionals\Third_Party\Site_Kit_Conditional; use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Search_Ranking_Compare_Repository; use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Page_Repository; use Yoast\WP\SEO\Dashboard\Application\Search_Rankings\Top_Query_Repository; use Yoast\WP\SEO\Dashboard\Application\Traffic\Organic_Sessions_Compare_Repository; use Yoast\WP\SEO\Dashboard\Application\Traffic\Organic_Sessions_Daily_Repository; use Yoast\WP\SEO\Dashboard\Domain\Data_Provider\Parameters; use Yoast\WP\SEO\Dashboard\Domain\Time_Based_SEO_Metrics\Repository_Not_Found_Exception; use Yoast\WP\SEO\Dashboard\Infrastructure\Analytics_4\Analytics_4_Parameters; use Yoast\WP\SEO\Dashboard\Infrastructure\Search_Console\Search_Console_Parameters; use Yoast\WP\SEO\Helpers\Capability_Helper; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; /** * Abstract scores route. */ final class Time_Based_SEO_Metrics_Route implements Route_Interface { /** * The namespace of the route. * * @var string */ public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE; /** * The prefix of the route. * * @var string */ public const ROUTE_NAME = '/time_based_seo_metrics'; /** * The data provider for page based search rankings. * * @var Top_Page_Repository */ private $top_page_repository; /** * The data provider for query based search rankings. * * @var Top_Query_Repository */ private $top_query_repository; /** * The data provider for comparison organic session traffic. * * @var Organic_Sessions_Compare_Repository */ private $organic_sessions_compare_repository; /** * The data provider for daily organic session traffic. * * @var Organic_Sessions_Daily_Repository */ private $organic_sessions_daily_repository; /** * The data provider for searching ranking comparison. * * @var Search_Ranking_Compare_Repository */ private $search_ranking_compare_repository; /** * Holds the capability helper instance. * * @var Capability_Helper */ private $capability_helper; /** * Returns the needed conditionals. * * @return array<string> The conditionals that must be met to load this. */ public static function get_conditionals(): array { return [ Google_Site_Kit_Feature_Conditional::class, Site_Kit_Conditional::class ]; } /** * The constructor. * * @param Top_Page_Repository $top_page_repository The data provider for page based search rankings. * @param Top_Query_Repository $top_query_repository The data provider for query based search rankings. * @param Organic_Sessions_Compare_Repository $organic_sessions_compare_repository The data provider for comparison organic session traffic. * @param Organic_Sessions_Daily_Repository $organic_sessions_daily_repository The data provider for daily organic session traffic. * @param Search_Ranking_Compare_Repository $search_ranking_compare_repository The data provider for searching ranking comparison. * @param Capability_Helper $capability_helper The capability helper. */ public function __construct( Top_Page_Repository $top_page_repository, Top_Query_Repository $top_query_repository, Organic_Sessions_Compare_Repository $organic_sessions_compare_repository, Organic_Sessions_Daily_Repository $organic_sessions_daily_repository, Search_Ranking_Compare_Repository $search_ranking_compare_repository, Capability_Helper $capability_helper ) { $this->top_page_repository = $top_page_repository; $this->top_query_repository = $top_query_repository; $this->organic_sessions_compare_repository = $organic_sessions_compare_repository; $this->organic_sessions_daily_repository = $organic_sessions_daily_repository; $this->search_ranking_compare_repository = $search_ranking_compare_repository; $this->capability_helper = $capability_helper; } /** * Registers routes for scores. * * @return void */ public function register_routes() { \register_rest_route( self::ROUTE_NAMESPACE, self::ROUTE_NAME, [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_time_based_seo_metrics' ], 'permission_callback' => [ $this, 'permission_manage_options' ], 'args' => [ 'limit' => [ 'type' => 'int', 'sanitize_callback' => 'absint', 'default' => 5, ], 'options' => [ 'type' => 'object', 'required' => true, 'properties' => [ 'widget' => [ 'type' => 'string', 'required' => true, 'sanitize_callback' => 'sanitize_text_field', ], ], ], ], ], ] ); } /** * Gets the time based SEO metrics. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response The success or failure response. * * @throws Repository_Not_Found_Exception When the given widget name is not implemented yet. */ public function get_time_based_seo_metrics( WP_REST_Request $request ): WP_REST_Response { try { $widget_name = $request->get_param( 'options' )['widget']; switch ( $widget_name ) { case 'query': $request_parameters = new Search_Console_Parameters(); $request_parameters = $this->set_date_range_parameters( $request_parameters ); $request_parameters->set_limit( $request->get_param( 'limit' ) ); $request_parameters->set_dimensions( [ 'query' ] ); $time_based_seo_metrics_container = $this->top_query_repository->get_data( $request_parameters ); break; case 'page': $request_parameters = new Search_Console_Parameters(); $request_parameters = $this->set_date_range_parameters( $request_parameters ); $request_parameters->set_limit( $request->get_param( 'limit' ) ); $request_parameters->set_dimensions( [ 'page' ] ); $time_based_seo_metrics_container = $this->top_page_repository->get_data( $request_parameters ); break; case 'organicSessionsDaily': $request_parameters = new Analytics_4_Parameters(); $request_parameters = $this->set_date_range_parameters( $request_parameters ); $request_parameters->set_dimensions( [ 'date' ] ); $request_parameters->set_metrics( [ 'sessions' ] ); $request_parameters->set_dimension_filters( [ 'sessionDefaultChannelGrouping' => [ 'Organic Search' ] ] ); $request_parameters->set_order_by( 'dimension', 'date' ); $time_based_seo_metrics_container = $this->organic_sessions_daily_repository->get_data( $request_parameters ); break; case 'organicSessionsCompare': $request_parameters = new Analytics_4_Parameters(); $request_parameters = $this->set_date_range_parameters( $request_parameters ); $request_parameters = $this->set_comparison_date_range_parameters( $request_parameters ); $request_parameters->set_metrics( [ 'sessions' ] ); $request_parameters->set_dimension_filters( [ 'sessionDefaultChannelGrouping' => [ 'Organic Search' ] ] ); $time_based_seo_metrics_container = $this->organic_sessions_compare_repository->get_data( $request_parameters ); break; case 'searchRankingCompare': $request_parameters = new Search_Console_Parameters(); $request_parameters = $this->set_date_range_parameters( $request_parameters ); $request_parameters = $this->set_comparison_date_range_parameters( $request_parameters ); $request_parameters->set_dimensions( [ 'date' ] ); $time_based_seo_metrics_container = $this->search_ranking_compare_repository->get_data( $request_parameters ); break; default: throw new Repository_Not_Found_Exception(); } } catch ( Exception $exception ) { return new WP_REST_Response( [ 'error' => $exception->getMessage(), ], $exception->getCode() ); } return new WP_REST_Response( $time_based_seo_metrics_container->to_array(), 200 ); } /** * Sets date range parameters. * * @param Parameters $request_parameters The request parameters. * * @return Parameters The request parameters with configured date range. */ public function set_date_range_parameters( Parameters $request_parameters ): Parameters { $date = $this->get_base_date(); $date->modify( '-28 days' ); $start_date = $date->format( 'Y-m-d' ); $date = $this->get_base_date(); $date->modify( '-1 days' ); $end_date = $date->format( 'Y-m-d' ); $request_parameters->set_start_date( $start_date ); $request_parameters->set_end_date( $end_date ); return $request_parameters; } /** * Sets comparison date range parameters. * * @param Parameters $request_parameters The request parameters. * * @return Parameters The request parameters with configured comparison date range. */ public function set_comparison_date_range_parameters( Parameters $request_parameters ): Parameters { $date = $this->get_base_date(); $date->modify( '-29 days' ); $compare_end_date = $date->format( 'Y-m-d' ); $date->modify( '-27 days' ); $compare_start_date = $date->format( 'Y-m-d' ); $request_parameters->set_compare_start_date( $compare_start_date ); $request_parameters->set_compare_end_date( $compare_end_date ); return $request_parameters; } /** * Gets the base date. * * @return DateTime The base date. */ private function get_base_date() { /** * Filter: 'wpseo_custom_site_kit_base_date' - Allow the base date for Site Kit requests to be dynamically set. * * @param string $base_date The custom base date for Site Kit requests, defaults to 'now'. */ $base_date = \apply_filters( 'wpseo_custom_site_kit_base_date', 'now' ); try { return new DateTime( $base_date, new DateTimeZone( 'UTC' ) ); } catch ( Exception $e ) { return new DateTime( 'now', new DateTimeZone( 'UTC' ) ); } } /** * Permission callback. * * @return bool True when user has the 'wpseo_manage_options' capability. */ public function permission_manage_options() { return $this->capability_helper->current_user_can( 'wpseo_manage_options' ); } } dashboard/user-interface/scores/readability-scores-route.php 0000644 00000001544 15025657560 0020356 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Scores; use Yoast\WP\SEO\Dashboard\Application\Score_Results\Readability_Score_Results\Readability_Score_Results_Repository; /** * Registers a route to get readability scores. */ class Readability_Scores_Route extends Abstract_Scores_Route { /** * The prefix of the route. * * @var string */ public const ROUTE_PREFIX = '/readability_scores'; /** * Constructs the class. * * @param Readability_Score_Results_Repository $readability_score_results_repository The readability score results repository. */ public function __construct( Readability_Score_Results_Repository $readability_score_results_repository ) { $this->score_results_repository = $readability_score_results_repository; } } dashboard/user-interface/scores/seo-scores-route.php 0000644 00000001414 15025657560 0016647 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Scores; use Yoast\WP\SEO\Dashboard\Application\Score_Results\SEO_Score_Results\SEO_Score_Results_Repository; /** * Registers a route to get SEO scores. */ class SEO_Scores_Route extends Abstract_Scores_Route { /** * The prefix of the route. * * @var string */ public const ROUTE_PREFIX = '/seo_scores'; /** * Constructs the class. * * @param SEO_Score_Results_Repository $seo_score_results_repository The SEO score results repository. */ public function __construct( SEO_Score_Results_Repository $seo_score_results_repository ) { $this->score_results_repository = $seo_score_results_repository; } } dashboard/user-interface/scores/abstract-scores-route.php 0000644 00000016225 15025657560 0017672 0 ustar 00 <?php // phpcs:disable Yoast.NamingConventions.NamespaceName.TooLong -- Needed in the folder structure. namespace Yoast\WP\SEO\Dashboard\User_Interface\Scores; use Exception; use WP_REST_Request; use WP_REST_Response; use WPSEO_Capability_Utils; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Dashboard\Application\Score_Results\Abstract_Score_Results_Repository; use Yoast\WP\SEO\Dashboard\Application\Taxonomies\Taxonomies_Repository; use Yoast\WP\SEO\Dashboard\Domain\Content_Types\Content_Type; use Yoast\WP\SEO\Dashboard\Domain\Taxonomies\Taxonomy; use Yoast\WP\SEO\Dashboard\Infrastructure\Content_Types\Content_Types_Collector; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Repositories\Indexable_Repository; use Yoast\WP\SEO\Routes\Route_Interface; /** * Abstract scores route. */ abstract class Abstract_Scores_Route implements Route_Interface { use No_Conditionals; /** * The namespace of the rout. * * @var string */ public const ROUTE_NAMESPACE = Main::API_V1_NAMESPACE; /** * The prefix of the rout. * * @var string */ public const ROUTE_PREFIX = null; /** * The content types collector. * * @var Content_Types_Collector */ protected $content_types_collector; /** * The taxonomies repository. * * @var Taxonomies_Repository */ protected $taxonomies_repository; /** * The indexable repository. * * @var Indexable_Repository */ protected $indexable_repository; /** * The scores repository. * * @var Abstract_Score_Results_Repository */ protected $score_results_repository; /** * Sets the collectors. * * @required * * @param Content_Types_Collector $content_types_collector The content type collector. * * @return void */ public function set_collectors( Content_Types_Collector $content_types_collector ) { $this->content_types_collector = $content_types_collector; } /** * Sets the repositories. * * @required * * @param Taxonomies_Repository $taxonomies_repository The taxonomies repository. * @param Indexable_Repository $indexable_repository The indexable repository. * * @return void */ public function set_repositories( Taxonomies_Repository $taxonomies_repository, Indexable_Repository $indexable_repository ) { $this->taxonomies_repository = $taxonomies_repository; $this->indexable_repository = $indexable_repository; } /** * Returns the route prefix. * * @return string The route prefix. * * @throws Exception If the ROUTE_PREFIX constant is not set in the child class. */ public static function get_route_prefix() { $class = static::class; $prefix = $class::ROUTE_PREFIX; if ( $prefix === null ) { throw new Exception( 'Score route without explicit prefix' ); } return $prefix; } /** * Registers routes for scores. * * @return void */ public function register_routes() { \register_rest_route( self::ROUTE_NAMESPACE, $this->get_route_prefix(), [ [ 'methods' => 'GET', 'callback' => [ $this, 'get_scores' ], 'permission_callback' => [ $this, 'permission_manage_options' ], 'args' => [ 'contentType' => [ 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ], 'taxonomy' => [ 'required' => false, 'type' => 'string', 'default' => '', 'sanitize_callback' => 'sanitize_text_field', ], 'term' => [ 'required' => false, 'type' => 'integer', 'default' => null, 'sanitize_callback' => static function ( $param ) { return \intval( $param ); }, ], 'troubleshooting' => [ 'required' => false, 'type' => 'bool', 'default' => null, 'sanitize_callback' => 'rest_sanitize_boolean', ], ], ], ] ); } /** * Gets the scores of a specific content type. * * @param WP_REST_Request $request The request object. * * @return WP_REST_Response The success or failure response. */ public function get_scores( WP_REST_Request $request ) { try { $content_type = $this->get_content_type( $request['contentType'] ); $taxonomy = $this->get_taxonomy( $request['taxonomy'], $content_type ); $term_id = $this->get_validated_term_id( $request['term'], $taxonomy ); $results = $this->score_results_repository->get_score_results( $content_type, $taxonomy, $term_id, $request['troubleshooting'] ); } catch ( Exception $exception ) { return new WP_REST_Response( [ 'error' => $exception->getMessage(), ], $exception->getCode() ); } return new WP_REST_Response( $results, 200 ); } /** * Gets the content type object. * * @param string $content_type The content type. * * @return Content_Type|null The content type object. * * @throws Exception When the content type is invalid. */ protected function get_content_type( string $content_type ): ?Content_Type { $content_types = $this->content_types_collector->get_content_types()->get(); if ( isset( $content_types[ $content_type ] ) && \is_a( $content_types[ $content_type ], Content_Type::class ) ) { return $content_types[ $content_type ]; } throw new Exception( 'Invalid content type.', 400 ); } /** * Gets the taxonomy object. * * @param string $taxonomy The taxonomy. * @param Content_Type $content_type The content type that the taxonomy is filtering. * * @return Taxonomy|null The taxonomy object. * * @throws Exception When the taxonomy is invalid. */ protected function get_taxonomy( string $taxonomy, Content_Type $content_type ): ?Taxonomy { if ( $taxonomy === '' ) { return null; } $valid_taxonomy = $this->taxonomies_repository->get_content_type_taxonomy( $content_type->get_name() ); if ( $valid_taxonomy && $valid_taxonomy->get_name() === $taxonomy ) { return $valid_taxonomy; } throw new Exception( 'Invalid taxonomy.', 400 ); } /** * Gets the term ID validated against the given taxonomy. * * @param int|null $term_id The term ID to be validated. * @param Taxonomy|null $taxonomy The taxonomy. * * @return int|null The validated term ID. * * @throws Exception When the term id is invalidated. */ protected function get_validated_term_id( ?int $term_id, ?Taxonomy $taxonomy ): ?int { if ( $term_id !== null && $taxonomy === null ) { throw new Exception( 'Term needs a provided taxonomy.', 400 ); } if ( $term_id === null && $taxonomy !== null ) { throw new Exception( 'Taxonomy needs a provided term.', 400 ); } if ( $term_id !== null ) { $term = \get_term( $term_id ); if ( ! $term || \is_wp_error( $term ) ) { throw new Exception( 'Invalid term.', 400 ); } if ( $taxonomy !== null && $term->taxonomy !== $taxonomy->get_name() ) { throw new Exception( 'Invalid term.', 400 ); } } return $term_id; } /** * Permission callback. * * @return bool True when user has the 'wpseo_manage_options' capability. */ public function permission_manage_options() { return WPSEO_Capability_Utils::current_user_can( 'wpseo_manage_options' ); } } values/indexables/indexable-builder-versions.php 0000644 00000002606 15025657560 0016136 0 ustar 00 <?php namespace Yoast\WP\SEO\Values\Indexables; /** * Class Indexable_Builder_Versions */ class Indexable_Builder_Versions { public const DEFAULT_INDEXABLE_BUILDER_VERSION = 1; /** * The list of indexable builder versions defined by Yoast SEO Free. * If the key is not in this list, the indexable type will not be managed. * These numbers should be increased if one of the builders implements a new feature. * * @var array */ protected $indexable_builder_versions_by_type = [ 'date-archive' => self::DEFAULT_INDEXABLE_BUILDER_VERSION, 'general' => self::DEFAULT_INDEXABLE_BUILDER_VERSION, 'home-page' => 2, 'post' => 2, 'post-type-archive' => 2, 'term' => 2, 'user' => 2, 'system-page' => self::DEFAULT_INDEXABLE_BUILDER_VERSION, ]; /** * Provides the most recent version number for an Indexable's object type. * * @param string $object_type The Indexable type for which you want to know the most recent version. * * @return int The most recent version number for the type, or 1 if the version doesn't exist. */ public function get_latest_version_for_type( $object_type ) { if ( ! \array_key_exists( $object_type, $this->indexable_builder_versions_by_type ) ) { return self::DEFAULT_INDEXABLE_BUILDER_VERSION; } return $this->indexable_builder_versions_by_type[ $object_type ]; } } values/twitter/images.php 0000644 00000001720 15025657560 0011536 0 ustar 00 <?php namespace Yoast\WP\SEO\Values\Twitter; use Yoast\WP\SEO\Helpers\Twitter\Image_Helper as Twitter_Image_Helper; use Yoast\WP\SEO\Values\Images as Base_Images; /** * Value object for the twitter Images. */ class Images extends Base_Images { /** * The twitter image helper. * * @var Twitter_Image_Helper */ protected $twitter_image; /** * Sets the helpers. * * @required * * @codeCoverageIgnore - Is handled by DI-container. * * @param Twitter_Image_Helper $twitter_image Image helper for twitter. * * @return void */ public function set_helpers( Twitter_Image_Helper $twitter_image ) { $this->twitter_image = $twitter_image; } /** * Adds an image to the list by image ID. * * @param int $image_id The image ID to add. * * @return void */ public function add_image_by_id( $image_id ) { $attachment = $this->twitter_image->get_by_id( $image_id ); if ( $attachment ) { $this->add_image( $attachment ); } } } values/oauth/oauth-token.php 0000644 00000006220 15025657560 0012145 0 ustar 00 <?php namespace Yoast\WP\SEO\Values\OAuth; use Yoast\WP\SEO\Exceptions\OAuth\Tokens\Empty_Property_Exception; use YoastSEO_Vendor\League\OAuth2\Client\Token\AccessTokenInterface; /** * Class OAuth_Token */ class OAuth_Token { /** * The access token. * * @var string */ public $access_token; /** * The refresh token. * * @var string */ public $refresh_token; /** * The expiration date. * * @var int */ public $expires; /** * Whether or not the token has expired. * * @var bool */ public $has_expired; /** * The timestamp at which the token was created. * * @var int */ public $created_at; /** * The number of times we've gotten an error trying to refresh this token. * * @var int */ public $error_count; /** * OAuth_Token constructor. * * @param string $access_token The access token. * @param string $refresh_token The refresh token. * @param int $expires The date and time at which the token will expire. * @param bool $has_expired Whether or not the token has expired. * @param int $created_at The timestamp of when the token was created. * @param int $error_count The number of times we've gotten an error trying to refresh this token. * * @throws Empty_Property_Exception Exception thrown if a token property is empty. */ public function __construct( $access_token, $refresh_token, $expires, $has_expired, $created_at, $error_count = 0 ) { if ( empty( $access_token ) ) { throw new Empty_Property_Exception( 'access_token' ); } $this->access_token = $access_token; if ( empty( $refresh_token ) ) { throw new Empty_Property_Exception( 'refresh_token' ); } $this->refresh_token = $refresh_token; if ( empty( $expires ) ) { throw new Empty_Property_Exception( 'expires' ); } $this->expires = $expires; if ( $has_expired === null ) { throw new Empty_Property_Exception( 'has_expired' ); } $this->has_expired = $has_expired; $this->created_at = $created_at; $this->error_count = $error_count; } /** * Creates a new instance based on the passed response. * * @param AccessTokenInterface $response The response object to create a new instance from. * * @return OAuth_Token The token object. * * @throws Empty_Property_Exception Exception thrown if a token property is empty. */ public static function from_response( AccessTokenInterface $response ) { return new self( $response->getToken(), $response->getRefreshToken(), $response->getExpires(), $response->hasExpired(), \time() ); } /** * Determines whether or not the token has expired. * * @return bool Whether or not the token has expired. */ public function has_expired() { return ( \time() >= $this->expires ) || $this->has_expired === true; } /** * Converts the object to an array. * * @return array The converted object. */ public function to_array() { return [ 'access_token' => $this->access_token, 'refresh_token' => $this->refresh_token, 'expires' => $this->expires, 'has_expired' => $this->has_expired(), 'created_at' => $this->created_at, 'error_count' => $this->error_count, ]; } } values/images.php 0000644 00000005420 15025657560 0010035 0 ustar 00 <?php namespace Yoast\WP\SEO\Values; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; /** * Class Images * * Value object for the Images. */ class Images { /** * The image size. * * @var string */ public $image_size = 'full'; /** * Holds the images that have been put out as image. * * @var array */ protected $images = []; /** * The image helper. * * @var Image_Helper */ protected $image; /** * The URL helper. * * @var Url_Helper */ protected $url; /** * Images constructor. * * @codeCoverageIgnore * * @param Image_Helper $image The image helper. * @param Url_Helper $url The url helper. */ public function __construct( Image_Helper $image, Url_Helper $url ) { $this->image = $image; $this->url = $url; } /** * Adds an image to the list by image ID. * * @param int $image_id The image ID to add. * * @return void */ public function add_image_by_id( $image_id ) { $image = $this->image->get_attachment_image_source( $image_id, $this->image_size ); if ( $image ) { $this->add_image( $image ); } } /** * Adds an image to the list by image ID. * * @param string $image_meta JSON encoded image meta. * * @return void */ public function add_image_by_meta( $image_meta ) { $this->add_image( (array) \json_decode( $image_meta ) ); } /** * Return the images array. * * @return array The images. */ public function get_images() { return $this->images; } /** * Check whether we have images or not. * * @return bool True if we have images, false if we don't. */ public function has_images() { return ! empty( $this->images ); } /** * Adds an image based on a given URL. * * @param string $url The given URL. * * @return number|null Returns the found image ID if it exists. Otherwise -1. * If the URL is empty we return null. */ public function add_image_by_url( $url ) { if ( empty( $url ) ) { return null; } $image_id = $this->image->get_attachment_by_url( $url ); if ( $image_id ) { $this->add_image_by_id( $image_id ); return $image_id; } $this->add_image( $url ); return -1; } /** * Adds an image to the list of images. * * @param string|array $image Image array. * * @return void */ public function add_image( $image ) { if ( \is_string( $image ) ) { $image = [ 'url' => $image ]; } if ( ! \is_array( $image ) || empty( $image['url'] ) || ! \is_string( $image['url'] ) ) { return; } if ( $this->url->is_relative( $image['url'] ) && $image['url'][0] === '/' ) { $image['url'] = $this->url->build_absolute_url( $image['url'] ); } if ( \array_key_exists( $image['url'], $this->images ) ) { return; } $this->images[ $image['url'] ] = $image; } } values/robots/user-agent.php 0000644 00000003226 15025657560 0012154 0 ustar 00 <?php namespace Yoast\WP\SEO\Values\Robots; /** * Class Directive */ class User_Agent { /** * The user agent identifier. * * @var string */ private $user_agent; /** * All directives that are allowed for this user agent. * * @var Directive */ private $allow_directive; /** * All directives that are disallowed for this user agent. * * @var Directive */ private $disallow_directive; /** * Constructor of the user agent value object. * * @param string $user_agent The user agent identifier. */ public function __construct( $user_agent ) { $this->user_agent = $user_agent; $this->allow_directive = new Directive(); $this->disallow_directive = new Directive(); } /** * Gets the user agent identifier. * * @return string */ public function get_user_agent() { return $this->user_agent; } /** * Adds a path to the directive object. * * @param string $path The path to add to the disallow directive. * * @return void */ public function add_disallow_directive( $path ) { $this->disallow_directive->add_path( $path ); } /** * Adds a path to the directive object. * * @param string $path The path to add to the allow directive. * * @return void */ public function add_allow_directive( $path ) { $this->allow_directive->add_path( $path ); } /** * Gets all disallow paths for this user agent. * * @return array */ public function get_disallow_paths() { return $this->disallow_directive->get_paths(); } /** * Gets all sallow paths for this user agent. * * @return array */ public function get_allow_paths() { return $this->allow_directive->get_paths(); } } values/robots/user-agent-list.php 0000644 00000003410 15025657560 0013120 0 ustar 00 <?php namespace Yoast\WP\SEO\Values\Robots; /** * Class User_Agent_List */ class User_Agent_List { /** * The list of user agents. * * @var array */ private $user_agent_list; /** * User Agent list constructor. */ public function __construct() { $this->user_agent_list = []; } /** * Checks if given user_agent is already registered. * * @param string $user_agent The user agent identifier. * * @return bool */ public function has_user_agent( $user_agent ) { return \array_key_exists( $user_agent, $this->user_agent_list ); } /** * Gets the user agent object. If it is not yet registered it creates it. * * @param string $user_agent The user agent identifier. * * @return User_Agent */ public function get_user_agent( $user_agent ) { if ( $this->has_user_agent( $user_agent ) ) { return $this->user_agent_list[ $user_agent ]; } $this->user_agent_list[ $user_agent ] = new User_Agent( $user_agent ); return $this->user_agent_list[ $user_agent ]; } /** * Gets the list of user agents. * * @return array */ public function get_user_agents() { return $this->user_agent_list; } /** * Gets a list of all disallow directives by user agent. * * @return array */ public function get_disallow_directives() { $directives = []; foreach ( $this->user_agent_list as $user_agent ) { $directives[ $user_agent->get_user_agent() ] = $user_agent->get_disallow_paths(); } return $directives; } /** * Gets a list of all sallow directives by user agent. * * @return array */ public function get_allow_directives() { $directives = []; foreach ( $this->user_agent_list as $user_agent ) { $directives[ $user_agent->get_user_agent() ] = $user_agent->get_allow_paths(); } return $directives; } } values/robots/directive.php 0000644 00000001225 15025657560 0012055 0 ustar 00 <?php namespace Yoast\WP\SEO\Values\Robots; /** * Class Directive */ class Directive { /** * Paths list. * * @var array All paths affected by this directive. */ private $paths; /** * Sets up the path array */ public function __construct() { $this->paths = []; } /** * Adds a path to the directive path list. * * @param string $path A path to add in the path list. * * @return void */ public function add_path( $path ) { if ( ! \in_array( $path, $this->paths, true ) ) { $this->paths[] = $path; } } /** * Returns all paths. * * @return array */ public function get_paths() { return $this->paths; } } values/open-graph/images.php 0000644 00000002232 15025657560 0012073 0 ustar 00 <?php namespace Yoast\WP\SEO\Values\Open_Graph; use Yoast\WP\SEO\Helpers\Open_Graph\Image_Helper as Open_Graph_Image_Helper; use Yoast\WP\SEO\Values\Images as Base_Images; /** * Value object for the Open Graph Images. */ class Images extends Base_Images { /** * The Open Graph image helper. * * @var Open_Graph_Image_Helper */ protected $open_graph_image; /** * Sets the helpers. * * @required * * @codeCoverageIgnore - Is handled by DI-container. * * @param Open_Graph_Image_Helper $open_graph_image Image helper for Open Graph. * * @return void */ public function set_helpers( Open_Graph_Image_Helper $open_graph_image ) { $this->open_graph_image = $open_graph_image; } /** * Outputs the images. * * @codeCoverageIgnore - The method is empty, nothing to test. * * @return void */ public function show() {} /** * Adds an image to the list by image ID. * * @param int $image_id The image ID to add. * * @return void */ public function add_image_by_id( $image_id ) { $attachment = $this->open_graph_image->get_image_by_id( $image_id ); if ( $attachment ) { $this->add_image( $attachment ); } } } generators/twitter-image-generator.php 0000644 00000004735 15025657560 0014220 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Twitter\Image_Helper as Twitter_Image_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Twitter\Images; /** * Represents the generator class for the Twitter images. */ class Twitter_Image_Generator implements Generator_Interface { /** * The Twitter image helper. * * @var Twitter_Image_Helper */ protected $twitter_image; /** * The image helper. * * @var Image_Helper */ protected $image; /** * The URL helper. * * @var Url_Helper */ protected $url; /** * Twitter_Image_Generator constructor. * * @codeCoverageIgnore * * @param Image_Helper $image The image helper. * @param Url_Helper $url The url helper. * @param Twitter_Image_Helper $twitter_image The Twitter image helper. */ public function __construct( Image_Helper $image, Url_Helper $url, Twitter_Image_Helper $twitter_image ) { $this->image = $image; $this->url = $url; $this->twitter_image = $twitter_image; } /** * Retrieves the images for an indexable. * * @param Meta_Tags_Context $context The context. * * @return array<array<string, string|int>> The images. */ public function generate( Meta_Tags_Context $context ) { $image_container = $this->get_image_container(); $this->add_from_indexable( $context->indexable, $image_container ); return $image_container->get_images(); } /** * Adds an image based on the given indexable. * * @param Indexable $indexable The indexable. * @param Images $image_container The image container. * * @return void */ protected function add_from_indexable( Indexable $indexable, Images $image_container ) { if ( $indexable->twitter_image_id ) { $image_container->add_image_by_id( $indexable->twitter_image_id ); return; } if ( $indexable->twitter_image ) { $image_container->add_image_by_url( $indexable->twitter_image ); } } /** * Retrieves an instance of the image container. * * @codeCoverageIgnore * * @return Images The image container. */ protected function get_image_container() { $image_container = new Images( $this->image, $this->url ); $image_container->image_size = $this->twitter_image->get_image_size(); $image_container->set_helpers( $this->twitter_image ); return $image_container; } } generators/generator-interface.php 0000644 00000000544 15025657560 0013370 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators; use Yoast\WP\SEO\Context\Meta_Tags_Context; interface Generator_Interface { /** * Returns a string, or other Thing that the associated presenter can handle. * * @param Meta_Tags_Context $context The meta tags context. * * @return mixed */ public function generate( Meta_Tags_Context $context ); } generators/schema/main-image.php 0000644 00000002216 15025657560 0012706 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns ImageObject schema data. */ class Main_Image extends Abstract_Schema_Piece { /** * Determines whether or not a piece should be added to the graph. * * @return bool */ public function is_needed() { return true; } /** * Adds a main image for the current URL to the schema if there is one. * * This can be either the featured image or the first image in the content of the page. * * @return array|false Image Schema. */ public function generate() { $image_id = $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH; // The featured image. if ( $this->context->main_image_id ) { $generated_schema = $this->helpers->schema->image->generate_from_attachment_id( $image_id, $this->context->main_image_id ); $this->context->main_image_url = $generated_schema['url']; return $generated_schema; } // The first image in the content. if ( $this->context->main_image_url ) { return $this->helpers->schema->image->generate_from_url( $image_id, $this->context->main_image_url ); } return false; } } generators/schema/website.php 0000644 00000005323 15025657560 0012346 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns schema Website data. */ class Website extends Abstract_Schema_Piece { /** * Determines whether or not a piece should be added to the graph. * * @return bool */ public function is_needed() { return true; } /** * Outputs code to allow recognition of the internal search engine. * * @return array Website data blob. */ public function generate() { $data = [ '@type' => 'WebSite', '@id' => $this->context->site_url . Schema_IDs::WEBSITE_HASH, 'url' => $this->context->site_url, 'name' => $this->helpers->schema->html->smart_strip_tags( $this->context->site_name ), 'description' => \get_bloginfo( 'description' ), ]; if ( $this->context->site_represents_reference ) { $data['publisher'] = $this->context->site_represents_reference; } $data = $this->add_alternate_name( $data ); $data = $this->internal_search_section( $data ); $data = $this->helpers->schema->language->add_piece_language( $data ); return $data; } /** * Returns an alternate name if one was specified in the Yoast SEO settings. * * @param array $data The website data array. * * @return array */ private function add_alternate_name( $data ) { if ( $this->context->alternate_site_name !== '' ) { $data['alternateName'] = $this->helpers->schema->html->smart_strip_tags( $this->context->alternate_site_name ); } return $data; } /** * Adds the internal search JSON LD code to the homepage if it's not disabled. * * @link https://developers.google.com/search/docs/data-types/sitelinks-searchbox * * @param array $data The website data array. * * @return array */ private function internal_search_section( $data ) { /** * Filter: 'disable_wpseo_json_ld_search' - Allow disabling of the json+ld output. * * @param bool $display_search Whether or not to display json+ld search on the frontend. */ if ( \apply_filters( 'disable_wpseo_json_ld_search', false ) ) { return $data; } /** * Filter: 'wpseo_json_ld_search_url' - Allows filtering of the search URL for Yoast SEO. * * @param string $search_url The search URL for this site with a `{search_term_string}` variable. */ $search_url = \apply_filters( 'wpseo_json_ld_search_url', $this->context->site_url . '?s={search_term_string}' ); $data['potentialAction'][] = [ '@type' => 'SearchAction', 'target' => [ '@type' => 'EntryPoint', 'urlTemplate' => $search_url, ], 'query-input' => [ '@type' => 'PropertyValueSpecification', 'valueRequired' => true, 'valueName' => 'search_term_string', ], ]; return $data; } } generators/schema/howto.php 0000644 00000012211 15025657560 0012036 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns schema HowTo data. */ class HowTo extends Abstract_Schema_Piece { /** * Determines whether or not a piece should be added to the graph. * * @return bool */ public function is_needed() { return ! empty( $this->context->blocks['yoast/how-to-block'] ); } /** * Renders a list of questions, referencing them by ID. * * @return array Our Schema graph. */ public function generate() { $graph = []; foreach ( $this->context->blocks['yoast/how-to-block'] as $index => $block ) { $this->add_how_to( $graph, $block, $index ); } return $graph; } /** * Adds the duration of the task to the Schema. * * @param array $data Our How-To schema data. * @param array $attributes The block data attributes. * * @return void */ private function add_duration( &$data, $attributes ) { if ( empty( $attributes['hasDuration'] ) ) { return; } $days = empty( $attributes['days'] ) ? 0 : $attributes['days']; $hours = empty( $attributes['hours'] ) ? 0 : $attributes['hours']; $minutes = empty( $attributes['minutes'] ) ? 0 : $attributes['minutes']; if ( ( $days + $hours + $minutes ) > 0 ) { $data['totalTime'] = \esc_attr( 'P' . $days . 'DT' . $hours . 'H' . $minutes . 'M' ); } } /** * Adds the steps to our How-To output. * * @param array $data Our How-To schema data. * @param array $steps Our How-To block's steps. * * @return void */ private function add_steps( &$data, $steps ) { foreach ( $steps as $step ) { $schema_id = $this->context->canonical . '#' . \esc_attr( $step['id'] ); $schema_step = [ '@type' => 'HowToStep', 'url' => $schema_id, ]; if ( isset( $step['jsonText'] ) ) { $json_text = $this->helpers->schema->html->sanitize( $step['jsonText'] ); } if ( isset( $step['jsonName'] ) ) { $json_name = $this->helpers->schema->html->smart_strip_tags( $step['jsonName'] ); } if ( empty( $json_name ) ) { if ( empty( $step['text'] ) ) { continue; } $schema_step['text'] = ''; $this->add_step_image( $schema_step, $step ); // If there is no text and no image, don't output the step. if ( empty( $json_text ) && empty( $schema_step['image'] ) ) { continue; } if ( ! empty( $json_text ) ) { $schema_step['text'] = $json_text; } } elseif ( empty( $json_text ) ) { $schema_step['text'] = $json_name; } else { $schema_step['name'] = $json_name; $this->add_step_description( $schema_step, $json_text ); $this->add_step_image( $schema_step, $step ); } $data['step'][] = $schema_step; } } /** * Checks if we have a step description, if we do, add it. * * @param array $schema_step Our Schema output for the Step. * @param string $json_text The step text. * * @return void */ private function add_step_description( &$schema_step, $json_text ) { $schema_step['itemListElement'] = [ [ '@type' => 'HowToDirection', 'text' => $json_text, ], ]; } /** * Checks if we have a step image, if we do, add it. * * @param array $schema_step Our Schema output for the Step. * @param array $step The step block data. * * @return void */ private function add_step_image( &$schema_step, $step ) { if ( isset( $step['text'] ) && \is_array( $step['text'] ) ) { foreach ( $step['text'] as $line ) { if ( \is_array( $line ) && isset( $line['type'] ) && $line['type'] === 'img' ) { $schema_step['image'] = $this->get_image_schema( \esc_url( $line['props']['src'] ) ); } } } } /** * Generates the HowTo schema for a block. * * @param array $graph Our Schema data. * @param array $block The How-To block content. * @param int $index The index of the current block. * * @return void */ protected function add_how_to( &$graph, $block, $index ) { $data = [ '@type' => 'HowTo', '@id' => $this->context->canonical . '#howto-' . ( $index + 1 ), 'name' => $this->helpers->schema->html->smart_strip_tags( $this->helpers->post->get_post_title_with_fallback( $this->context->id ) ), 'mainEntityOfPage' => [ '@id' => $this->context->main_schema_id ], 'description' => '', ]; if ( $this->context->has_article ) { $data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id . Schema_IDs::ARTICLE_HASH ]; } if ( isset( $block['attrs']['jsonDescription'] ) ) { $data['description'] = $this->helpers->schema->html->sanitize( $block['attrs']['jsonDescription'] ); } $this->add_duration( $data, $block['attrs'] ); if ( isset( $block['attrs']['steps'] ) ) { $this->add_steps( $data, $block['attrs']['steps'] ); } $data = $this->helpers->schema->language->add_piece_language( $data ); $graph[] = $data; } /** * Generates the image schema from the attachment $url. * * @param string $url Attachment url. * * @return array Image schema. */ protected function get_image_schema( $url ) { $schema_id = $this->context->canonical . '#schema-image-' . \md5( $url ); return $this->helpers->schema->image->generate_from_url( $schema_id, $url ); } } generators/schema/abstract-schema-piece.php 0000644 00000001432 15025657560 0015025 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Surfaces\Helpers_Surface; /** * Class Abstract_Schema_Piece. */ abstract class Abstract_Schema_Piece { /** * The meta tags context. * * @var Meta_Tags_Context */ public $context; /** * The helpers surface * * @var Helpers_Surface */ public $helpers; /** * Optional identifier for this schema piece. * * Used in the `Schema_Generator::filter_graph_pieces_to_generate()` method. * * @var string */ public $identifier; /** * Generates the schema piece. * * @return mixed */ abstract public function generate(); /** * Determines whether the schema piece is needed. * * @return bool */ abstract public function is_needed(); } generators/schema/organization.php 0000644 00000005145 15025657560 0013412 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns schema Organization data. */ class Organization extends Abstract_Schema_Piece { /** * Determines whether an Organization graph piece should be added. * * @return bool */ public function is_needed() { return $this->context->site_represents === 'company'; } /** * Returns the Organization Schema data. * * @return array The Organization schema. */ public function generate() { $logo_schema_id = $this->context->site_url . Schema_IDs::ORGANIZATION_LOGO_HASH; if ( $this->context->company_logo_meta ) { $logo = $this->helpers->schema->image->generate_from_attachment_meta( $logo_schema_id, $this->context->company_logo_meta, $this->context->company_name ); } else { $logo = $this->helpers->schema->image->generate_from_attachment_id( $logo_schema_id, $this->context->company_logo_id, $this->context->company_name ); } $organization = [ '@type' => 'Organization', '@id' => $this->context->site_url . Schema_IDs::ORGANIZATION_HASH, 'name' => $this->helpers->schema->html->smart_strip_tags( $this->context->company_name ), ]; if ( ! empty( $this->context->company_alternate_name ) ) { $organization['alternateName'] = $this->context->company_alternate_name; } $organization['url'] = $this->context->site_url; $organization['logo'] = $logo; $organization['image'] = [ '@id' => $logo['@id'] ]; $same_as = \array_values( \array_unique( \array_filter( $this->fetch_social_profiles() ) ) ); if ( ! empty( $same_as ) ) { $organization['sameAs'] = $same_as; } if ( \is_array( $this->context->schema_page_type ) && \in_array( 'ProfilePage', $this->context->schema_page_type, true ) ) { $organization['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id, ]; } return $organization; } /** * Retrieve the social profiles to display in the organization schema. * * @return array An array of social profiles. */ private function fetch_social_profiles() { $profiles = $this->helpers->social_profiles->get_organization_social_profiles(); if ( isset( $profiles['other_social_urls'] ) ) { $other_social_urls = $profiles['other_social_urls']; unset( $profiles['other_social_urls'] ); $profiles = \array_merge( $profiles, $other_social_urls ); } /** * Filter: 'wpseo_schema_organization_social_profiles' - Allows filtering social profiles for the * represented organization. * * @param string[] $profiles */ $profiles = \apply_filters( 'wpseo_schema_organization_social_profiles', $profiles ); return $profiles; } } generators/schema/webpage.php 0000644 00000011613 15025657560 0012315 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use WP_Post; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns schema WebPage data. */ class WebPage extends Abstract_Schema_Piece { /** * Determines whether or not a piece should be added to the graph. * * @return bool */ public function is_needed() { if ( $this->context->indexable->object_type === 'unknown' ) { return false; } return ! ( $this->context->indexable->object_type === 'system-page' && $this->context->indexable->object_sub_type === '404' ); } /** * Returns WebPage schema data. * * @return array<string|array<string>> WebPage schema data. */ public function generate() { $data = [ '@type' => $this->context->schema_page_type, '@id' => $this->context->main_schema_id, 'url' => $this->context->canonical, 'name' => $this->helpers->schema->html->smart_strip_tags( $this->context->title ), 'isPartOf' => [ '@id' => $this->context->site_url . Schema_IDs::WEBSITE_HASH, ], ]; if ( empty( $this->context->canonical ) && \is_search() ) { $data['url'] = $this->build_search_url(); } if ( $this->helpers->current_page->is_front_page() ) { if ( $this->context->site_represents_reference ) { $data['about'] = $this->context->site_represents_reference; } } $data = $this->add_image( $data ); if ( $this->context->indexable->object_type === 'post' ) { $data['datePublished'] = $this->helpers->date->format( $this->context->post->post_date_gmt ); if ( \strtotime( $this->context->post->post_modified_gmt ) > \strtotime( $this->context->post->post_date_gmt ) ) { $data['dateModified'] = $this->helpers->date->format( $this->context->post->post_modified_gmt ); } if ( $this->context->indexable->object_sub_type === 'post' ) { $data = $this->add_author( $data, $this->context->post ); } } if ( ! empty( $this->context->description ) ) { $data['description'] = $this->helpers->schema->html->smart_strip_tags( $this->context->description ); } if ( $this->add_breadcrumbs() ) { $data['breadcrumb'] = [ '@id' => $this->context->canonical . Schema_IDs::BREADCRUMB_HASH, ]; } if ( ! empty( $this->context->main_entity_of_page ) ) { $data['mainEntity'] = $this->context->main_entity_of_page; } $data = $this->helpers->schema->language->add_piece_language( $data ); $data = $this->add_potential_action( $data ); return $data; } /** * Adds an author property to the $data if the WebPage is not represented. * * @param array<string|array<string>> $data The WebPage schema. * @param WP_Post $post The post the context is representing. * * @return array<string|array<string>> The WebPage schema. */ public function add_author( $data, $post ) { if ( $this->context->site_represents === false ) { $data['author'] = [ '@id' => $this->helpers->schema->id->get_user_schema_id( $post->post_author, $this->context ) ]; } return $data; } /** * If we have an image, make it the primary image of the page. * * @param array<string|array<string>> $data WebPage schema data. * * @return array<string|array<string>> */ public function add_image( $data ) { if ( $this->context->has_image ) { $data['primaryImageOfPage'] = [ '@id' => $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH ]; $data['image'] = [ '@id' => $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH ]; $data['thumbnailUrl'] = $this->context->main_image_url; } return $data; } /** * Determine if we should add a breadcrumb attribute. * * @return bool */ private function add_breadcrumbs() { if ( $this->context->indexable->object_type === 'system-page' && $this->context->indexable->object_sub_type === '404' ) { return false; } return true; } /** * Adds the potential action property to the WebPage Schema piece. * * @param array<string|array<string>> $data The WebPage data. * * @return array<string|array<string>> The WebPage data with the potential action added. */ private function add_potential_action( $data ) { $url = $this->context->canonical; if ( $data['@type'] === 'CollectionPage' || ( \is_array( $data['@type'] ) && \in_array( 'CollectionPage', $data['@type'], true ) ) ) { return $data; } /** * Filter: 'wpseo_schema_webpage_potential_action_target' - Allows filtering of the schema WebPage potentialAction target. * * @param array<string> $targets The URLs for the WebPage potentialAction target. */ $targets = \apply_filters( 'wpseo_schema_webpage_potential_action_target', [ $url ] ); $data['potentialAction'][] = [ '@type' => 'ReadAction', 'target' => $targets, ]; return $data; } /** * Creates the search URL for use when if there is no canonical. * * @return string Search URL. */ private function build_search_url() { return $this->context->site_url . '?s=' . \rawurlencode( \get_search_query() ); } } generators/schema/person.php 0000644 00000023057 15025657560 0012216 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use WP_User; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns schema Person data. */ class Person extends Abstract_Schema_Piece { /** * Array of the social profiles we display for a Person. * * @var string[] */ private $social_profiles = [ 'facebook', 'instagram', 'linkedin', 'pinterest', 'twitter', 'myspace', 'youtube', 'soundcloud', 'tumblr', 'wikipedia', ]; /** * The Schema type we use for this class. * * @var string[] */ protected $type = [ 'Person', 'Organization' ]; /** * Determine whether we should return Person schema. * * @return bool */ public function is_needed() { // Using an author piece instead. if ( $this->site_represents_current_author() ) { return false; } return $this->context->site_represents === 'person'; } /** * Returns Person Schema data. * * @return bool|array<string|string[]> Person data on success, false on failure. */ public function generate() { $user_id = $this->determine_user_id(); if ( ! $user_id ) { return false; } return $this->build_person_data( $user_id ); } /** * Determines a User ID for the Person data. * * @return bool|int User ID or false upon return. */ protected function determine_user_id() { /** * Filter: 'wpseo_schema_person_user_id' - Allows filtering of user ID used for person output. * * @param int|bool $user_id The user ID currently determined. */ $user_id = \apply_filters( 'wpseo_schema_person_user_id', $this->context->site_user_id ); // It should to be an integer higher than 0. if ( \is_int( $user_id ) && $user_id > 0 ) { return $user_id; } return false; } /** * Retrieve a list of social profile URLs for Person. * * @param string[] $same_as_urls Array of SameAs URLs. * @param int $user_id User ID. * * @return string[] A list of SameAs URLs. */ protected function get_social_profiles( $same_as_urls, $user_id ) { /** * Filter: 'wpseo_schema_person_social_profiles' - Allows filtering of social profiles per user. * * @param string[] $social_profiles The array of social profiles to retrieve. Each should be a user meta field * key. As they are retrieved using the WordPress function `get_the_author_meta`. * @param int $user_id The current user we're grabbing social profiles for. */ $social_profiles = \apply_filters( 'wpseo_schema_person_social_profiles', $this->social_profiles, $user_id ); // We can only handle an array. if ( ! \is_array( $social_profiles ) ) { return $same_as_urls; } foreach ( $social_profiles as $profile ) { // Skip non-string values. if ( ! \is_string( $profile ) ) { continue; } $social_url = $this->url_social_site( $profile, $user_id ); if ( $social_url ) { $same_as_urls[] = $social_url; } } return $same_as_urls; } /** * Builds our array of Schema Person data for a given user ID. * * @param int $user_id The user ID to use. * @param bool $add_hash Wether or not the person's image url hash should be added to the image id. * * @return array<string|string[]> An array of Schema Person data. */ protected function build_person_data( $user_id, $add_hash = false ) { $user_data = \get_userdata( $user_id ); $data = [ '@type' => $this->type, '@id' => $this->helpers->schema->id->get_user_schema_id( $user_id, $this->context ), ]; // Safety check for the `get_userdata` WP function, which could return false. if ( $user_data === false ) { return $data; } $data['name'] = $this->helpers->schema->html->smart_strip_tags( $user_data->display_name ); $data = $this->add_image( $data, $user_data, $add_hash ); if ( ! empty( $user_data->description ) ) { $data['description'] = $this->helpers->schema->html->smart_strip_tags( $user_data->description ); } if ( \is_array( $this->context->schema_page_type ) && \in_array( 'ProfilePage', $this->context->schema_page_type, true ) ) { $data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id, ]; } $data = $this->add_same_as_urls( $data, $user_data, $user_id ); /** * Filter: 'wpseo_schema_person_data' - Allows filtering of schema data per user. * * @param array $data The schema data we have for this person. * @param int $user_id The current user we're collecting schema data for. */ $data = \apply_filters( 'wpseo_schema_person_data', $data, $user_id ); return $data; } /** * Returns an ImageObject for the persons avatar. * * @param array<string|string[]> $data The Person schema. * @param WP_User $user_data User data. * @param bool $add_hash Wether or not the person's image url hash should be added to the image id. * * @return array<string|string[]> The Person schema. */ protected function add_image( $data, $user_data, $add_hash = false ) { $schema_id = $this->context->site_url . Schema_IDs::PERSON_LOGO_HASH; $data = $this->set_image_from_options( $data, $schema_id, $add_hash, $user_data ); if ( ! isset( $data['image'] ) ) { $data = $this->set_image_from_avatar( $data, $user_data, $schema_id, $add_hash ); } if ( \is_array( $this->type ) && \in_array( 'Organization', $this->type, true ) ) { $data_logo = ( $data['image']['@id'] ?? $schema_id ); $data['logo'] = [ '@id' => $data_logo ]; } return $data; } /** * Generate the person image from our settings. * * @param array<string|string[]> $data The Person schema. * @param string $schema_id The string used in the `@id` for the schema. * @param bool $add_hash Whether or not the person's image url hash should be added to the image id. * @param WP_User|null $user_data User data. * * @return array<string|string[]> The Person schema. */ protected function set_image_from_options( $data, $schema_id, $add_hash = false, $user_data = null ) { if ( $this->context->site_represents !== 'person' ) { return $data; } if ( \is_array( $this->context->person_logo_meta ) ) { $data['image'] = $this->helpers->schema->image->generate_from_attachment_meta( $schema_id, $this->context->person_logo_meta, $data['name'], $add_hash ); } return $data; } /** * Generate the person logo from gravatar. * * @param array<string|string[]> $data The Person schema. * @param WP_User $user_data User data. * @param string $schema_id The string used in the `@id` for the schema. * @param bool $add_hash Wether or not the person's image url hash should be added to the image id. * * @return array<string|string[]> The Person schema. */ protected function set_image_from_avatar( $data, $user_data, $schema_id, $add_hash = false ) { // If we don't have an image in our settings, fall back to an avatar, if we're allowed to. $show_avatars = \get_option( 'show_avatars' ); if ( ! $show_avatars ) { return $data; } $url = \get_avatar_url( $user_data->user_email ); if ( empty( $url ) ) { return $data; } $data['image'] = $this->helpers->schema->image->simple_image_object( $schema_id, $url, $user_data->display_name, $add_hash ); return $data; } /** * Returns an author's social site URL. * * @param string $social_site The social site to retrieve the URL for. * @param int|false $user_id The user ID to use function outside of the loop. * * @return string */ protected function url_social_site( $social_site, $user_id = false ) { $url = \get_the_author_meta( $social_site, $user_id ); if ( ! empty( $url ) && $social_site === 'twitter' ) { $url = 'https://x.com/' . $url; } return $url; } /** * Checks the site is represented by the same person as this indexable. * * @param WP_User|null $user_data User data. * * @return bool True when the site is represented by the same person as this indexable. */ protected function site_represents_current_author( $user_data = null ) { // Can only be the case when the site represents a user. if ( $this->context->site_represents !== 'person' ) { return false; } // Article post from the same user as the site represents. if ( $this->context->indexable->object_type === 'post' && $this->helpers->schema->article->is_author_supported( $this->context->indexable->object_sub_type ) && $this->context->schema_article_type !== 'None' ) { $user_id = ( $user_data instanceof WP_User && isset( $user_data->ID ) ) ? $user_data->ID : $this->context->indexable->author_id; return $this->context->site_user_id === $user_id; } // Author archive from the same user as the site represents. return $this->context->indexable->object_type === 'user' && $this->context->site_user_id === $this->context->indexable->object_id; } /** * Builds our SameAs array. * * @param array<string|string[]> $data The Person schema data. * @param WP_User $user_data The user data object. * @param int $user_id The user ID to use. * * @return array<string|string[]> The Person schema data. */ protected function add_same_as_urls( $data, $user_data, $user_id ) { $same_as_urls = []; // Add the "Website" field from WordPress' contact info. if ( ! empty( $user_data->user_url ) ) { $same_as_urls[] = $user_data->user_url; } // Add the social profiles. $same_as_urls = $this->get_social_profiles( $same_as_urls, $user_id ); if ( ! empty( $same_as_urls ) ) { $same_as_urls = \array_values( \array_unique( $same_as_urls ) ); $data['sameAs'] = $same_as_urls; } return $data; } } generators/schema/faq.php 0000644 00000005643 15025657560 0011460 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; /** * Returns schema FAQ data. */ class FAQ extends Abstract_Schema_Piece { /** * Determines whether a piece should be added to the graph. * * @return bool */ public function is_needed() { if ( empty( $this->context->blocks['yoast/faq-block'] ) ) { return false; } if ( ! \is_array( $this->context->schema_page_type ) ) { $this->context->schema_page_type = [ $this->context->schema_page_type ]; } $this->context->schema_page_type[] = 'FAQPage'; $this->context->main_entity_of_page = $this->generate_ids(); return true; } /** * Generate the IDs so we can link to them in the main entity. * * @return array */ private function generate_ids() { $ids = []; foreach ( $this->context->blocks['yoast/faq-block'] as $block ) { if ( isset( $block['attrs']['questions'] ) ) { foreach ( $block['attrs']['questions'] as $question ) { if ( empty( $question['jsonAnswer'] ) ) { continue; } $ids[] = [ '@id' => $this->context->canonical . '#' . \esc_attr( $question['id'] ) ]; } } } return $ids; } /** * Render a list of questions, referencing them by ID. * * @return array Our Schema graph. */ public function generate() { $graph = []; $questions = []; foreach ( $this->context->blocks['yoast/faq-block'] as $block ) { if ( isset( $block['attrs']['questions'] ) ) { $questions = \array_merge( $questions, $block['attrs']['questions'] ); } } foreach ( $questions as $index => $question ) { if ( ! isset( $question['jsonAnswer'] ) || empty( $question['jsonAnswer'] ) ) { continue; } $graph[] = $this->generate_question_block( $question, ( $index + 1 ) ); } return $graph; } /** * Generate a Question piece. * * @param array $question The question to generate schema for. * @param int $position The position of the question. * * @return array Schema.org Question piece. */ protected function generate_question_block( $question, $position ) { $url = $this->context->canonical . '#' . \esc_attr( $question['id'] ); $data = [ '@type' => 'Question', '@id' => $url, 'position' => $position, 'url' => $url, 'name' => $this->helpers->schema->html->smart_strip_tags( $question['jsonQuestion'] ), 'answerCount' => 1, 'acceptedAnswer' => $this->add_accepted_answer_property( $question ), ]; return $this->helpers->schema->language->add_piece_language( $data ); } /** * Adds the Questions `acceptedAnswer` property. * * @param array $question The question to add the acceptedAnswer to. * * @return array Schema.org Question piece. */ protected function add_accepted_answer_property( $question ) { $data = [ '@type' => 'Answer', 'text' => $this->helpers->schema->html->sanitize( $question['jsonAnswer'] ), ]; return $this->helpers->schema->language->add_piece_language( $data ); } } generators/schema/article.php 0000644 00000016466 15025657560 0012341 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use WP_User; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns schema Article data. */ class Article extends Abstract_Schema_Piece { /** * Determines whether or not a piece should be added to the graph. * * @return bool */ public function is_needed() { if ( $this->context->indexable->object_type !== 'post' ) { return false; } // If we cannot output a publisher, we shouldn't output an Article. if ( $this->context->site_represents === false ) { return false; } // If we cannot output an author, we shouldn't output an Article. if ( ! $this->helpers->schema->article->is_author_supported( $this->context->indexable->object_sub_type ) ) { return false; } if ( $this->context->schema_article_type !== 'None' ) { $this->context->has_article = true; return true; } return false; } /** * Returns Article data. * * @return array Article data. */ public function generate() { $author = \get_userdata( $this->context->post->post_author ); $data = [ '@type' => $this->context->schema_article_type, '@id' => $this->context->canonical . Schema_IDs::ARTICLE_HASH, 'isPartOf' => [ '@id' => $this->context->main_schema_id ], 'author' => [ 'name' => ( $author instanceof WP_User ) ? $this->helpers->schema->html->smart_strip_tags( $author->display_name ) : '', '@id' => $this->helpers->schema->id->get_user_schema_id( $this->context->post->post_author, $this->context ), ], 'headline' => $this->helpers->schema->html->smart_strip_tags( $this->helpers->post->get_post_title_with_fallback( $this->context->id ) ), 'datePublished' => $this->helpers->date->format( $this->context->post->post_date_gmt ), ]; if ( \strtotime( $this->context->post->post_modified_gmt ) > \strtotime( $this->context->post->post_date_gmt ) ) { $data['dateModified'] = $this->helpers->date->format( $this->context->post->post_modified_gmt ); } $data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id ]; $data['wordCount'] = $this->word_count( $this->context->post->post_content, $this->context->post->post_title ); if ( $this->context->post->comment_status === 'open' ) { $data['commentCount'] = \intval( $this->context->post->comment_count, 10 ); } if ( $this->context->site_represents_reference ) { $data['publisher'] = $this->context->site_represents_reference; } $data = $this->add_image( $data ); $data = $this->add_keywords( $data ); $data = $this->add_sections( $data ); $data = $this->helpers->schema->language->add_piece_language( $data ); if ( \post_type_supports( $this->context->post->post_type, 'comments' ) && $this->context->post->comment_status === 'open' ) { $data = $this->add_potential_action( $data ); } return $data; } /** * Adds tags as keywords, if tags are assigned. * * @param array $data Article data. * * @return array Article data. */ private function add_keywords( $data ) { /** * Filter: 'wpseo_schema_article_keywords_taxonomy' - Allow changing the taxonomy used to assign keywords to a post type Article data. * * @param string $taxonomy The chosen taxonomy. */ $taxonomy = \apply_filters( 'wpseo_schema_article_keywords_taxonomy', 'post_tag' ); return $this->add_terms( $data, 'keywords', $taxonomy ); } /** * Adds categories as sections, if categories are assigned. * * @param array $data Article data. * * @return array Article data. */ private function add_sections( $data ) { /** * Filter: 'wpseo_schema_article_sections_taxonomy' - Allow changing the taxonomy used to assign keywords to a post type Article data. * * @param string $taxonomy The chosen taxonomy. */ $taxonomy = \apply_filters( 'wpseo_schema_article_sections_taxonomy', 'category' ); return $this->add_terms( $data, 'articleSection', $taxonomy ); } /** * Adds a term or multiple terms, comma separated, to a field. * * @param array $data Article data. * @param string $key The key in data to save the terms in. * @param string $taxonomy The taxonomy to retrieve the terms from. * * @return mixed Article data. */ protected function add_terms( $data, $key, $taxonomy ) { $terms = \get_the_terms( $this->context->id, $taxonomy ); if ( ! \is_array( $terms ) ) { return $data; } $callback = static function ( $term ) { // We are using the WordPress internal translation. return $term->name !== \__( 'Uncategorized', 'default' ); }; $terms = \array_filter( $terms, $callback ); if ( empty( $terms ) ) { return $data; } $data[ $key ] = \wp_list_pluck( $terms, 'name' ); return $data; } /** * Adds an image node if the post has a featured image. * * @param array $data The Article data. * * @return array The Article data. */ private function add_image( $data ) { if ( $this->context->main_image_url !== null ) { $data['image'] = [ '@id' => $this->context->canonical . Schema_IDs::PRIMARY_IMAGE_HASH, ]; $data['thumbnailUrl'] = $this->context->main_image_url; } return $data; } /** * Adds the potential action property to the Article Schema piece. * * @param array $data The Article data. * * @return array The Article data with the potential action added. */ private function add_potential_action( $data ) { /** * Filter: 'wpseo_schema_article_potential_action_target' - Allows filtering of the schema Article potentialAction target. * * @param array $targets The URLs for the Article potentialAction target. */ $targets = \apply_filters( 'wpseo_schema_article_potential_action_target', [ $this->context->canonical . '#respond' ] ); $data['potentialAction'][] = [ '@type' => 'CommentAction', 'name' => 'Comment', 'target' => $targets, ]; return $data; } /** * Does a simple word count but tries to be relatively smart about it. * * @param string $post_content The post content. * @param string $post_title The post title. * * @return int The number of words in the content. */ private function word_count( $post_content, $post_title = '' ) { // Add the title to our word count. $post_content = $post_title . ' ' . $post_content; // Strip pre/code blocks and their content. $post_content = \preg_replace( '@<(pre|code)[^>]*?>.*?</\\1>@si', '', $post_content ); // Add space between tags that don't have it. $post_content = \preg_replace( '@><@', '> <', $post_content ); // Strips all other tags. $post_content = \wp_strip_all_tags( $post_content ); $characters = ''; if ( \preg_match( '@[а-я]@ui', $post_content ) ) { // Correct counting of the number of words in the Russian and Ukrainian languages. $alphabet = [ 'ru' => 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя', 'ua' => 'абвгґдеєжзиіїйклмнопрстуфхцчшщьюя', ]; $characters = \implode( '', $alphabet ); $characters = \preg_split( '//u', $characters, -1, \PREG_SPLIT_NO_EMPTY ); $characters = \array_unique( $characters ); $characters = \implode( '', $characters ); $characters .= \mb_strtoupper( $characters ); } // Remove characters from HTML entities. $post_content = \preg_replace( '@&[a-z0-9]+;@i', ' ', \htmlentities( $post_content ) ); return \str_word_count( $post_content, 0, $characters ); } } generators/schema/author.php 0000644 00000005736 15025657560 0012216 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; /** * Returns schema Author data. */ class Author extends Person { /** * Determine whether we should return Person schema. * * @return bool */ public function is_needed() { if ( $this->context->indexable->object_type === 'user' ) { return true; } if ( $this->context->indexable->object_type === 'post' && $this->helpers->schema->article->is_author_supported( $this->context->indexable->object_sub_type ) && $this->context->schema_article_type !== 'None' ) { return true; } return false; } /** * Returns Person Schema data. * * @return bool|array Person data on success, false on failure. */ public function generate() { $user_id = $this->determine_user_id(); if ( ! $user_id ) { return false; } $data = $this->build_person_data( $user_id ); if ( $this->site_represents_current_author() === false ) { $data['@type'] = [ 'Person' ]; unset( $data['logo'] ); } // If this is an author page, the Person object is the main object, so we set it as such here. if ( $this->context->indexable->object_type === 'user' ) { $data['mainEntityOfPage'] = [ '@id' => $this->context->main_schema_id, ]; } // If this is a post and the author archives are enabled, set the author archive url as the author url. if ( $this->context->indexable->object_type === 'post' ) { if ( $this->helpers->options->get( 'disable-author' ) !== true ) { $data['url'] = $this->helpers->user->get_the_author_posts_url( $user_id ); } } return $data; } /** * Determines a User ID for the Person data. * * @return bool|int User ID or false upon return. */ protected function determine_user_id() { $user_id = 0; if ( $this->context->indexable->object_type === 'post' ) { $user_id = (int) $this->context->post->post_author; } if ( $this->context->indexable->object_type === 'user' ) { $user_id = $this->context->indexable->object_id; } /** * Filter: 'wpseo_schema_person_user_id' - Allows filtering of user ID used for person output. * * @param int|bool $user_id The user ID currently determined. */ $user_id = \apply_filters( 'wpseo_schema_person_user_id', $user_id ); if ( \is_int( $user_id ) && $user_id > 0 ) { return $user_id; } return false; } /** * An author should not have an image from options, this only applies to persons. * * @param array $data The Person schema. * @param string $schema_id The string used in the `@id` for the schema. * @param bool $add_hash Whether or not the person's image url hash should be added to the image id. * @param WP_User|null $user_data User data. * * @return array The Person schema. */ protected function set_image_from_options( $data, $schema_id, $add_hash = false, $user_data = null ) { if ( $this->site_represents_current_author( $user_data ) ) { return parent::set_image_from_options( $data, $schema_id, $add_hash, $user_data ); } return $data; } } generators/schema/breadcrumb.php 0000644 00000012070 15025657560 0013007 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators\Schema; use Yoast\WP\SEO\Config\Schema_IDs; /** * Returns schema Breadcrumb data. */ class Breadcrumb extends Abstract_Schema_Piece { /** * Determine if we should add a breadcrumb attribute. * * @return bool */ public function is_needed() { if ( $this->context->indexable->object_type === 'unknown' ) { return false; } if ( $this->context->indexable->object_type === 'system-page' && $this->context->indexable->object_sub_type === '404' ) { return false; } return true; } /** * Returns Schema breadcrumb data to allow recognition of page's position in the site hierarchy. * * @link https://developers.google.com/search/docs/data-types/breadcrumb * * @return bool|array Array on success, false on failure. */ public function generate() { $breadcrumbs = $this->context->presentation->breadcrumbs; $list_elements = []; // In case of pagination, replace the last breadcrumb, because it only contains "Page [number]" and has no URL. if ( ( $this->helpers->current_page->is_paged() || $this->context->indexable->number_of_pages > 1 ) && ( // Do not replace the last breadcrumb on static post pages. ! $this->helpers->current_page->is_static_posts_page() // Do not remove the last breadcrumb if only one exists (bugfix for custom paginated frontpages). && \count( $breadcrumbs ) > 1 ) ) { \array_pop( $breadcrumbs ); } // Only output breadcrumbs that are not hidden. $breadcrumbs = \array_filter( $breadcrumbs, [ $this, 'not_hidden' ] ); \reset( $breadcrumbs ); /* * Check whether at least one of the breadcrumbs is broken. * If so, do not output anything. */ foreach ( $breadcrumbs as $breadcrumb ) { if ( $this->is_broken( $breadcrumb ) ) { return false; } } // Create the last breadcrumb. $last_breadcrumb = \array_pop( $breadcrumbs ); $breadcrumbs[] = $this->format_last_breadcrumb( $last_breadcrumb ); // If this is a static front page, prevent nested pages from creating a trail. if ( $this->helpers->current_page->is_home_static_page() ) { // Check if we're dealing with a nested page. if ( \count( $breadcrumbs ) > 1 ) { // Store the breadcrumbs home variable before dropping the parent page from the Schema. $breadcrumbs_home = $breadcrumbs[0]['text']; $breadcrumbs = [ \array_pop( $breadcrumbs ) ]; // Make the child page show the breadcrumbs home variable rather than its own title. $breadcrumbs[0]['text'] = $breadcrumbs_home; } } $breadcrumbs = \array_filter( $breadcrumbs, [ $this, 'not_empty_text' ] ); $breadcrumbs = \array_values( $breadcrumbs ); // Create intermediate breadcrumbs. foreach ( $breadcrumbs as $index => $breadcrumb ) { $list_elements[] = $this->create_breadcrumb( $index, $breadcrumb ); } return [ '@type' => 'BreadcrumbList', '@id' => $this->context->canonical . Schema_IDs::BREADCRUMB_HASH, 'itemListElement' => $list_elements, ]; } /** * Returns a breadcrumb array. * * @param int $index The position in the list. * @param array $breadcrumb The position in the list. * * @return array A breadcrumb listItem. */ private function create_breadcrumb( $index, $breadcrumb ) { $crumb = [ '@type' => 'ListItem', 'position' => ( $index + 1 ), 'name' => $this->helpers->schema->html->smart_strip_tags( $breadcrumb['text'] ), ]; if ( ! empty( $breadcrumb['url'] ) ) { $crumb['item'] = $breadcrumb['url']; } return $crumb; } /** * Creates the last breadcrumb in the breadcrumb list, omitting the URL per Google's spec. * * @link https://developers.google.com/search/docs/data-types/breadcrumb * * @param array $breadcrumb The position in the list. * * @return array The last of the breadcrumbs. */ private function format_last_breadcrumb( $breadcrumb ) { unset( $breadcrumb['url'] ); return $breadcrumb; } /** * Tests if the breadcrumb is broken. * A breadcrumb is considered broken: * - when it is not an array. * - when it has no URL or text. * * @param array $breadcrumb The breadcrumb to test. * * @return bool `true` if the breadcrumb is broken. */ private function is_broken( $breadcrumb ) { // A breadcrumb is broken if it is not an array. if ( ! \is_array( $breadcrumb ) ) { return true; } // A breadcrumb is broken if it does not contain a URL or text. if ( ! \array_key_exists( 'url', $breadcrumb ) || ! \array_key_exists( 'text', $breadcrumb ) ) { return true; } return false; } /** * Checks whether the breadcrumb is not set to be hidden. * * @param array $breadcrumb The breadcrumb array. * * @return bool If the breadcrumb should not be hidden. */ private function not_hidden( $breadcrumb ) { return empty( $breadcrumb['hide_in_schema'] ); } /** * Checks whether the breadcrumb has a not empty text. * * @param array $breadcrumb The breadcrumb array. * * @return bool If the breadcrumb has a not empty text. */ private function not_empty_text( $breadcrumb ) { return ! empty( $breadcrumb['text'] ); } } generators/open-graph-image-generator.php 0000644 00000014114 15025657560 0014546 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators; use Error; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Helpers\Image_Helper; use Yoast\WP\SEO\Helpers\Open_Graph\Image_Helper as Open_Graph_Image_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Values\Open_Graph\Images; /** * Represents the generator class for the Open Graph images. */ class Open_Graph_Image_Generator implements Generator_Interface { /** * The Open Graph image helper. * * @var Open_Graph_Image_Helper */ protected $open_graph_image; /** * The image helper. * * @var Image_Helper */ protected $image; /** * The URL helper. * * @var Url_Helper */ protected $url; /** * The options helper. * * @var Options_Helper */ private $options; /** * Images constructor. * * @codeCoverageIgnore * * @param Open_Graph_Image_Helper $open_graph_image Image helper for Open Graph. * @param Image_Helper $image The image helper. * @param Options_Helper $options The options helper. * @param Url_Helper $url The url helper. */ public function __construct( Open_Graph_Image_Helper $open_graph_image, Image_Helper $image, Options_Helper $options, Url_Helper $url ) { $this->open_graph_image = $open_graph_image; $this->image = $image; $this->options = $options; $this->url = $url; } /** * Retrieves the images for an indexable. * * For legacy reasons some plugins might expect we filter a WPSEO_Opengraph_Image object. That might cause * type errors. This is why we try/catch our filters. * * @param Meta_Tags_Context $context The context. * * @return array The images. */ public function generate( Meta_Tags_Context $context ) { $image_container = $this->get_image_container(); $backup_image_container = $this->get_image_container(); try { /** * Filter: wpseo_add_opengraph_images - Allow developers to add images to the Open Graph tags. * * @param Yoast\WP\SEO\Values\Open_Graph\Images $image_container The current object. */ \apply_filters( 'wpseo_add_opengraph_images', $image_container ); } catch ( Error $error ) { $image_container = $backup_image_container; } $this->add_from_indexable( $context->indexable, $image_container ); $backup_image_container = $image_container; try { /** * Filter: wpseo_add_opengraph_additional_images - Allows to add additional images to the Open Graph tags. * * @param Yoast\WP\SEO\Values\Open_Graph\Images $image_container The current object. */ \apply_filters( 'wpseo_add_opengraph_additional_images', $image_container ); } catch ( Error $error ) { $image_container = $backup_image_container; } $this->add_from_templates( $context, $image_container ); $this->add_from_default( $image_container ); return $image_container->get_images(); } /** * Retrieves the images for an author archive indexable. * * This is a custom method to address the case of Author Archives, since they always have an Open Graph image * set in the indexable (even if it is an empty default Gravatar). * * @param Meta_Tags_Context $context The context. * * @return array The images. */ public function generate_for_author_archive( Meta_Tags_Context $context ) { $image_container = $this->get_image_container(); $this->add_from_templates( $context, $image_container ); if ( $image_container->has_images() ) { return $image_container->get_images(); } return $this->generate( $context ); } /** * Adds an image based on the given indexable. * * @param Indexable $indexable The indexable. * @param Images $image_container The image container. * * @return void */ protected function add_from_indexable( Indexable $indexable, Images $image_container ) { if ( $indexable->open_graph_image_meta ) { $image_container->add_image_by_meta( $indexable->open_graph_image_meta ); return; } if ( $indexable->open_graph_image_id ) { $image_container->add_image_by_id( $indexable->open_graph_image_id ); return; } if ( $indexable->open_graph_image ) { $meta_data = []; if ( $indexable->open_graph_image_meta && \is_string( $indexable->open_graph_image_meta ) ) { $meta_data = \json_decode( $indexable->open_graph_image_meta, true ); } $image_container->add_image( \array_merge( (array) $meta_data, [ 'url' => $indexable->open_graph_image, ] ) ); } } /** * Retrieves the default Open Graph image. * * @param Images $image_container The image container. * * @return void */ protected function add_from_default( Images $image_container ) { if ( $image_container->has_images() ) { return; } $default_image_id = $this->options->get( 'og_default_image_id', '' ); if ( $default_image_id ) { $image_container->add_image_by_id( $default_image_id ); return; } $default_image_url = $this->options->get( 'og_default_image', '' ); if ( $default_image_url ) { $image_container->add_image_by_url( $default_image_url ); } } /** * Retrieves the default Open Graph image. * * @param Meta_Tags_Context $context The context. * @param Images $image_container The image container. * * @return void */ protected function add_from_templates( Meta_Tags_Context $context, Images $image_container ) { if ( $image_container->has_images() ) { return; } if ( $context->presentation->open_graph_image_id ) { $image_container->add_image_by_id( $context->presentation->open_graph_image_id ); return; } if ( $context->presentation->open_graph_image ) { $image_container->add_image_by_url( $context->presentation->open_graph_image ); } } /** * Retrieves an instance of the image container. * * @codeCoverageIgnore * * @return Images The image container. */ protected function get_image_container() { $image_container = new Images( $this->image, $this->url ); $image_container->set_helpers( $this->open_graph_image ); return $image_container; } } generators/breadcrumbs-generator.php 0000644 00000031167 15025657560 0013726 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Helpers\Current_Page_Helper; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Helpers\Pagination_Helper; use Yoast\WP\SEO\Helpers\Post_Type_Helper; use Yoast\WP\SEO\Helpers\Url_Helper; use Yoast\WP\SEO\Models\Indexable; use Yoast\WP\SEO\Repositories\Indexable_Repository; /** * Represents the generator class for the breadcrumbs. */ class Breadcrumbs_Generator implements Generator_Interface { /** * The indexable repository. * * @var Indexable_Repository */ private $repository; /** * The options helper. * * @var Options_Helper */ private $options; /** * The current page helper. * * @var Current_Page_Helper */ private $current_page_helper; /** * The post type helper. * * @var Post_Type_Helper */ private $post_type_helper; /** * The URL helper. * * @var Url_Helper */ private $url_helper; /** * The pagination helper. * * @var Pagination_Helper */ private $pagination_helper; /** * Breadcrumbs_Generator constructor. * * @param Indexable_Repository $repository The repository. * @param Options_Helper $options The options helper. * @param Current_Page_Helper $current_page_helper The current page helper. * @param Post_Type_Helper $post_type_helper The post type helper. * @param Url_Helper $url_helper The URL helper. * @param Pagination_Helper $pagination_helper The pagination helper. */ public function __construct( Indexable_Repository $repository, Options_Helper $options, Current_Page_Helper $current_page_helper, Post_Type_Helper $post_type_helper, Url_Helper $url_helper, Pagination_Helper $pagination_helper ) { $this->repository = $repository; $this->options = $options; $this->current_page_helper = $current_page_helper; $this->post_type_helper = $post_type_helper; $this->url_helper = $url_helper; $this->pagination_helper = $pagination_helper; } /** * Generates the breadcrumbs. * * @param Meta_Tags_Context $context The meta tags context. * * @return array<array<int, string>> An array of associative arrays that each have a 'text' and a 'url'. */ public function generate( Meta_Tags_Context $context ) { $static_ancestors = []; $breadcrumbs_home = $this->options->get( 'breadcrumbs-home' ); if ( $breadcrumbs_home !== '' && ! \in_array( $this->current_page_helper->get_page_type(), [ 'Home_Page', 'Static_Home_Page' ], true ) ) { $front_page_id = $this->current_page_helper->get_front_page_id(); if ( $front_page_id === 0 ) { $home_page_ancestor = $this->repository->find_for_home_page(); if ( \is_a( $home_page_ancestor, Indexable::class ) ) { $static_ancestors[] = $home_page_ancestor; } } else { $static_ancestor = $this->repository->find_by_id_and_type( $front_page_id, 'post' ); if ( \is_a( $static_ancestor, Indexable::class ) && $static_ancestor->post_status !== 'unindexed' ) { $static_ancestors[] = $static_ancestor; } } } $page_for_posts = \get_option( 'page_for_posts' ); if ( $this->should_have_blog_crumb( $page_for_posts, $context ) ) { $static_ancestor = $this->repository->find_by_id_and_type( $page_for_posts, 'post' ); if ( \is_a( $static_ancestor, Indexable::class ) && $static_ancestor->post_status !== 'unindexed' ) { $static_ancestors[] = $static_ancestor; } } if ( $context->indexable->object_type === 'post' && $context->indexable->object_sub_type !== 'post' && $context->indexable->object_sub_type !== 'page' && $this->post_type_helper->has_archive( $context->indexable->object_sub_type ) ) { $static_ancestor = $this->repository->find_for_post_type_archive( $context->indexable->object_sub_type ); if ( \is_a( $static_ancestor, Indexable::class ) ) { $static_ancestors[] = $static_ancestor; } } if ( $context->indexable->object_type === 'term' ) { $parent = $this->get_taxonomy_post_type_parent( $context->indexable->object_sub_type ); if ( $parent && $parent !== 'post' && $this->post_type_helper->has_archive( $parent ) ) { $static_ancestor = $this->repository->find_for_post_type_archive( $parent ); if ( \is_a( $static_ancestor, Indexable::class ) ) { $static_ancestors[] = $static_ancestor; } } } $indexables = []; if ( ! \in_array( $this->current_page_helper->get_page_type(), [ 'Home_Page', 'Static_Home_Page' ], true ) ) { // Get all ancestors of the indexable and append itself to get all indexables in the full crumb. $indexables = $this->repository->get_ancestors( $context->indexable ); } $indexables[] = $context->indexable; if ( ! empty( $static_ancestors ) ) { \array_unshift( $indexables, ...$static_ancestors ); } $indexables = \apply_filters( 'wpseo_breadcrumb_indexables', $indexables, $context ); $indexables = \is_array( $indexables ) ? $indexables : []; $indexables = \array_filter( $indexables, static function ( $indexable ) { return \is_a( $indexable, Indexable::class ); } ); $crumbs = \array_map( [ $this, 'get_post_type_crumb' ], $indexables ); if ( $breadcrumbs_home !== '' ) { $crumbs[0]['text'] = $breadcrumbs_home; } $crumbs = $this->add_paged_crumb( $crumbs, $context->indexable ); /** * Filter: 'wpseo_breadcrumb_links' - Allow the developer to filter the Yoast SEO breadcrumb links, add to them, change order, etc. * * @param array $crumbs The crumbs array. */ $filtered_crumbs = \apply_filters( 'wpseo_breadcrumb_links', $crumbs ); // Basic check to make sure the filtered crumbs are in an array. if ( ! \is_array( $filtered_crumbs ) ) { \_doing_it_wrong( 'Filter: \'wpseo_breadcrumb_links\'', 'The `wpseo_breadcrumb_links` filter should return a multi-dimensional array.', 'YoastSEO v20.0' ); } else { $crumbs = $filtered_crumbs; } $filter_callback = static function ( $link_info, $index ) use ( $crumbs ) { /** * Filter: 'wpseo_breadcrumb_single_link_info' - Allow developers to filter the Yoast SEO Breadcrumb link information. * * @param array $link_info The breadcrumb link information. * @param int $index The index of the breadcrumb in the list. * @param array $crumbs The complete list of breadcrumbs. */ return \apply_filters( 'wpseo_breadcrumb_single_link_info', $link_info, $index, $crumbs ); }; return \array_map( $filter_callback, $crumbs, \array_keys( $crumbs ) ); } /** * Returns the modified post crumb. * * @param string[] $crumb The crumb. * @param Indexable $ancestor The indexable. * * @return array<int, string> The crumb. */ private function get_post_crumb( $crumb, $ancestor ) { $crumb['id'] = $ancestor->object_id; return $crumb; } /** * Adds the correct ID to the crumb array based on the ancestor provided. * * @param Indexable $ancestor The ancestor indexable. * * @return string[] */ private function get_post_type_crumb( Indexable $ancestor ) { $crumb = [ 'url' => $ancestor->permalink, 'text' => $ancestor->breadcrumb_title, ]; switch ( $ancestor->object_type ) { case 'post': $crumb = $this->get_post_crumb( $crumb, $ancestor ); break; case 'post-type-archive': $crumb = $this->get_post_type_archive_crumb( $crumb, $ancestor ); break; case 'term': $crumb = $this->get_term_crumb( $crumb, $ancestor ); break; case 'system-page': $crumb = $this->get_system_page_crumb( $crumb, $ancestor ); break; case 'user': $crumb = $this->get_user_crumb( $crumb, $ancestor ); break; case 'date-archive': $crumb = $this->get_date_archive_crumb( $crumb ); break; default: // Handle unknown object types (optional). break; } return $crumb; } /** * Returns the modified post type crumb. * * @param string[] $crumb The crumb. * @param Indexable $ancestor The indexable. * * @return string[] The crumb. */ private function get_post_type_archive_crumb( $crumb, $ancestor ) { $crumb['ptarchive'] = $ancestor->object_sub_type; return $crumb; } /** * Returns the modified term crumb. * * @param string[] $crumb The crumb. * @param Indexable $ancestor The indexable. * * @return array<int, string> The crumb. */ private function get_term_crumb( $crumb, $ancestor ) { $crumb['term_id'] = $ancestor->object_id; $crumb['taxonomy'] = $ancestor->object_sub_type; return $crumb; } /** * Returns the modified system page crumb. * * @param string[] $crumb The crumb. * @param Indexable $ancestor The indexable. * * @return string[] The crumb. */ private function get_system_page_crumb( $crumb, $ancestor ) { if ( $ancestor->object_sub_type === 'search-result' ) { $crumb['text'] = $this->options->get( 'breadcrumbs-searchprefix' ) . ' ' . \esc_html( \get_search_query() ); $crumb['url'] = \get_search_link(); } elseif ( $ancestor->object_sub_type === '404' ) { $crumb['text'] = $this->options->get( 'breadcrumbs-404crumb' ); } return $crumb; } /** * Returns the modified user crumb. * * @param string[] $crumb The crumb. * @param Indexable $ancestor The indexable. * * @return string[] The crumb. */ private function get_user_crumb( $crumb, $ancestor ) { $display_name = \get_the_author_meta( 'display_name', $ancestor->object_id ); $crumb['text'] = $this->options->get( 'breadcrumbs-archiveprefix' ) . ' ' . $display_name; return $crumb; } /** * Returns the modified date archive crumb. * * @param string[] $crumb The crumb. * * @return string[] The crumb. */ protected function get_date_archive_crumb( $crumb ) { $home_url = $this->url_helper->home(); $prefix = $this->options->get( 'breadcrumbs-archiveprefix' ); if ( \is_day() ) { $day = \esc_html( \get_the_date() ); $crumb['url'] = $home_url . \get_the_date( 'Y/m/d' ) . '/'; $crumb['text'] = $prefix . ' ' . $day; } elseif ( \is_month() ) { $month = \esc_html( \trim( \single_month_title( ' ', false ) ) ); $crumb['url'] = $home_url . \get_the_date( 'Y/m' ) . '/'; $crumb['text'] = $prefix . ' ' . $month; } elseif ( \is_year() ) { $year = \get_the_date( 'Y' ); $crumb['url'] = $home_url . $year . '/'; $crumb['text'] = $prefix . ' ' . $year; } return $crumb; } /** * Returns whether or not a blog crumb should be added. * * @param int $page_for_posts The page for posts ID. * @param Meta_Tags_Context $context The meta tags context. * * @return bool Whether or not a blog crumb should be added. */ protected function should_have_blog_crumb( $page_for_posts, $context ) { // When there is no page configured as blog page. if ( \get_option( 'show_on_front' ) !== 'page' || ! $page_for_posts ) { return false; } if ( $context->indexable->object_type === 'term' ) { $parent = $this->get_taxonomy_post_type_parent( $context->indexable->object_sub_type ); return $parent === 'post'; } if ( $this->options->get( 'breadcrumbs-display-blog-page' ) !== true ) { return false; } // When the current page is the home page, searchpage or isn't a singular post. if ( \is_home() || \is_search() || ! \is_singular( 'post' ) ) { return false; } return true; } /** * Returns the post type parent of a given taxonomy. * * @param string $taxonomy The taxonomy. * * @return string|false The parent if it exists, false otherwise. */ protected function get_taxonomy_post_type_parent( $taxonomy ) { $parent = $this->options->get( 'taxonomy-' . $taxonomy . '-ptparent' ); if ( empty( $parent ) || (string) $parent === '0' ) { return false; } return $parent; } /** * Adds a crumb for the current page, if we're on an archive page or paginated post. * * @param string[] $crumbs The array of breadcrumbs. * @param Indexable $current_indexable The current indexable. * * @return string[] The breadcrumbs. */ protected function add_paged_crumb( array $crumbs, $current_indexable ) { $is_simple_page = $this->current_page_helper->is_simple_page(); // If we're not on a paged page do nothing. if ( ! $is_simple_page && ! $this->current_page_helper->is_paged() ) { return $crumbs; } // If we're not on a paginated post do nothing. if ( $is_simple_page && $current_indexable->number_of_pages === null ) { return $crumbs; } $current_page_number = $this->pagination_helper->get_current_page_number(); if ( $current_page_number <= 1 ) { return $crumbs; } $crumbs[] = [ 'text' => \sprintf( /* translators: %s expands to the current page number */ \__( 'Page %s', 'wordpress-seo' ), $current_page_number ), ]; return $crumbs; } } generators/open-graph-locale-generator.php 0000644 00000012767 15025657560 0014737 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators; use Yoast\WP\SEO\Context\Meta_Tags_Context; /** * Class Open_Graph_Locale_Generator. */ class Open_Graph_Locale_Generator implements Generator_Interface { /** * Generates the OG Locale. * * @param Meta_Tags_Context $context The context. * * @return string The OG locale. */ public function generate( Meta_Tags_Context $context ) { /** * Filter: 'wpseo_locale' - Allow changing the locale output. * * Note that this filter is different from `wpseo_og_locale`, which is run _after_ the OG specific filtering. * * @param string $locale Locale string. */ $locale = \apply_filters( 'wpseo_locale', \get_locale() ); // Catch some weird locales served out by WP that are not easily doubled up. $fix_locales = [ 'ca' => 'ca_ES', 'en' => 'en_US', 'el' => 'el_GR', 'et' => 'et_EE', 'ja' => 'ja_JP', 'sq' => 'sq_AL', 'uk' => 'uk_UA', 'vi' => 'vi_VN', 'zh' => 'zh_CN', ]; if ( isset( $fix_locales[ $locale ] ) ) { return $fix_locales[ $locale ]; } // Convert locales like "es" to "es_ES", in case that works for the given locale (sometimes it does). if ( \strlen( $locale ) === 2 ) { $locale = \strtolower( $locale ) . '_' . \strtoupper( $locale ); } // These are the locales FB supports. $fb_valid_fb_locales = [ 'af_ZA', // Afrikaans. 'ak_GH', // Akan. 'am_ET', // Amharic. 'ar_AR', // Arabic. 'as_IN', // Assamese. 'ay_BO', // Aymara. 'az_AZ', // Azerbaijani. 'be_BY', // Belarusian. 'bg_BG', // Bulgarian. 'bp_IN', // Bhojpuri. 'bn_IN', // Bengali. 'br_FR', // Breton. 'bs_BA', // Bosnian. 'ca_ES', // Catalan. 'cb_IQ', // Sorani Kurdish. 'ck_US', // Cherokee. 'co_FR', // Corsican. 'cs_CZ', // Czech. 'cx_PH', // Cebuano. 'cy_GB', // Welsh. 'da_DK', // Danish. 'de_DE', // German. 'el_GR', // Greek. 'en_GB', // English (UK). 'en_PI', // English (Pirate). 'en_UD', // English (Upside Down). 'en_US', // English (US). 'em_ZM', 'eo_EO', // Esperanto. 'es_ES', // Spanish (Spain). 'es_LA', // Spanish. 'es_MX', // Spanish (Mexico). 'et_EE', // Estonian. 'eu_ES', // Basque. 'fa_IR', // Persian. 'fb_LT', // Leet Speak. 'ff_NG', // Fulah. 'fi_FI', // Finnish. 'fo_FO', // Faroese. 'fr_CA', // French (Canada). 'fr_FR', // French (France). 'fy_NL', // Frisian. 'ga_IE', // Irish. 'gl_ES', // Galician. 'gn_PY', // Guarani. 'gu_IN', // Gujarati. 'gx_GR', // Classical Greek. 'ha_NG', // Hausa. 'he_IL', // Hebrew. 'hi_IN', // Hindi. 'hr_HR', // Croatian. 'hu_HU', // Hungarian. 'ht_HT', // Haitian Creole. 'hy_AM', // Armenian. 'id_ID', // Indonesian. 'ig_NG', // Igbo. 'is_IS', // Icelandic. 'it_IT', // Italian. 'ik_US', 'iu_CA', 'ja_JP', // Japanese. 'ja_KS', // Japanese (Kansai). 'jv_ID', // Javanese. 'ka_GE', // Georgian. 'kk_KZ', // Kazakh. 'km_KH', // Khmer. 'kn_IN', // Kannada. 'ko_KR', // Korean. 'ks_IN', // Kashmiri. 'ku_TR', // Kurdish (Kurmanji). 'ky_KG', // Kyrgyz. 'la_VA', // Latin. 'lg_UG', // Ganda. 'li_NL', // Limburgish. 'ln_CD', // Lingala. 'lo_LA', // Lao. 'lt_LT', // Lithuanian. 'lv_LV', // Latvian. 'mg_MG', // Malagasy. 'mi_NZ', // Maori. 'mk_MK', // Macedonian. 'ml_IN', // Malayalam. 'mn_MN', // Mongolian. 'mr_IN', // Marathi. 'ms_MY', // Malay. 'mt_MT', // Maltese. 'my_MM', // Burmese. 'nb_NO', // Norwegian (bokmal). 'nd_ZW', // Ndebele. 'ne_NP', // Nepali. 'nl_BE', // Dutch (Belgie). 'nl_NL', // Dutch. 'nn_NO', // Norwegian (nynorsk). 'nr_ZA', // Southern Ndebele. 'ns_ZA', // Northern Sotho. 'ny_MW', // Chewa. 'om_ET', // Oromo. 'or_IN', // Oriya. 'pa_IN', // Punjabi. 'pl_PL', // Polish. 'ps_AF', // Pashto. 'pt_BR', // Portuguese (Brazil). 'pt_PT', // Portuguese (Portugal). 'qc_GT', // Quiché. 'qu_PE', // Quechua. 'qr_GR', 'qz_MM', // Burmese (Zawgyi). 'rm_CH', // Romansh. 'ro_RO', // Romanian. 'ru_RU', // Russian. 'rw_RW', // Kinyarwanda. 'sa_IN', // Sanskrit. 'sc_IT', // Sardinian. 'se_NO', // Northern Sami. 'si_LK', // Sinhala. 'su_ID', // Sundanese. 'sk_SK', // Slovak. 'sl_SI', // Slovenian. 'sn_ZW', // Shona. 'so_SO', // Somali. 'sq_AL', // Albanian. 'sr_RS', // Serbian. 'ss_SZ', // Swazi. 'st_ZA', // Southern Sotho. 'sv_SE', // Swedish. 'sw_KE', // Swahili. 'sy_SY', // Syriac. 'sz_PL', // Silesian. 'ta_IN', // Tamil. 'te_IN', // Telugu. 'tg_TJ', // Tajik. 'th_TH', // Thai. 'tk_TM', // Turkmen. 'tl_PH', // Filipino. 'tl_ST', // Klingon. 'tn_BW', // Tswana. 'tr_TR', // Turkish. 'ts_ZA', // Tsonga. 'tt_RU', // Tatar. 'tz_MA', // Tamazight. 'uk_UA', // Ukrainian. 'ur_PK', // Urdu. 'uz_UZ', // Uzbek. 've_ZA', // Venda. 'vi_VN', // Vietnamese. 'wo_SN', // Wolof. 'xh_ZA', // Xhosa. 'yi_DE', // Yiddish. 'yo_NG', // Yoruba. 'zh_CN', // Simplified Chinese (China). 'zh_HK', // Traditional Chinese (Hong Kong). 'zh_TW', // Traditional Chinese (Taiwan). 'zu_ZA', // Zulu. 'zz_TR', // Zazaki. ]; // Check to see if the locale is a valid FB one, if not, use en_US as a fallback. if ( \in_array( $locale, $fb_valid_fb_locales, true ) ) { return $locale; } $locale = \strtolower( \substr( $locale, 0, 2 ) ) . '_' . \strtoupper( \substr( $locale, 0, 2 ) ); if ( ! \in_array( $locale, $fb_valid_fb_locales, true ) ) { return 'en_US'; } return $locale; } } generators/schema-generator.php 0000644 00000032630 15025657560 0012671 0 ustar 00 <?php namespace Yoast\WP\SEO\Generators; use WP_Block_Parser_Block; use Yoast\WP\SEO\Context\Meta_Tags_Context; use Yoast\WP\SEO\Generators\Schema\Abstract_Schema_Piece; use Yoast\WP\SEO\Helpers\Schema\Replace_Vars_Helper; use Yoast\WP\SEO\Surfaces\Helpers_Surface; /** * Class Schema_Generator. */ class Schema_Generator implements Generator_Interface { /** * The helpers surface. * * @var Helpers_Surface */ protected $helpers; /** * The Schema replace vars helper. * * @var Replace_Vars_Helper */ protected $schema_replace_vars_helper; /** * Generator constructor. * * @param Helpers_Surface $helpers The helpers surface. * @param Replace_Vars_Helper $schema_replace_vars_helper The replace vars helper. */ public function __construct( Helpers_Surface $helpers, Replace_Vars_Helper $schema_replace_vars_helper ) { $this->helpers = $helpers; $this->schema_replace_vars_helper = $schema_replace_vars_helper; } /** * Returns a Schema graph array. * * @param Meta_Tags_Context $context The meta tags context. * * @return array The graph. */ public function generate( Meta_Tags_Context $context ) { $pieces = $this->get_graph_pieces( $context ); $this->schema_replace_vars_helper->register_replace_vars( $context ); foreach ( \array_keys( $context->blocks ) as $block_type ) { /** * Filter: 'wpseo_pre_schema_block_type_<block-type>' - Allows hooking things to change graph output based on the blocks on the page. * * @param WP_Block_Parser_Block[] $blocks All the blocks of this block type. * @param Meta_Tags_Context $context A value object with context variables. */ \do_action( 'wpseo_pre_schema_block_type_' . $block_type, $context->blocks[ $block_type ], $context ); } // Do a loop before everything else to inject the context and helpers. foreach ( $pieces as $piece ) { if ( \is_a( $piece, Abstract_Schema_Piece::class ) ) { $piece->context = $context; $piece->helpers = $this->helpers; } } $pieces_to_generate = $this->filter_graph_pieces_to_generate( $pieces ); $graph = $this->generate_graph( $pieces_to_generate, $context ); $graph = $this->add_schema_blocks_graph_pieces( $graph, $context ); $graph = $this->finalize_graph( $graph, $context ); return [ '@context' => 'https://schema.org', '@graph' => $graph, ]; } /** * Filters out any graph pieces that should not be generated. * (Using the `wpseo_schema_needs_<graph_piece_identifier>` series of filters). * * @param array $graph_pieces The current list of graph pieces that we want to generate. * * @return array The graph pieces to generate. */ protected function filter_graph_pieces_to_generate( $graph_pieces ) { $pieces_to_generate = []; foreach ( $graph_pieces as $piece ) { $identifier = \strtolower( \str_replace( 'Yoast\WP\SEO\Generators\Schema\\', '', \get_class( $piece ) ) ); if ( isset( $piece->identifier ) ) { $identifier = $piece->identifier; } /** * Filter: 'wpseo_schema_needs_<identifier>' - Allows changing which graph pieces we output. * * @param bool $is_needed Whether or not to show a graph piece. */ $is_needed = \apply_filters( 'wpseo_schema_needs_' . $identifier, $piece->is_needed() ); if ( ! $is_needed ) { continue; } $pieces_to_generate[ $identifier ] = $piece; } return $pieces_to_generate; } /** * Generates the schema graph. * * @param array $graph_piece_generators The schema graph pieces to generate. * @param Meta_Tags_Context $context The meta tags context to use. * * @return array The generated schema graph. */ protected function generate_graph( $graph_piece_generators, $context ) { $graph = []; foreach ( $graph_piece_generators as $identifier => $graph_piece_generator ) { $graph_pieces = $graph_piece_generator->generate(); // If only a single graph piece was returned. if ( $graph_pieces !== false && \array_key_exists( '@type', $graph_pieces ) ) { $graph_pieces = [ $graph_pieces ]; } if ( ! \is_array( $graph_pieces ) ) { continue; } foreach ( $graph_pieces as $graph_piece ) { /** * Filter: 'wpseo_schema_<identifier>' - Allows changing graph piece output. * This filter can be called with either an identifier or a block type (see `add_schema_blocks_graph_pieces()`). * * @param array $graph_piece The graph piece to filter. * @param Meta_Tags_Context $context A value object with context variables. * @param Abstract_Schema_Piece $graph_piece_generator A value object with context variables. * @param Abstract_Schema_Piece[] $graph_piece_generators A value object with context variables. */ $graph_piece = \apply_filters( 'wpseo_schema_' . $identifier, $graph_piece, $context, $graph_piece_generator, $graph_piece_generators ); $graph_piece = $this->type_filter( $graph_piece, $identifier, $context, $graph_piece_generator, $graph_piece_generators ); $graph_piece = $this->validate_type( $graph_piece ); if ( \is_array( $graph_piece ) ) { $graph[] = $graph_piece; } } } /** * Filter: 'wpseo_schema_graph' - Allows changing graph output. * * @param array $graph The graph to filter. * @param Meta_Tags_Context $context A value object with context variables. */ $graph = \apply_filters( 'wpseo_schema_graph', $graph, $context ); return $graph; } /** * Adds schema graph pieces from Gutenberg blocks on the current page to * the given schema graph. * * Think of blocks like the Yoast FAQ block or the How To block. * * @param array $graph The current schema graph. * @param Meta_Tags_Context $context The meta tags context. * * @return array The graph with the schema blocks graph pieces added. */ protected function add_schema_blocks_graph_pieces( $graph, $context ) { foreach ( $context->blocks as $block_type => $blocks ) { foreach ( $blocks as $block ) { $block_type = \strtolower( $block['blockName'] ); /** * Filter: 'wpseo_schema_block_<block-type>'. * This filter is documented in the `generate_graph()` function in this class. */ $graph = \apply_filters( 'wpseo_schema_block_' . $block_type, $graph, $block, $context ); if ( isset( $block['attrs']['yoast-schema'] ) ) { $graph[] = $this->schema_replace_vars_helper->replace( $block['attrs']['yoast-schema'], $context->presentation ); } } } return $graph; } /** * Finalizes the schema graph after all filtering is done. * * @param array $graph The current schema graph. * @param Meta_Tags_Context $context The meta tags context. * * @return array The schema graph. */ protected function finalize_graph( $graph, $context ) { $graph = $this->remove_empty_breadcrumb( $graph, $context ); return $graph; } /** * Removes the breadcrumb schema if empty. * * @param array $graph The current schema graph. * @param Meta_Tags_Context $context The meta tags context. * * @return array The schema graph with empty breadcrumbs taken out. */ protected function remove_empty_breadcrumb( $graph, $context ) { if ( $this->helpers->current_page->is_home_static_page() || $this->helpers->current_page->is_home_posts_page() ) { return $graph; } // Remove the breadcrumb piece, if it's empty. $index_to_remove = 0; foreach ( $graph as $key => $piece ) { if ( \in_array( 'BreadcrumbList', $this->get_type_from_piece( $piece ), true ) ) { if ( isset( $piece['itemListElement'] ) && \is_array( $piece['itemListElement'] ) && \count( $piece['itemListElement'] ) === 1 ) { $index_to_remove = $key; break; } } } // If the breadcrumb piece has been removed, we should remove its reference from the WebPage node. if ( $index_to_remove !== 0 ) { \array_splice( $graph, $index_to_remove, 1 ); // Get the type of the WebPage node. $webpage_types = \is_array( $context->schema_page_type ) ? $context->schema_page_type : [ $context->schema_page_type ]; foreach ( $graph as $key => $piece ) { if ( ! empty( \array_intersect( $webpage_types, $this->get_type_from_piece( $piece ) ) ) && isset( $piece['breadcrumb'] ) ) { unset( $piece['breadcrumb'] ); $graph[ $key ] = $piece; } } } return $graph; } /** * Adapts the WebPage graph piece for password-protected posts. * * It should only have certain whitelisted properties. * The type should always be WebPage. * * @param array $graph_piece The WebPage graph piece that should be adapted for password-protected posts. * * @return array The WebPage graph piece that has been adapted for password-protected posts. */ public function protected_webpage_schema( $graph_piece ) { $properties_to_show = \array_flip( [ '@type', '@id', 'url', 'name', 'isPartOf', 'inLanguage', 'datePublished', 'dateModified', 'breadcrumb', ] ); $graph_piece = \array_intersect_key( $graph_piece, $properties_to_show ); $graph_piece['@type'] = 'WebPage'; return $graph_piece; } /** * Gets all the graph pieces we need. * * @param Meta_Tags_Context $context The meta tags context. * * @return Abstract_Schema_Piece[] A filtered array of graph pieces. */ protected function get_graph_pieces( $context ) { if ( $context->indexable->object_type === 'post' && \post_password_required( $context->post ) ) { $schema_pieces = [ new Schema\WebPage(), new Schema\Website(), new Schema\Organization(), ]; \add_filter( 'wpseo_schema_webpage', [ $this, 'protected_webpage_schema' ], 1 ); } else { $schema_pieces = [ new Schema\Article(), new Schema\WebPage(), new Schema\Main_Image(), new Schema\Breadcrumb(), new Schema\Website(), new Schema\Organization(), new Schema\Person(), new Schema\Author(), new Schema\FAQ(), new Schema\HowTo(), ]; } /** * Filter: 'wpseo_schema_graph_pieces' - Allows adding pieces to the graph. * * @param array $pieces The schema pieces. * @param Meta_Tags_Context $context An object with context variables. */ return \apply_filters( 'wpseo_schema_graph_pieces', $schema_pieces, $context ); } /** * Allows filtering the graph piece by its schema type. * * Note: We removed the Abstract_Schema_Piece type-hint from the $graph_piece_generator argument, because * it caused conflicts with old code, Yoast SEO Video specifically. * * @param array $graph_piece The graph piece we're filtering. * @param string $identifier The identifier of the graph piece that is being filtered. * @param Meta_Tags_Context $context The meta tags context. * @param Abstract_Schema_Piece $graph_piece_generator A value object with context variables. * @param Abstract_Schema_Piece[] $graph_piece_generators A value object with context variables. * * @return array The filtered graph piece. */ private function type_filter( $graph_piece, $identifier, Meta_Tags_Context $context, $graph_piece_generator, array $graph_piece_generators ) { $types = $this->get_type_from_piece( $graph_piece ); foreach ( $types as $type ) { $type = \strtolower( $type ); // Prevent running the same filter twice. This makes sure we run f/i. for 'author' and for 'person'. if ( $type && $type !== $identifier ) { /** * Filter: 'wpseo_schema_<type>' - Allows changing graph piece output by @type. * * @param array $graph_piece The graph piece to filter. * @param Meta_Tags_Context $context A value object with context variables. * @param Abstract_Schema_Piece $graph_piece_generator A value object with context variables. * @param Abstract_Schema_Piece[] $graph_piece_generators A value object with context variables. */ $graph_piece = \apply_filters( 'wpseo_schema_' . $type, $graph_piece, $context, $graph_piece_generator, $graph_piece_generators ); } } return $graph_piece; } /** * Retrieves the type from a graph piece. * * @param array $piece The graph piece. * * @return array An array of the piece's types. */ private function get_type_from_piece( $piece ) { if ( isset( $piece['@type'] ) ) { if ( \is_array( $piece['@type'] ) ) { // Return as-is, but remove unusable values, like sub-arrays, objects, null. return \array_filter( $piece['@type'], 'is_string' ); } return [ $piece['@type'] ]; } return []; } /** * Validates a graph piece's type. * * When the type is an array: * - Ensure the values are unique. * - Only 1 value? Use that value without the array wrapping. * * @param array $piece The graph piece. * * @return array The graph piece. */ private function validate_type( $piece ) { if ( ! isset( $piece['@type'] ) ) { // No type to validate. return $piece; } // If it is not an array, we can return immediately. if ( ! \is_array( $piece['@type'] ) ) { return $piece; } /* * Ensure the types are unique. * Use array_values to reset the indices (e.g. no 0, 2 because 1 was a duplicate). */ $piece['@type'] = \array_values( \array_unique( $piece['@type'] ) ); // Use the first value if there is only 1 type. if ( \count( $piece['@type'] ) === 1 ) { $piece['@type'] = \reset( $piece['@type'] ); } return $piece; } } elementor/infrastructure/request-post.php 0000644 00000011161 15025657560 0015015 0 ustar 00 <?php namespace Yoast\WP\SEO\Elementor\Infrastructure; use WP_Post; /** * Retrieve the WP_Post from the request. */ class Request_Post { /** * Retrieves the WP_Post, applicable to the current request. * * @return WP_Post|null */ public function get_post(): ?WP_Post { return \get_post( $this->get_post_id() ); } /** * Retrieves the post ID, applicable to the current request. * * @return int|null The post ID. */ public function get_post_id(): ?int { switch ( $this->get_server_request_method() ) { case 'GET': // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reason: We are not processing form information. if ( isset( $_GET['post'] ) && \is_numeric( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Recommended -- Reason: No sanitization needed because we cast to an integer,We are not processing form information. return (int) \wp_unslash( $_GET['post'] ); } break; case 'POST': // Only allow POST requests when doing AJAX. if ( ! \wp_doing_ajax() ) { break; } switch ( $this->get_post_action() ) { // Our Yoast SEO form submission, it should include `post_id`. case 'wpseo_elementor_save': // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( isset( $_POST['post_id'] ) && \is_numeric( $_POST['post_id'] ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing -- Reason: No sanitization needed because we cast to an integer,We are not processing form information. return (int) \wp_unslash( $_POST['post_id'] ); } break; // Elementor editor AJAX request. case 'elementor_ajax': return $this->get_document_id(); } break; } return null; } /** * Returns the server request method. * * @return string|null The server request method, in upper case. */ private function get_server_request_method(): ?string { if ( ! isset( $_SERVER['REQUEST_METHOD'] ) ) { return null; } if ( ! \is_string( $_SERVER['REQUEST_METHOD'] ) ) { return null; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are only comparing it later. return \strtoupper( \wp_unslash( $_SERVER['REQUEST_METHOD'] ) ); } /** * Retrieves the action from the POST request. * * @return string|null The action or null if not found. */ private function get_post_action(): ?string { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( isset( $_POST['action'] ) && \is_string( $_POST['action'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Reason: We are not processing form information, we are only strictly comparing. return (string) \wp_unslash( $_POST['action'] ); } return null; } /** * Retrieves the document ID from the POST request. * * Note: this is specific to Elementor' `elementor_ajax` action. And then the `get_document_config` internal action. * Currently, you can see this in play when: * - showing the Site Settings in the Elementor editor * - going to another Recent post/page in the Elementor editor V2 * * @return int|null The document ID or null if not found. */ private function get_document_id(): ?int { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Reason: We are not processing form information. if ( ! ( isset( $_POST['actions'] ) && \is_string( $_POST['actions'] ) ) ) { return null; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.NonceVerification.Missing -- Reason: No sanitization needed because we cast to an integer (after JSON decode and type/exist checks),We are not processing form information. $actions = \json_decode( \wp_unslash( $_POST['actions'] ), true ); if ( ! \is_array( $actions ) ) { return null; } // Elementor sends everything in a `document-{ID}` format. $action = \array_shift( $actions ); if ( $action === null ) { return null; } // There are multiple action types. We only care about the "get_document_config" one. if ( ! ( isset( $action['action'] ) && $action['action'] === 'get_document_config' ) ) { return null; } // Return the ID from the data, if it is set and numeric. if ( isset( $action['data']['id'] ) && \is_numeric( $action['data']['id'] ) ) { return (int) $action['data']['id']; } return null; } } content-type-visibility/application/content-type-visibility-watcher-actions.php 0000644 00000013747 15025657560 0024334 0 ustar 00 <?php namespace Yoast\WP\SEO\Content_Type_Visibility\Application; use Yoast\WP\SEO\Conditionals\Admin_Conditional; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast\WP\SEO\Integrations\Integration_Interface; use Yoast_Notification; use Yoast_Notification_Center; /** * Handles showing notifications for new content types. * Actions are used in the indexable taxonomy and post type change watchers. */ class Content_Type_Visibility_Watcher_Actions implements Integration_Interface { /** * Holds the Options_Helper instance. * * @var Options_Helper */ private $options; /** * The notifications center. * * @var Yoast_Notification_Center */ private $notification_center; /** * The notifications center. * * @var Content_Type_Visibility_Dismiss_Notifications */ private $content_type_dismiss_notifications; /** * Indexable_Post_Type_Change_Watcher constructor. * * @param Options_Helper $options The options helper. * @param Yoast_Notification_Center $notification_center The notification center. * @param Content_Type_Visibility_Dismiss_Notifications $content_type_dismiss_notifications The content type dismiss notifications. */ public function __construct( Options_Helper $options, Yoast_Notification_Center $notification_center, Content_Type_Visibility_Dismiss_Notifications $content_type_dismiss_notifications ) { $this->options = $options; $this->notification_center = $notification_center; $this->content_type_dismiss_notifications = $content_type_dismiss_notifications; } /** * Returns the conditionals based on which this loadable should be active. * * @return array */ public static function get_conditionals() { return [ Admin_Conditional::class ]; } /** * Initializes the integration. * * Register actions that are used in the post types and taxonomies indexable watcher. * * @return void */ public function register_hooks() { // Used in Idexable_Post_Type_Change_Watcher class. \add_action( 'new_public_post_type_notifications', [ $this, 'new_post_type' ], 10, 1 ); \add_action( 'clean_new_public_post_type_notifications', [ $this, 'clean_new_public_post_type' ], 10, 1 ); // Used in Idexable_Taxonomy_Change_Watcher class. \add_action( 'new_public_taxonomy_notifications', [ $this, 'new_taxonomy' ], 10, 1 ); \add_action( 'clean_new_public_taxonomy_notifications', [ $this, 'clean_new_public_taxonomy' ], 10, 1 ); } /** * Update db and tigger notification when a new post type is registered. * * @param array $newly_made_public_post_types The newly made public post types. * @return void */ public function new_post_type( $newly_made_public_post_types ) { $this->options->set( 'new_post_types', $newly_made_public_post_types ); $this->options->set( 'show_new_content_type_notification', true ); $this->maybe_add_notification(); } /** * Update db when a post type is made removed. * * @param array $newly_made_non_public_post_types The newly made non public post types. * @return void */ public function clean_new_public_post_type( $newly_made_non_public_post_types ) { // See if post types that needs review were removed and update option. $needs_review = $this->options->get( 'new_post_types', [] ); $new_needs_review = \array_diff( $needs_review, $newly_made_non_public_post_types ); if ( \count( $new_needs_review ) !== \count( $needs_review ) ) { $this->options->set( 'new_post_types', $new_needs_review ); $this->content_type_dismiss_notifications->maybe_dismiss_notifications( [ 'new_post_types' => $new_needs_review ] ); } } /** * Update db and tigger notification when a new taxonomy is registered. * * @param array $newly_made_public_taxonomies The newly made public post types. * @return void */ public function new_taxonomy( $newly_made_public_taxonomies ) { $this->options->set( 'new_taxonomies', $newly_made_public_taxonomies ); $this->options->set( 'show_new_content_type_notification', true ); $this->maybe_add_notification(); } /** * Update db when a post type is made removed. * * @param array $newly_made_non_public_taxonomies The newly made non public post types. * @return void */ public function clean_new_public_taxonomy( $newly_made_non_public_taxonomies ) { // See if post types that needs review were removed and update option. $needs_review = $this->options->get( 'new_taxonomies', [] ); $new_needs_review = \array_diff( $needs_review, $newly_made_non_public_taxonomies ); if ( \count( $new_needs_review ) !== \count( $needs_review ) ) { $this->options->set( 'new_taxonomies', $new_needs_review ); $this->content_type_dismiss_notifications->maybe_dismiss_notifications( [ 'new_taxonomies' => $new_needs_review ] ); } } /** * Decides if a notification should be added in the notification center. * * @return void */ public function maybe_add_notification() { $notification = $this->notification_center->get_notification_by_id( 'content-types-made-public' ); if ( $notification === null ) { $this->add_notification(); } } /** * Adds a notification to be shown on the next page request since posts are updated in an ajax request. * * @return void */ private function add_notification() { $message = \sprintf( /* translators: 1: Opening tag of the link to the Search appearance settings page, 2: Link closing tag. */ \esc_html__( 'You\'ve added a new type of content. We recommend that you review the corresponding %1$sSearch appearance settings%2$s.', 'wordpress-seo' ), '<a href="' . \esc_url( \admin_url( 'admin.php?page=wpseo_page_settings' ) ) . '">', '</a>' ); $notification = new Yoast_Notification( $message, [ 'type' => Yoast_Notification::WARNING, 'id' => 'content-types-made-public', 'capabilities' => 'wpseo_manage_options', 'priority' => 0.8, ] ); $this->notification_center->add_notification( $notification ); } } content-type-visibility/application/content-type-visibility-dismiss-notifications.php 0000644 00000010522 15025657560 0025547 0 ustar 00 <?php namespace Yoast\WP\SEO\Content_Type_Visibility\Application; use Yoast\WP\SEO\Helpers\Options_Helper; use Yoast_Notification_Center; /** * Handles dismissing notifications and "New" badges for new content types. */ class Content_Type_Visibility_Dismiss_Notifications { /** * Holds the Options_Helper instance. * * @var Options_Helper */ private $options; /** * Constructs Content_Type_Visibility_Dismiss_New_Route. * * @param Options_Helper $options The options helper. */ public function __construct( Options_Helper $options ) { $this->options = $options; } /** * Removes New badge from a post type in the Settings, remove notifications if needed. * * @param string $post_type_name The post type name from the request. * @return array The response. */ public function post_type_dismiss( $post_type_name ) { $success = true; $message = \__( 'Post type is not new.', 'wordpress-seo' ); $post_types_needs_review = $this->options->get( 'new_post_types', [] ); if ( $post_types_needs_review && \in_array( $post_type_name, $post_types_needs_review, true ) ) { $new_needs_review = \array_diff( $post_types_needs_review, [ $post_type_name ] ); $success = $this->options->set( 'new_post_types', $new_needs_review ); $message = ( $success ) ? \__( 'Post type is no longer new.', 'wordpress-seo' ) : \__( 'Error: Post type was not removed from new_post_types list.', 'wordpress-seo' ); if ( $success ) { $this->maybe_dismiss_notifications( [ 'new_post_types' => $new_needs_review ] ); } } $status = ( $success ) ? 200 : 400; return [ 'message' => $message, 'success' => $success, 'status' => $status, ]; } /** * Removes New badge from a taxonomy in the Settings, remove notifications if needed. * * @param string $taxonomy_name The taxonomy name from the request. * @return array The response. */ public function taxonomy_dismiss( $taxonomy_name ) { $success = true; $message = \__( 'Taxonomy is not new.', 'wordpress-seo' ); $taxonomies_needs_review = $this->options->get( 'new_taxonomies', [] ); if ( \in_array( $taxonomy_name, $taxonomies_needs_review, true ) ) { $new_needs_review = \array_diff( $taxonomies_needs_review, [ $taxonomy_name ] ); $success = $this->options->set( 'new_taxonomies', $new_needs_review ); $message = ( $success ) ? \__( 'Taxonomy is no longer new.', 'wordpress-seo' ) : \__( 'Error: Taxonomy was not removed from new_taxonomies list.', 'wordpress-seo' ); if ( $success ) { $this->maybe_dismiss_notifications( [ 'new_taxonomies' => $new_needs_review ] ); } } $status = ( $success ) ? 200 : 400; return [ 'message' => $message, 'success' => $success, 'status' => $status, ]; } /** * Checks if there are new content types or taxonomies. * * @param array $new_content_types The new content types. * @return void */ public function maybe_dismiss_notifications( $new_content_types = [] ) { $post_types_needs_review = ( \array_key_exists( 'new_post_types', $new_content_types ) ) ? $new_content_types['new_post_types'] : $this->options->get( 'new_post_types', [] ); $taxonomies_needs_review = ( \array_key_exists( 'new_taxonomies', $new_content_types ) ) ? $new_content_types['new_taxonomies'] : $this->options->get( 'new_taxonomies', [] ); if ( $post_types_needs_review || $taxonomies_needs_review ) { return; } $this->dismiss_notifications(); } /** * Dismisses the notification in the notification center when there are no more new content types. * * @return bool */ public function dismiss_notifications() { $notification_center = Yoast_Notification_Center::get(); $notification_center->remove_notification_by_id( 'content-types-made-public' ); return $this->options->set( 'show_new_content_type_notification', false ); } /** * Check if there is a new content type to show notification only once in the settings. * * @return bool Should the notification be shown. */ public function maybe_add_settings_notification() { $show_new_content_type_notification = $this->options->get( 'show_new_content_type_notification', false ); if ( $show_new_content_type_notification ) { $this->options->set( 'show_new_content_type_notification', false ); } return $show_new_content_type_notification; } } content-type-visibility/user-interface/content-type-visibility-dismiss-new-route.php 0000644 00000010412 15025657560 0025232 0 ustar 00 <?php namespace Yoast\WP\SEO\Content_Type_Visibility\User_Interface; use WP_REST_Request; use WP_REST_Response; use Yoast\WP\SEO\Conditionals\No_Conditionals; use Yoast\WP\SEO\Content_Type_Visibility\Application\Content_Type_Visibility_Dismiss_Notifications; use Yoast\WP\SEO\Main; use Yoast\WP\SEO\Routes\Route_Interface; /** * Handles the dismiss route for "New" badges of new content types in settings menu. * @phpcs:disable Yoast.NamingConventions.ObjectNameDepth.MaxExceeded */ class Content_Type_Visibility_Dismiss_New_Route implements Route_Interface { use No_Conditionals; /** * Represents the alerts route prefix. * * @var string */ public const ROUTE_PREFIX = 'new-content-type-visibility'; /** * Represents post type dismiss route. * * @var string */ public const POST_TYPE_DISMISS_ROUTE = self::ROUTE_PREFIX . '/dismiss-post-type'; /** * Represents taxonomy dismiss route. * * @var string */ public const TAXONOMY_DISMISS_ROUTE = self::ROUTE_PREFIX . '/dismiss-taxonomy'; /** * Holds the Options_Helper instance. * * @var Content_Type_Visibility_Dismiss_Notifications */ private $dismiss_notifications; /** * Constructs Content_Type_Visibility_Dismiss_New_Route. * * @param Content_Type_Visibility_Dismiss_Notifications $dismiss_notifications The options helper. */ public function __construct( Content_Type_Visibility_Dismiss_Notifications $dismiss_notifications ) { $this->dismiss_notifications = $dismiss_notifications; } /** * Registers routes with WordPress. * * @return void */ public function register_routes() { $post_type_dismiss_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'post_type_dismiss_callback' ], 'permission_callback' => [ $this, 'can_dismiss' ], 'args' => [ 'postTypeName' => [ 'validate_callback' => [ $this, 'validate_post_type' ], ], ], ]; $taxonomy_dismiss_route_args = [ 'methods' => 'POST', 'callback' => [ $this, 'taxonomy_dismiss_callback' ], 'permission_callback' => [ $this, 'can_dismiss' ], 'args' => [ 'taxonomyName' => [ 'validate_callback' => [ $this, 'validate_taxonomy' ], ], ], ]; \register_rest_route( Main::API_V1_NAMESPACE, self::POST_TYPE_DISMISS_ROUTE, $post_type_dismiss_route_args ); \register_rest_route( Main::API_V1_NAMESPACE, self::TAXONOMY_DISMISS_ROUTE, $taxonomy_dismiss_route_args ); } /** * Whether or not the current user is allowed to dismiss alerts. * * @return bool Whether or not the current user is allowed to dismiss alerts. */ public function can_dismiss() { return \current_user_can( 'edit_posts' ); } /** * Validates post type. * * @param string $param The parameter. * @param WP_REST_Request $request Full details about the request. * @param string $key The key. * * @return bool */ public function validate_post_type( $param, $request, $key ) { return \post_type_exists( $param ); } /** * Wrapper method for Content_Type_Visibility_Dismiss_Notifications::post_type_dismiss(). * * @param WP_REST_Request $request The request. This request should have a key param set. * * @return WP_REST_Response The response. */ public function post_type_dismiss_callback( $request ) { $response = $this->dismiss_notifications->post_type_dismiss( $request['post_type_name'] ); return new WP_REST_Response( (object) $response, $response['status'] ); } /** * Validates taxonomy. * * @param string $param The parameter. * @param WP_REST_Request $request Full details about the request. * @param string $key The key. * * @return bool */ public function validate_taxonomy( $param, $request, $key ) { return \taxonomy_exists( $param ); } /** * Wrapper method for Content_Type_Visibility_Dismiss_Notifications::taxonomy_dismiss(). * * @param WP_REST_Request $request The request. This request should have a key param set. * * @return WP_REST_Response The response. */ public function taxonomy_dismiss_callback( WP_REST_Request $request ) { $response = $this->dismiss_notifications->taxonomy_dismiss( $request['taxonomy_name'] ); return new WP_REST_Response( (object) $response, $response['status'] ); } }
| ver. 1.4 |
Github
|
.
| PHP 7.4.33 | Generation time: 0.18 |
proxy
|
phpinfo
|
Settings