Skip to content

Commit

Permalink
Merge branch 'fix/post-tag-duplication' of github.com:10up/ElasticPre…
Browse files Browse the repository at this point in the history
…ss into fix/post-tag-duplication
  • Loading branch information
oscarssanchez committed Sep 23, 2021
2 parents 9546e1e + 0e7adc3 commit ffacb69
Show file tree
Hide file tree
Showing 19 changed files with 357 additions and 232 deletions.
6 changes: 3 additions & 3 deletions includes/classes/Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -1314,7 +1314,7 @@ private function index_occurring() {
}

if ( $dashboard_syncing || $wpcli_syncing ) {
WP_CLI::error( esc_html__( 'An index is already occuring. Try again later.', 'elasticpress' ) );
WP_CLI::error( esc_html__( 'An index is already occurring. Try again later.', 'elasticpress' ) );
}
}

Expand All @@ -1336,7 +1336,7 @@ private function reset_transient( $items_indexed, $total_items, $slug ) {
}

/**
* Delete transient that indicates indexing is occuring
* Delete transient that indicates indexing is occurring
*
* @since 3.1
*/
Expand Down Expand Up @@ -1531,7 +1531,7 @@ public function stop_indexing( $args, $assoc_args ) {
if ( empty( \ElasticPress\Utils\get_indexing_status() ) ) {
WP_CLI::warning( esc_html__( 'There is no indexing operation running.', 'elasticpress' ) );
} else {
WP_CLI::line( esc_html__( 'Stoping indexing...', 'elasticpress' ) );
WP_CLI::line( esc_html__( 'Stopping indexing...', 'elasticpress' ) );

if ( isset( $indexing_status['method'] ) && 'cli' === $indexing_status['method'] ) {
set_transient( 'ep_wpcli_sync_interrupted', true, 5 );
Expand Down
148 changes: 104 additions & 44 deletions includes/classes/Feature/Autosuggest/Autosuggest.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,57 +237,117 @@ public function set_fuzziness( $fuzziness, $search_fields, $args ) {
* @return array $query adjusted ES Query arguments
*/
public function adjust_fuzzy_fields( $query, $post_type, $args ) {
if ( Utils\is_integrated_request( $this->slug, [ 'public' ] ) && ! empty( $args['s'] ) ) {
/**
* Filter autosuggest ngram fields
*
* @hook ep_autosuggest_ngram_fields
* @param {array} $fields Fields available to ngram
* @return {array} New fields array
*/
$ngram_fields = apply_filters(
'ep_autosuggest_ngram_fields',
[
'post_title' => 'post_title.suggest',
]
);
if ( ! Utils\is_integrated_request( $this->slug, [ 'public' ] ) || empty( $args['s'] ) ) {
return $query;
}

if ( isset( $query['bool'] ) && isset( $query['bool']['must'] ) ) {
foreach ( $query['bool']['must'] as $q_index => $must_query ) {
if ( isset( $must_query['bool'] ) && isset( $must_query['bool']['should'] ) ) {
foreach ( $must_query['bool']['should'] as $index => $current_bool_should ) {
if (
isset( $current_bool_should['multi_match'] ) &&
isset( $current_bool_should['multi_match']['fields'] ) &&
(
(
isset( $current_bool_should['multi_match']['fuzziness'] ) &&
0 !== $current_bool_should['multi_match']['fuzziness']
) ||
(
isset( $current_bool_should['multi_match']['slop'] ) &&
0 !== $current_bool_should['multi_match']['slop']
)
)
) {
foreach ( $current_bool_should['multi_match']['fields'] as $key => $field ) {
foreach ( $ngram_fields as $plain_field => $ngram_field ) {
if ( preg_match( '/^(' . $plain_field . ')(\^(\d+))?$/', $field, $match ) ) {
if ( isset( $match[3] ) && $match[3] > 1 ) {
$weight = $match[3] - 1;
} else {
$weight = 1;
}
$query['bool']['must'][ $q_index ]['bool']['should'][ $index ]['multi_match']['fields'][] = $ngram_field . '^' . $weight;
}
}
}
if ( ! isset( $query['bool'] ) || ! isset( $query['bool']['must'] ) ) {
return $query;
}

/**
* Filter autosuggest ngram fields
*
* @hook ep_autosuggest_ngram_fields
* @param {array} $fields Fields available to ngram
* @return {array} New fields array
*/
$ngram_fields = apply_filters(
'ep_autosuggest_ngram_fields',
[
'post_title' => 'post_title.suggest',
'terms\.(.+)\.name' => 'term_suggest',
]
);

/**
* At this point, `$query` might look like this (using the 3.5 search algorithm):
*
* [
* [bool] => [
* [must] => [
* [0] => [
* [bool] => [
* [should] => [
* [0] => [
* [multi_match] => [
* [query] => ep_autosuggest_placeholder
* [type] => phrase
* [fields] => [
* [0] => post_title^1
* ...
* [n] => terms.category.name^27
* ]
* [boost] => 3
* ]
* ]
* [1] => [
* [multi_match] => [
* [query] => ep_autosuggest_placeholder
* [fields] => [ ... ]
* [type] => phrase
* [slop] => 5
* ]
* ]
* ]
* ]
* ]
* ]
* ]
* ...
* ]
*
* Also, note the usage of `&$must_query`. This means that by changing `$must_query`
* you will be actually changing `$query`.
*/
foreach ( $query['bool']['must'] as &$must_query ) {
if ( ! isset( $must_query['bool'] ) || ! isset( $must_query['bool']['should'] ) ) {
continue;
}
foreach ( $must_query['bool']['should'] as &$current_bool_should ) {
if ( ! isset( $current_bool_should['multi_match'] ) || ! isset( $current_bool_should['multi_match']['fields'] ) ) {
continue;
}

/**
* `fuzziness` is used in the original algorithm.
* `slop` is used in `3.5`.
*
* @see \ElasticPress\Indexable\Post\Post::format_args()
*/
if ( empty( $current_bool_should['multi_match']['fuzziness'] ) && empty( $current_bool_should['multi_match']['slop'] ) ) {
continue;
}

$fields_to_add = [];

/**
* If the regex used in `$ngram_fields` matches more than one field,
* like taxonomies, for example, we use the min value - 1.
*/
foreach ( $current_bool_should['multi_match']['fields'] as $field ) {
foreach ( $ngram_fields as $regex => $ngram_field ) {
if ( preg_match( '/^(' . $regex . ')(\^(\d+))?$/', $field, $match ) ) {
$weight = 1;
if ( isset( $match[4] ) && $match[4] > 1 ) {
$weight = $match[4] - 1;
}

if ( isset( $fields_to_add[ $ngram_field ] ) ) {
$fields_to_add[ $ngram_field ] = min( $fields_to_add[ $ngram_field ], $weight );
} else {
$fields_to_add[ $ngram_field ] = $weight;
}
}
}
}

foreach ( $fields_to_add as $field => $weight ) {
$current_bool_should['multi_match']['fields'][] = "{$field}^{$weight}";
}
}
}

return $query;
}

Expand Down
2 changes: 1 addition & 1 deletion includes/classes/Feature/Search/Search.php
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ public function output_feature_box_long() {
*/
public function integrate_search_queries( $enabled, $query ) {
if ( ! Utils\is_integrated_request( $this->slug ) ) {
return;
return false;
}

if ( ! is_a( $query, 'WP_Query' ) ) {
Expand Down
128 changes: 76 additions & 52 deletions includes/classes/Indexable/Post/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -288,58 +288,7 @@ public function determine_mapping_version() {
return false;
}

$version = 'unknown';

if ( isset( $mapping[ $index ]['mappings']['post']['_meta']['mapping_version'] ) ) {
$version = $mapping[ $index ]['mappings']['post']['_meta']['mapping_version'];
} elseif ( isset( $mapping[ $index ]['mappings']['_meta']['mapping_version'] ) ) {
$version = $mapping[ $index ]['mappings']['_meta']['mapping_version'];
}

// mapping does not have meta value set - use legacy detection
if ( 'unknown' === $version ) {

// check for pre-5-0 mapping
if ( isset( $mapping[ $index ]['mappings']['post']['properties']['post_name']['fields']['raw']['ignore_above'] ) ) {
$val = $mapping[ $index ]['mappings']['post']['properties']['post_name']['fields']['raw']['ignore_above'];
if ( ! $val || 10922 !== $val ) {
$version = 'pre-5-0.php';
} elseif ( $val && 10922 === $val ) {
$version = 'not-pre-5-0';
}
}

// check for 5-0 mapping
if ( 'not-pre-5-0' === $version ) {
if ( isset( $mapping[ $index ]['mappings']['post']['properties']['post_content_filtered']['fields'] ) ) {
$version = '5-0.php';
} else {
$version = 'not-5-0';
}
}

// check for 5-2 mapping
if ( 'not-5-0' === $version ) {
if ( isset( $mapping[ $index ]['mappings']['post']['_all'] ) ) {
$version = '5-2.php';
} else {
$version = 'not-5-2';
}
}

// check for 7-0 mapping
if ( 'not-5-2' === $version ) {
if ( isset( $mapping[ $index ]['settings']['index.max_shingle_diff'] ) ) {
$version = '7-0.php';
} else {
$version = 'not-7-0';
}
}

if ( preg_match( '/^not-.*/', $version ) ) {
$version = 'unknown';
}
}
$version = $this->determine_mapping_version_based_on_existing( $mapping, $index );

/**
* Filter the mapping version for posts.
Expand Down Expand Up @@ -2018,4 +1967,79 @@ protected function get_orderby_array( $orderbys ) {

return $orderbys;
}

/**
* Given a mapping content, try to determine the version used.
*
* @since 3.6.3
*
* @param array $mapping Mapping content.
* @param string $index Index name
* @return string Version of the mapping being used.
*/
protected function determine_mapping_version_based_on_existing( $mapping, $index ) {
if ( isset( $mapping[ $index ]['mappings']['post']['_meta']['mapping_version'] ) ) {
return $mapping[ $index ]['mappings']['post']['_meta']['mapping_version'];
}
if ( isset( $mapping[ $index ]['mappings']['_meta']['mapping_version'] ) ) {
return $mapping[ $index ]['mappings']['_meta']['mapping_version'];
}

/**
* Check for 7-0 mapping.
* If mapping has a `post` type, it can't be ES 7, as mapping types were removed in that release.
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html
*/
if ( ! isset( $mapping[ $index ]['mappings']['post'] ) ) {
return '7-0.php';
}

$post_mapping = $mapping[ $index ]['mappings']['post'];

/**
* Starting at this point, our tests rely on the post_title.fields.sortable field.
* As this field is present in all our mappings, if this field is not present in
* the mapping, this is a custom mapping.
*
* To have this code working with custom mappings, use the `ep_post_mapping_version_determined` filter.
*/
if ( ! isset( $post_mapping['properties']['post_title']['fields']['sortable'] ) ) {
return 'unknown';
}

$post_title_sortable = $post_mapping['properties']['post_title']['fields']['sortable'];

/**
* Check for 5-2 mapping.
* Normalizers on keyword fields were only made available in ES 5.2
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.2/release-notes-5.2.0.html
*/
if ( isset( $post_title_sortable['normalizer'] ) ) {
return '5-2.php';
}

/**
* Check for 5-0 mapping.
* `keyword` fields were only made available in ES 5.0
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.0/release-notes-5.0.0.html
*/
if ( 'keyword' === $post_title_sortable['type'] ) {
return '5-0.php';
}

/**
* Check for pre-5-0 mapping.
* `string` fields were deprecated in ES 5.0 in favor of text/keyword
*
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.0/release-notes-5.0.0.html
*/
if ( 'string' === $post_title_sortable['type'] ) {
return 'pre-5-0.php';
}

return 'unknown';
}
}
2 changes: 1 addition & 1 deletion includes/classes/Indexable/Term/Term.php
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ public function format_args( $query_vars ) {
/**
* Support `parent` query var.
*/
if ( ! empty( $query_vars['parent'] ) ) {
if ( isset( $query_vars['parent'] ) && '' !== $query_vars['parent'] ) {
$filter['bool']['must'][]['bool']['must'] = [
'term' => [
'parent' => (int) $query_vars['parent'],
Expand Down
37 changes: 37 additions & 0 deletions tests/php/indexables/TestTerm.php
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,43 @@ public function testTermQueryOrderParent() {
$this->assertTrue( $term_query->terms[0]->term_id > $term_query->terms[ count( $term_query->terms ) - 1 ]->term_id );
}

/**
* Test a term query parent
*
* @since 3.6.3
* @group term
*/
public function testTermQueryParent() {
$parent_term_id = Functions\create_and_sync_term( 'parent-term-no-post', 'Parent Category', 'Parent/Child Terms', 'category' );
$child_term_id = Functions\create_and_sync_term( 'child-term-post', 'Child Category', 'Parent/Child Terms', 'category', [], $parent_term_id );

$term_query = new \WP_Term_Query(
[
'hide_empty' => false,
'taxonomy' => 'category',
'parent' => $parent_term_id,
'ep_integrate' => true,
]
);

$this->assertTrue( $term_query->elasticsearch_success );
$this->assertCount( 1, $term_query->terms );
$this->assertSame( $term_query->terms[0]->term_id, $child_term_id );

$term_query = new \WP_Term_Query(
[
'hide_empty' => false,
'taxonomy' => 'category',
'parent' => 0,
'ep_integrate' => true,
]
);

$this->assertTrue( $term_query->elasticsearch_success );
$this->assertCount( 2, $term_query->terms ); // "Uncategorized" is also returned in this case.
$this->assertContains( $parent_term_id, wp_list_pluck( $term_query->terms, 'term_id' ) );
}

/**
* Test a term query hide_empty
*
Expand Down
Loading

0 comments on commit ffacb69

Please sign in to comment.