ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } ndexable = $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(); } /** * Returns most recently modified posts of a post type. * * @param string $post_type The post type. * @param int $limit The maximum number of posts to return. * @param bool $exclude_older_than_one_year Whether to exclude posts older than one year. * @param string $search_filter Optional. A search filter to apply to the breadcrumb title. * * @return Indexable[] array of indexables. */ public function get_recently_modified_posts( string $post_type, int $limit, bool $exclude_older_than_one_year, string $search_filter = '' ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->order_by_desc( 'object_last_modified' ) ->limit( $limit ); if ( $exclude_older_than_one_year === true ) { $query->where_gte( 'object_published_at', \gmdate( 'Y-m-d H:i:s', \strtotime( '-1 year' ) ) ); } if ( $search_filter !== '' ) { $query->where_like( 'breadcrumb_title', '%' . $search_filter . '%' ); } $query->order_by_desc( 'object_last_modified' ) ->limit( $limit ); return $query->find_many(); } /** * Returns the most recently modified cornerstone content of a post type. * * @param string $post_type The post type. * @param int|null $limit The maximum number of posts to return. * * @return Indexable[] array of indexables. */ public function get_recent_cornerstone_for_post_type( string $post_type, ?int $limit ) { $query = $this->query() ->where( 'object_type', 'post' ) ->where( 'object_sub_type', $post_type ) ->where_raw( '( is_public IS NULL OR is_public = 1 )' ) ->where( 'is_cornerstone', 1 ) ->order_by_desc( 'object_last_modified' ); if ( $limit !== null ) { $query->limit( $limit ); } 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(); } } > array( 'image' => $this->get_schema_boolean( true ), 'title' => $this->get_schema_boolean( true ), 'price' => $this->get_schema_boolean( true ), 'rating' => $this->get_schema_boolean( true ), 'button' => $this->get_schema_boolean( true ), ), ); } /** * Get the schema for the orderby attribute. * * @return array Property definition of `orderby` attribute. */ protected function get_schema_orderby() { return array( 'type' => 'string', 'enum' => array( 'date', 'popularity', 'price_asc', 'price_desc', 'rating', 'title', 'menu_order' ), 'default' => 'date', ); } /** * Get the block's attributes. * * @param array $attributes Block attributes. Default empty array. * @return array Block attributes merged with defaults. */ protected function parse_attributes( $attributes ) { // These should match what's set in JS `registerBlockType`. $defaults = array( 'columns' => wc_get_theme_support( 'product_blocks::default_columns', 3 ), 'rows' => wc_get_theme_support( 'product_blocks::default_rows', 3 ), 'alignButtons' => false, 'categories' => array(), 'catOperator' => 'any', 'contentVisibility' => array( 'image' => true, 'title' => true, 'price' => true, 'rating' => true, 'button' => true, ), 'stockStatus' => array_keys( wc_get_product_stock_status_options() ), ); return wp_parse_args( $attributes, $defaults ); } /** * Parse query args. * * @return array */ protected function parse_query_args() { // Store the original meta query. $this->meta_query = WC()->query->get_meta_query(); $query_args = array( 'post_type' => 'product', 'post_status' => 'publish', 'fields' => 'ids', 'ignore_sticky_posts' => true, 'no_found_rows' => false, 'orderby' => '', 'order' => '', 'meta_query' => $this->meta_query, // phpcs:ignore WordPress.DB.SlowDBQuery 'tax_query' => array(), // phpcs:ignore WordPress.DB.SlowDBQuery 'posts_per_page' => $this->get_products_limit(), ); $this->set_block_query_args( $query_args ); $this->set_ordering_query_args( $query_args ); $this->set_categories_query_args( $query_args ); $this->set_visibility_query_args( $query_args ); $this->set_stock_status_query_args( $query_args ); return $query_args; } /** * Parse query args. * * @param array $query_args Query args. */ protected function set_ordering_query_args( &$query_args ) { if ( isset( $this->attributes['orderby'] ) ) { if ( 'price_desc' === $this->attributes['orderby'] ) { $query_args['orderby'] = 'price'; $query_args['order'] = 'DESC'; } elseif ( 'price_asc' === $this->attributes['orderby'] ) { $query_args['orderby'] = 'price'; $query_args['order'] = 'ASC'; } elseif ( 'date' === $this->attributes['orderby'] ) { $query_args['orderby'] = 'date'; $query_args['order'] = 'DESC'; } else { $query_args['orderby'] = $this->attributes['orderby']; } } $query_args = array_merge( $query_args, WC()->query->get_catalog_ordering_args( $query_args['orderby'], $query_args['order'] ) ); } /** * Set args specific to this block * * @param array $query_args Query args. */ abstract protected function set_block_query_args( &$query_args ); /** * Set categories query args. * * @param array $query_args Query args. */ protected function set_categories_query_args( &$query_args ) { if ( ! empty( $this->attributes['categories'] ) ) { $categories = array_map( 'absint', $this->attributes['categories'] ); $query_args['tax_query'][] = array( 'taxonomy' => 'product_cat', 'terms' => $categories, 'field' => 'term_id', 'operator' => 'all' === $this->attributes['catOperator'] ? 'AND' : 'IN', /* * When cat_operator is AND, the children categories should be excluded, * as only products belonging to all the children categories would be selected. */ 'include_children' => 'all' === $this->attributes['catOperator'] ? false : true, ); } } /** * Set visibility query args. * * @param array $query_args Query args. */ protected function set_visibility_query_args( &$query_args ) { $product_visibility_terms = wc_get_product_visibility_term_ids(); $product_visibility_not_in = array( $product_visibility_terms['exclude-from-catalog'] ); if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) { $product_visibility_not_in[] = $product_visibility_terms[ ProductStockStatus::OUT_OF_STOCK ]; } $query_args['tax_query'][] = array( 'taxonomy' => 'product_visibility', 'field' => 'term_taxonomy_id', 'terms' => $product_visibility_not_in, 'operator' => 'NOT IN', ); } /** * Set which stock status to use when displaying products. * * @param array $query_args Query args. * @return void */ protected function set_stock_status_query_args( &$query_args ) { $stock_statuses = array_keys( wc_get_product_stock_status_options() ); // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query if ( isset( $this->attributes['stockStatus'] ) && $stock_statuses !== $this->attributes['stockStatus'] ) { // Reset meta_query then update with our stock status. $query_args['meta_query'] = $this->meta_query; $query_args['meta_query'][] = array( 'key' => '_stock_status', 'value' => array_merge( [ '' ], $this->attributes['stockStatus'] ), 'compare' => 'IN', ); } else { $query_args['meta_query'] = $this->meta_query; } // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query } /** * Works out the item limit based on rows and columns, or returns default. * * @return int */ protected function get_products_limit() { if ( isset( $this->attributes['rows'], $this->attributes['columns'] ) && ! empty( $this->attributes['rows'] ) ) { $this->attributes['limit'] = intval( $this->attributes['columns'] ) * intval( $this->attributes['rows'] ); } return intval( $this->attributes['limit'] ); } /** * Run the query and return an array of product IDs * * @return array List of product IDs */ protected function get_products() { /** * Filters whether or not the product grid is cacheable. * * @param boolean $is_cacheable The list of script dependencies. * @param array $query_args Query args for the products query passed to BlocksWpQuery. * @return array True to enable cache, false to disable cache. * * @since 2.5.0 */ $is_cacheable = (bool) apply_filters( 'woocommerce_blocks_product_grid_is_cacheable', true, $this->query_args ); $transient_version = \WC_Cache_Helper::get_transient_version( 'product_query' ); $query = new BlocksWpQuery( $this->query_args ); $results = wp_parse_id_list( $is_cacheable ? $query->get_cached_posts( $transient_version ) : $query->get_posts() ); // Remove ordering query arguments which may have been added by get_catalog_ordering_args. WC()->query->remove_ordering_args(); // Prime caches to reduce future queries. Note _prime_post_caches is private--we could replace this with our own // query if it becomes unavailable. if ( is_callable( '_prime_post_caches' ) ) { _prime_post_caches( $results ); } $this->prime_product_variations( $results ); return $results; } /** * Retrieve IDs that are not already present in the cache. * * Based on WordPress function: _get_non_cached_ids * * @param int[] $product_ids Array of IDs. * @param string $cache_key The cache bucket to check against. * @return int[] Array of IDs not present in the cache. */ protected function get_non_cached_ids( $product_ids, $cache_key ) { $non_cached_ids = array(); $cache_values = wp_cache_get_multiple( $product_ids, $cache_key ); foreach ( $cache_values as $id => $value ) { if ( ! $value ) { $non_cached_ids[] = (int) $id; } } return $non_cached_ids; } /** * Prime query cache of product variation meta data. * * Prepares values in the product_ID_variation_meta_data cache for later use in the ProductSchema::get_variations() * method. Doing so here reduces the total number of queries needed. * * @param int[] $product_ids Product ids to prime variation cache for. */ protected function prime_product_variations( $product_ids ) { $cache_group = 'product_variation_meta_data'; $prime_product_ids = $this->get_non_cached_ids( wp_parse_id_list( $product_ids ), $cache_group ); if ( ! $prime_product_ids ) { return; } global $wpdb; // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared $product_variations = $wpdb->get_results( "SELECT ID as variation_id, post_parent as product_id from {$wpdb->posts} WHERE post_parent IN ( " . implode( ',', $prime_product_ids ) . ' )', ARRAY_A ); $prime_variation_ids = array_column( $product_variations, 'variation_id' ); $variation_ids_by_parent = array_column( $product_variations, 'product_id', 'variation_id' ); if ( empty( $prime_variation_ids ) ) { return; } $all_variation_meta_data = $wpdb->get_results( $wpdb->prepare( "SELECT post_id as variation_id, meta_key as attribute_key, meta_value as attribute_value FROM {$wpdb->postmeta} WHERE post_id IN (" . implode( ',', array_map( 'esc_sql', $prime_variation_ids ) ) . ') AND meta_key LIKE %s', $wpdb->esc_like( 'attribute_' ) . '%' ) ); // phpcs:enable // Prepare the data to cache by indexing by the parent product. $primed_data = array_reduce( $all_variation_meta_data, function( $values, $data ) use ( $variation_ids_by_parent ) { $values[ $variation_ids_by_parent[ $data->variation_id ] ?? 0 ][] = $data; return $values; }, array_fill_keys( $prime_product_ids, [] ) ); // Cache everything. foreach ( $primed_data as $product_id => $variation_meta_data ) { wp_cache_set( $product_id, [ 'last_modified' => get_the_modified_date( 'U', $product_id ), 'data' => $variation_meta_data, ], $cache_group ); } } /** * Get the list of classes to apply to this block. * * @return string space-separated list of classes. */ protected function get_container_classes() { $classes = array( 'wc-block-grid', "wp-block-{$this->block_name}", "wp-block-woocommerce-{$this->block_name}", "wc-block-{$this->block_name}", "has-{$this->attributes['columns']}-columns", ); if ( $this->attributes['rows'] > 1 ) { $classes[] = 'has-multiple-rows'; } if ( isset( $this->attributes['align'] ) ) { $classes[] = "align{$this->attributes['align']}"; } if ( ! empty( $this->attributes['alignButtons'] ) ) { $classes[] = 'has-aligned-buttons'; } if ( ! empty( $this->attributes['className'] ) ) { $classes[] = $this->attributes['className']; } return implode( ' ', $classes ); } /** * Render a single products. * * @param \WC_Product $product Product object. * @return string Rendered product output. */ protected function render_product( $product ) { $data = (object) array( 'permalink' => esc_url( $product->get_permalink() ), 'image' => $this->get_image_html( $product ), 'title' => $this->get_title_html( $product ), 'rating' => $this->get_rating_html( $product ), 'price' => $this->get_price_html( $product ), 'badge' => $this->get_sale_badge_html( $product ), 'button' => $this->get_button_html( $product ), ); /** * Filters the HTML for products in the grid. * * @param string $html Product grid item HTML. * @param array $data Product data passed to the template. * @param \WC_Product $product Product object. * @return string Updated product grid item HTML. * * @since 2.2.0 */ return apply_filters( 'woocommerce_blocks_product_grid_item_html', "
  • permalink}\" class=\"wc-block-grid__product-link\"> {$data->badge} {$data->image} {$data->title} {$data->price} {$data->rating} {$data->button}
  • ", $data, $product ); } /** * Get the product image. * * @param \WC_Product $product Product. * @return string */ protected function get_image_html( $product ) { if ( array_key_exists( 'image', $this->attributes['contentVisibility'] ) && false === $this->attributes['contentVisibility']['image'] ) { return ''; } $attr = array( 'alt' => '', ); if ( $product->get_image_id() ) { $image_alt = get_post_meta( $product->get_image_id(), '_wp_attachment_image_alt', true ); $attr = array( 'alt' => ( $image_alt ? $image_alt : $product->get_name() ), ); } return '
    ' . $product->get_image( 'woocommerce_thumbnail', $attr ) . '
    '; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped } /** * Get the product title. * * @param \WC_Product $product Product. * @return string */ protected function get_title_html( $product ) { if ( empty( $this->attributes['contentVisibility']['title'] ) ) { return ''; } return '
    ' . wp_kses_post( $product->get_title() ) . '
    '; } /** * Render the rating icons. * * @param WC_Product $product Product. * @return string Rendered product output. */ protected function get_rating_html( $product ) { if ( empty( $this->attributes['contentVisibility']['rating'] ) ) { return ''; } $rating_count = $product->get_rating_count(); $average = $product->get_average_rating(); if ( $rating_count > 0 ) { return sprintf( '
    %s
    ', wc_get_rating_html( $average, $rating_count ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ); } return ''; } /** * Get the price. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_price_html( $product ) { if ( empty( $this->attributes['contentVisibility']['price'] ) ) { return ''; } return sprintf( '
    %s
    ', wp_kses_post( $product->get_price_html() ) ); } /** * Get the sale badge. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_sale_badge_html( $product ) { if ( empty( $this->attributes['contentVisibility']['price'] ) ) { return ''; } if ( empty( $this->attributes['contentVisibility']['image'] ) ) { return ''; } if ( ! $product->is_on_sale() ) { return; } return '
    ' . esc_html__( 'Product on sale', 'woocommerce' ) . '
    '; } /** * Get the button. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_button_html( $product ) { if ( empty( $this->attributes['contentVisibility']['button'] ) ) { return ''; } return '
    ' . $this->get_add_to_cart( $product ) . '
    '; } /** * Get the "add to cart" button. * * @param \WC_Product $product Product. * @return string Rendered product output. */ protected function get_add_to_cart( $product ) { $attributes = array( 'aria-label' => $product->add_to_cart_description(), 'data-quantity' => '1', 'data-product_id' => $product->get_id(), 'data-product_sku' => $product->get_sku(), 'data-price' => wc_get_price_to_display( $product ), 'rel' => 'nofollow', 'class' => 'wp-block-button__link ' . ( function_exists( 'wc_wp_theme_get_element_class_name' ) ? wc_wp_theme_get_element_class_name( 'button' ) : '' ) . ' add_to_cart_button', ); if ( $product->supports( 'ajax_add_to_cart' ) && $product->is_purchasable() && ( $product->is_in_stock() || $product->backorders_allowed() ) ) { $attributes['class'] .= ' ajax_add_to_cart'; } /** * Filter to manipulate (add/modify/remove) attributes in the HTML code of the generated add to cart button. * * @since 8.6.0 * * @param array $attributes An associative array containing default HTML attributes of the add to cart button. * @param WC_Product $product The WC_Product instance of the product that will be added to the cart once the button is pressed. * * @return array Returns an associative array derived from the default array passed as an argument and added the extra HTML attributes. */ $attributes = apply_filters( 'woocommerce_blocks_product_grid_add_to_cart_attributes', $attributes, $product ); return sprintf( '%s', esc_url( $product->add_to_cart_url() ), wc_implode_html_attributes( $attributes ), esc_html( $product->add_to_cart_text() ) ); } /** * Extra data passed through from server to client for block. * * @param array $attributes Any attributes that currently are available from the block. * Note, this will be empty in the editor context when the block is * not in the post content on editor load. */ protected function enqueue_data( array $attributes = [] ) { parent::enqueue_data( $attributes ); $this->asset_data_registry->add( 'minColumns', wc_get_theme_support( 'product_blocks::min_columns', 1 ) ); $this->asset_data_registry->add( 'maxColumns', wc_get_theme_support( 'product_blocks::max_columns', 6 ) ); $this->asset_data_registry->add( 'defaultColumns', wc_get_theme_support( 'product_blocks::default_columns', 3 ) ); $this->asset_data_registry->add( 'minRows', wc_get_theme_support( 'product_blocks::min_rows', 1 ) ); $this->asset_data_registry->add( 'maxRows', wc_get_theme_support( 'product_blocks::max_rows', 6 ) ); $this->asset_data_registry->add( 'defaultRows', wc_get_theme_support( 'product_blocks::default_rows', 3 ) ); } /** * Get the frontend style handle for this block type. * * @return string[] */ protected function get_block_type_style() { // Currently these blocks rely on the styles from the All Products block. return [ 'wc-blocks-style', 'wc-blocks-style-all-products' ]; } }
    Fatal error: Uncaught Error: Class "Automattic\WooCommerce\Blocks\BlockTypes\AbstractProductGrid" not found in /htdocs/wp-content/plugins/woocommerce/src/Blocks/BlockTypes/HandpickedProducts.php:9 Stack trace: #0 /htdocs/wp-content/plugins/woocommerce/vendor/jetpack-autoloader/class-php-autoloader.php(102): require() #1 /htdocs/wp-content/plugins/woocommerce/src/Blocks/BlockTypesController.php(118): Automattic\Jetpack\Autoloader\jp66cd040767dcc115cc8018307ec36356\al5_0_0\PHP_Autoloader::load_class('Automattic\\WooC...') #2 /htdocs/wp-includes/class-wp-hook.php(341): Automattic\WooCommerce\Blocks\BlockTypesController->register_blocks('') #3 /htdocs/wp-includes/class-wp-hook.php(365): WP_Hook->apply_filters(NULL, Array) #4 /htdocs/wp-includes/plugin.php(522): WP_Hook->do_action(Array) #5 /htdocs/wp-settings.php(743): do_action('init') #6 /htdocs/wp-config.php(99): require_once('/htdocs/wp-sett...') #7 /htdocs/wp-load.php(50): require_once('/htdocs/wp-conf...') #8 /htdocs/wp-blog-header.php(13): require_once('/htdocs/wp-load...') #9 /htdocs/index.php(17): require('/htdocs/wp-blog...') #10 {main} thrown in /htdocs/wp-content/plugins/woocommerce/src/Blocks/BlockTypes/HandpickedProducts.php on line 9