his->group_words_by_indexable_id( $candidates_words );
$batch_scores_size = 0;
foreach ( $candidates_words_by_indexable_ids as $id => $candidate_data ) {
$scores[ $id ] = $this->calculate_score_for_indexable( $request_data, $request_vector_length, $candidate_data );
++$batch_scores_size;
}
// Sort the list of scores and keep only the top $limit of the scores.
$scores = $this->get_top_suggestions( $scores, $limit );
++$page;
} while ( $batch_scores_size === $batch_size );
return $scores;
}
/**
* Normalizes the raw score based on the length of the prominent word vectors.
*
* @param float $raw_score The raw (non-normalized) score.
* @param float $vector_length_candidate The vector lengths of the candidate indexable.
* @param float $vector_length_request The vector length of the words from the request.
*
* @return int|float The score, normalized on vector lengths.
*/
protected function normalize_score( $raw_score, $vector_length_candidate, $vector_length_request ) {
$normalizing_factor = ( $vector_length_request * $vector_length_candidate );
if ( $normalizing_factor === 0.0 ) {
// We can't divide by 0, so set the score to 0 instead.
return 0;
}
return ( $raw_score / $normalizing_factor );
}
/**
* Sorts the indexable ids based on the score and returns the top N indexable ids based on a specified limit.
* (Returns all indexable ids if there are less indexable ids than specified by the limit.)
*
* @param array $scores The array matching indexable ids to their scores.
* @param int $limit The maximum number of indexables that should be returned.
*
* @return array The top N indexable ids, sorted from highest to lowest score.
*/
protected function get_top_suggestions( $scores, $limit ) {
// Sort the indexables by descending score.
\uasort(
$scores,
static function ( $score_1, $score_2 ) {
if ( $score_1 === $score_2 ) {
return 0;
}
return ( ( $score_1 < $score_2 ) ? 1 : -1 );
}
);
// Take the top $limit suggestions, while preserving their ids specified in the keys of the array elements.
return \array_slice( $scores, 0, $limit, true );
}
/**
* Gets the singular label of the given combination of object type and sub type.
*
* @param string $object_type An object type. For example 'post' or 'term'.
* @param string $object_sub_type An object sub type. For example 'page' or 'category'.
*
* @return string The singular label of the given combination of object type and sub type,
* or the empty string if the singular label does not exist.
*/
protected function get_sub_type_singular_label( $object_type, $object_sub_type ) {
switch ( $object_type ) {
case 'post':
$post_type = \get_post_type_object( $object_sub_type );
if ( $post_type ) {
return $post_type->labels->singular_name;
}
break;
case 'term':
$taxonomy = \get_taxonomy( $object_sub_type );
if ( $taxonomy ) {
return $taxonomy->labels->singular_name;
}
break;
}
return '';
}
/**
* Creates link suggestion data based on the indexables that should be suggested and the scores for these
* indexables.
*
* @param Indexable[] $indexables The indexables for which to create linking suggestions.
* @param array $scores The scores for the linking suggestions.
*
* @return array The internal linking suggestions.
*/
protected function create_suggestions( $indexables, $scores ) {
$objects = $this->retrieve_object_titles( $indexables );
$link_suggestions = [];
foreach ( $indexables as $indexable ) {
if ( ! \array_key_exists( $indexable->object_type, $objects ) ) {
continue;
}
// Object tied to this indexable. E.g. post, page, term.
if ( ! \array_key_exists( $indexable->object_id, $objects[ $indexable->object_type ] ) ) {
continue;
}
$link_suggestions[] = [
'object_type' => $indexable->object_type,
'id' => (int) ( $indexable->object_id ),
'title' => $objects[ $indexable->object_type ][ $indexable->object_id ]['title'],
'link' => $indexable->permalink,
'isCornerstone' => (bool) $indexable->is_cornerstone,
'labels' => $this->get_labels( $indexable ),
'score' => \round( (float) ( $scores[ $indexable->id ] ), 2 ),
];
}
/*
* Because the request to the indexables table messes up with the ordering of the suggestions,
* we have to sort again.
*/
$this->sort_suggestions_by_field( $link_suggestions, 'score' );
$cornerstone_suggestions = $this->filter_suggestions( $link_suggestions, true );
$non_cornerstone_suggestions = $this->filter_suggestions( $link_suggestions, false );
return \array_merge_recursive( [], $cornerstone_suggestions, $non_cornerstone_suggestions );
}
/**
* Retrieves the labels for the link suggestion.
*
* @param Indexable $indexable The indexable to determine the labels for.
*
* @return array The labels.
*/
protected function get_labels( Indexable $indexable ) {
$labels = [];
if ( $indexable->is_cornerstone ) {
$labels[] = 'cornerstone';
}
$labels[] = $this->get_sub_type_singular_label( $indexable->object_type, $indexable->object_sub_type );
return $labels;
}
/**
* Sorts the given link suggestion by field.
*
* @param array $link_suggestions The link suggestions to sort.
* @param string $field The field to sort suggestions by.
*/
protected function sort_suggestions_by_field( array &$link_suggestions, $field ) {
\usort(
$link_suggestions,
static function ( $suggestion_1, $suggestion_2 ) use ( $field ) {
if ( $suggestion_1[ $field ] === $suggestion_2[ $field ] ) {
return 0;
}
return ( ( $suggestion_1[ $field ] < $suggestion_2[ $field ] ) ? 1 : -1 );
}
);
}
/**
* Filters the suggestions by cornerstone status.
*
* @param array $link_suggestions The suggestions to filter.
* @param bool $cornerstone Whether or not to include the cornerstone suggestions.
*
* @return array The filtered suggestions.
*/
protected function filter_suggestions( $link_suggestions, $cornerstone ) {
return \array_filter(
$link_suggestions,
static function ( $suggestion ) use ( $cornerstone ) {
return (bool) $suggestion['isCornerstone'] === $cornerstone;
}
);
}
}
نقد و بررسیها
هنوز بررسیای ثبت نشده است.