Related Searches:
' );
*
* @global $wpdb The WordPress database interface.
* @global $relevanssi_variables The global Relevanssi variables, used for the database table names.
*
* @param string $query The search query (get_search_query() is a good way to get the current query).
* @param string $pre What is printed before the results, default '- '.
* @param string $sep The separator between individual results, default '
- '.
* @param string $post What is printed after the results, default '
'.
* @param int $number Number of related searches to show, default 5.
*
* @author John Blackbourn
*/
function relevanssi_related( $query, $pre = '', $number = 5 ) {
global $wpdb, $relevanssi_variables;
$output = array();
$related = array();
$tokens = relevanssi_tokenize( $query, true, -1, 'search_query' );
if ( empty( $tokens ) ) {
return;
}
$query_slug = sanitize_title( $query );
$related = get_transient( 'related-' . $query_slug );
if ( ! $related ) {
$related = array();
/**
* Loop over each token in the query and return logged queries which:
*
* - Contain a matching token
* - Don't match the query or the token exactly
* - Have at least 2 hits
* - Have been queried at least twice
*
* then order by most queried with a max of $number results.
*/
foreach ( $tokens as $token => $count ) {
$escaped_token = '%' . $wpdb->esc_like( "$token" ) . '%';
$log_table = $relevanssi_variables['log_table'];
$results = $wpdb->get_results(
$wpdb->prepare(
'SELECT query ' .
"FROM $log_table " . // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'WHERE query LIKE %s
AND query NOT IN (%s, %s)
AND hits > 1
GROUP BY query
HAVING count(query) > 1
ORDER BY count(query) DESC
LIMIT %d',
$escaped_token,
$token,
$query,
$number
)
);
if ( is_array( $results ) ) {
foreach ( $results as $result ) {
$related[] = $result->query;
}
}
}
if ( empty( $related ) ) {
return;
} else {
set_transient( 'related-' . $query_slug, $related, 60 * 60 * 24 * 7 );
}
}
// Order results by most matching tokens then slice to a maximum of $number results.
$related = array_keys( array_count_values( $related ) );
$related = array_slice( $related, 0, $number );
foreach ( $related as $rel ) {
$url = add_query_arg(
array(
's' => rawurlencode( $rel ),
),
home_url()
);
$rel = esc_attr( $rel );
$output[] = "$rel";
}
echo $pre . implode( $sep, $output ) . $post; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Replaces get_posts() in a way that handles users and taxonomy terms.
*
* Custom-made get_posts() replacement that creates post objects for users and
* taxonomy terms. For regular posts, the function uses get_posts() and a
* caching mechanism.
*
* @global array $relevanssi_post_array The global Relevanssi post array used as
* a cache.
*
* @param int|string $id The post ID to fetch. If the ID is a string and
* begins with 'u_', it's considered a user ID and if it begins with '**', it's
* considered a taxonomy term.
* @param int $blog_id The blog ID, used to make caching work in
* multisite environment. Defaults to -1, which means the blog id is not used.
*
* @return object|WP_Error $post The post object for the post ID or a WP_Error
* object if the post ID is not found.
*/
function relevanssi_premium_get_post( $id, int $blog_id = -1 ) {
global $relevanssi_post_array;
$type = substr( $id, 0, 2 );
switch ( $type ) {
case 'u_':
list( , $id ) = explode( '_', $id );
$user = get_userdata( $id );
$post = new stdClass();
$post->post_title = $user->display_name;
$post->post_content = $user->description;
$post->post_type = 'user';
$post->ID = $id;
$post->relevanssi_link = get_author_posts_url( $id );
$post->post_status = 'publish';
$post->post_date = gmdate( 'Y-m-d H:i:s' );
$post->post_author = 0;
$post->post_name = '';
$post->post_excerpt = '';
$post->comment_status = '';
$post->ping_status = '';
$post->user_id = $id;
/**
* Filters the user profile post object.
*
* After a post object is created from the user profile, it is
* passed through this filter so it can be modified.
*
* @param object $post The post object.
*/
$post = apply_filters( 'relevanssi_user_profile_to_post', $post );
break;
case 'p_':
list( , $id ) = explode( '_', $id );
$post_type_name = relevanssi_get_post_type_by_id( $id );
$post_type = get_post_type_object( $post_type_name );
$post = new stdClass();
$post->post_title = $post_type->label;
$post->post_content = $post_type->description;
$post->post_type = 'post_type';
$post->ID = $id;
$post->relevanssi_link = get_post_type_archive_link( $post_type_name );
$post->post_status = 'publish';
$post->post_date = gmdate( 'Y-m-d H:i:s' );
$post->post_author = 0;
$post->post_name = '';
$post->post_excerpt = '';
$post->comment_status = '';
$post->ping_status = '';
$post->post_type_id = $post_type_name;
/**
* Filters the post type post object.
*
* After a post object is created from a post type, it is passed
* through this filter so it can be modified.
*
* @param stdClass $post The post object.
*/
$post = apply_filters( 'relevanssi_post_type_to_post', $post );
break;
case '**':
list( , $taxonomy, $id ) = explode( '**', $id );
$term = get_term( $id, $taxonomy );
if ( is_wp_error( $term ) ) {
return new WP_Error( 'term_not_found', "Taxonomy term wasn't found." );
}
$post = new stdClass();
$post->post_title = $term->name;
$post->post_content = $term->description;
$post->post_type = $taxonomy;
$post->ID = -1;
$post->post_status = 'publish';
$post->post_date = gmdate( 'Y-m-d H:i:s' );
$post->relevanssi_link = get_term_link( $term, $taxonomy );
$post->post_author = 0;
$post->post_name = '';
$post->post_excerpt = '';
$post->comment_status = '';
$post->ping_status = '';
$post->term_id = $id;
$post->post_parent = $term->parent;
/**
* Filters the taxonomy term post object.
*
* After a post object is created from the taxonomy term, it is
* passed through this filter so it can be modified.
*
* @param Object $post The post object.
*/
$post = apply_filters( 'relevanssi_taxonomy_term_to_post', $post );
break;
default:
$cache_id = $id;
if ( -1 !== $blog_id ) {
$cache_id = $blog_id . '|' . $id;
}
if ( isset( $relevanssi_post_array[ $cache_id ] ) ) {
// Post exists in the cache.
$post = $relevanssi_post_array[ $cache_id ];
} else {
$post = get_post( $id );
$relevanssi_post_array[ $cache_id ] = $post;
}
if (
'on' === get_option( 'relevanssi_link_pdf_files' )
&& ! empty( $post->post_mime_type )
) {
/**
* Filters the URL to the attachment file.
*
* If you set the attachment indexing to index attachments that
* are stored outside the WP attachment system, use this filter
* to provide a link to the attachment.
*
* @param string The URL to the attachment file.
* @param int The attachment post ID number.
*/
$post->relevanssi_link = apply_filters(
'relevanssi_get_attachment_url',
wp_get_attachment_url( $post->ID ),
$post->ID
);
}
}
if ( ! $post ) {
$post = new WP_Error( 'post_not_found', __( 'The requested post does not exist.' ) );
}
return $post;
}
/**
* Returns a list of indexed taxonomies.
*
* This will also include "user", if user profiles are indexed, and "post_type", if
* post type archives are indexed.
*
* @return array $non_post_post_types_array An array of taxonomies Relevanssi is set
* to index (and "user" or "post_type").
*/
function relevanssi_get_non_post_post_types() {
// These post types are not posts, ie. they are taxonomy terms and user profiles.
$non_post_post_types_array = array();
if ( get_option( 'relevanssi_index_taxonomies' ) ) {
$taxonomies = get_option( 'relevanssi_index_terms' );
if ( is_array( $taxonomies ) ) {
$non_post_post_types_array = $taxonomies;
}
}
if ( get_option( 'relevanssi_index_users' ) ) {
$non_post_post_types_array[] = 'user';
}
if ( get_option( 'relevanssi_index_post_type_archives' ) ) {
$non_post_post_types_array[] = 'post_type';
}
return $non_post_post_types_array;
}
/**
* Gets the PDF content for the child posts of the post.
*
* @global $wpdb The WordPress database interface.
*
* @param int $post_id The post ID of the parent post.
*
* @return array $pdf_content The PDF content of the child posts.
*/
function relevanssi_get_child_pdf_content( $post_id ): array {
global $wpdb;
$post_id = intval( $post_id );
$pdf_content = '';
if ( $post_id > 0 ) {
/**
* Filters the custom field value before indexing.
*
* @param array Custom field values.
* @param string $field The custom field name.
* @param int $post_id The post ID.
*/
return apply_filters(
'relevanssi_custom_field_value',
$wpdb->get_col( "SELECT meta_value FROM $wpdb->postmeta AS pm, $wpdb->posts AS p WHERE pm.post_id = p.ID AND p.post_parent = $post_id AND meta_key = '_relevanssi_pdf_content'" ), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'_relevanssi_pdf_content',
$post_id
);
// Only user-provided variable is $post_id, and that's from Relevanssi and sanitized as an int.
}
return array();
}
/**
* Provides the Premium version "Did you mean" recommendations.
*
* Provides a better version of "Did you mean" recommendations, using the
* spelling corrector class to generate a correct spelling.
*
* @global WP_Query $wp_query The query object, used to check the number of
* posts found.
*
* @param string $query The search query to correct.
* @param string $pre Text printed out before the suggestion.
* @param string $post Text printed out after the suggestion.
* @param int $n Maximum number of hits before the suggestions are shown,
* default 5.
*
* @return string Empty string if there's nothing to correct; otherwise a string
* with the HTML link to the corrected search.
*/
function relevanssi_premium_didyoumean( $query, $pre, $post, $n = 5 ) {
global $wp_query;
$total_results = $wp_query->found_posts;
$result = '';
if ( $total_results > $n ) {
return $result;
}
$suggestion = relevanssi_premium_generate_suggestion( $query );
if ( true === $suggestion ) {
return $result;
}
if ( empty( $suggestion ) ) {
$suggestion = relevanssi_simple_generate_suggestion( $query );
}
$result = null;
if ( $suggestion ) {
$url = trailingslashit( get_bloginfo( 'url' ) );
$url = esc_attr(
add_query_arg(
array(
's' => rawurlencode( $suggestion ),
),
$url
)
);
/** This filter is documented in lib/didyoumean.php */
$url = apply_filters( 'relevanssi_didyoumean_url', $url, $query, $suggestion );
// Escape the suggestion to avoid XSS attacks.
$suggestion = htmlspecialchars( $suggestion );
/** This filter is documented in lib/didyoumean.php */
$result = apply_filters( 'relevanssi_didyoumean_suggestion', "$pre$suggestion$post" );
}
return $result;
}
/**
* Generates the "Did you mean" suggestion.
*
* Generates "Did you mean" suggestions given a query to correct, using the
* spelling corrector method.
*
* @param string $query The search query to correct.
*
* @return string $query Corrected query, empty string if there are no
* corrections available and true if the query was already correct.
*/
function relevanssi_premium_generate_suggestion( $query ) {
$corrected_query = '';
if ( class_exists( 'Relevanssi_SpellCorrector' ) ) {
$query = htmlspecialchars_decode( $query, ENT_QUOTES );
$tokens = relevanssi_tokenize( $query, true, -1, 'search_query' );
$sc = new Relevanssi_SpellCorrector();
$correct = array();
$exact_matches = 0;
foreach ( array_keys( $tokens ) as $token ) {
/**
* Filters the tokens for Did you mean suggestions.
*
* You can use this filter hook to modify the tokens before Relevanssi
* tries to come up with Did you mean suggestions for them. If you
* return an empty string, the token will be skipped and no suggestion
* will be made for the token.
*
* @param string $token An individual word from the search query.
*
* @return string The token.
*/
$token = apply_filters( 'relevanssi_didyoumean_token', trim( $token ) );
if ( ! $token ) {
continue;
}
$c = $sc->correct( $token );
if ( true === $c ) {
++$exact_matches;
} elseif ( ! empty( $c ) && strval( $token ) !== $c ) {
array_push( $correct, $c );
$query = str_ireplace( $token, $c, $query ); // Replace misspelled word in query with suggestion.
}
}
if ( count( $tokens ) === $exact_matches ) {
// All tokens are correct.
return true;
}
if ( count( $correct ) > 0 ) {
// Strip quotes, because they are likely incorrect.
$query = str_replace( '"', '', $query );
$corrected_query = $query;
}
}
return $corrected_query;
}
/**
* Multisite-friendly get_post().
*
* Gets a post using relevanssi_get_post() from the specified subsite.
*
* @param int $blogid The blog ID.
* @param int $id The post ID.
*
* @return object|WP_Error $post The post object or a WP_Error if the post
* cannot be found.
*/
function relevanssi_get_multisite_post( $blogid, $id ) {
switch_to_blog( $blogid );
if ( ! is_numeric( mb_substr( $id, 0, 1 ) ) ) {
// The post ID does not start with a number; this is a user or a
// taxonomy term, so suspend cache addition to avoid getting garbage in
// the cache.
wp_suspend_cache_addition( true );
}
$post = relevanssi_get_post( $id, $blogid );
restore_current_blog();
return $post;
}
/**
* Initializes things for Relevanssi Premium.
*
* Adds metaboxes, depending on settings; adds synonym indexing filter if
* necessary and removes an unnecessary action.
*/
function relevanssi_premium_init() {
$show_post_controls = true;
if ( 'on' === get_option( 'relevanssi_hide_post_controls' ) ) {
$show_post_controls = false;
/**
* Adjusts the capability required to show the Relevanssi post controls
* for admins.
*
* @param string $capability The minimum capability required, default
* 'manage_options'.
*/
if (
'on' === get_option( 'relevanssi_show_post_controls' ) &&
current_user_can( apply_filters( 'relevanssi_options_capability', 'manage_options' ) )
) {
$show_post_controls = true;
}
}
if ( $show_post_controls ) {
add_action( 'add_meta_boxes', 'relevanssi_add_metaboxes' );
}
if ( 'on' === get_option( 'relevanssi_index_synonyms' ) ) {
add_filter( 'relevanssi_indexing_tokens', 'relevanssi_add_indexing_synonyms', 10 );
}
// If the relevanssi_save_postdata is not disabled, scheduled publication
// will swipe out the Relevanssi post controls settings.
add_action(
'future_to_publish',
function () {
remove_action( 'save_post', 'relevanssi_save_postdata' );
}
);
if ( function_exists( 'do_blocks' ) ) {
add_action( 'init', 'relevanssi_register_gutenberg_actions', 11 );
}
global $pagenow, $relevanssi_variables;
$on_relevanssi_page = false;
if ( isset( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
$page = sanitize_file_name( wp_unslash( $_GET['page'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
$base = sanitize_file_name( wp_unslash( plugin_basename( $relevanssi_variables['file'] ) ) );
if ( $base === $page ) {
$on_relevanssi_page = true;
}
}
if ( function_exists( 'is_multisite' ) && is_multisite() && function_exists( 'get_blog_status' ) ) {
$public = (bool) get_blog_status( get_current_blog_id(), 'public' );
if ( ! $public && 'options-general.php' === $pagenow && $on_relevanssi_page ) {
add_action(
'admin_notices',
function () {
printf(
"",
esc_html__( 'Your site is not public. By default, Relevanssi does not search private sites. If you want to be able to search on this site, either make it public or add a filter function that returns true on \'relevanssi_multisite_public_status\' filter hook.', 'relevanssi' )
);
}
);
}
}
add_filter( 'relevanssi_remove_punctuation', 'relevanssi_wildcards_pre', 8 );
add_filter( 'relevanssi_remove_punctuation', 'relevanssi_wildcards_post', 12 );
add_filter( 'relevanssi_term_where', 'relevanssi_query_wildcards', 10, 2 );
add_filter( 'relevanssi_indexing_restriction', 'relevanssi_hide_post_restriction' );
if ( defined( 'RELEVANSSI_API_KEY' ) ) {
add_filter(
'pre_option_relevanssi_api_key',
function () {
return RELEVANSSI_API_KEY;
}
);
add_filter(
'pre_site_option_relevanssi_api_key',
function () {
return RELEVANSSI_API_KEY;
}
);
}
$update_translations = false;
if ( 'on' === get_option( 'relevanssi_update_translations' ) ) {
$update_translations = true;
}
if ( 'on' === get_option( 'relevanssi_do_not_call_home' ) ) {
$update_translations = false;
}
/**
* Filters whether to update the Relevanssi translations.
*
* @param boolean $update_translations If false, don't update translations.
*/
$update_translations = apply_filters( 'relevanssi_update_translations', $update_translations );
if ( $update_translations ) {
$t15s_updater = new Relevanssi_Language_Packs(
'plugin',
'relevanssi',
'https://packages.translationspress.com/relevanssi/relevanssi/packages.json'
);
$t15s_updater->add_project();
}
add_action(
'in_plugin_update_message-' . $relevanssi_variables['plugin_basename'],
'relevanssi_premium_modify_plugin_update_message'
);
// Add the related posts filters if necessary.
relevanssi_related_init();
}
/**
* Adds the Relevanssi Premium hide post filter to the indexing restrictions.
*
* @global object $wpdb The WP database interface.
*
* @param array $restrictions The current set of restrictions.
*
* @return array The updated restrictions.
*/
function relevanssi_hide_post_restriction( $restrictions ) {
global $wpdb;
$restrictions['mysql'] .= " AND post.ID NOT IN (SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_relevanssi_hide_post' AND meta_value = 'on')";
$restrictions['reason'] .= ' ' . __( 'Relevanssi index exclude', 'relevanssi' );
return $restrictions;
}
/**
* Replaces the standard permalink with $post->relevanssi_link if it exists.
*
* Relevanssi adds a link to the user profile or taxonomy term page to
* $post->relevanssi_link. This function replaces permalink with that link, if
* it exists.
*
* @param string $permalink The permalink to filter.
* @param int $post_id The post ID.
*
* @return string $permalink Modified permalink.
*/
function relevanssi_post_link_replace( $permalink, $post_id ) {
$post = relevanssi_get_post( $post_id );
if ( property_exists( $post, 'relevanssi_link' ) ) {
$permalink = $post->relevanssi_link;
}
return $permalink;
}
/**
* Fetches a list of words from the Relevanssi database for spelling corrector.
*
* A helper function for the spelling corrector. Gets the word list from the
* 'relevanssi_words' option. If the data is expired (more than a month old),
* this function triggers an asynchronous refresh action that fetches new words
* from the Relevanssi database to use as a source material for spelling
* suggestions.
*
* @return array $words An array of words, with the word as the key and number
* of occurrances as the value.
*/
function relevanssi_get_words() {
$data = get_option(
'relevanssi_words',
array(
'expire' => 0,
'words' => array(),
)
);
if ( time() > $data['expire'] ) {
relevanssi_launch_ajax_action( 'relevanssi_get_words' );
}
return $data['words'];
}
/**
* Adds the Premium options.
*
* @global array $relevanssi_variables The global Relevanssi variables, used to set the link boost default.
*/
function relevanssi_premium_install() {
global $relevanssi_variables;
add_option( 'relevanssi_api_key', '' );
add_option( 'relevanssi_click_tracking', 'on' );
add_option( 'relevanssi_disable_shortcodes', '' );
add_option( 'relevanssi_do_not_call_home', 'off' );
add_option( 'relevanssi_hide_post_controls', 'off' );
add_option( 'relevanssi_index_pdf_parent', 'off' );
add_option( 'relevanssi_index_post_type_archives', 'off' );
add_option( 'relevanssi_index_subscribers', 'off' );
add_option( 'relevanssi_index_synonyms', 'off' );
add_option( 'relevanssi_index_taxonomies', 'off' );
add_option( 'relevanssi_index_terms', array() );
add_option( 'relevanssi_index_users', 'off' );
add_option( 'relevanssi_internal_links', 'noindex' );
add_option( 'relevanssi_link_boost', $relevanssi_variables['link_boost_default'] );
add_option( 'relevanssi_link_pdf_files', 'on' );
add_option( 'relevanssi_max_excerpts', 1 );
add_option( 'relevanssi_mysql_columns', '' );
add_option( 'relevanssi_post_type_weights', '' );
add_option( 'relevanssi_read_new_files', 'off' );
add_option( 'relevanssi_redirects', array() );
add_option( 'relevanssi_related_settings', relevanssi_related_default_settings() );
add_option( 'relevanssi_related_style', relevanssi_related_default_styles() );
add_option( 'relevanssi_send_pdf_files', 'off' );
add_option( 'relevanssi_server_location', relevanssi_default_server_location() );
add_option( 'relevanssi_show_post_controls', 'off' );
add_option( 'relevanssi_spamblock', array() );
add_option( 'relevanssi_thousand_separator', '' );
add_option( 'relevanssi_trim_click_logs', '180' );
add_option( 'relevanssi_update_translations', 'off' );
add_option(
'relevanssi_recency_bonus',
array(
'bonus' => '',
'days' => '',
)
);
}
/**
* Makes an educated guess whether the default attachment server location should
* be US or EU, based on the site locale setting.
*
* @uses get_locale()
*
* @return string 'eu' or 'us', depending on the locale.
*/
function relevanssi_default_server_location(): string {
$server = 'us';
$locale = get_locale();
if ( strpos( $locale, '_' ) === false ) {
$language = $locale;
} else {
list( $language, $country ) = explode( '_', $locale );
}
$eu_languages = array( 'ast', 'bel', 'ca', 'cy', 'el', 'et', 'eu', 'fi', 'fur', 'gd', 'hr', 'hsb', 'lv', 'oci', 'roh', 'sq', 'uk' );
$eu_countries = array( 'AL', 'AT', 'BA', 'BE', 'BG', 'CH', 'CY', 'DE', 'EE', 'ES', 'FR', 'GB', 'GR', 'HR', 'HU', 'IE', 'IL', 'IS', 'IT', 'LI', 'LT', 'LU', 'LV', 'MC', 'MD', 'ME', 'MT', 'NL', 'NO', 'PL', 'PT', 'RO', 'RS', 'SE', 'SI', 'SK', 'UA' );
if ( in_array( strtolower( $language ), $eu_languages, true ) ||
in_array( strtoupper( $country ), $eu_countries, true ) ) {
$server = 'eu';
}
return $server;
}
/**
* Returns the attachment reading server URL.
*
* Checks the correct server from 'relevanssi_server_location' option and returns the
* correct URL from the constants.
*
* @return string The attachment reading server URL.
*/
function relevanssi_get_server_url() {
$server = RELEVANSSI_US_SERVICES_URL;
if ( 'eu' === get_option( 'relevanssi_server_location' ) ) {
$server = RELEVANSSI_EU_SERVICES_URL;
}
/**
* Allows changing the attachment reading server URL.
*
* @param string The server URL.
*/
return apply_filters( 'relevanssi_attachment_server_url', $server );
}
/**
* Extracts taxonomy specifiers from the search query.
*
* Finds all {taxonomy:search term} specifiers from the query. If any are
* found, they are stored in $relevanssi_variables global variable and the
* filtering function is activated.
*
* @global array $relevanssi_variables Used to store the target data.
*
* @param string $query The query.
*
* @return string The query with the specifier tags removed.
*/
function relevanssi_extract_specifier( $query ) {
global $relevanssi_variables;
$targets = array();
if ( preg_match_all( '/{(.*?):(.*?)}/', $query, $matches, PREG_SET_ORDER ) ) {
foreach ( $matches as $match ) {
list( $whole, $target, $keyword ) = $match;
$phrases = relevanssi_extract_phrases( $keyword );
if ( ! empty( $phrases ) ) {
foreach ( $phrases as $phrase ) {
$relevanssi_variables['phrase_targets'][ $phrase ] = $target;
}
} else {
if ( is_numeric( $keyword ) ) {
$keyword = ' ' . $keyword;
}
$targets[ $keyword ][] = $target;
}
$query = str_replace( $whole, $keyword, $query );
}
}
if ( ! empty( $targets ) ) {
$relevanssi_variables['targets'] = $targets;
add_filter( 'relevanssi_match', 'relevanssi_target_matches' );
}
return $query;
}
/**
* Filters posts by taxonomy specifiers.
*
* If taxonomy specifiers are found in the query, this filtering function is
* activated and will set the post weight to 0 in the cases where the post
* matches the search term, but not the specifiers.
*
* @global array $relevanssi_variables Used to store the target data.
*
* @param object $match_object The Relevanssi match object.
*
* @return object The match object, with the weight modified if necessary.
*/
function relevanssi_target_matches( $match_object ) {
global $relevanssi_variables;
if ( is_numeric( $match_object->term ) ) {
$match_object->term = ' ' . $match_object->term;
}
$fuzzy = get_option( 'relevanssi_fuzzy' );
if ( 'always' === $fuzzy || 'sometimes' === $fuzzy ) {
foreach ( $relevanssi_variables['targets'] as $term => $target ) {
if (
substr( $match_object->term, 0, strlen( $term ) ) === $term ||
substr( strrev( $match_object->term ), 0, strlen( $term ) ) === strrev( $term )
) {
$relevanssi_variables['targets'][ $match_object->term ] =
$relevanssi_variables['targets'][ $term ];
}
}
}
$no_matches = false;
if ( isset( $relevanssi_variables['targets'][ $match_object->term ] ) ) {
$no_matches = true;
foreach ( $relevanssi_variables['targets'][ $match_object->term ] as $target ) {
if ( isset( $match_object->$target ) && '0' !== $match_object->$target ) {
$no_matches = false;
break;
}
if ( $match_object->customfield_detail && ! is_object( $match_object->customfield_detail ) ) {
$match_object->customfield_detail = json_decode( $match_object->customfield_detail );
}
if (
! empty( $match_object->customfield_detail ) &&
isset( $match_object->customfield_detail->$target ) &&
'0' !== $match_object->customfield_detail->$target
) {
$no_matches = false;
break;
}
if ( ! is_object( $match_object->taxonomy_detail ) ) {
$match_object->taxonomy_detail = json_decode( $match_object->taxonomy_detail );
}
if (
! empty( $match_object->taxonomy_detail ) &&
isset( $match_object->taxonomy_detail->$target ) &&
'0' !== $match_object->taxonomy_detail->$target
) {
$no_matches = false;
break;
}
if ( ! is_object( $match_object->mysqlcolumn_detail ) ) {
$match_object->mysqlcolumn_detail = json_decode( $match_object->mysqlcolumn_detail );
}
if (
! empty( $match_object->mysqlcolumn_detail ) &&
isset( $match_object->mysqlcolumn_detail->$target ) &&
'0' !== $match_object->mysqlcolumn_detail->$target
) {
$no_matches = false;
break;
}
}
}
if ( $no_matches ) {
$match_object->weight = 0;
}
if ( is_object( $match_object->customfield_detail ) ) {
$match_object->customfield_detail = wp_json_encode( $match_object->customfield_detail );
}
if ( is_object( $match_object->taxonomy_detail ) ) {
$match_object->taxonomy_detail = wp_json_encode( $match_object->taxonomy_detail );
}
if ( is_object( $match_object->mysqlcolumn_detail ) ) {
$match_object->mysqlcolumn_detail = wp_json_encode( $match_object->mysqlcolumn_detail );
}
return $match_object;
}
/**
* Generates queries for targeted phrases.
*
* Goes through the targeted phrases from the Relevanssi global variable
* $relevanssi_variables['phrase_targets'] and generates the queries for the
* phrases taking note of the target restrictions. Some of this is slightly
* hacky, as some default inclusions generated by the
* relevanssi_generate_phrase_queries() are simply removed.
*
* @see relevanssi_generate_phrase_queries()
*
* @global array $relevanssi_variables The global Relevanssi variables.
*
* @param string $phrase The source phrase for the queries.
*
* @return array An array of queries per phrase.
*/
function relevanssi_targeted_phrases( $phrase ) {
global $relevanssi_variables;
$target = $relevanssi_variables['phrase_targets'][ $phrase ];
$taxonomies = array();
$excerpt = 'off';
$fields = array();
if ( 'excerpt' === $target ) {
$excerpt = 'on';
}
if ( 'tag' === $target ) {
$target = 'post_tag';
}
if ( taxonomy_exists( $target ) ) {
$taxonomies = array( $target );
} else {
$fields = array( $target );
}
$queries = relevanssi_generate_phrase_queries(
array( $phrase ),
$taxonomies,
$fields,
$excerpt
);
if ( 'excerpt' === $target ) {
$find = array(
"post_content LIKE '%$phrase%' OR ",
"post_title LIKE '%$phrase%' OR ",
);
$queries[ $phrase ][0] = str_replace( $find, '', $queries[ $phrase ][0] );
} elseif ( 'title' === $target ) {
$find = array(
"post_content LIKE '%$phrase%' OR ",
);
$queries[ $phrase ][0] = str_replace( $find, '', $queries[ $phrase ][0] );
} else {
unset( $queries[ $phrase ][0] ); // Remove the generic post content or title query.
}
if ( $fields ) {
// Custom field targeting, remove PDF content custom frield from the list.
$queries[ $phrase ][1] = str_replace(
",'_relevanssi_pdf_content'",
'',
$queries[ $phrase ][1]
);
}
return $queries;
}
/**
* Adds the Relevanssi Premium phrase filters for PDF content, terms and users.
*
* Hooks on to `relevanssi_phrase_queries` to include the phrase queries for
* Relevanssi Premium features: looking for phrases in PDF content, taxonomy
* term names and user fields.
*
* @param array $queries The array of queries where the new queries are added.
* @param string $phrase The current phrase, already MySQL escaped.
* @param string $status MySQL escaped post status value to use in queries.
*
* @return array The queries, with new queries added.
*/
function relevanssi_premium_phrase_queries( $queries, $phrase, $status ) {
global $wpdb;
$index_post_types = get_option( 'relevanssi_index_post_types', array() );
if ( in_array( 'attachment', $index_post_types, true ) ) {
$query = "(SELECT ID
FROM $wpdb->posts AS p, $wpdb->postmeta AS m
WHERE p.ID = m.post_id
AND m.meta_key = '_relevanssi_pdf_content'
AND m.meta_value LIKE '%$phrase%'
AND p.post_status IN ($status))";
$queries[] = array(
'query' => $query,
'target' => 'doc',
);
}
if ( 'on' === get_option( 'relevanssi_index_pdf_parent' ) ) {
$query = "(SELECT parent.ID
FROM $wpdb->posts AS p, $wpdb->postmeta AS m, $wpdb->posts AS parent
WHERE p.ID = m.post_id
AND p.post_parent = parent.ID
AND m.meta_key = '_relevanssi_pdf_content'
AND m.meta_value LIKE '%$phrase%'
AND p.post_status = 'inherit')";
$queries[] = array(
'query' => $query,
'target' => 'doc',
);
}
$index_taxonomies = get_option( 'relevanssi_index_terms', array() );
if ( ! empty( $index_taxonomies ) ) {
$taxonomies_escaped = implode( "','", array_map( 'esc_sql', $index_taxonomies ) );
$taxonomies_sql = "AND tt.taxonomy IN ('$taxonomies_escaped')";
$query = "(SELECT t.term_id
FROM $wpdb->terms AS t, $wpdb->term_taxonomy AS tt
WHERE t.term_id = tt.term_id
AND t.name LIKE '%$phrase%'
$taxonomies_sql)";
$queries[] = array(
'query' => $query,
'target' => 'item',
);
}
$index_users = get_option( 'relevanssi_index_users', 'off' );
if ( 'on' === $index_users ) {
$extra_fields = get_option( 'relevanssi_index_user_fields' );
$meta_keys = array( 'description', 'first_name', 'last_name' );
if ( $extra_fields ) {
$meta_keys = array_merge( $meta_keys, explode( ',', $extra_fields ) );
}
$meta_keys_escaped = implode( "','", array_map( 'esc_sql', $meta_keys ) );
$meta_keys_sql = "um.meta_key IN ('$meta_keys_escaped')";
$query = "(SELECT DISTINCT(u.ID)
FROM $wpdb->users AS u LEFT JOIN $wpdb->usermeta AS um
ON u.ID = um.user_id
WHERE ($meta_keys_sql AND meta_value LIKE '%$phrase%')
OR u.display_name LIKE '%$phrase%')";
$queries[] = array(
'query' => $query,
'target' => 'item',
);
}
return $queries;
}
/**
* Fetches database words to the relevanssi_words option.
*
* @global $wpdb The WordPress database interface.
* @global $relevanssi_variables The global Relevanssi variables, used for the
* database table names.
*/
function relevanssi_update_words_option() {
global $wpdb, $relevanssi_variables;
/**
* The minimum limit of occurrances to include a word.
*
* To save resources, only words with more than this many occurrances are
* fed to the spelling corrector. If there are problems with the spelling
* corrector, increasing this value may fix those problems.
*
* @param int $number The number of occurrances must be more than this
* value, default 2.
*/
$count = apply_filters( 'relevanssi_get_words_having', 2 );
if ( ! is_numeric( $count ) ) {
$count = 2;
}
$q = 'SELECT term,
SUM(title + content + comment + tag + link + author + category + excerpt + taxonomy + customfield)
AS c FROM ' . $relevanssi_variables['relevanssi_table'] .
" GROUP BY term HAVING c > $count"; // Safe: $count is numeric.
$results = $wpdb->get_results( $q ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$words = array();
foreach ( $results as $result ) {
$words[ $result->term ] = $result->c;
}
$expire = time() + MONTH_IN_SECONDS;
$data = array(
'expire' => $expire,
'words' => $words,
);
update_option( 'relevanssi_words', $data, false );
}
/**
* Adds the "Must have" part for the missing terms list.
*
* Assumes there's just one missing term (this is checked outside this
* function).
*
* @param WP_Post $post The post object.
*
* @return string A string containing the "Must have" link.
*/
function relevanssi_add_must_have( $post ) {
$query_string = $GLOBALS['wp']->query_string ?? '';
$request = $GLOBALS['request'] ?? '/';
$search_term = implode( '', $post->relevanssi_hits['missing_terms'] );
$search_page_url = add_query_arg( $query_string, '', home_url( $request ) );
$search_page_url = str_replace( rawurlencode( $search_term ), '%2B' . $search_term, $search_page_url );
return apply_filters(
'relevanssi_missing_terms_must_have',
' | ' . __( 'Must have', 'relevanssi' ) . ': ' . $search_term . ''
);
}
/**
* Updates the $term_hits array used for showing how many hits were found for
* each term.
*
* @param array $term_hits The term hits array (passed as reference).
* @param array $match_arrays The matches array (passed as reference).
* @param stdClass $match_object The match object.
* @param string $term The search term.
*/
function relevanssi_premium_update_term_hits( &$term_hits, &$match_arrays, $match_object, $term ) {
relevanssi_increase_value( $match_arrays['mysqlcolumn'][ $match_object->doc ], $match_object->mysqlcolumn );
$match_arrays['customfield_detail'][ $match_object->doc ] = array();
$match_arrays['taxonomy_detail'][ $match_object->doc ] = array();
$match_arrays['mysqlcolumn_detail'][ $match_object->doc ] = array();
if ( ! empty( $match_object->customfield_detail ) ) {
$match_arrays['customfield_detail'][ $match_object->doc ][ $term ] = $match_object->customfield_detail;
}
if ( ! empty( $match_object->taxonomy_detail ) ) {
$match_arrays['taxonomy_detail'][ $match_object->doc ][ $term ] = $match_object->taxonomy_detail;
}
if ( ! empty( $match_object->mysqlcolumn_detail ) ) {
$match_arrays['mysqlcolumn_detail'][ $match_object->doc ][ $term ] = $match_object->mysqlcolumn_detail;
}
}
/**
* Adds Premium features to the $return array from $match_arrays.
*
* @param array $return_value The search return value array, passed as a
* reference.
* @param array $match_arrays The match array for source data.
*/
function relevanssi_premium_update_return_array( &$return_value, $match_arrays ) {
$match_arrays['mysqlcolumn_matches'] = $match_arrays['mysqlcolumn_matches'] ?? '';
$match_arrays['customfield_detail'] = $match_arrays['customfield_detail'] ?? '';
$match_arrays['taxonomy_detail'] = $match_arrays['taxonomy_detail'] ?? '';
$match_arrays['mysqlcolumn_detail'] = $match_arrays['mysqlcolumn_detail'] ?? '';
$additions = array(
'mysqlcolumn' => $match_arrays['mysqlcolumn_matches'],
'customfield_detail' => $match_arrays['customfield_detail'],
'taxonomy_detail' => $match_arrays['taxonomy_detail'],
'mysqlcolumn_detail' => $match_arrays['mysqlcolumn_detail'],
);
$return_value = array_merge( $return_value, $additions );
}
/**
* Adds Premium features to the $post->relevanssi_hits source array.
*
* @param array $hits The search hits array.
* @param array $data The source data.
* @param int $post_id The post ID.
*/
function relevanssi_premium_add_matches( &$hits, $data, $post_id ) {
$hits['mysqlcolumn'] = $data['mysqlcolumn_matches'][ $post_id ] ?? 0;
$hits['customfield_detail'] = $data['customfield_detail'][ $post_id ] ?? array();
$hits['taxonomy_detail'] = $data['taxonomy_detail'][ $post_id ] ?? array();
$hits['mysqlcolumn_detail'] = $data['mysqlcolumn_detail'][ $post_id ] ?? array();
$hits['customfield_detail'] = array_map(
function ( $value ) {
return (array) json_decode( $value );
},
$hits['customfield_detail']
);
}
/**
* Returns a string of custom field content for the user.
*
* Fetches the user custom field content based on the field indexing settings
* and concatenates it as a single space-separated string.
*
* @uses relevanssi_get_user_field_content
*
* @param string $user_id The ID of the user.
*
* @return string The custom field content.
*/
function relevanssi_get_user_custom_field_content( $user_id ): string {
$custom_field_content = '';
$fields = relevanssi_get_user_field_content( $user_id );
if ( ! empty( $fields ) ) {
$custom_field_content = implode( ' ', array_values( $fields ) );
}
return $custom_field_content;
}
/**
* Returns an array of user custom field names.
*
* Gets the indexed user field names from relevanssi_index_user_fields and
* relevanssi_index_user_meta options and returns an array of field names.
*
* @return array Array of user custom field names.
*/
function relevanssi_generate_list_of_user_fields(): array {
$user_fields = array();
$user_fields_option = get_option( 'relevanssi_index_user_fields' );
if ( $user_fields_option ) {
$user_fields = explode( ',', $user_fields_option );
}
$user_meta = get_option( 'relevanssi_index_user_meta' );
if ( $user_meta ) {
$user_fields = array_merge( $user_fields, explode( ',', $user_meta ) );
}
$user_fields = array_map( 'trim', $user_fields );
return $user_fields;
}
/**
* Returns an array of user custom field content.
*
* Gets the indexed user field content from the fields specified in the user
* field indexing options.
*
* @uses relevanssi_generate_list_of_user_fields
*
* @param string $user_id The ID of the user.
*
* @return array An array of (field, value) pairs.
*/
function relevanssi_get_user_field_content( $user_id ): array {
$fields = relevanssi_generate_list_of_user_fields();
$user = get_user_by( 'id', $user_id );
$user_vars = get_object_vars( $user );
$values = array();
foreach ( $fields as $field ) {
$field_value = '';
if ( isset( $user_vars[ $field ] ) ) {
$field_value = $user_vars[ $field ];
}
if ( empty( $field_value ) && isset( $user_vars['data']->$field ) ) {
$field_value = $user_vars['data']->$field;
}
if ( empty( $field_value ) ) {
$field_value = get_user_meta( $user_id, $field, true );
}
$values[ $field ] = $field_value;
}
return $values;
}