Файловый менеджер - Редактировать - /home/freeclou/app.optimyar.com/front-web/build/assets/resources/agGrid/builders.tar
Назад
indexable-author-builder.php 0000755 00000017117 15111617422 0012146 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 ); } } indexable-builder.php 0000755 00000032031 15111617422 0010636 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 } indexable-date-archive-builder.php 0000755 00000003227 15111617422 0013175 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; } } indexable-hierarchy-builder.php 0000755 00000026361 15111617422 0012623 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 ); } } indexable-home-page-builder.php 0000755 00000010542 15111617422 0012501 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 } } indexable-link-builder.php 0000755 00000044056 15111617422 0011603 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 ); /** * Fires to signal that incoming link counts for related indexables were updated. * * @param int[] $related_indexable_ids The related indexable Ids to this link change. * * @internal */ \do_action( 'wpseo_related_indexables_incoming_links_updated', $related_indexable_ids ); 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; } } } indexable-post-builder.php 0000755 00000030307 15111617422 0011625 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; } } indexable-post-type-archive-builder.php 0000755 00000012324 15111617422 0014222 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 } } indexable-social-image-trait.php 0000755 00000011504 15111617422 0012665 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; } } indexable-system-page-builder.php 0000755 00000004150 15111617422 0013073 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; } } indexable-term-builder.php 0000755 00000021030 15111617422 0011600 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 } } primary-term-builder.php 0000755 00000005222 15111617422 0011335 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 ); } }
| ver. 1.4 |
Github
|
.
| PHP 8.1.33 | Генерация страницы: 0.01 |
proxy
|
phpinfo
|
Настройка