Skip to content

Commit

Permalink
Expand job listing search (#2821)
Browse files Browse the repository at this point in the history
  • Loading branch information
gikaragia authored May 14, 2024
2 parents 2c9cb97 + 07706c2 commit fa43c76
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 47 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
202 changes: 157 additions & 45 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,178 @@ 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;

if ( ! function_exists( 'job_manager_construct_secondary_conditions' ) && ! function_exists( 'job_manager_construct_post_conditions' ) ) {
/**
* 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;

if ( empty( $search_term ) ) {
return [];
}

$searchable_meta_keys = apply_filters( 'job_listing_searchable_meta_keys', $searchable_meta_keys );
$searchable_meta_keys = [
'_application',
'_company_name',
'_company_tagline',
'_company_website',
'_company_twitter',
'_job_location',
];

// Set Search DB Conditions.
$conditions = [];
/**
* 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 = [];
$meta_value = $wildcard_search . $wpdb->esc_like( $search_term ) . $wildcard_search;

/**
* 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 ) ) {

// 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 Post Meta.
if ( apply_filters( 'job_listing_search_post_meta', true ) ) {
// 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 );

// 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 ) . "%' )";
return $conditions;
}
}

// 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 ) . "%' )";
/**
* Constructs SQL clauses that return posts which include or exclude the search term in the provided columns.
* The function replicates the functionality of WP_Query::parse_search.
*
* @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.
*
* @return array The SQL clauses.
*/
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 ) {
$search_column = esc_sql( $search_column );
//phpcs:disabled WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Variables are safe or escaped.
$conditions[] = $wpdb->prepare( "( {$wpdb->posts}.$search_column $like_op %s )", $like );
}

// Filter documented in WP_Query::get_posts.
$allow_query_attachment_by_filename = apply_filters( 'wp_allow_query_attachment_by_filename', false );
if ( ! empty( $allow_query_attachment_by_filename ) ) {
// sq1 is the wp_postmeta join for attachments in WP_Query::get_posts.
$conditions[] = $wpdb->prepare( "(sq1.meta_value $like_op %s)", $like );
//phpcs:enabled WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}

return $conditions;
}
}

/**
* Filters the conditions to use when querying job listings. Resulting array is joined with OR statements.
*
* @since 1.26.0
*
* @param array $conditions Conditions to join by OR when querying job listings.
* @param string $job_manager_keyword Search query.
* 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.
*/
$conditions = apply_filters( 'job_listing_search_conditions', $conditions, $job_manager_keyword );
if ( empty( $conditions ) ) {
return $search;
$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 ];
}

$conditions_str = implode( ' OR ', $conditions );
// Filter documented in WP_Query::parse_search.
$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;
}

// 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 ( ! empty( $search ) ) {
$search = preg_replace( '/^ AND /', '', $search );
$search = " AND ( {$search} OR ( {$conditions_str} ) )";
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 ) );

$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 fa43c76

Please sign in to comment.