Skip to content

Commit

Permalink
Expand job listing search
Browse files Browse the repository at this point in the history
This expands job listing meta and term search to have similar
functionality to WP core's search. As the core's search includes
clauses that are aggregated with ORs and ANDs, the only way to
include meta fields in this search was to rebuild the search query
from scratch.

With this change, get_job_listings_keyword_search generates the
clauses that are generated in WP_Query::parse_search and adds
additional clauses that search the post meta and terms.
  • Loading branch information
gikaragia committed May 1, 2024
1 parent cc0ae60 commit 73a2663
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 45 deletions.
4 changes: 2 additions & 2 deletions includes/class-wp-job-manager-post-types.php
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ public function job_feed() {

if ( ! empty( $job_manager_keyword ) ) {
$query_args['s'] = $job_manager_keyword;
add_filter( 'posts_search', 'get_job_listings_keyword_search' );
add_filter( 'posts_search', 'get_job_listings_keyword_search', 10, 2 );
}

if ( empty( $query_args['meta_query'] ) ) {
Expand All @@ -808,7 +808,7 @@ public function job_feed() {
add_action( 'rss2_ns', [ $this, 'job_feed_namespace' ] );
add_action( 'rss2_item', [ $this, 'job_feed_item' ] );
do_feed_rss2( false );
remove_filter( 'posts_search', 'get_job_listings_keyword_search' );
remove_filter( 'posts_search', 'get_job_listings_keyword_search', 10 );
}

/**
Expand Down
191 changes: 148 additions & 43 deletions wp-job-manager-functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ function get_job_listings( $args = [] ) {

if ( ! empty( $job_manager_keyword ) && strlen( $job_manager_keyword ) >= apply_filters( 'job_manager_get_listings_keyword_length_threshold', 2 ) ) {
$query_args['s'] = $job_manager_keyword;
add_filter( 'posts_search', 'get_job_listings_keyword_search' );
add_filter( 'posts_search', 'get_job_listings_keyword_search', 10, 2 );
}

$query_args = apply_filters( 'job_manager_get_listings', $query_args, $args );
Expand Down Expand Up @@ -266,7 +266,7 @@ function get_job_listings( $args = [] ) {

do_action( 'after_get_job_listings', $query_args, $args );

remove_filter( 'posts_search', 'get_job_listings_keyword_search' );
remove_filter( 'posts_search', 'get_job_listings_keyword_search', 10 );

return $result;
}
Expand Down Expand Up @@ -303,66 +303,171 @@ function _wpjm_shuffle_featured_post_results_helper( $a, $b ) {
* @since 1.21.0
* @since 1.26.0 Moved from the `posts_clauses` filter to the `posts_search` to use WP Query's keyword
* search for `post_title` and `post_content`.
* @param string $search
* @since $$next-version$$ Reimplemented to provide the same functionality with WP core search:
* - Support for double quotes and negating terms (-).
* - Breaks down terms into individual words.
* - Meta and taxonomy name search happens together with search in title, excerpt and post content.
*
* @param string $search The search string.
* @param WP_Query $wp_query The query.
*
* @return string
*/
function get_job_listings_keyword_search( $search ) {
global $wpdb, $job_manager_keyword;

// Searchable Meta Keys: set to empty to search all meta keys.
$searchable_meta_keys = [
'_job_location',
'_company_name',
'_application',
'_company_name',
'_company_tagline',
'_company_website',
'_company_twitter',
];
function get_job_listings_keyword_search( $search, $wp_query ) {
global $wpdb;

$searchable_meta_keys = apply_filters( 'job_listing_searchable_meta_keys', $searchable_meta_keys );
/**
* Constructs SQL clauses that return posts which have metas and terms that include or exclude the search term.
*
* @param string $search_term The search term.
* @param bool $is_excluding Whether posts should be excluded if they match the search terms.
* @param string $wildcard_search The wildcard character or empty string for exact matches.
*
* @return array The SQL clauses.
*/
function job_manager_construct_secondary_conditions( $search_term, $is_excluding, $wildcard_search ) {
global $wpdb;

// Set Search DB Conditions.
$conditions = [];
if ( empty( $search_term ) ) {
return [];
}

// Search Post Meta.
if ( apply_filters( 'job_listing_search_post_meta', true ) ) {
$searchable_meta_keys = [
'_application',
'_company_name',
'_company_tagline',
'_company_website',
'_company_twitter',
'_job_location',
];

// Only selected meta keys.
if ( $searchable_meta_keys ) {
$conditions[] = "{$wpdb->posts}.ID IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key IN ( '" . implode( "','", array_map( 'esc_sql', $searchable_meta_keys ) ) . "' ) AND meta_value LIKE '%" . esc_sql( $job_manager_keyword ) . "%' )";
} else {
// No meta keys defined, search all post meta value.
$conditions[] = "{$wpdb->posts}.ID IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_value LIKE '%" . esc_sql( $job_manager_keyword ) . "%' )";
/**
* Filters the meta keys that are used in job search.
*
* @param array $searchable_meta_keys The meta keys.
*/
$searchable_meta_keys = apply_filters( 'job_listing_searchable_meta_keys', $searchable_meta_keys );

$not_string = $is_excluding ? 'NOT ' : '';
$conditions = [];

/**
* Can be used to disable searching post meta for job searches.
*
* @param bool $enable_meta_search Return false to disable meta search.
*/
if ( apply_filters( 'job_listing_search_post_meta', true ) ) {

$meta_value = $wildcard_search . $wpdb->esc_like( $search_term ) . $wildcard_search;
// Only selected meta keys.
if ( $searchable_meta_keys ) {
$meta_keys = implode( "','", array_map( 'esc_sql', $searchable_meta_keys ) );
//phpcs:disabled WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Variables are safe or escaped.
$conditions[] = $wpdb->prepare( "{$wpdb->posts}.ID {$not_string}IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key IN ( '${meta_keys}' ) AND meta_value LIKE %s )", $meta_value );
} else {
// No meta keys defined, search all post meta value.
$conditions[] = $wpdb->prepare( "{$wpdb->posts}.ID {$not_string}IN ( SELECT post_id FROM {$wpdb->postmeta} WHERE meta_value LIKE %s )", $meta_value );
//phpcs:enabled WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}
}
}

// Search taxonomy.
$conditions[] = "{$wpdb->posts}.ID IN ( SELECT object_id FROM {$wpdb->term_relationships} AS tr LEFT JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id LEFT JOIN {$wpdb->terms} AS t ON tt.term_id = t.term_id WHERE t.name LIKE '%" . esc_sql( $job_manager_keyword ) . "%' )";
// Search taxonomy.
$conditions[] = $wpdb->prepare( "{$wpdb->posts}.ID ${not_string}IN ( SELECT object_id FROM {$wpdb->term_relationships} AS tr LEFT JOIN {$wpdb->term_taxonomy} AS tt ON tr.term_taxonomy_id = tt.term_taxonomy_id LEFT JOIN {$wpdb->terms} AS t ON tt.term_id = t.term_id WHERE t.name LIKE %s )", $meta_value );

return $conditions;
}

/**
* Filters the conditions to use when querying job listings. Resulting array is joined with OR statements.
* Constructs SQL clauses that return posts which include or exclude the search term in the provided columns.
*
* @since 1.26.0
* @see WP_Query::parse_search()
*
* @param string $search_term The search term to match.
* @param bool $is_excluding Whether posts that match the search term should be excluded.
* @param string $wildcard_search The wildcard character or empty string for exact matches.
* @param array $search_columns The columns to check.
*
* @param array $conditions Conditions to join by OR when querying job listings.
* @param string $job_manager_keyword Search query.
* @return array The SQL clauses.
*/
$conditions = apply_filters( 'job_listing_search_conditions', $conditions, $job_manager_keyword );
if ( empty( $conditions ) ) {
return $search;
function job_manager_construct_post_conditions( $search_term, $is_excluding, $wildcard_search, $search_columns ) {
global $wpdb;

if ( $is_excluding ) {
$like_op = 'NOT LIKE';
} else {
$like_op = 'LIKE';
}

$like = $wildcard_search . $wpdb->esc_like( $search_term ) . $wildcard_search;

$conditions = [];
foreach ( $search_columns as $search_column ) {
//phpcs:disabled WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Variables are safe or escaped.
$conditions[] = $wpdb->prepare( "( {$wpdb->posts}.$search_column $like_op %s )", $like );
}

$allow_query_attachment_by_filename = apply_filters( 'wp_allow_query_attachment_by_filename', false );
if ( ! empty( $allow_query_attachment_by_filename ) ) {
$conditions[] = $wpdb->prepare( "(sq1.meta_value $like_op %s)", $like );
//phpcs:enabled WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}

return $conditions;
}

/**
* This function aims to provide similar search functionality with WP core while also including meta and taxonomy terms
* in the searched columns. The functionality of WP_Query::parse_search is replicated but with additional SQL
* clauses which are generated in the job_manager_construct_secondary_conditions function.
*/
$default_search_columns = [ 'post_title', 'post_excerpt', 'post_content' ];
$search_columns = ! empty( $wp_query->query_vars['search_columns'] ) ? $wp_query->query_vars['search_columns'] : $default_search_columns;
if ( ! is_array( $search_columns ) ) {
$search_columns = [ $search_columns ];
}

$search_columns = (array) apply_filters( 'post_search_columns', $search_columns, $wp_query->query_vars['s'], $wp_query );

// Use only supported search columns.
$search_columns = array_intersect( $search_columns, $default_search_columns );
if ( empty( $search_columns ) ) {
$search_columns = $default_search_columns;
}

$conditions_str = implode( ' OR ', $conditions );
// Search terms starting with the exclusion prefix should be removed from the job search results.
$exclusion_prefix = apply_filters( 'wp_query_search_exclusion_prefix', '-' );
$wildcard_search = ! empty( $wp_query->query_vars['exact'] ) ? '' : '%';
$new_search = '';
$searchand = '';

foreach ( $wp_query->query_vars['search_terms'] as $search_term ) {
$is_excluding = $exclusion_prefix && str_starts_with( $search_term, $exclusion_prefix );

if ( $is_excluding ) {
$search_term = substr( $search_term, 1 );
$andor_op = 'AND';
} else {
$andor_op = 'OR';
}

$conditions = job_manager_construct_post_conditions( $search_term, $is_excluding, $wildcard_search, $search_columns );
$conditions = array_merge( $conditions, job_manager_construct_secondary_conditions( $search_term, $is_excluding, $wildcard_search ) );

if ( ! empty( $search ) ) {
$search = preg_replace( '/^ AND /', '', $search );
$search = " AND ( {$search} OR ( {$conditions_str} ) )";
$new_search .= "$searchand(" . implode( " $andor_op ", $conditions ) . ')';

$searchand = ' AND ';
}

if ( ! empty( $new_search ) ) {
$new_search = " AND ({$new_search}) ";
if ( ! is_user_logged_in() ) {
$new_search .= " AND ({$wpdb->posts}.post_password = '') ";
}
} else {
$search = " AND ( {$conditions_str} )";
return $search;
}

return $search;
return $new_search;
}
endif;

Expand Down

0 comments on commit 73a2663

Please sign in to comment.