ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } } ay( $indexable->id, $this->saved_ancestors, true ) ) { return true; } $this->saved_ancestors[] = $indexable->id; return false; } /** * Saves the ancestors. * * @param Indexable $indexable The indexable. * * @return void */ private function save_ancestors( $indexable ) { if ( empty( $indexable->ancestors ) ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, 0, 0 ); return; } $depth = \count( $indexable->ancestors ); foreach ( $indexable->ancestors as $ancestor ) { $this->indexable_hierarchy_repository->add_ancestor( $indexable->id, $ancestor->id, $depth ); --$depth; } } /** * Adds ancestors for a post. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $post_id The post id, this is the id of the post currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_post( $indexable_id, $post_id, &$parents ) { $post = $this->post->get_post( $post_id ); if ( ! isset( $post->post_parent ) ) { return; } if ( $post->post_parent !== 0 && $this->post->get_post( $post->post_parent ) !== null ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $post->post_parent, 'post' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_post( $indexable_id, $ancestor->object_id, $parents ); return; } $primary_term_id = $this->find_primary_term_id_for_post( $post ); if ( $primary_term_id === 0 ) { return; } $ancestor = $this->indexable_repository->find_by_id_and_type( $primary_term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { return; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; $this->add_ancestors_for_term( $indexable_id, $ancestor->object_id, $parents ); } /** * Adds ancestors for a term. * * @param int $indexable_id The indexable id, this is the id of the original indexable. * @param int $term_id The term id, this is the id of the term currently being evaluated. * @param int[] $parents The indexable IDs of all parents. * * @return void */ private function add_ancestors_for_term( $indexable_id, $term_id, &$parents = [] ) { $term = \get_term( $term_id ); $term_parents = $this->get_term_parents( $term ); foreach ( $term_parents as $parent ) { $ancestor = $this->indexable_repository->find_by_id_and_type( $parent->term_id, 'term' ); if ( $this->is_invalid_ancestor( $ancestor, $indexable_id, $parents ) ) { continue; } $parents[ $this->get_indexable_id( $ancestor ) ] = $ancestor; } } /** * Gets the primary term ID for a post. * * @param WP_Post $post The post. * * @return int The primary term ID. 0 if none exists. */ private function find_primary_term_id_for_post( $post ) { $main_taxonomy = $this->options->get( 'post_types-' . $post->post_type . '-maintax' ); if ( ! $main_taxonomy || $main_taxonomy === '0' ) { return 0; } $primary_term_id = $this->get_primary_term_id( $post->ID, $main_taxonomy ); if ( $primary_term_id ) { $term = \get_term( $primary_term_id ); if ( $term !== null && ! \is_wp_error( $term ) ) { return $primary_term_id; } } $terms = \get_the_terms( $post->ID, $main_taxonomy ); if ( ! \is_array( $terms ) || empty( $terms ) ) { return 0; } return $this->find_deepest_term_id( $terms ); } /** * Find the deepest term in an array of term objects. * * @param array $terms Terms set. * * @return int The deepest term ID. */ private function find_deepest_term_id( $terms ) { /* * Let's find the deepest term in this array, by looping through and then * unsetting every term that is used as a parent by another one in the array. */ $terms_by_id = []; foreach ( $terms as $term ) { $terms_by_id[ $term->term_id ] = $term; } foreach ( $terms as $term ) { unset( $terms_by_id[ $term->parent ] ); } /* * As we could still have two subcategories, from different parent categories, * let's pick the one with the lowest ordered ancestor. */ $parents_count = -1; $term_order = 9999; // Because ASC. $deepest_term = \reset( $terms_by_id ); foreach ( $terms_by_id as $term ) { $parents = $this->get_term_parents( $term ); $new_parents_count = \count( $parents ); if ( $new_parents_count < $parents_count ) { continue; } $parent_order = 9999; // Set default order. foreach ( $parents as $parent ) { if ( $parent->parent === 0 && isset( $parent->term_order ) ) { $parent_order = $parent->term_order; } } // Check if parent has lowest order. if ( $new_parents_count > $parents_count || $parent_order < $term_order ) { $term_order = $parent_order; $deepest_term = $term; } $parents_count = $new_parents_count; } return $deepest_term->term_id; } /** * Get a term's parents. * * @param WP_Term $term Term to get the parents for. * * @return WP_Term[] An array of all this term's parents. */ private function get_term_parents( $term ) { $tax = $term->taxonomy; $parents = []; while ( (int) $term->parent !== 0 ) { $term = \get_term( $term->parent, $tax ); $parents[] = $term; } return $parents; } /** * Checks if an ancestor is valid to add. * * @param Indexable $ancestor The ancestor (presumed indexable) to check. * @param int $indexable_id The indexable id we're adding ancestors for. * @param int[] $parents The indexable ids of the parents already added. * * @return bool */ private function is_invalid_ancestor( $ancestor, $indexable_id, $parents ) { // If the ancestor is not an Indexable, it is invalid by default. if ( ! \is_a( $ancestor, 'Yoast\WP\SEO\Models\Indexable' ) ) { return true; } // Don't add ancestors if they're unindexed, already added or the same as the main object. if ( $ancestor->post_status === 'unindexed' ) { return true; } $ancestor_id = $this->get_indexable_id( $ancestor ); if ( \array_key_exists( $ancestor_id, $parents ) ) { return true; } if ( $ancestor_id === $indexable_id ) { return true; } return false; } /** * Returns the ID for an indexable. Catches situations where the id is null due to errors. * * @param Indexable $indexable The indexable. * * @return string|int A unique ID for the indexable. */ private function get_indexable_id( Indexable $indexable ) { if ( $indexable->id === 0 ) { return "{$indexable->object_type}:{$indexable->object_id}"; } return $indexable->id; } /** * Returns the primary term id of a post. * * @param int $post_id The post ID. * @param string $main_taxonomy The main taxonomy. * * @return int The ID of the primary term. */ private function get_primary_term_id( $post_id, $main_taxonomy ) { $primary_term = $this->primary_term_repository->find_by_post_id_and_taxonomy( $post_id, $main_taxonomy, false ); if ( $primary_term ) { return $primary_term->term_id; } return \get_post_meta( $post_id, WPSEO_Meta::$meta_prefix . 'primary_' . $main_taxonomy, true ); } }
Fatal error: Uncaught Error: Class "Yoast\WP\SEO\Builders\Indexable_Hierarchy_Builder" not found in /htdocs/wp-content/plugins/wordpress-seo/src/generated/container.php:1629 Stack trace: #0 /htdocs/wp-content/plugins/wordpress-seo/src/generated/container.php(1568): Yoast\WP\SEO\Generated\Cached_Container->getIndexableHierarchyBuilderService() #1 /htdocs/wp-content/plugins/wordpress-seo/src/generated/container.php(5278): Yoast\WP\SEO\Generated\Cached_Container->getIndexableBuilderService() #2 /htdocs/wp-content/plugins/wordpress-seo/vendor_prefixed/symfony/dependency-injection/Container.php(271): Yoast\WP\SEO\Generated\Cached_Container->getIndexableRepositoryService() #3 /htdocs/wp-content/plugins/wordpress-seo/src/surfaces/classes-surface.php(38): YoastSEO_Vendor\Symfony\Component\DependencyInjection\Container->get('Yoast\\WP\\SEO\\Re...') #4 /htdocs/wp-content/plugins/wordpress-seo/inc/class-wpseo-admin-bar-menu.php(132): Yoast\WP\SEO\Surfaces\Classes_Surface->get('Yoast\\WP\\SEO\\Re...') #5 /htdocs/wp-content/plugins/wordpress-seo/inc/wpseo-non-ajax-functions.php(20): WPSEO_Admin_Bar_Menu->__construct() #6 /htdocs/wp-includes/class-wp-hook.php(324): wpseo_initialize_admin_bar('') #7 /htdocs/wp-includes/class-wp-hook.php(348): WP_Hook->apply_filters(NULL, Array) #8 /htdocs/wp-includes/plugin.php(517): WP_Hook->do_action(Array) #9 /htdocs/wp-settings.php(722): do_action('wp_loaded') #10 /htdocs/wp-config.php(90): require_once('/htdocs/wp-sett...') #11 /htdocs/wp-load.php(50): require_once('/htdocs/wp-conf...') #12 /htdocs/wp-blog-header.php(13): require_once('/htdocs/wp-load...') #13 /htdocs/index.php(17): require('/htdocs/wp-blog...') #14 {main} thrown in /htdocs/wp-content/plugins/wordpress-seo/src/generated/container.php on line 1629