mirror of
https://git.sindominio.net/estibadores/wordpress.git
synced 2024-11-23 02:51:06 +01:00
573 lines
18 KiB
PHP
573 lines
18 KiB
PHP
<?php
|
|
/**
|
|
* /lib/search-query-restrictions.php
|
|
*
|
|
* Responsible for converting query parameters to MySQL query restrictions.
|
|
*
|
|
* @package Relevanssi
|
|
* @author Mikko Saari
|
|
* @license https://wordpress.org/about/gpl/ GNU General Public License
|
|
* @see https://www.relevanssi.com/
|
|
*/
|
|
|
|
/**
|
|
* Processes the arguments to create the query restrictions.
|
|
*
|
|
* All individual parts are tested.
|
|
*
|
|
* @param array $args The query arguments.
|
|
*
|
|
* @return array An array containing `query_restriction` and `query_join`.
|
|
*/
|
|
function relevanssi_process_query_args( $args ) {
|
|
$query_restrictions = '';
|
|
$query_join = '';
|
|
$query = '';
|
|
$query_no_synonyms = '';
|
|
|
|
$phrase_query_restrictions = array(
|
|
'and' => '',
|
|
'or' => array(),
|
|
);
|
|
|
|
if ( function_exists( 'wp_encode_emoji' ) ) {
|
|
$query = wp_encode_emoji( $args['q'] );
|
|
$query_no_synonyms = wp_encode_emoji( $args['q_no_synonyms'] );
|
|
}
|
|
|
|
if ( $args['sentence'] ) {
|
|
$query = str_replace( array( '"', '“', '”' ), '', $query );
|
|
$query = '"' . $query . '"';
|
|
}
|
|
|
|
if ( is_array( $args['tax_query'] ) ) {
|
|
$query_restrictions .= relevanssi_process_tax_query( $args['tax_query_relation'], $args['tax_query'] );
|
|
}
|
|
|
|
if ( is_array( $args['post_query'] ) ) {
|
|
$query_restrictions .= relevanssi_process_post_query( $args['post_query'] );
|
|
}
|
|
|
|
if ( is_array( $args['parent_query'] ) ) {
|
|
$query_restrictions .= relevanssi_process_parent_query( $args['parent_query'] );
|
|
}
|
|
|
|
if ( is_array( $args['meta_query'] ) ) {
|
|
$processed_meta = relevanssi_process_meta_query( $args['meta_query'] );
|
|
$query_restrictions .= $processed_meta['where'];
|
|
$query_join .= $processed_meta['join'];
|
|
}
|
|
|
|
if ( $args['date_query'] instanceof WP_Date_Query ) {
|
|
$query_restrictions .= relevanssi_process_date_query( $args['date_query'] );
|
|
}
|
|
|
|
if ( $args['expost'] ) {
|
|
$query_restrictions .= relevanssi_process_expost( $args['expost'] );
|
|
}
|
|
|
|
if ( $args['author'] ) {
|
|
$query_restrictions .= relevanssi_process_author( $args['author'] );
|
|
}
|
|
|
|
if ( $args['by_date'] ) {
|
|
$query_restrictions .= relevanssi_process_by_date( $args['by_date'] );
|
|
}
|
|
|
|
$phrases = relevanssi_recognize_phrases( $query, $args['operator'] );
|
|
if ( $phrases ) {
|
|
$phrase_query_restrictions = $phrases;
|
|
}
|
|
|
|
if ( $args['post_type'] || $args['include_attachments'] ) {
|
|
$query_restrictions .= relevanssi_process_post_type(
|
|
$args['post_type'],
|
|
$args['admin_search'],
|
|
$args['include_attachments']
|
|
);
|
|
}
|
|
|
|
if ( $args['post_status'] ) {
|
|
$query_restrictions .= relevanssi_process_post_status( $args['post_status'] );
|
|
}
|
|
|
|
return array(
|
|
'query_restrictions' => $query_restrictions,
|
|
'query_join' => $query_join,
|
|
'query_query' => $query,
|
|
'query_no_synonyms' => $query_no_synonyms,
|
|
'phrase_queries' => $phrase_query_restrictions,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Processes the 'in' and 'not in' parameters to MySQL query restrictions.
|
|
*
|
|
* Checks that the parameters are integers and formulates a MySQL query restriction
|
|
* from them. If the same posts are both included and excluded, exclusion will take
|
|
* precedence.
|
|
*
|
|
* Tested.
|
|
*
|
|
* @param array $post_query An array where included posts are in $post_query['in']
|
|
* and excluded posts are in $post_query['not in'].
|
|
*
|
|
* @return string MySQL query restrictions matching the array.
|
|
*/
|
|
function relevanssi_process_post_query( $post_query ) {
|
|
$query_restrictions = '';
|
|
$valid_exclude_values = array();
|
|
if ( ! empty( $post_query['not in'] ) ) {
|
|
foreach ( $post_query['not in'] as $post_not_in_id ) {
|
|
if ( is_numeric( $post_not_in_id ) ) {
|
|
$valid_exclude_values[] = $post_not_in_id;
|
|
}
|
|
}
|
|
$posts = implode( ',', $valid_exclude_values );
|
|
if ( ! empty( $posts ) ) {
|
|
$query_restrictions .= " AND relevanssi.doc NOT IN ($posts)";
|
|
// Clean: $posts is checked to be integers.
|
|
}
|
|
}
|
|
if ( ! empty( $post_query['in'] ) ) {
|
|
$valid_values = array();
|
|
foreach ( $post_query['in'] as $post_in_id ) {
|
|
if ( is_numeric( $post_in_id ) ) {
|
|
$valid_values[] = $post_in_id;
|
|
}
|
|
}
|
|
// If same values appear in both arrays, exclusion will override inclusion.
|
|
$valid_values = array_diff( $valid_values, $valid_exclude_values );
|
|
$posts = implode( ',', $valid_values );
|
|
if ( ! empty( $posts ) ) {
|
|
$query_restrictions .= " AND relevanssi.doc IN ($posts)";
|
|
// Clean: $posts is checked to be integers.
|
|
}
|
|
}
|
|
return $query_restrictions;
|
|
}
|
|
|
|
/**
|
|
* Processes the 'parent in' and 'parent not in' parameters to MySQL query
|
|
* restrictions.
|
|
*
|
|
* Checks that the parameters are integers and formulates a MySQL query restriction
|
|
* from them. If the same posts are both included and excluded, exclusion will take
|
|
* precedence.
|
|
*
|
|
* Tested.
|
|
*
|
|
* @param array $parent_query An array where included posts are in
|
|
* $post_query['parent in'] and excluded posts are in $post_query['parent not in'].
|
|
*
|
|
* @return string MySQL query restrictions matching the array.
|
|
*/
|
|
function relevanssi_process_parent_query( $parent_query ) {
|
|
global $wpdb;
|
|
|
|
$query_restrictions = '';
|
|
$valid_exclude_values = array();
|
|
if ( isset( $parent_query['parent not in'] ) ) {
|
|
foreach ( $parent_query['parent not in'] as $post_not_in_id ) {
|
|
if ( is_int( $post_not_in_id ) ) {
|
|
$valid_exclude_values[] = $post_not_in_id;
|
|
}
|
|
}
|
|
$posts = implode( ',', $valid_exclude_values );
|
|
if ( isset( $posts ) ) {
|
|
$query_restrictions .= " AND relevanssi.doc NOT IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
|
|
// Clean: $posts is checked to be integers.
|
|
}
|
|
}
|
|
if ( isset( $parent_query['parent in'] ) ) {
|
|
$valid_values = array();
|
|
foreach ( $parent_query['parent in'] as $post_in_id ) {
|
|
if ( is_int( $post_in_id ) ) {
|
|
$valid_values[] = $post_in_id;
|
|
}
|
|
}
|
|
$valid_values = array_diff( $valid_values, $valid_exclude_values );
|
|
$posts = implode( ',', $valid_values );
|
|
if ( strlen( $posts ) > 0 ) {
|
|
$query_restrictions .= " AND relevanssi.doc IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
|
|
// Clean: $posts is checked to be integers.
|
|
}
|
|
}
|
|
|
|
return $query_restrictions;
|
|
}
|
|
|
|
/**
|
|
* Processes the meta query parameter to MySQL query restrictions.
|
|
*
|
|
* Uses the WP_Meta_Query object to parse the query variables to create the MySQL
|
|
* JOIN and WHERE clauses.
|
|
*
|
|
* Tested.
|
|
*
|
|
* @see WP_Meta_Query
|
|
*
|
|
* @param array $meta_query A meta query array.
|
|
*
|
|
* @return array Index 'where' is the WHERE, index 'join' is the JOIN.
|
|
*/
|
|
function relevanssi_process_meta_query( $meta_query ) {
|
|
$mq_vars = array( 'meta_query' => $meta_query );
|
|
|
|
$mq = new WP_Meta_Query();
|
|
$mq->parse_query_vars( $mq_vars );
|
|
$meta_sql = $mq->get_sql( 'post', 'relevanssi', 'doc' );
|
|
$meta_join = '';
|
|
$meta_where = '';
|
|
if ( $meta_sql ) {
|
|
$meta_join = $meta_sql['join'];
|
|
$meta_where = $meta_sql['where'];
|
|
}
|
|
|
|
return array(
|
|
'where' => $meta_where,
|
|
'join' => $meta_join,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Processes the date query parameter to MySQL query restrictions.
|
|
*
|
|
* Uses the WP_Date_Query object to parse the query variables to create the
|
|
* MySQL WHERE clause. By default using a date query will block taxonomy terms
|
|
* and user profiles from the search (because they don't have a post ID and
|
|
* also don't have date information associated with them). If you want to keep
|
|
* the user profiles and taxonomy terms in the search, set the filter hook
|
|
* `relevanssi_date_query_non_posts` to return true.
|
|
*
|
|
* @see WP_Date_Query
|
|
*
|
|
* @global object $wpdb The WP database interface.
|
|
*
|
|
* @param WP_Date_Query $date_query A date query object.
|
|
*
|
|
* @return string The MySQL query restriction.
|
|
*/
|
|
function relevanssi_process_date_query( $date_query ) {
|
|
global $wpdb;
|
|
|
|
$query_restrictions = '';
|
|
if ( method_exists( $date_query, 'get_sql' ) ) {
|
|
$sql = $date_query->get_sql(); // Format: AND (the query).
|
|
$query = " relevanssi.doc IN (
|
|
SELECT DISTINCT(ID) FROM $wpdb->posts WHERE 1 $sql )
|
|
";
|
|
/**
|
|
* If true, include non-posts (users, terms) in searches with a date
|
|
* query filter.
|
|
*
|
|
* @param boolean Allow non-posts? Default false.
|
|
*/
|
|
if ( apply_filters( 'relevanssi_date_query_non_posts', false ) ) {
|
|
$query_restrictions = " AND ( $query OR relevanssi.doc = -1 ) ";
|
|
// Clean: $sql generated by $date_query->get_sql() query.
|
|
} else {
|
|
$query_restrictions = " AND $query ";
|
|
// Clean: $sql generated by $date_query->get_sql() query.
|
|
}
|
|
}
|
|
return $query_restrictions;
|
|
}
|
|
|
|
/**
|
|
* Processes the post exclusion parameter to MySQL query restrictions.
|
|
*
|
|
* Takes a comma-separated list of post ID numbers and creates a MySQL query
|
|
* restriction from them.
|
|
*
|
|
* @param string $expost The post IDs to exclude, comma-separated.
|
|
*
|
|
* @return string The MySQL query restriction.
|
|
*/
|
|
function relevanssi_process_expost( $expost ) {
|
|
$posts_to_exclude = '';
|
|
$excluded_post_ids_unchecked = explode( ',', trim( $expost, ' ,' ) );
|
|
$excluded_post_ids = array();
|
|
foreach ( $excluded_post_ids_unchecked as $excluded_post_id ) {
|
|
$excluded_post_ids[] = intval( trim( $excluded_post_id, ' -' ) );
|
|
}
|
|
$excluded_post_ids_string = implode( ',', $excluded_post_ids );
|
|
$posts_to_exclude .= " AND relevanssi.doc NOT IN ($excluded_post_ids_string)";
|
|
// Clean: escaped.
|
|
return $posts_to_exclude;
|
|
}
|
|
|
|
/**
|
|
* Processes the author parameter to MySQL query restrictions.
|
|
*
|
|
* Takes an array of author ID numbers and creates the MySQL query restriction code
|
|
* from them. Negative values are counted as exclusion and positive values as
|
|
* inclusion.
|
|
*
|
|
* Tested.
|
|
*
|
|
* @global object $wpdb The WP database interface.
|
|
*
|
|
* @param array $author An array of authors. Positive values are inclusion,
|
|
* negative values are exclusion.
|
|
*
|
|
* @return string The MySQL query restriction.
|
|
*/
|
|
function relevanssi_process_author( $author ) {
|
|
global $wpdb;
|
|
|
|
$query_restrictions = '';
|
|
|
|
$author_in = array();
|
|
$author_not_in = array();
|
|
foreach ( $author as $id ) {
|
|
if ( ! is_numeric( $id ) ) {
|
|
continue;
|
|
}
|
|
if ( $id > 0 ) {
|
|
$author_in[] = $id;
|
|
} else {
|
|
$author_not_in[] = abs( $id );
|
|
}
|
|
}
|
|
if ( count( $author_in ) > 0 ) {
|
|
$authors = implode( ',', $author_in );
|
|
$query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
|
|
WHERE posts.post_author IN ($authors))";
|
|
// Clean: $authors is always just numbers.
|
|
}
|
|
if ( count( $author_not_in ) > 0 ) {
|
|
$authors = implode( ',', $author_not_in );
|
|
$query_restrictions .= " AND relevanssi.doc NOT IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
|
|
WHERE posts.post_author IN ($authors))";
|
|
// Clean: $authors is always just numbers.
|
|
}
|
|
|
|
return $query_restrictions;
|
|
}
|
|
|
|
/**
|
|
* Processes the by_date parameter to MySQL query restrictions.
|
|
*
|
|
* The by_date parameter is a simple data parameter in the format '24h', that is a
|
|
* number followed by an unit (h, d, m, y, or w).
|
|
*
|
|
* Tested.
|
|
*
|
|
* @global object $wpdb The WP database interface.
|
|
*
|
|
* @param string $n The date parameter.
|
|
*
|
|
* @return string The MySQL query restriction.
|
|
*/
|
|
function relevanssi_process_by_date( $n ) {
|
|
global $wpdb;
|
|
$query_restrictions = '';
|
|
|
|
$u = substr( $n, -1, 1 );
|
|
switch ( $u ) {
|
|
case 'h':
|
|
$unit = 'HOUR';
|
|
break;
|
|
case 'd':
|
|
$unit = 'DAY';
|
|
break;
|
|
case 'm':
|
|
$unit = 'MONTH';
|
|
break;
|
|
case 'y':
|
|
$unit = 'YEAR';
|
|
break;
|
|
case 'w':
|
|
$unit = 'WEEK';
|
|
break;
|
|
default:
|
|
$unit = 'DAY';
|
|
}
|
|
|
|
$n = preg_replace( '/[hdmyw]/', '', $n );
|
|
|
|
if ( is_numeric( $n ) ) {
|
|
$query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
|
|
WHERE posts.post_date > DATE_SUB(NOW(), INTERVAL $n $unit))";
|
|
// Clean: $n is always numeric, $unit is Relevanssi-generated.
|
|
}
|
|
|
|
return $query_restrictions;
|
|
}
|
|
|
|
/**
|
|
* Extracts the post types from a comma-separated list or an array.
|
|
*
|
|
* Handles the non-post post types as well (user, taxonomies, etc.) and escapes the
|
|
* post types for SQL injections.
|
|
*
|
|
* Tested.
|
|
*
|
|
* @param string|array $post_type An array or a comma-separated list of
|
|
* post types.
|
|
* @param boolean $admin_search True if this is an admin search.
|
|
* @param boolean $include_attachments True if attachments are allowed in the
|
|
* search.
|
|
*
|
|
* @global object $wpdb The WP database interface.
|
|
*
|
|
* @return array Array containing the 'post_type' and 'non_post_post_type' (which
|
|
* defaults to null).
|
|
*/
|
|
function relevanssi_process_post_type( $post_type, $admin_search, $include_attachments ) {
|
|
global $wpdb;
|
|
|
|
// If $post_type is not set, see if there are post types to exclude from the search.
|
|
// If $post_type is set, there's no need to exclude, as we only include.
|
|
$negative_post_type = null;
|
|
if ( ! $post_type && ! $admin_search ) {
|
|
$negative_post_type = relevanssi_get_negative_post_type( $include_attachments );
|
|
}
|
|
|
|
$non_post_post_type = null;
|
|
$non_post_post_types_array = array();
|
|
if ( function_exists( 'relevanssi_get_non_post_post_types' ) ) {
|
|
// Relevanssi Premium includes post types which are not actually posts.
|
|
$non_post_post_types_array = relevanssi_get_non_post_post_types();
|
|
}
|
|
|
|
if ( $post_type ) {
|
|
if ( ! is_array( $post_type ) ) {
|
|
$post_types = explode( ',', $post_type );
|
|
} else {
|
|
$post_types = $post_type;
|
|
}
|
|
|
|
// This array will contain all regular post types involved in the search parameters.
|
|
$post_post_types = array_diff( $post_types, $non_post_post_types_array );
|
|
|
|
// This array has the non-post post types involved.
|
|
$non_post_post_types = array_intersect( $post_types, $non_post_post_types_array );
|
|
|
|
// Escape both for SQL queries, just in case.
|
|
$non_post_post_types = esc_sql( $non_post_post_types );
|
|
$post_types = esc_sql( $post_post_types );
|
|
|
|
// Implode to a parameter string, or set to null if empty.
|
|
$non_post_post_type = null;
|
|
if ( count( $non_post_post_types ) > 0 ) {
|
|
$non_post_post_type = "'" . implode( "', '", $non_post_post_types ) . "'";
|
|
}
|
|
$post_type = null;
|
|
if ( count( $post_types ) > 0 ) {
|
|
$post_type = "'" . implode( "', '", $post_types ) . "'";
|
|
}
|
|
}
|
|
|
|
$query_restrictions = '';
|
|
|
|
if ( $post_type ) {
|
|
$restriction = " AND (
|
|
relevanssi.doc IN (
|
|
SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
|
|
WHERE posts.post_type IN ($post_type)
|
|
) *np*
|
|
)"; // Clean: $post_type is escaped.
|
|
|
|
// There are post types involved that are taxonomies or users, so can't
|
|
// match to wp_posts. Add a relevanssi.type restriction.
|
|
if ( $non_post_post_type ) {
|
|
$restriction = str_replace( '*np*', "OR (relevanssi.type IN ($non_post_post_type))", $restriction );
|
|
// Clean: $non_post_post_types is escaped.
|
|
} else {
|
|
// No non-post post types, so remove the placeholder.
|
|
$restriction = str_replace( '*np*', '', $restriction );
|
|
}
|
|
$query_restrictions .= $restriction;
|
|
} else {
|
|
// No regular post types.
|
|
if ( $non_post_post_type ) {
|
|
// But there is a non-post post type restriction.
|
|
$query_restrictions .= " AND (relevanssi.type IN ($non_post_post_type))";
|
|
// Clean: $non_post_post_types is escaped.
|
|
}
|
|
}
|
|
|
|
if ( $negative_post_type ) {
|
|
$query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
|
|
WHERE posts.post_type NOT IN ($negative_post_type))) OR (doc = -1))";
|
|
// Clean: $negative_post_type is escaped.
|
|
}
|
|
|
|
return $query_restrictions;
|
|
}
|
|
|
|
/**
|
|
* Processes the post status parameter.
|
|
*
|
|
* Takes the post status parameter and creates a MySQL query restriction from it.
|
|
* Checks if this is in admin context: if the query isn't, there's a catch added to
|
|
* capture user profiles and taxonomy terms.
|
|
*
|
|
* @param string $post_status A post status string.
|
|
*
|
|
* @global WP_Query $wp_query The WP Query object.
|
|
* @global object $wpdb The WP database interface.
|
|
* @global boolean $relevanssi_admin_test If true, an admin search. for tests.
|
|
*
|
|
* @return string The MySQL query restriction.
|
|
*/
|
|
function relevanssi_process_post_status( $post_status ) {
|
|
global $wp_query, $wpdb, $relevanssi_admin_test;
|
|
$query_restrictions = '';
|
|
|
|
if ( ! is_array( $post_status ) ) {
|
|
$post_statuses = esc_sql( explode( ',', $post_status ) );
|
|
} else {
|
|
$post_statuses = esc_sql( $post_status );
|
|
}
|
|
|
|
$escaped_post_status = '';
|
|
if ( count( $post_statuses ) > 0 ) {
|
|
$escaped_post_status = "'" . implode( "', '", $post_statuses ) . "'";
|
|
}
|
|
|
|
if ( $escaped_post_status ) {
|
|
if ( $wp_query->is_admin || $relevanssi_admin_test ) {
|
|
$query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
|
|
WHERE posts.post_status IN ($escaped_post_status))))";
|
|
} else {
|
|
// The -1 is there to get user profiles and category pages.
|
|
$query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
|
|
WHERE posts.post_status IN ($escaped_post_status))) OR (doc = -1))";
|
|
}
|
|
}
|
|
|
|
return $query_restrictions;
|
|
}
|
|
|
|
/**
|
|
* Adds phrase restrictions to the query.
|
|
*
|
|
* For OR searches, adds the phrases only for matching terms that are in the
|
|
* phrases, achieving the OR search effect for phrases: posts without the phrase
|
|
* but with another search term are not excluded from the search. In AND
|
|
* searches, all search terms must match to documents containing the phrase.
|
|
*
|
|
* @param string $query_restrictions The MySQL query restriction for the search.
|
|
* @param array $phrase_queries The phrase queries - 'and' contains the
|
|
* main query, while 'or' has the phrase-specific queries.
|
|
* @param string $term The current search term.
|
|
* @param string $operator AND or OR.
|
|
*
|
|
* @return string The query restrictions with the phrase restrictions added.
|
|
*/
|
|
function relevanssi_add_phrase_restrictions( $query_restrictions, $phrase_queries, $term, $operator ) {
|
|
if ( 'OR' === $operator ) {
|
|
foreach ( $phrase_queries['or'] as $phrase_terms => $restriction ) {
|
|
if ( relevanssi_stripos( $phrase_terms, $term ) !== false ) {
|
|
$query_restrictions .= ' AND ' . $restriction;
|
|
}
|
|
}
|
|
} else {
|
|
$query_restrictions .= $phrase_queries['and'];
|
|
}
|
|
return $query_restrictions;
|
|
}
|