indexable_repository = $indexable_repository;
$this->indexable_hierarchy_builder = $indexable_hierarchy_builder;
$this->indexable_hierarchy_repository = $indexable_hierarchy_repository;
$this->indexable_helper = $indexable_helper;
$this->permalink_helper = $permalink_helper;
$this->post_type_helper = $post_type_helper;
}
/**
* Registers the appropriate hooks.
*
* @return void
*/
public function register_hooks() {
\add_action( 'wpseo_save_indexable', [ $this, 'reset_children' ], \PHP_INT_MAX, 2 );
}
/**
* Returns the conditionals based on which this loadable should be active.
*
* @return array
*/
public static function get_conditionals() {
return [ Migrations_Conditional::class ];
}
/**
* If an indexable's permalink has changed, updates its children in the hierarchy table and resets the children's permalink.
*
* @param Indexable $indexable The indexable.
* @param Indexable $indexable_before The old indexable.
*
* @return bool True if the children were reset.
*/
public function reset_children( $indexable, $indexable_before ) {
if ( ! \in_array( $indexable->object_type, [ 'post', 'term' ], true ) ) {
return false;
}
// If the permalink was null it means it was reset instead of changed.
if ( $indexable->permalink === $indexable_before->permalink || $indexable_before->permalink === null ) {
return false;
}
$child_indexable_ids = $this->indexable_hierarchy_repository->find_children( $indexable );
$child_indexables = $this->indexable_repository->find_by_ids( $child_indexable_ids );
\array_walk( $child_indexables, [ $this, 'update_hierarchy_and_permalink' ] );
if ( $indexable->object_type === 'term' ) {
$child_indexables_for_term = $this->get_children_for_term( $indexable->object_id, $child_indexables );
\array_walk( $child_indexables_for_term, [ $this, 'update_hierarchy_and_permalink' ] );
}
return true;
}
/**
* Finds all child indexables for the given term.
*
* @param int $term_id Term to fetch the indexable for.
* @param array $child_indexables The already known child indexables.
*
* @return array The list of additional child indexables for a given term.
*/
public function get_children_for_term( $term_id, array $child_indexables ) {
// Finds object_ids (posts) for the term.
$post_object_ids = $this->get_object_ids_for_term( $term_id, $child_indexables );
// Removes the objects that are already present in the children.
$existing_post_indexables = \array_filter(
$child_indexables,
static function ( $indexable ) {
return $indexable->object_type === 'post';
},
);
$existing_post_object_ids = \wp_list_pluck( $existing_post_indexables, 'object_id' );
$post_object_ids = \array_diff( $post_object_ids, $existing_post_object_ids );
// Finds the indexables for the fetched post_object_ids.
$post_indexables = $this->indexable_repository->find_by_multiple_ids_and_type( $post_object_ids, 'post', false );
// Finds the indexables for the posts that are attached to the term.
$post_indexable_ids = \wp_list_pluck( $post_indexables, 'id' );
$additional_indexable_ids = $this->indexable_hierarchy_repository->find_children_by_ancestor_ids( $post_indexable_ids );
// Makes sure we only have indexable id's that we haven't fetched before.
$additional_indexable_ids = \array_diff( $additional_indexable_ids, $post_indexable_ids );
// Finds the additional indexables.
$additional_indexables = $this->indexable_repository->find_by_ids( $additional_indexable_ids );
// Merges all fetched indexables.
return \array_merge( $post_indexables, $additional_indexables );
}
/**
* Updates the indexable hierarchy and indexable permalink.
*
* @param Indexable $indexable The indexable to update the hierarchy and permalink for.
*
* @return void
*/
protected function update_hierarchy_and_permalink( $indexable ) {
if ( \is_a( $indexable, Indexable::class ) ) {
$this->indexable_hierarchy_builder->build( $indexable );
$indexable->permalink = $this->permalink_helper->get_permalink_for_indexable( $indexable );
$this->indexable_helper->save_indexable( $indexable );
}
}
/**
* Retrieves the object id's for a term based on the term-post relationship.
*
* @param int $term_id The term to get the object id's for.
* @param array $child_indexables The child indexables.
*
* @return array List with object ids for the term.
*/
protected function get_object_ids_for_term( $term_id, $child_indexables ) {
global $wpdb;
$filter_terms = static function ( $child ) {
return $child->object_type === 'term';
};
$child_terms = \array_filter( $child_indexables, $filter_terms );
$child_object_ids = \array_merge( [ $term_id ], \wp_list_pluck( $child_terms, 'object_id' ) );
// Get the term-taxonomy id's for the term and its children.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
$term_taxonomy_ids = $wpdb->get_col(
$wpdb->prepare(
'SELECT term_taxonomy_id
FROM %i
WHERE term_id IN( ' . \implode( ', ', \array_fill( 0, ( \count( $child_object_ids ) ), '%s' ) ) . ' )',
$wpdb->term_taxonomy,
...$child_object_ids,
),
);
// In the case of faulty data having been saved the above query can return 0 results.
if ( empty( $term_taxonomy_ids ) ) {
return [];
}
// Get the (post) object id's that are attached to the term.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching
return $wpdb->get_col(
$wpdb->prepare(
'SELECT DISTINCT object_id
FROM %i
WHERE term_taxonomy_id IN( ' . \implode( ', ', \array_fill( 0, \count( $term_taxonomy_ids ), '%s' ) ) . ' )',
$wpdb->term_relationships,
...$term_taxonomy_ids,
),
);
}
}