Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sync with A8C forked version #92

Merged
merged 27 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
974abf2
Add VIP Search adapter
nickdaugherty May 15, 2020
0b39d7f
Merge pull request #1 from Automattic/update/merge-upstream
nickdaugherty May 15, 2020
0649c9f
Merge branch 'master' into update/add-vip-search-adapter
nickdaugherty May 15, 2020
f15f5dc
Merge pull request #2 from Automattic/update/add-vip-search-adapter
nickdaugherty May 15, 2020
4f8462c
Fix tag taxonomy name in fields mapping
nickdaugherty May 18, 2020
8e5869d
Merge pull request #3 from Automattic/fix/vip-search-adapter-fixes
nickdaugherty Jun 11, 2020
244c038
Account for a new total format in es67
srtfisher Jun 5, 2020
d0dc7d0
Use raw post type for post_type field
nickdaugherty Jun 12, 2020
1741c24
Merge pull request #4 from Automattic/fix/post_type-in-vip-search-map…
nickdaugherty Jun 15, 2020
39e0096
Use the lowercase version of term names
nickdaugherty Jun 25, 2020
f804b03
User lowercase meta values
nickdaugherty Jun 25, 2020
3dec381
Merge pull request #5 from Automattic/update/lowercase-term-and-meta-…
nickdaugherty Jun 25, 2020
d7dc843
Ensure we can return just the _source fields if desired
Feb 18, 2019
ad0b31a
Return total hits by default
nickdaugherty Sep 9, 2020
f925eba
Merge pull request #6 from Automattic/update/get-total-hits-from-es
nickdaugherty Sep 9, 2020
6644e1b
Use raw field for post title in adapter
Sep 17, 2020
d263ba4
Merge pull request #7 from Automattic/update/use_raw_field_for_post_t…
Sep 17, 2020
5918926
Use raw field for post name in adapter
t-wright Sep 21, 2020
7f661cf
Merge pull request #8 from Automattic/t-wright/use-raw-field-for-post…
Sep 21, 2020
dc95da4
Fix is_main_query() not registering when hooked onto pre_get_posts
rebeccahum Oct 5, 2020
a809688
Merge pull request #9 from Automattic/vip/fix_is_main_query
nickdaugherty Oct 5, 2020
d660b99
Use login field instead of display_name for cases where user's displa…
rebeccahum Oct 16, 2020
cc291c1
Use .raw because it's not analyzed
rebeccahum Oct 20, 2020
2442e57
Merge pull request #10 from Automattic/reb/use_login_for_mapping
nickdaugherty Oct 20, 2020
fa4ce71
Merge branch 'main' of https://github.com/alleyinteractive/es-wp-quer…
rebeccahum Sep 27, 2021
8bcce18
Merge pull request #12 from Automattic/alleyinteractive-main
rebeccahum Sep 29, 2021
05a254e
Pull from mu-plugins
rebeccahum Nov 19, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
296 changes: 296 additions & 0 deletions adapters/vip-search.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
/**
* ES_WP_Query adapters: VIP Search adapter
*
* @package ES_WP_Query
*/

// phpcs:disable Generic.Classes.DuplicateClassName.Found

/**
* An adapter for VIP Search.
*/
class ES_WP_Query extends ES_WP_Query_Wrapper {

/**
* Implements the abstract function query_es from ES_WP_Query_Wrapper.
*
* @param array $es_args Arguments to pass to the Elasticsearch server.
* @access protected
* @return array The response from the Elasticsearch server.
*/
protected function query_es( $es_args ) {
if ( class_exists( '\\Automattic\\VIP\\Search\\Search' ) ) {
$vip_search = \Automattic\VIP\Search\Search::instance();

if ( method_exists( $vip_search, 'query_es' ) ) {
$result = $vip_search->query_es( 'post', $es_args );

return $result;
}
}
}

/**
* Sets the posts array to the list of found post IDs.
*
* @param array $q Query arguments.
* @param array|WP_Error $es_response Response from VIP Search.
* @access protected
*/
protected function set_posts( $q, $es_response ) {
$this->posts = array();
if ( ! is_wp_error( $es_response ) && isset( $es_response['documents'] ) ) {
switch ( $q['fields'] ) {
case 'ids':
foreach ( $es_response['documents'] as $hit ) {
$post_id = (array) $hit[ $this->es_map( 'post_id' ) ];
$this->posts[] = reset( $post_id );
}
return;

case 'id=>parent':
foreach ( $es_response['documents'] as $hit ) {
$post_id = (array) $hit[ $this->es_map( 'post_id' ) ];
$post_parent = (array) $hit[ $this->es_map( 'post_parent' ) ];
$this->posts[ reset( $post_id ) ] = reset( $post_parent );
}
return;

default:
if ( apply_filters( 'es_query_use_source', false ) ) {
$this->posts = wp_list_pluck( $es_response['documents'], '_source' );
return;
} else {
$post_ids = array();
foreach ( $es_response['documents'] as $hit ) {
$post_id = (array) $hit[ $this->es_map( 'post_id' ) ];
$post_ids[] = absint( reset( $post_id ) );
}
$post_ids = array_filter( $post_ids );
if ( ! empty( $post_ids ) ) {
global $wpdb;
$post__in = implode( ',', $post_ids );
$this->posts = $wpdb->get_results( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN ($post__in) ORDER BY FIELD( {$wpdb->posts}.ID, $post__in )" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.VIP.DirectDatabaseQuery.NoCaching, WordPress.VIP.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery
}
return;
}
}
} else {
$this->posts = array();
}
}

/**
* Set up the amount of found posts and the number of pages (if limit clause was used)
* for the current query.
*
* @param array $q Query arguments.
* @param array|WP_Error $es_response The response from the Elasticsearch server.
* @access public
*/
public function set_found_posts( $q, $es_response ) {
if ( ! is_wp_error( $es_response ) && isset( $es_response['found_documents']['value'] ) ) {
$this->found_posts = absint( $es_response['found_documents']['value'] );
} else {
$this->found_posts = 0;
}
$this->found_posts = apply_filters_ref_array( 'es_found_posts', array( $this->found_posts, &$this ) );
$this->max_num_pages = ceil( $this->found_posts / $q['posts_per_page'] );
}
}

/**
* Maps Elasticsearch DSL keys to their VIP-specific naming conventions.
*
* @param array $es_map Additional fields to map.
* @return array The final field mapping.
*/
function vip_es_field_map( $es_map ) {
return wp_parse_args(
array(
'post_author' => 'post_author.id',
'post_author.user_nicename' => 'post_author.login.raw',
'post_date' => 'post_date',
'post_date.year' => 'date_terms.year',
'post_date.month' => 'date_terms.month',
'post_date.week' => 'date_terms.week',
'post_date.day' => 'date_terms.day',
'post_date.day_of_year' => 'date_terms.dayofyear',
'post_date.day_of_week' => 'date_terms.dayofweek',
'post_date.hour' => 'date_terms.hour',
'post_date.minute' => 'date_terms.minute',
'post_date.second' => 'date_terms.second',
'post_date_gmt' => 'post_date_gmt',
'post_date_gmt.year' => 'date_gmt_terms.year',
'post_date_gmt.month' => 'date_gmt_terms.month',
'post_date_gmt.week' => 'date_gmt_terms.week',
'post_date_gmt.day' => 'date_gmt_terms.day',
'post_date_gmt.day_of_year' => 'date_gmt_terms.day_of_year',
'post_date_gmt.day_of_week' => 'date_gmt_terms.day_of_week',
'post_date_gmt.hour' => 'date_gmt_terms.hour',
'post_date_gmt.minute' => 'date_gmt_terms.minute',
'post_date_gmt.second' => 'date_gmt_terms.second',
'post_content' => 'post_content',
'post_content.analyzed' => 'post_content',
'post_title' => 'post_title.raw',
'post_title.analyzed' => 'post_title',
'post_type' => 'post_type.raw',
'post_excerpt' => 'post_excerpt',
'post_password' => 'post_password', // This isn't indexed on VIP.
'post_name' => 'post_name.raw',
'post_modified' => 'post_modified',
'post_modified.year' => 'modified_date_terms.year',
'post_modified.month' => 'modified_date_terms.month',
'post_modified.week' => 'modified_date_terms.week',
'post_modified.day' => 'modified_date_terms.day',
'post_modified.day_of_year' => 'modified_date_terms.day_of_year',
'post_modified.day_of_week' => 'modified_date_terms.day_of_week',
'post_modified.hour' => 'modified_date_terms.hour',
'post_modified.minute' => 'modified_date_terms.minute',
'post_modified.second' => 'modified_date_terms.second',
'post_modified_gmt' => 'post_modified_gmt',
'post_modified_gmt.year' => 'modified_date_gmt_terms.year',
'post_modified_gmt.month' => 'modified_date_gmt_terms.month',
'post_modified_gmt.week' => 'modified_date_gmt_terms.week',
'post_modified_gmt.day' => 'modified_date_gmt_terms.day',
'post_modified_gmt.day_of_year' => 'modified_date_gmt_terms.day_of_year',
'post_modified_gmt.day_of_week' => 'modified_date_gmt_terms.day_of_week',
'post_modified_gmt.hour' => 'modified_date_gmt_terms.hour',
'post_modified_gmt.minute' => 'modified_date_gmt_terms.minute',
'post_modified_gmt.second' => 'modified_date_gmt_terms.second',
'post_parent' => 'post_parent',
'menu_order' => 'menu_order',
'post_mime_type' => 'post_mime_type',
'comment_count' => 'comment_count',
'post_meta' => 'meta.%s.value.sortable',
'post_meta.analyzed' => 'meta.%s.value',
'post_meta.long' => 'meta.%s.long',
'post_meta.double' => 'meta.%s.double',
'post_meta.binary' => 'meta.%s.boolean',
'term_id' => 'terms.%s.term_id',
'term_slug' => 'terms.%s.slug',
'term_name' => 'terms.%s.name.sortable',
'category_id' => 'terms.category.term_id',
'category_slug' => 'terms.category.slug',
'category_name' => 'terms.category.name.sortable',
'tag_id' => 'terms.post_tag.term_id',
'tag_slug' => 'terms.post_tag.slug',
'tag_name' => 'terms.post_tag.name.sortable',
),
$es_map
);
}
add_filter( 'es_field_map', 'vip_es_field_map' );

/**
* Returns the lowercase version of a meta value.
*
* @param mixed $meta_value The meta value.
* @param string $meta_key The meta key.
* @param string $meta_compare The comparison operation.
* @param string $meta_type The type of meta (post, user, term, etc).
* @return mixed If value is a string, returns the lowercase version. Otherwise, returns the original value, unmodified.
*/
function vip_es_meta_value_tolower( $meta_value, $meta_key, $meta_compare, $meta_type ) {
if ( ! is_string( $meta_value ) || empty( $meta_value ) ) {
return $meta_value;
}
return strtolower( $meta_value );
}
add_filter( 'es_meta_query_meta_value', 'vip_es_meta_value_tolower', 10, 4 );

/**
* Normalise term name to lowercase as we are mapping that against the "sortable" field, which is a lowercased keyword.
*
* @param string|mixed $term Term's name which should be normalised to
* lowercase.
* @param string $taxonomy Taxonomy of the term.
* @return mixed If $term is a string, lowercased string is returned. Otherwise
* original value is return unchanged.
*/
function vip_es_term_name_slug_tolower( $term, $taxonomy ) {
if ( ! is_string( $term ) || empty( $term ) ) {
return $term;
}
return strtolower( $term );
}
add_filter( 'es_tax_query_term_name', 'vip_es_term_name_slug_tolower', 10, 2 );

/**
* Advanced Post Cache and es-wp-query do not work well together. In
* particular, the WP_Query->found_posts attribute gets corrupted when using
* both of these plugins, so here we disable Advanced Post Cache completely
* when queries are being made using Elasticsearch.
*
* On the other hand, if a non-Elasticsearch query is run, and we disabled
* Advanced Post Cache earlier, we enable it again, to make use of its caching
* features.
*
* Note that this applies only to calls done via WP_Query(), and not
* ES_WP_Query()
*
* @param WP_Query|ES_WP_Query|ES_WP_Query_Wrapper $query The query to examine.
*/
function vip_es_disable_advanced_post_cache( &$query ) {
global $advanced_post_cache_object;

static $disabled_apc = false;

if ( empty( $advanced_post_cache_object ) || ! is_object( $advanced_post_cache_object ) ) {
return;
}

/*
* These two might be passsed to us; we only
* handle WP_Query, so ignore these.
*/
if (
( $query instanceof ES_WP_Query_Wrapper ) ||
( $query instanceof ES_WP_Query )
) {
return;
}

if ( $query->get( 'es' ) ) {
if ( true === $disabled_apc ) {
// Already disabled, don't try again.
return;
}

/*
* An Elasticsearch-enabled query is being run. Disable Advanced Post Cache
* entirely.
*
* Note that there is one action-hook that is not deactivated: The switch_blog
* action is not deactivated, because it might be called in-between
* Elasticsearch-enabled query, and a non-Elasticsearch query, and because it
* does not have an effect on WP_Query()-results directly.
*/

remove_filter( 'posts_request', array( $advanced_post_cache_object, 'posts_request' ) );
remove_filter( 'posts_results', array( $advanced_post_cache_object, 'posts_results' ) );

remove_filter( 'post_limits_request', array( $advanced_post_cache_object, 'post_limits_request' ), 999 );

remove_filter( 'found_posts_query', array( $advanced_post_cache_object, 'found_posts_query' ) );
remove_filter( 'found_posts', array( $advanced_post_cache_object, 'found_posts' ) );

$disabled_apc = true;
} else {
// A non-ES query.
if ( true === $disabled_apc ) {
/*
* Earlier, we disabled Advanced Post Cache
* entirely, but now a non-Elasticsearch query is
* being run, and in such cases it might be useful
* to have the Cache enabled. Here we enable
* it again.
*/
$advanced_post_cache_object->__construct();

$disabled_apc = false;
}
}
}
add_action( 'pre_get_posts', 'vip_es_disable_advanced_post_cache', -100 );
3 changes: 3 additions & 0 deletions class-es-wp-query-wrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,9 @@ public function get_posts() {
$size = apply_filters( 'es_query_max_results', 1000 );
$this->es_args['size'] = $size;
}

// ES > 7.0 doesn't return the actual total hits by default (capped at 10k), but we need accurate counts
$this->es_args[ 'track_total_hits' ] = true;

if ( ! $q['suppress_filters'] ) {
$this->es_args = apply_filters_ref_array( 'es_posts_request', array( $this->es_args, &$this ) );
Expand Down
2 changes: 1 addition & 1 deletion class-es-wp-tax-query.php
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ private function clean_query( &$query ) {
return;
}

$query['terms'] = array_unique( (array) $query['terms'] );
$query['terms'] = array_values( array_unique( (array) $query['terms'] ) );

if ( is_taxonomy_hierarchical( $query['taxonomy'] ) && $query['include_children'] ) {
$this->transform_query( $query, 'term_id' );
Expand Down
4 changes: 2 additions & 2 deletions functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ function es_get_posts( $args = null ) {
/**
* Loads one of the included adapters.
*
* @param string $adapter Which adapter to include. Currently allows searchpress, wpcom-vip, travis and jetpack-search.
* @param string $adapter Which adapter to include. Currently allows searchpress, wpcom-vip, travis, jetpack-search, and vip-search.
* @return void
*/
function es_wp_query_load_adapter( $adapter ) {
if ( in_array( $adapter, array( 'searchpress', 'wpcom-vip', 'travis', 'jetpack-search' ), true ) ) {
if ( in_array( $adapter, array( 'searchpress', 'wpcom-vip', 'travis', 'jetpack-search', 'vip-search' ), true ) ) {
require_once ES_WP_QUERY_PATH . "/adapters/{$adapter}.php";
}
}