get_post_type_default_settings( $post_type );
$weight = isset( $post_type_settings[ $key ] ) && isset( $post_type_settings[ $key ]['weight'] ) ? (int) $post_type_settings[ $key ]['weight'] : 0;
$range_disabled = '';
$enabled = (
isset( $post_type_settings ) &&
isset( $post_type_settings[ $key ] ) &&
isset( $post_type_settings[ $key ]['enabled'] )
)
? boolval( $post_type_settings[ $key ]['enabled'] ) : false;
if ( ! $enabled ) {
$range_disabled = 'disabled="disabled" ';
$weight = 0;
}
?>
save_weighting_configuration( $_POST );
$redirect_url = admin_url( 'admin.php?page=elasticpress-weighting' );
$redirect_url = add_query_arg( 'settings-updated', true, $redirect_url );
$this->redirect( $redirect_url );
}
/**
* We need this method to test handle_save properly.
*
* @param string $redirect_url Redirect URL.
*/
protected function redirect( $redirect_url ) {
// @codeCoverageIgnoreStart
wp_safe_redirect( $redirect_url );
exit();
// @codeCoverageIgnoreEnd
}
/**
* Save weighting configuration for each searchable post_type
*
* @param array $settings weighting settings
*
* @return array final settings
* @since 3.4.1
*/
public function save_weighting_configuration( $settings ) {
$new_config = array();
$previous_config_formatted = array();
$current_config = $this->get_weighting_configuration();
foreach ( $current_config as $post_type => $post_type_weighting ) {
// This also ensures the string is safe, since this would return false otherwise
if ( ! post_type_exists( $post_type ) ) {
continue;
}
// We need a way to know if fields have been explicitly set before, let's compare a previous state against $_POST['weighting']
foreach ( $post_type_weighting as $weighting_field => $weighting_values ) {
$previous_config_formatted[ $post_type ][ sanitize_text_field( $weighting_field ) ] = [
'weight' => isset( $settings['weighting'][ $post_type ][ $weighting_field ]['weight'] ) ? intval( $settings['weighting'][ $post_type ][ $weighting_field ]['weight'] ) : 0,
'enabled' => isset( $settings['weighting'][ $post_type ][ $weighting_field ]['enabled'] ) && 'on' === $settings['weighting'][ $post_type ][ $weighting_field ]['enabled'] ? true : false,
];
}
}
$search = Features::factory()->get_registered_feature( 'search' );
$post_types = $search->get_searchable_post_types();
foreach ( $post_types as $post_type ) {
// This also ensures the string is safe, since this would return false otherwise
if ( ! post_type_exists( $post_type ) ) {
continue;
}
/** override default post_type settings while saving */
$new_config[ $post_type ] = array();
if ( isset( $settings['weighting'][ $post_type ] ) ) {
foreach ( $settings['weighting'][ $post_type ] as $weighting_field => $weighting_values ) {
$new_config[ $post_type ][ sanitize_text_field( $weighting_field ) ] = [
'weight' => isset( $weighting_values['weight'] ) ? intval( $weighting_values['weight'] ) : 0,
'enabled' => isset( $weighting_values['enabled'] ) && 'on' === $weighting_values['enabled'] ? true : false,
];
}
}
}
$final_config = array_replace_recursive( $previous_config_formatted, $new_config );
update_option( 'elasticpress_weighting', $final_config );
/**
* Fires right after the weighting configuration is saved.
*
* @since 3.5.x
* @hook ep_saved_weighting_configuration
*/
do_action( 'ep_saved_weighting_configuration' );
return $final_config;
}
/**
* Iterates through arrays in the formatted args to find "fields" and injects weighting values
*
* @param array $fieldset Current subset of formatted ES args
* @param array $weights Weight configuration
*/
public function recursively_inject_weights_to_fields( &$fieldset, $weights ) {
if ( ! is_array( $fieldset ) ) {
return;
}
if ( is_array( $fieldset ) && isset( $fieldset['fields'] ) ) {
// Add any fields to the search that aren't already in there (weighting handled in next step)
foreach ( $weights as $field => $settings ) {
if ( ! in_array( $field, $fieldset['fields'], true ) ) {
$fieldset['fields'][] = $field;
}
}
foreach ( $fieldset['fields'] as $key => $field ) {
if ( isset( $weights[ $field ] ) && false !== $weights[ $field ]['enabled'] ) {
$weight = (int) $weights[ $field ]['weight'];
if ( 0 !== $weight ) {
if ( 'author_name' === $field ) {
$field = 'post_author.display_name';
}
/**
* Filter fields and their weitghting as used in the Elasticsearch query.
*
* @hook ep_query_weighting_fields
* @param {string} $weighted_field The field and its weight as used in the ES query.
* @param {string} $field Field name
* @param {string} $weight Weight value
* @param {array} $fieldset Current subset of formatted ES args
* @param {array} $weights Weight configuration
* @return {array} New weighted field string
*
* @since 3.5.5
*/
$fieldset['fields'][ $key ] = apply_filters(
'ep_query_weighting_fields',
"{$field}^{$weight}",
$field,
$weight,
$fieldset,
$weights
);
}
} else {
// this handles removing post_author.login field added in Post::format_args() if author search field has being disabled
if ( 'author_name' === $field ) {
$author_key = array_search( 'post_author.login', $fieldset['fields'], true );
if ( false !== $author_key ) {
unset( $fieldset['fields'][ $author_key ] );
}
}
unset( $fieldset['fields'][ $key ] );
}
// else: Leave anything that isn't explicitly disabled alone. Could have been added by search_fields, and if it is not present in the UI, we shouldn't touch it here
// If fieldset has fuzziness enabled and fuzziness is disabled for this field, unset the field
if ( isset( $fieldset['fuzziness'] ) && $fieldset['fuzziness'] && isset( $weights[ $field ]['fuzziness'] ) && false === $weights[ $field ]['fuzziness'] ) {
unset( $fieldset['fields'][ $key ] );
}
}
// Reindex the array
$fieldset['fields'] = array_values( $fieldset['fields'] );
} else {
foreach ( $fieldset as &$field ) {
$this->recursively_inject_weights_to_fields( $field, $weights );
}
}
// Most likely to occur with the ordering results not being allowed in fuzzy, and weighting turning off fields for this otherwise
if ( isset( $fieldset['fields'] ) && empty( $fieldset['fields'] ) ) {
$fieldset = null;
}
}
/**
* Determine if a post type has any fields enabled for search
*
* @param string $post_type Post Type
* @param array $args WP_Query args
* @return boolean true/false depending on any fields enabled == true
*/
public function post_type_has_fields( $post_type, $args = [] ) {
// define keys which are irrelevant for this consideration
/**
* Filter fields considered in weighting
*
* @hook ep_weighting_ignore_fields_in_consideration
* @param {array} $fields Current fields
* @return {array} New fields
*/
$ignore_keys = apply_filters( 'ep_weighting_ignore_fields_in_consideration', [ 'terms.ep_custom_result.name' => true ] );
$weight_config = $this->get_weighting_configuration();
/**
* Filter weighting configuration for search
*
* @hook ep_weighting_configuration_for_search
* @param {array} $weight_config Current weight config
* @param {array} $args WP Query arguments
* @return {array} New configutation
*/
$weight_config = apply_filters( 'ep_weighting_configuration_for_search', $weight_config, $args );
if ( ! isset( $weight_config[ $post_type ] ) ) {
$weights = $this->get_post_type_default_settings( $post_type );
} else {
$weights = $weight_config[ $post_type ];
}
$fields = array_diff_key( $weights, $ignore_keys );
$found_enabled = false;
foreach ( $fields as $field ) {
if ( filter_var( $field['enabled'], FILTER_VALIDATE_BOOLEAN ) ) {
$found_enabled = true;
break;
}
}
return $found_enabled;
}
/**
* Adjusts the query for configured weighting values
*
* @param array $formatted_args Formatted ES args
* @param array $args WP_Query args
*
* @return array Formatted ES args
*/
public function do_weighting( $formatted_args, $args ) {
/**
* If search fields is set on the query, we should use those instead of the weighting, since the query was
* overridden by some custom code
*/
if ( isset( $args['search_fields'] ) && ! empty( $args['search_fields'] ) ) {
return $formatted_args;
}
$weight_config = $this->get_weighting_configuration();
/**
* Filter weighting configuration for search
*
* @hook ep_weighting_configuration_for_search
* @param {array} $weight_config Current weight config
* @param {array} $args WP Query arguments
* @return {array} New configutation
*/
$weight_config = apply_filters( 'ep_weighting_configuration_for_search', $weight_config, $args );
if ( Utils\is_integrated_request( 'weighting', [ 'public', 'rest' ] ) && ! empty( $args['s'] ) ) {
/*
* This section splits up the single query clause for all post types into separate nested clauses (one for each post type)
* which then get combined into one result set. By having separate clauses for each post type, we can then
* weight fields such as post_title per post type so that we can have fine grained control over weights by post
* type, rather than globally on the query
*/
$new_query = [
'bool' => [
'should' => [],
],
];
// grab the query and keep track of whether or not it is nested in a function score
$function_score = isset( $formatted_args['query']['function_score'] );
$query = $function_score ? $formatted_args['query']['function_score']['query'] : $formatted_args['query'];
foreach ( (array) $args['post_type'] as $post_type ) {
if ( false === $this->post_type_has_fields( $post_type, $args ) ) {
continue;
}
// Copy the query, so we can set specific weight values
$current_query = $query;
if ( isset( $weight_config[ $post_type ] ) ) {
// Find all "fields" values and inject weights for the current post type
$this->recursively_inject_weights_to_fields( $current_query, $weight_config[ $post_type ] );
} else {
// Use the default values for the post type
$this->recursively_inject_weights_to_fields( $current_query, $this->get_post_type_default_settings( $post_type ) );
}
// Check for any segments with null fields from recursively_inject function and remove them
if ( isset( $current_query['bool'] ) && isset( $current_query['bool']['should'] ) ) {
foreach ( $current_query['bool']['should'] as $index => $current_bool_should ) {
if ( isset( $current_bool_should['multi_match'] ) && null === $current_bool_should['multi_match'] ) {
unset( $current_query['bool']['should'][ $index ] );
}
}
}
/**
* Filter weighting query for a post type
*
* @hook ep_weighted_query_for_post_type
* @param {array} $query Weighting query
* @param {string} $post_type Post type
* @param {array} $args WP Query arguments
* @return {array} New query
*/
$new_query['bool']['should'][] = apply_filters(
'ep_weighted_query_for_post_type',
[
'bool' => [
'must' => [
$current_query,
],
'filter' => [
[
'match' => [
'post_type.raw' => $post_type,
],
],
],
],
],
$post_type,
$args
);
}
// put the new query back in the correct location
if ( $function_score ) {
$formatted_args['query']['function_score']['query'] = $new_query;
} else {
$formatted_args['query'] = $new_query;
}
/**
* Hook after weighting is added to Elasticsearch query
*
* @hook ep_weighting_added
* @param {array} $formatted_args Elasticsearch query
* @param {array} $args WP Query arguments
*/
do_action( 'ep_weighting_added', $formatted_args, $args );
}
return $formatted_args;
}
/**
* Adjust weighting when the type is cross_fields, as it just works with weight = 1.
*
* @since 4.0.0
*
* @param string $weighted_field The field and its weight as used in the ES query.
* @param string $field Field name
* @param string $weight Weight value
* @param array $fieldset Current subset of formatted ES args
* @return array New weighted field string
*/
public function adjust_weight_for_cross_fields( $weighted_field, $field, $weight, $fieldset ) {
if ( ! empty( $fieldset['type'] ) && 'cross_fields' === $fieldset['type'] ) {
$weighted_field = "{$field}^1";
}
return $weighted_field;
}
}