Relevanssi – A Better Search - Version 4.13.0

Version Description

  • New feature: New filter hook relevanssi_phrase filters each phrase before it's used in the MySQL query.
  • New feature: Relevanssi can now add Google-style missing term lists to the search results. You can either use the %missing% tag in the search results breakdown settings, or you can create your own code: the missing terms are also in $post->missing_terms. Relevanssi Premium will also add "Must have" links when there's just one missing term.
  • New feature: New filter hook relevanssi_missing_terms_tag controls which tag is used to wrap the missing terms.
  • New feature: New filter hook relevanssi_missing_terms_template can be used to filter the template used to display the missing terms.
  • New feature: New function relevanssi_get_post_meta_for_all_posts() can be used to fetch particular meta field for a number of posts with just one query.
  • New feature: New filter hook relevanssi_post_author lets you filter the post author display_name before it is indexed.
  • Changed behaviour: relevanssi_strip_tags() used to add spaces between HTML tags before stripping them. It no longer does that, but instead adds a space after specific list of tags (p, br, h1-h6, div, blockquote, hr, li, img) to avoid words being stuck to each other in excerpts.
  • Changed behaviour: Relevanssi now indexes the contents of Oxygen Builder PHP & HTML code blocks.
  • Changed behaviour: Relevanssi now handles synonyms inside phrases differently. If the new filter hook relevanssi_phrase_synonyms returns true (default value), synonyms create a new phrase (with synonym 'dog=hound', phrase "dog biscuits" becomes "dog biscuits" "hound biscuits"). If the value is false, synonyms inside phrases are ignored.
  • Minor fix: Warnings when creating excerpts with search terms that contain a slash were removed.
  • Minor fix: Better Ninja Tables compatibility to avoid problems with lightbox images.
  • Minor fix: Relevanssi did not work well in the Media Library grid view. Relevanssi is now blocked there. If you need Relevanssi in Media Library searches, use the list view.
  • Minor fix: Relevanssi excerpt creation didn't work correctly when numerical search terms were used.
Download this release

Release Info

Developer msaari
Plugin Icon 128x128 Relevanssi – A Better Search
Version 4.13.0
Comparing to
See all releases

Code changes from version 4.12.5 to 4.13.0

changelog.txt CHANGED
@@ -1,3 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  = 4.8.3 =
2
  * New feature: Both `relevanssi_fuzzy_query` and `relevanssi_term_where` now get the current search term as a parameter.
3
  * Minor fix: Relevanssi database tables don't have PRIMARY keys, only UNIQUE keys. In case this is a problem (for example on Digital Ocean servers), deactivate and activate Relevanssi to fix the problem.
1
+ = 4.9.1 =
2
+ * Changed behaviour: The `relevanssi_excerpt_part` filter hook now gets the post ID as a second parameter. The documentation for the filter has been fixed to match actual use: this filter is applied to the excerpt part after the highlighting and the ellipsis have been added.
3
+ * Changed behaviour: The `relevanssi_index_custom_fields` filter hook is no longer used when determining which custom fields are used for phrase searching. If you have a use case where this change matters, please contact us.
4
+ * Minor fix: The `relevanssi_excerpt` filter hook was removed in 4.9.0. It is now restored and behaves the way it did before.
5
+ * Minor fix: Avoids undefined variable warnings from the Pretty Links compatibility code.
6
+ * Minor fix: The Oxygen Builder compatibility has been improved. Now shortcodes in Oxygen Builder content are expanded, if that setting is enabled in Relevanssi settings.
7
+
8
+ = 4.9.0 =
9
+ * New feature: There's now a "Debugging" tab in the Relevanssi settings, letting you see how the Relevanssi index sees posts. This is familiar to Premium users, but is now available in the free version as well.
10
+ * New feature: The SEO Framework plugin is now supported and posts set excluded from the search in SEO Framework settings will be excluded from the index.
11
+ * New feature: There's a new option, "Expand highlights". Enabling it makes Relevanssi expand partial-word highlights to cover the full word. This is useful when doing partial matching and when using a stemmer.
12
+ * New feature: New filter hook `relevanssi_excerpt_part` allows you to modify the excerpt parts before they are combined together. This doesn't do much in the free version.
13
+ * New feature: Improved compatibility with Oxygen Builder. Relevanssi automatically indexes the Oxygen Builder content and cleans it up. New filter hooks `relevanssi_oxygen_section_filters` and `relevanssi_oxygen_section_content` allow easier filtering of Oxygen content to eg. remove unwanted sections.
14
+ * Changed behaviour: The "Uncheck this for non-ASCII highlights" option has been removed. Highlights are now done in a slightly different way that should work in all cases, including for example Cyrillic text, thus this option is no longer necessary.
15
+ * Minor fix: Fixes phrase searching using non-US alphabet.
16
+ * Minor fix: Relevanssi would break admin searching for hierarchical post types. This is now fixed, Relevanssi won't do that anymore.
17
+ * Minor fix: Relevanssi indexing now survives better shortcodes that change the global `$post`.
18
+ * Minor fix: Warnings about missing `relevanssi_update_counts` function are now removed.
19
+ * Minor fix: Paid Membership Pro support now takes notice of the "filter queries" setting.
20
+ * Minor fix: OR logic didn't work correctly when two phrases both had the same word (for example "freedom of speech" and "free speech"). The search would always be an AND search in those cases. That has been fixed.
21
+ * Minor fix: Relevanssi no longer blocks the Pretty Links admin page search.
22
+ * Minor fix: The "Respect 'exclude_from_search'" setting did not work if no post type parameter was included in the search parameters.
23
+ * Minor fix: The category inclusion and exclusion setting checkboxes on the Searching tab didn't work. The setting was saved, but the checkboxes wouldn't appear.
24
+
25
  = 4.8.3 =
26
  * New feature: Both `relevanssi_fuzzy_query` and `relevanssi_term_where` now get the current search term as a parameter.
27
  * Minor fix: Relevanssi database tables don't have PRIMARY keys, only UNIQUE keys. In case this is a problem (for example on Digital Ocean servers), deactivate and activate Relevanssi to fix the problem.
lib/common.php CHANGED
@@ -19,59 +19,22 @@
19
  * @param array $data The source data.
20
  */
21
  function relevanssi_add_matches( &$post, $data ) {
22
- $hits = array(
23
- 'body' => 0,
24
- 'title' => 0,
25
- 'comment' => 0,
26
- 'author' => 0,
27
- 'excerpt' => 0,
28
- 'customfield' => 0,
29
- 'mysqlcolumn' => 0,
30
- 'taxonomy' => array(
31
- 'tag' => 0,
32
- 'category' => 0,
33
- 'taxonomy' => 0,
34
- ),
35
- 'score' => 0,
36
- 'terms' => array(),
37
- );
38
- if ( isset( $data['body_matches'][ $post->ID ] ) ) {
39
- $hits['body'] = $data['body_matches'][ $post->ID ];
40
- }
41
- if ( isset( $data['title_matches'][ $post->ID ] ) ) {
42
- $hits['title'] = $data['title_matches'][ $post->ID ];
43
- }
44
- if ( isset( $data['tag_matches'][ $post->ID ] ) ) {
45
- $hits['taxonomy']['tag'] = $data['tag_matches'][ $post->ID ];
46
- }
47
- if ( isset( $data['category_matches'][ $post->ID ] ) ) {
48
- $hits['taxonomy']['category'] = $data['category_matches'][ $post->ID ];
49
- }
50
- if ( isset( $data['taxonomy_matches'][ $post->ID ] ) ) {
51
- $hits['taxonomy']['taxonomy'] = $data['taxonomy_matches'][ $post->ID ];
52
- }
53
- if ( isset( $data['comment_matches'][ $post->ID ] ) ) {
54
- $hits['comment'] = $data['comment_matches'][ $post->ID ];
55
- }
56
- if ( isset( $data['author_matches'][ $post->ID ] ) ) {
57
- $hits['author'] = $data['author_matches'][ $post->ID ];
58
- }
59
- if ( isset( $data['excerpt_matches'][ $post->ID ] ) ) {
60
- $hits['excerpt'] = $data['excerpt_matches'][ $post->ID ];
61
- }
62
- if ( isset( $data['customfield_matches'][ $post->ID ] ) ) {
63
- $hits['customfield'] = $data['customfield_matches'][ $post->ID ];
64
- }
65
- if ( isset( $data['mysqlcolumn_matches'][ $post->ID ] ) ) {
66
- $hits['mysqlcolumn'] = $data['mysqlcolumn_matches'][ $post->ID ];
67
- }
68
- if ( isset( $data['doc_weights'][ $post->ID ] ) ) {
69
- $hits['score'] = round( $data['doc_weights'][ $post->ID ], 2 );
70
- }
71
- if ( isset( $data['term_hits'][ $post->ID ] ) ) {
72
- $hits['terms'] = $data['term_hits'][ $post->ID ];
73
- arsort( $hits['terms'] );
74
- }
75
  $post->relevanssi_hits = $hits;
76
  }
77
 
@@ -94,6 +57,7 @@ function relevanssi_show_matches( $post ) {
94
  }
95
 
96
  $text = stripslashes( get_option( 'relevanssi_show_matches_text' ) );
 
97
  $replace_these = array(
98
  '%body%',
99
  '%title%',
@@ -108,6 +72,7 @@ function relevanssi_show_matches( $post ) {
108
  '%score%',
109
  '%terms%',
110
  '%total%',
 
111
  );
112
  $replacements = array(
113
  $post->relevanssi_hits['body'],
@@ -123,6 +88,7 @@ function relevanssi_show_matches( $post ) {
123
  $post->relevanssi_hits['score'],
124
  $term_hits,
125
  $total_hits,
 
126
  );
127
  $result = ' ' . str_replace( $replace_these, $replacements, $text );
128
 
@@ -137,6 +103,56 @@ function relevanssi_show_matches( $post ) {
137
  return apply_filters( 'relevanssi_show_matches', $result );
138
  }
139
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
  /**
141
  * Checks whether the user is allowed to see the post.
142
  *
@@ -238,7 +254,7 @@ function relevanssi_populate_array( $matches, $blog_id = -1 ) {
238
  foreach ( $matches as $match ) {
239
  $cache_id = $blog_id . '|' . $match->doc;
240
  if ( $match->doc > 0 && ! isset( $relevanssi_post_array[ $cache_id ] ) ) {
241
- array_push( $ids, $match->doc );
242
  }
243
  }
244
 
@@ -805,6 +821,40 @@ function relevanssi_add_synonyms( $query ) {
805
  }
806
 
807
  if ( count( $synonyms ) > 0 ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
808
  $new_terms = array();
809
  $terms = array_keys( relevanssi_tokenize( $query, false ) ); // Remove stopwords is false here.
810
  if ( ! in_array( $query, $terms, true ) ) {
@@ -823,6 +873,12 @@ function relevanssi_add_synonyms( $query ) {
823
  }
824
  }
825
  }
 
 
 
 
 
 
826
  if ( count( $new_terms ) > 0 ) {
827
  $new_terms = array_unique( $new_terms );
828
  foreach ( $new_terms as $new_term ) {
@@ -832,7 +888,7 @@ function relevanssi_add_synonyms( $query ) {
832
  }
833
  }
834
 
835
- return $query;
836
  }
837
 
838
  /**
@@ -960,7 +1016,7 @@ function relevanssi_permalink( $link, $link_post = null ) {
960
  $link = $link_post->relevanssi_link;
961
  }
962
 
963
- if ( is_search() && isset( $link_post->relevance_score ) ) {
964
  $link = relevanssi_add_highlight( $link, $link_post );
965
  }
966
  return $link;
@@ -1065,11 +1121,13 @@ function relevanssi_get_forbidden_post_types() {
1065
  'jp_mem_plan', // Jetpack.
1066
  'tablepress_table', // TablePress.
1067
  'ninja-table', // Ninja Tables.
 
1068
  'shop_order', // WooCommerce.
1069
  'shop_order_refund', // WooCommerce.
1070
  'wc_order_status', // WooCommerce.
1071
  'wc_order_email', // WooCommerce.
1072
  'shop_webhook', // WooCommerce.
 
1073
  'et_theme_builder', // Divi.
1074
  'et_template', // Divi.
1075
  'et_header_layout', // Divi.
@@ -1110,6 +1168,11 @@ function relevanssi_get_forbidden_post_types() {
1110
  'um_form', // Ultimate Member.
1111
  'um_directory', // Ultimate Member.
1112
  'mailpoet_page', // Mailpoet Page.
 
 
 
 
 
1113
  );
1114
  }
1115
 
@@ -1128,6 +1191,9 @@ function relevanssi_get_forbidden_taxonomies() {
1128
  'amp_template', // AMP.
1129
  'edd_commission_status', // Easy Digital Downloads.
1130
  'edd_log_type', // Easy Digital Downloads.
 
 
 
1131
  );
1132
  }
1133
 
@@ -1568,3 +1634,36 @@ function relevanssi_update_synonyms_setting() {
1568
  $array_synonyms[ $current_language ] = $synonyms;
1569
  update_option( 'relevanssi_synonyms', $array_synonyms );
1570
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  * @param array $data The source data.
20
  */
21
  function relevanssi_add_matches( &$post, $data ) {
22
+ $hits['body'] = $data['body_matches'][ $post->ID ] ?? 0;
23
+ $hits['title'] = $data['title_matches'][ $post->ID ] ?? 0;
24
+ $hits['taxonomy']['tag'] = $data['tag_matches'][ $post->ID ] ?? 0;
25
+ $hits['taxonomy']['category'] = $data['category_matches'][ $post->ID ] ?? 0;
26
+ $hits['taxonomy']['taxonomy'] = $data['taxonomy_matches'][ $post->ID ] ?? 0;
27
+ $hits['comment'] = $data['comment_matches'][ $post->ID ] ?? 0;
28
+ $hits['author'] = $data['author_matches'][ $post->ID ] ?? 0;
29
+ $hits['excerpt'] = $data['excerpt_matches'][ $post->ID ] ?? 0;
30
+ $hits['customfield'] = $data['customfield_matches'][ $post->ID ] ?? 0;
31
+ $hits['mysqlcolumn'] = $data['mysqlcolumn_matches'][ $post->ID ] ?? 0;
32
+ $hits['score'] = isset( $data['doc_weights'][ $post->ID ] ) ? round( $data['doc_weights'][ $post->ID ], 2 ) : 0;
33
+ $hits['terms'] = $data['term_hits'][ $post->ID ] ?? array();
34
+ $hits['missing_terms'] = $data['missing_terms'][ $post->ID ] ?? array();
35
+
36
+ arsort( $hits['terms'] );
37
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  $post->relevanssi_hits = $hits;
39
  }
40
 
57
  }
58
 
59
  $text = stripslashes( get_option( 'relevanssi_show_matches_text' ) );
60
+ $missing_terms = strstr( $text, '%missing%' ) !== false ? relevanssi_generate_missing_terms_list( $post ) : '';
61
  $replace_these = array(
62
  '%body%',
63
  '%title%',
72
  '%score%',
73
  '%terms%',
74
  '%total%',
75
+ '%missing%',
76
  );
77
  $replacements = array(
78
  $post->relevanssi_hits['body'],
88
  $post->relevanssi_hits['score'],
89
  $term_hits,
90
  $total_hits,
91
+ $missing_terms,
92
  );
93
  $result = ' ' . str_replace( $replace_these, $replacements, $text );
94
 
103
  return apply_filters( 'relevanssi_show_matches', $result );
104
  }
105
 
106
+ /**
107
+ * Generates the "Missing:" element for the search results breakdown.
108
+ *
109
+ * @param WP_Post $post The post object, which should have the missing terms in
110
+ * $post->relevanssi_hits['missing_terms'].
111
+ *
112
+ * @return string The missing terms.
113
+ */
114
+ function relevanssi_generate_missing_terms_list( $post ) {
115
+ $missing_terms = '';
116
+ if ( ! empty( $post->relevanssi_hits['missing_terms'] ) ) {
117
+ $missing_terms_list = implode(
118
+ ' ',
119
+ array_map(
120
+ function ( $term ) {
121
+ /**
122
+ * Determines the tag used for missing terms, default <s>.
123
+ *
124
+ * @param string The tag, without angle brackets. Default 's'.
125
+ */
126
+ $tag = apply_filters( 'relevanssi_missing_terms_tag', 's' );
127
+ return $tag ? "<$tag>$term</$tag>" : $term;
128
+ },
129
+ $post->relevanssi_hits['missing_terms']
130
+ )
131
+ );
132
+ $missing_terms = sprintf(
133
+ /**
134
+ * Filters the template for showing missing terms. Make sure you
135
+ * include the '%s', as that is where the missing terms will be
136
+ * inserted.
137
+ *
138
+ * @param string The template.
139
+ */
140
+ apply_filters(
141
+ 'relevanssi_missing_terms_template',
142
+ '<span class="missing_terms">' . __( 'Missing', 'relevanssi' ) . ': %s</span>'
143
+ ),
144
+ $missing_terms_list
145
+ );
146
+ }
147
+ if (
148
+ 1 === count( $post->relevanssi_hits['missing_terms'] )
149
+ && function_exists( 'relevanssi_add_must_have' )
150
+ ) {
151
+ $missing_terms .= relevanssi_add_must_have( $post );
152
+ }
153
+ return $missing_terms;
154
+ }
155
+
156
  /**
157
  * Checks whether the user is allowed to see the post.
158
  *
254
  foreach ( $matches as $match ) {
255
  $cache_id = $blog_id . '|' . $match->doc;
256
  if ( $match->doc > 0 && ! isset( $relevanssi_post_array[ $cache_id ] ) ) {
257
+ $ids[] = $match->doc;
258
  }
259
  }
260
 
821
  }
822
 
823
  if ( count( $synonyms ) > 0 ) {
824
+ $query = str_replace( array( '”', '“' ), '"', $query );
825
+ $phrases = relevanssi_extract_phrases( $query );
826
+ $new_phrases = array();
827
+ /**
828
+ * Controls how synonyms are handled when they appear inside
829
+ * phrases.
830
+ *
831
+ * @param bool If true, synonyms inside phrases create new phrases.
832
+ * If false, synonyms inside phrases are ignored.
833
+ */
834
+ if ( apply_filters( 'relevanssi_phrase_synonyms', true ) ) {
835
+ foreach ( $phrases as $phrase ) {
836
+ $new_phrases[] = $phrase;
837
+ $words = explode( ' ', $phrase );
838
+ foreach ( array_keys( $synonyms ) as $synonym_source ) {
839
+ if ( in_array( $synonym_source, $words, true ) ) {
840
+ foreach ( array_keys( $synonyms[ $synonym_source ] ) as $synonym_replacement ) {
841
+ $new_phrases[] = str_replace( $synonym_source, $synonym_replacement, $phrase );
842
+ }
843
+ }
844
+ }
845
+ }
846
+ } else {
847
+ $new_phrases = $phrases;
848
+ }
849
+
850
+ $query = trim(
851
+ str_replace(
852
+ array_map( 'relevanssi_add_quotes', $phrases ),
853
+ '',
854
+ $query
855
+ )
856
+ );
857
+
858
  $new_terms = array();
859
  $terms = array_keys( relevanssi_tokenize( $query, false ) ); // Remove stopwords is false here.
860
  if ( ! in_array( $query, $terms, true ) ) {
873
  }
874
  }
875
  }
876
+ if ( count( $new_phrases ) > 0 ) {
877
+ $new_terms = array_merge(
878
+ $new_terms,
879
+ array_map( 'relevanssi_add_quotes', $new_phrases )
880
+ );
881
+ }
882
  if ( count( $new_terms ) > 0 ) {
883
  $new_terms = array_unique( $new_terms );
884
  foreach ( $new_terms as $new_term ) {
888
  }
889
  }
890
 
891
+ return trim( $query );
892
  }
893
 
894
  /**
1016
  $link = $link_post->relevanssi_link;
1017
  }
1018
 
1019
+ if ( is_search() && property_exists( $link_post, 'relevance_score' ) ) {
1020
  $link = relevanssi_add_highlight( $link, $link_post );
1021
  }
1022
  return $link;
1121
  'jp_mem_plan', // Jetpack.
1122
  'tablepress_table', // TablePress.
1123
  'ninja-table', // Ninja Tables.
1124
+ 'shop_coupon', // WooCommerce.
1125
  'shop_order', // WooCommerce.
1126
  'shop_order_refund', // WooCommerce.
1127
  'wc_order_status', // WooCommerce.
1128
  'wc_order_email', // WooCommerce.
1129
  'shop_webhook', // WooCommerce.
1130
+ 'woo_product_tab', // Woo Product Tab.
1131
  'et_theme_builder', // Divi.
1132
  'et_template', // Divi.
1133
  'et_header_layout', // Divi.
1168
  'um_form', // Ultimate Member.
1169
  'um_directory', // Ultimate Member.
1170
  'mailpoet_page', // Mailpoet Page.
1171
+ 'mc4wp_form', // MailChimp.
1172
+ 'elementor_font', // Elementor.
1173
+ 'elementor_icons', // Elementor.
1174
+ 'elementor_library', // Elementor.
1175
+ 'elementor_snippet', // Elementor.
1176
  );
1177
  }
1178
 
1191
  'amp_template', // AMP.
1192
  'edd_commission_status', // Easy Digital Downloads.
1193
  'edd_log_type', // Easy Digital Downloads.
1194
+ 'elementor_library_type', // Elementor.
1195
+ 'elementor_library_category', // Elementor.
1196
+ 'elementor_font_type', // Elementor.
1197
  );
1198
  }
1199
 
1634
  $array_synonyms[ $current_language ] = $synonyms;
1635
  update_option( 'relevanssi_synonyms', $array_synonyms );
1636
  }
1637
+
1638
+ /**
1639
+ * Replaces synonyms in an array with their original counterparts.
1640
+ *
1641
+ * If there's a synonym "dog=hound", and the array of terms contains "hound",
1642
+ * it will be replaced with "dog". If there are multiple matches, all
1643
+ * replacements will happen.
1644
+ *
1645
+ * @param array $terms An array of words.
1646
+ *
1647
+ * @return array An array of words with backwards synonym replacement.
1648
+ */
1649
+ function relevanssi_replace_synonyms_in_terms( array $terms ) : array {
1650
+ $all_synonyms = get_option( 'relevanssi_synonyms', array() );
1651
+ $synonyms = explode( ';', $all_synonyms[ relevanssi_get_current_language() ] ?? '' );
1652
+
1653
+ return array_map(
1654
+ function ( $term ) use ( $synonyms ) {
1655
+ $new_term = array();
1656
+ foreach ( $synonyms as $pair ) {
1657
+ list( $key, $value ) = explode( '=', $pair );
1658
+ if ( $value === $term ) {
1659
+ $new_term[] = $key;
1660
+ }
1661
+ }
1662
+ if ( ! empty( $new_term ) ) {
1663
+ $term = implode( ' ', $new_term );
1664
+ }
1665
+ return $term;
1666
+ },
1667
+ $terms
1668
+ );
1669
+ }
lib/compatibility/ninjatables.php CHANGED
@@ -77,7 +77,17 @@ function relevanssi_index_ninja_table( $table_id ) {
77
  )
78
  );
79
  foreach ( $rows as $row ) {
80
- $table_contents .= ' ' . implode( ' ', array_values( get_object_vars( json_decode( $row->value ) ) ) );
 
 
 
 
 
 
 
 
 
 
81
  }
82
 
83
  return $table_contents;
77
  )
78
  );
79
  foreach ( $rows as $row ) {
80
+ $array_values = array_map(
81
+ function( $value ) {
82
+ if ( is_object( $value ) ) {
83
+ return '';
84
+ }
85
+ return strval( $value );
86
+ },
87
+ array_values( get_object_vars( json_decode( $row->value ) ) )
88
+ );
89
+
90
+ $table_contents .= ' ' . implode( ' ', $array_values );
91
  }
92
 
93
  return $table_contents;
lib/compatibility/oxygen.php CHANGED
@@ -13,6 +13,7 @@
13
  add_filter( 'relevanssi_custom_field_value', 'relevanssi_oxygen_compatibility', 10, 3 );
14
  add_filter( 'relevanssi_index_custom_fields', 'relevanssi_add_oxygen' );
15
  add_filter( 'option_relevanssi_index_fields', 'relevanssi_oxygen_fix_none_setting' );
 
16
 
17
  /**
18
  * Cleans up the Oxygen Builder custom field for Relevanssi consumption.
@@ -140,3 +141,25 @@ function relevanssi_oxygen_fix_none_setting( $value ) {
140
 
141
  return $value;
142
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  add_filter( 'relevanssi_custom_field_value', 'relevanssi_oxygen_compatibility', 10, 3 );
14
  add_filter( 'relevanssi_index_custom_fields', 'relevanssi_add_oxygen' );
15
  add_filter( 'option_relevanssi_index_fields', 'relevanssi_oxygen_fix_none_setting' );
16
+ add_filter( 'relevanssi_oxygen_section_content', 'relevanssi_oxygen_code_block' );
17
 
18
  /**
19
  * Cleans up the Oxygen Builder custom field for Relevanssi consumption.
141
 
142
  return $value;
143
  }
144
+
145
+ /**
146
+ * Indexes the Base64 encoded PHP & HTML code block contents.
147
+ *
148
+ * @param string $content The section content from the
149
+ * relevanssi_oxygen_section_content filter hook.
150
+ *
151
+ * @return string $content The content with the decoded code block content
152
+ * added to the end.
153
+ */
154
+ function relevanssi_oxygen_code_block( $content ) {
155
+ if ( preg_match_all( '/\[ct_code_block.*?ct_code_block\]/', $content, $matches ) ) {
156
+ foreach ( $matches[0] as $match ) {
157
+ if ( preg_match_all( '/"code-php":"(.*?)"/', $match, $block_matches ) ) {
158
+ foreach ( $block_matches[1] as $encoded_text ) {
159
+ $content .= ' ' . base64_decode( $encoded_text ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions
160
+ }
161
+ }
162
+ }
163
+ }
164
+ return $content;
165
+ }
lib/excerpts-highlights.php CHANGED
@@ -71,6 +71,16 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
71
  } else {
72
  $untokenized_terms = array_filter( explode( ' ', $query ) );
73
  }
 
 
 
 
 
 
 
 
 
 
74
  $untokenized_terms = array_flip(
75
  relevanssi_remove_stopwords_from_array( $untokenized_terms )
76
  );
@@ -338,7 +348,7 @@ function relevanssi_create_excerpt( $content, $terms, $query, $excerpt_length =
338
  *
339
  * @param string $content The content.
340
  * @param array $terms The search terms, tokenized.
341
- * @param string $query The search query (not used anymore).
342
  * @param int $excerpt_length The length of the excerpt, default 30.
343
  * @param string $excerpt_type Either 'chars' or 'words', default 'words'.
344
  *
@@ -348,7 +358,10 @@ function relevanssi_create_excerpt( $content, $terms, $query, $excerpt_length =
348
  * content.
349
  */
350
  function relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length = 30, $excerpt_type = 'words' ) {
351
- $content = ' ' . preg_replace( '/\s+/u', ' ', $content );
 
 
 
352
  $content = html_entity_decode( $content );
353
  // Finds all the phrases in the query.
354
  $phrases = relevanssi_extract_phrases( stripslashes( $query ) );
@@ -384,7 +397,7 @@ function relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length =
384
  relevanssi_extract_relevant(
385
  array_keys( $terms ),
386
  $content,
387
- $excerpt_length + 1,
388
  $prev_count
389
  );
390
  $excerpt = array(
@@ -1436,3 +1449,56 @@ function relevanssi_excerpt_post_the_content() {
1436
  remove_shortcode( 'noindex' );
1437
  add_shortcode( 'noindex', 'relevanssi_noindex_shortcode' );
1438
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  } else {
72
  $untokenized_terms = array_filter( explode( ' ', $query ) );
73
  }
74
+ $untokenized_terms = array_map(
75
+ function( $term ) {
76
+ if ( is_numeric( $term ) ) {
77
+ $term = " $term";
78
+ }
79
+ return $term;
80
+ },
81
+ $untokenized_terms
82
+ );
83
+
84
  $untokenized_terms = array_flip(
85
  relevanssi_remove_stopwords_from_array( $untokenized_terms )
86
  );
348
  *
349
  * @param string $content The content.
350
  * @param array $terms The search terms, tokenized.
351
+ * @param string $query The search query.
352
  * @param int $excerpt_length The length of the excerpt, default 30.
353
  * @param string $excerpt_type Either 'chars' or 'words', default 'words'.
354
  *
358
  * content.
359
  */
360
  function relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length = 30, $excerpt_type = 'words' ) {
361
+ $content = preg_replace( '/\s+/u', ' ', $content );
362
+ if ( ' ' !== relevanssi_substr( $content, 0, 1 ) ) {
363
+ $content = ' ' . $content;
364
+ }
365
  $content = html_entity_decode( $content );
366
  // Finds all the phrases in the query.
367
  $phrases = relevanssi_extract_phrases( stripslashes( $query ) );
397
  relevanssi_extract_relevant(
398
  array_keys( $terms ),
399
  $content,
400
+ $excerpt_length + 1, // There's one space in the beginning of the content.
401
  $prev_count
402
  );
403
  $excerpt = array(
1449
  remove_shortcode( 'noindex' );
1450
  add_shortcode( 'noindex', 'relevanssi_noindex_shortcode' );
1451
  }
1452
+
1453
+ /**
1454
+ * Adds a highlighted title in the post object in $post->post_highlighted_title.
1455
+ *
1456
+ * @param WP_Post $post The post object (passed as reference).
1457
+ * @param string $query The search query.
1458
+ *
1459
+ * @uses relevanssi_highlight_terms
1460
+ */
1461
+ function relevanssi_highlight_post_title( &$post, $query ) {
1462
+ $post->post_highlighted_title = wp_strip_all_tags( $post->post_title );
1463
+ $highlight = get_option( 'relevanssi_highlight' );
1464
+ if ( 'none' !== $highlight ) {
1465
+ if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
1466
+ $q_for_highlight = 'on' === get_option( 'relevanssi_index_synonyms', 'off' )
1467
+ ? relevanssi_add_synonyms( $query )
1468
+ : $query;
1469
+
1470
+ $post->post_highlighted_title = relevanssi_highlight_terms(
1471
+ $post->post_highlighted_title,
1472
+ $q_for_highlight
1473
+ );
1474
+ }
1475
+ }
1476
+ }
1477
+
1478
+ /**
1479
+ * Replaces $post->post_excerpt with the Relevanssi-generated excerpt and puts
1480
+ * the original excerpt in $post->original_excerpt.
1481
+ *
1482
+ * @param WP_Post $post The post object (passed as reference).
1483
+ * @param string $query The search query.
1484
+ *
1485
+ * @uses relevanssi_do_excerpt
1486
+ */
1487
+ function relevanssi_add_excerpt( &$post, $query ) {
1488
+ if ( isset( $post->blog_id ) ) {
1489
+ switch_to_blog( $post->blog_id );
1490
+ }
1491
+ $excerpt_length = get_option( 'relevanssi_excerpt_length' );
1492
+ $excerpt_type = get_option( 'relevanssi_excerpt_type' );
1493
+ $post->original_excerpt = $post->post_excerpt;
1494
+ $post->post_excerpt = relevanssi_do_excerpt(
1495
+ $post,
1496
+ $query,
1497
+ $excerpt_length,
1498
+ $excerpt_type
1499
+ );
1500
+
1501
+ if ( isset( $post->blog_id ) ) {
1502
+ restore_current_blog();
1503
+ }
1504
+ }
lib/indexing.php CHANGED
@@ -542,7 +542,7 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
542
  }
543
 
544
  if ( 'on' === get_option( 'relevanssi_index_author' ) ) {
545
- $n += relevanssi_index_author( $insert_data, $post->post_author, $min_word_length, $debug );
546
  }
547
 
548
  $n += relevanssi_index_custom_fields( $insert_data, $post->ID, $custom_fields, $min_word_length, $debug );
@@ -1179,12 +1179,22 @@ function relevanssi_index_comments( &$insert_data, $post_id, $min_word_length, $
1179
  * @param int $post_author The post author id.
1180
  * @param int $min_word_length The minimum word length.
1181
  * @param boolean $debug If true, print out debug notices.
 
1182
  *
1183
  * @return int The number of tokens added to the data.
1184
  */
1185
- function relevanssi_index_author( &$insert_data, $post_author, $min_word_length, $debug ) {
1186
  $n = 0;
1187
  $display_name = get_the_author_meta( 'display_name', $post_author );
 
 
 
 
 
 
 
 
 
1188
  /** This filter is documented in lib/indexing.php */
1189
  $name_tokens = apply_filters(
1190
  'relevanssi_indexing_tokens',
542
  }
543
 
544
  if ( 'on' === get_option( 'relevanssi_index_author' ) ) {
545
+ $n += relevanssi_index_author( $insert_data, $post->post_author, $min_word_length, $debug, $post );
546
  }
547
 
548
  $n += relevanssi_index_custom_fields( $insert_data, $post->ID, $custom_fields, $min_word_length, $debug );
1179
  * @param int $post_author The post author id.
1180
  * @param int $min_word_length The minimum word length.
1181
  * @param boolean $debug If true, print out debug notices.
1182
+ * @param WP_Post $post The post object.
1183
  *
1184
  * @return int The number of tokens added to the data.
1185
  */
1186
+ function relevanssi_index_author( &$insert_data, $post_author, $min_word_length, $debug, $post ) {
1187
  $n = 0;
1188
  $display_name = get_the_author_meta( 'display_name', $post_author );
1189
+
1190
+ /**
1191
+ * Filters the post author display_name before indexing it.
1192
+ *
1193
+ * @param string $post_author The author display_name.
1194
+ * @param WP_Post $post The post object.
1195
+ */
1196
+ $display_name = apply_filters( 'relevanssi_post_author', $display_name, $post );
1197
+
1198
  /** This filter is documented in lib/indexing.php */
1199
  $name_tokens = apply_filters(
1200
  'relevanssi_indexing_tokens',
lib/init.php CHANGED
@@ -22,6 +22,7 @@ add_filter( 'posts_request', 'relevanssi_prevent_default_request', 10, 2 );
22
  add_filter( 'relevanssi_search_ok', 'relevanssi_block_on_admin_searches', 10, 2 );
23
  add_filter( 'relevanssi_admin_search_ok', 'relevanssi_block_on_admin_searches', 10, 2 );
24
  add_filter( 'relevanssi_prevent_default_request', 'relevanssi_block_on_admin_searches', 10, 2 );
 
25
 
26
  // Post indexing.
27
  add_action( 'wp_insert_post', 'relevanssi_insert_edit', 99, 1 );
22
  add_filter( 'relevanssi_search_ok', 'relevanssi_block_on_admin_searches', 10, 2 );
23
  add_filter( 'relevanssi_admin_search_ok', 'relevanssi_block_on_admin_searches', 10, 2 );
24
  add_filter( 'relevanssi_prevent_default_request', 'relevanssi_block_on_admin_searches', 10, 2 );
25
+ add_filter( 'relevanssi_search_ok', 'relevanssi_control_media_queries', 11, 2 );
26
 
27
  // Post indexing.
28
  add_action( 'wp_insert_post', 'relevanssi_insert_edit', 99, 1 );
lib/phrases.php CHANGED
@@ -179,7 +179,17 @@ $custom_fields, string $excerpts ) : array {
179
  $phrase = $wpdb->esc_like( $phrase );
180
  $phrase = str_replace( array( '‘', '’', "'", '"', '”', '“', '“', '„', '´' ), '_', $phrase );
181
  $phrase = htmlspecialchars( $phrase );
182
- $phrase = esc_sql( $phrase );
 
 
 
 
 
 
 
 
 
 
183
 
184
  $excerpt = '';
185
  if ( 'on' === $excerpts ) {
179
  $phrase = $wpdb->esc_like( $phrase );
180
  $phrase = str_replace( array( '‘', '’', "'", '"', '”', '“', '“', '„', '´' ), '_', $phrase );
181
  $phrase = htmlspecialchars( $phrase );
182
+
183
+ /**
184
+ * Filters each phrase before it's passed through esc_sql() and used in
185
+ * the MySQL query. You can use this filter hook to for example run
186
+ * htmlentities() on the phrase in case your database needs that.
187
+ *
188
+ * @param string $phrase The phrase after quotes are replaced with a
189
+ * MySQL wild card and the phrase has been passed through esc_like() and
190
+ * htmlspecialchars().
191
+ */
192
+ $phrase = esc_sql( apply_filters( 'relevanssi_phrase', $phrase ) );
193
 
194
  $excerpt = '';
195
  if ( 'on' === $excerpts ) {
lib/search.php CHANGED
@@ -50,16 +50,6 @@ function relevanssi_query( $posts, $query = false ) {
50
  $search_ok = false; // No search term.
51
  }
52
 
53
- $indexed_post_types = array_flip(
54
- get_option( 'relevanssi_index_post_types', array() )
55
- );
56
- $images_indexed = get_option( 'relevanssi_index_image_files', 'off' );
57
- if ( $search_ok && ( false === isset( $indexed_post_types['attachment'] ) || 'off' === $images_indexed ) ) {
58
- if ( 'attachment' === $query->query_vars['post_type'] && 'inherit,private' === $query->query_vars['post_status'] ) {
59
- $search_ok = false;
60
- }
61
- }
62
-
63
  if ( $query->get( 'relevanssi' ) ) {
64
  $search_ok = true; // Manual override, always search.
65
  }
@@ -114,8 +104,7 @@ function relevanssi_query( $posts, $query = false ) {
114
  * @return array An array of return values.
115
  */
116
  function relevanssi_search( $args ) {
117
- global $wpdb, $relevanssi_variables;
118
- $relevanssi_table = $relevanssi_variables['relevanssi_table'];
119
 
120
  /**
121
  * Filters the search parameters.
@@ -150,6 +139,9 @@ function relevanssi_search( $args ) {
150
  $terms = relevanssi_tokenize( $q, $remove_stopwords, $min_length );
151
  $terms = array_keys( $terms ); // Don't care about tf in query.
152
 
 
 
 
153
  if ( function_exists( 'relevanssi_process_terms' ) ) {
154
  $process_terms_results = relevanssi_process_terms( $terms, $q );
155
  $query_restrictions .= $process_terms_results['query_restrictions'];
@@ -192,139 +184,31 @@ function relevanssi_search( $args ) {
192
  }
193
  }
194
 
195
- $total_hits = 0;
196
-
197
- $title_matches = array();
198
- $tag_matches = array();
199
- $comment_matches = array();
200
- $link_matches = array();
201
- $body_matches = array();
202
- $category_matches = array();
203
- $taxonomy_matches = array();
204
- $customfield_matches = array();
205
- $mysqlcolumn_matches = array();
206
- $author_matches = array();
207
- $excerpt_matches = array();
208
  $term_hits = array();
209
-
210
- $fuzzy = get_option( 'relevanssi_fuzzy' );
211
-
212
- $no_matches = true;
213
-
214
- $post_type_weights = get_option( 'relevanssi_post_type_weights' );
215
-
216
- $recency_bonus = false;
217
- $recency_cutoff_date = false;
218
- if ( function_exists( 'relevanssi_get_recency_bonus' ) ) {
219
- $recency_details = relevanssi_get_recency_bonus();
220
- $recency_bonus = $recency_details['bonus'];
221
- $recency_cutoff_date = $recency_details['cutoff'];
222
- }
223
-
224
- $exact_match_bonus = false;
225
- if ( 'on' === get_option( 'relevanssi_exact_match_bonus' ) ) {
226
- $exact_match_bonus = true;
227
- /**
228
- * Filters the exact match bonus.
229
- *
230
- * @param array The title bonus under 'title' (default 5) and the content
231
- * bonus under 'content' (default 2).
232
- */
233
- $exact_match_boost = apply_filters(
234
- 'relevanssi_exact_match_bonus',
235
- array(
236
- 'title' => 5,
237
- 'content' => 2,
238
- )
239
- );
240
-
241
- }
242
-
243
- $search_again = false;
244
-
245
- $content_boost = floatval( get_option( 'relevanssi_content_boost', 1 ) ); // Default value, because this option was added late.
246
- $title_boost = floatval( get_option( 'relevanssi_title_boost' ) );
247
- $link_boost = floatval( get_option( 'relevanssi_link_boost' ) );
248
- $comment_boost = floatval( get_option( 'relevanssi_comment_boost' ) );
249
-
250
- $tag = $relevanssi_variables['post_type_weight_defaults']['post_tag'];
251
- $cat = $relevanssi_variables['post_type_weight_defaults']['category'];
252
-
253
- if ( ! empty( $post_type_weights['post_tagged_with_post_tag'] ) ) {
254
- $tag = $post_type_weights['post_tagged_with_post_tag'];
255
- }
256
- if ( ! empty( $post_type_weights['post_tagged_with_category'] ) ) {
257
- $cat = $post_type_weights['post_tagged_with_category'];
258
- }
259
-
260
- // Legacy code, improvement introduced in 2.1.8, remove at some point.
261
- // phpcs:ignore Squiz.Commenting.InlineComment
262
- // @codeCoverageIgnoreStart
263
- if ( ! empty( $post_type_weights['post_tag'] ) ) {
264
- $tag = $post_type_weights['post_tag'];
265
- }
266
- if ( ! empty( $post_type_weights['category'] ) ) {
267
- $cat = $post_type_weights['category'];
268
- }
269
- // @codeCoverageIgnoreEnd
270
- /* End legacy code. */
271
-
272
  $include_these_posts = array();
273
  $include_these_items = array();
274
- $df_counts = array();
275
  $doc_weight = array();
 
 
 
 
 
276
 
277
  do {
278
- foreach ( $terms as $term ) {
279
- $term_cond = relevanssi_generate_term_where( $term, $search_again, $no_terms );
280
- if ( null === $term_cond ) {
281
- continue;
282
- }
283
-
284
- $this_query_restrictions = relevanssi_add_phrase_restrictions(
285
- $query_restrictions,
286
- $phrase_queries,
287
- $term,
288
- $operator
289
- );
290
-
291
- $query = "SELECT COUNT(DISTINCT(relevanssi.doc)) FROM $relevanssi_table AS relevanssi
292
- $query_join WHERE $term_cond $this_query_restrictions";
293
- // Clean: $this_query_restrictions is escaped, $term_cond is escaped.
294
- /**
295
- * Filters the DF query.
296
- *
297
- * This query is used to calculate the df for the tf * idf calculations.
298
- *
299
- * @param string MySQL query to filter.
300
- */
301
- $query = apply_filters( 'relevanssi_df_query_filter', $query );
302
-
303
- $df = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
304
-
305
- if ( $df < 1 && 'sometimes' === $fuzzy ) {
306
- $term_cond = relevanssi_generate_term_where( $term, true, $no_terms );
307
- $query = "
308
- SELECT COUNT(DISTINCT(relevanssi.doc))
309
- FROM $relevanssi_table AS relevanssi
310
- $query_join WHERE $term_cond $query_restrictions";
311
- // Clean: $query_restrictions is escaped, $term is escaped.
312
- /** Documented in lib/search.php. */
313
- $query = apply_filters( 'relevanssi_df_query_filter', $query );
314
- $df = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
315
- }
316
-
317
- $df_counts[ $term ] = $df;
318
- }
319
-
320
- // Sort the terms in ascending DF order, so that rarest terms are searched
321
- // for first. This is to make sure the throttle doesn't cut off posts with
322
- // rare search terms.
323
- asort( $df_counts );
324
 
325
  foreach ( $df_counts as $term => $df ) {
326
- $term_cond = relevanssi_generate_term_where( $term, $search_again, $no_terms );
327
-
328
  $this_query_restrictions = relevanssi_add_phrase_restrictions(
329
  $query_restrictions,
330
  $phrase_queries,
@@ -332,84 +216,27 @@ function relevanssi_search( $args ) {
332
  $operator
333
  );
334
 
335
- $query = "SELECT DISTINCT(relevanssi.doc), relevanssi.*, relevanssi.title * $title_boost +
336
- relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
337
- relevanssi.tag * $tag + relevanssi.link * $link_boost +
338
- relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
339
- relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
340
- FROM $relevanssi_table AS relevanssi $query_join WHERE $term_cond $this_query_restrictions";
341
- /** Clean: $this_query_restrictions is escaped, $term_cond is escaped. */
342
-
343
- /**
344
- * Filters the Relevanssi MySQL query.
345
- *
346
- * The last chance to filter the MySQL query before it is run.
347
- *
348
- * @param string MySQL query for the Relevanssi search.
349
- */
350
- $query = apply_filters( 'relevanssi_query_filter', $query );
351
  $matches = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
 
352
  if ( count( $matches ) < 1 ) {
353
  continue;
354
- } else {
355
- $no_matches = false;
356
- if ( count( $include_these_posts ) > 0 ) {
357
- $existing_ids = array();
358
- foreach ( $matches as $match ) {
359
- $existing_ids[] = $match->doc;
360
- }
361
- $existing_ids = array_keys( array_flip( $existing_ids ) );
362
- $added_post_ids = array_diff( array_keys( $include_these_posts ), $existing_ids );
363
- if ( count( $added_post_ids ) > 0 ) {
364
- $offset = 0;
365
- $slice_length = 20;
366
- $total_ids = count( $added_post_ids );
367
- do {
368
- $current_slice = array_slice( $added_post_ids, $offset, $slice_length );
369
- $post_ids_to_add = implode( ',', $current_slice );
370
- if ( ! empty( $post_ids_to_add ) ) {
371
- $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
372
- relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
373
- relevanssi.tag * $tag + relevanssi.link * $link_boost +
374
- relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
375
- relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
376
- FROM $relevanssi_table AS relevanssi WHERE relevanssi.doc IN ($post_ids_to_add)
377
- AND $term_cond";
378
-
379
- // Clean: no unescaped user inputs.
380
- $matches_to_add = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
381
- $matches = array_merge( $matches, $matches_to_add );
382
- }
383
- $offset += $slice_length;
384
- } while ( $offset <= $total_ids );
385
- }
386
- }
387
- if ( count( $include_these_items ) > 0 ) {
388
- $existing_items = array();
389
- foreach ( $matches as $match ) {
390
- if ( 0 !== intval( $match->item ) ) {
391
- $existing_items[] = $match->item;
392
- }
393
- }
394
- $existing_items = array_keys( array_flip( $existing_items ) );
395
- $items_to_add = implode( ',', array_diff( array_keys( $include_these_items ), $existing_items ) );
396
-
397
- if ( ! empty( $items_to_add ) ) {
398
- $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
399
- relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
400
- relevanssi.tag * $tag + relevanssi.link * $link_boost +
401
- relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
402
- relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
403
- FROM $relevanssi_table AS relevanssi WHERE relevanssi.item IN ($items_to_add)
404
- AND $term_cond";
405
-
406
- // Clean: no unescaped user inputs.
407
- $matches_to_add = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
408
- $matches = array_merge( $matches, $matches_to_add );
409
- }
410
- }
411
  }
412
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
  relevanssi_populate_array( $matches );
414
 
415
  $total_hits += count( $matches );
@@ -421,162 +248,31 @@ function relevanssi_search( $args ) {
421
  }
422
 
423
  foreach ( $matches as $match ) {
424
- if ( 'user' === $match->type ) {
425
- $match->doc = 'u_' . $match->item;
426
- } elseif ( 'post_type' === $match->type ) {
427
- $match->doc = 'p_' . $match->item;
428
- } elseif ( ! in_array( $match->type, array( 'post', 'attachment' ), true ) ) {
429
- $match->doc = '**' . $match->type . '**' . $match->item;
430
- }
431
-
432
- if ( ! empty( $match->taxonomy_detail ) ) {
433
- relevanssi_taxonomy_score( $match, $post_type_weights );
434
- } else {
435
- $tag_weight = 1;
436
- if ( isset( $post_type_weights['post_tagged_with_post_tag'] ) && is_numeric( $post_type_weights['post_tagged_with_post_tag'] ) ) {
437
- $tag_weight = $post_type_weights['post_tagged_with_post_tag'];
438
- }
439
-
440
- $category_weight = 1;
441
- if ( isset( $post_type_weights['post_tagged_with_category'] ) && is_numeric( $post_type_weights['post_tagged_with_category'] ) ) {
442
- $category_weight = $post_type_weights['post_tagged_with_category'];
443
- }
444
-
445
- // Legacy code from 2.1.8. Remove at some point.
446
- // phpcs:ignore Squiz.Commenting.InlineComment
447
- // @codeCoverageIgnoreStart
448
- if ( isset( $post_type_weights['post_tag'] ) && is_numeric( $post_type_weights['post_tag'] ) ) {
449
- $tag_weight = $post_type_weights['post_tag'];
450
- }
451
-
452
- $category_weight = 1;
453
- if ( isset( $post_type_weights['category'] ) && is_numeric( $post_type_weights['category'] ) ) {
454
- $category_weight = $post_type_weights['category'];
455
- }
456
- // @codeCoverageIgnoreEnd
457
- /* End legacy code. */
458
-
459
- $taxonomy_weight = 1;
460
-
461
- $match->taxonomy_score =
462
- $match->tag * $tag_weight +
463
- $match->category * $category_weight +
464
- $match->taxonomy * $taxonomy_weight;
465
- }
466
-
467
- $match->tf =
468
- $match->title * $title_boost +
469
- $match->content * $content_boost +
470
- $match->comment * $comment_boost +
471
- $match->link * $link_boost +
472
- $match->author +
473
- $match->excerpt +
474
- $match->taxonomy_score +
475
- $match->customfield +
476
- $match->mysqlcolumn;
477
-
478
- $term_hits[ $match->doc ][ $term ] =
479
- $match->title +
480
- $match->content +
481
- $match->comment +
482
- $match->tag +
483
- $match->link +
484
- $match->author +
485
- $match->category +
486
- $match->excerpt +
487
- $match->taxonomy +
488
- $match->customfield +
489
- $match->mysqlcolumn;
490
-
491
- $match->weight = $match->tf * $idf;
492
-
493
- if ( $recency_bonus ) {
494
- $post = relevanssi_get_post( $match->doc );
495
- if ( strtotime( $post->post_date ) > $recency_cutoff_date ) {
496
- $match->weight = $match->weight * $recency_bonus;
497
- }
498
- }
499
-
500
- if ( $exact_match_bonus ) {
501
- $post = relevanssi_get_post( $match->doc );
502
- $clean_q = relevanssi_remove_quotes( $q_no_synonyms );
503
- if ( $post && $clean_q ) {
504
- if ( stristr( $post->post_title, $clean_q ) !== false ) {
505
- $match->weight *= $exact_match_boost['title'];
506
- }
507
- if ( stristr( $post->post_content, $clean_q ) !== false ) {
508
- $match->weight *= $exact_match_boost['content'];
509
- }
510
- }
511
- }
512
-
513
- if ( ! isset( $body_matches[ $match->doc ] ) ) {
514
- $body_matches[ $match->doc ] = 0;
515
- }
516
- if ( ! isset( $title_matches[ $match->doc ] ) ) {
517
- $title_matches[ $match->doc ] = 0;
518
- }
519
- if ( ! isset( $link_matches[ $match->doc ] ) ) {
520
- $link_matches[ $match->doc ] = 0;
521
- }
522
- if ( ! isset( $tag_matches[ $match->doc ] ) ) {
523
- $tag_matches[ $match->doc ] = 0;
524
- }
525
- if ( ! isset( $category_matches[ $match->doc ] ) ) {
526
- $category_matches[ $match->doc ] = 0;
527
- }
528
- if ( ! isset( $taxonomy_matches[ $match->doc ] ) ) {
529
- $taxonomy_matches[ $match->doc ] = 0;
530
- }
531
- if ( ! isset( $comment_matches[ $match->doc ] ) ) {
532
- $comment_matches[ $match->doc ] = 0;
533
- }
534
- if ( ! isset( $customfield_matches[ $match->doc ] ) ) {
535
- $customfield_matches[ $match->doc ] = 0;
536
- }
537
- if ( ! isset( $author_matches[ $match->doc ] ) ) {
538
- $author_matches[ $match->doc ] = 0;
539
- }
540
- if ( ! isset( $excerpt_matches[ $match->doc ] ) ) {
541
- $excerpt_matches[ $match->doc ] = 0;
542
- }
543
- if ( ! isset( $mysqlcolumn_matches[ $match->doc ] ) ) {
544
- $mysqlcolumn_matches[ $match->doc ] = 0;
545
- }
546
- $body_matches[ $match->doc ] += $match->content;
547
- $title_matches[ $match->doc ] += $match->title;
548
- $link_matches[ $match->doc ] += $match->link;
549
- $tag_matches[ $match->doc ] += $match->tag;
550
- $category_matches[ $match->doc ] += $match->category;
551
- $taxonomy_matches[ $match->doc ] += $match->taxonomy;
552
- $comment_matches[ $match->doc ] += $match->comment;
553
- $customfield_matches[ $match->doc ] += $match->customfield;
554
- $author_matches[ $match->doc ] += $match->author;
555
- $excerpt_matches[ $match->doc ] += $match->excerpt;
556
- $mysqlcolumn_matches[ $match->doc ] += $match->mysqlcolumn;
557
-
558
- /* Post type weights. */
559
- $type = relevanssi_get_post_type( $match->doc );
560
- if ( ! empty( $post_type_weights[ $type ] ) ) {
561
- $match->weight = $match->weight * $post_type_weights[ $type ];
562
- }
563
-
564
- /* Weight boost for taxonomy terms based on taxonomy. */
565
- if ( ! empty( $post_type_weights[ 'taxonomy_term_' . $match->type ] ) ) {
566
- $match->weight = $match->weight * $post_type_weights[ 'taxonomy_term_' . $match->type ];
567
- }
568
 
569
  /**
570
- * Filters the hit.
 
 
 
 
 
571
  *
572
- * This filter hook can be used to adjust the weights of found hits.
573
- * Calculate the new weight and set the $match->weight to the new
574
- * value.
 
 
 
 
575
  *
576
- * @param object $match The match object.
577
- * @param int $idf The IDF value, if you want to recalculate
578
- * TF * IDF values (TF is in $match->tf).
579
- * @param string $term The current search term.
 
580
  */
581
  $match = apply_filters( 'relevanssi_match', $match, $idf, $term );
582
  if ( $match->weight <= 0 ) {
@@ -597,6 +293,8 @@ function relevanssi_search( $args ) {
597
  */
598
  $post_ok = apply_filters( 'relevanssi_post_ok', $post_ok, $match->doc );
599
  if ( $post_ok ) {
 
 
600
  $doc_terms[ $match->doc ][ $term ] = true; // Count how many terms are matched to a doc.
601
  if ( ! isset( $doc_weight[ $match->doc ] ) ) {
602
  $doc_weight[ $match->doc ] = 0;
@@ -635,9 +333,9 @@ function relevanssi_search( $args ) {
635
  /**
636
  * Filters the parameters for fallback search.
637
  *
638
- * If you want to make Relevanssi search again with different parameters, you
639
- * can use this filter hook to adjust the parameters. Set
640
- * $params['search_again'] to true to make Relevanssi do a new search.
641
  *
642
  * @param array The search parameters.
643
  */
@@ -658,6 +356,14 @@ function relevanssi_search( $args ) {
658
  }
659
  $total_terms = count( $terms_without_stops );
660
 
 
 
 
 
 
 
 
 
661
  if ( isset( $doc_weight ) ) {
662
  /**
663
  * Filters the results Relevanssi finds.
@@ -670,6 +376,8 @@ function relevanssi_search( $args ) {
670
  $doc_weight = apply_filters( 'relevanssi_results', $doc_weight );
671
  }
672
 
 
 
673
  if ( isset( $doc_weight ) && count( $doc_weight ) > 0 ) {
674
  arsort( $doc_weight );
675
  $i = 0;
@@ -679,21 +387,36 @@ function relevanssi_search( $args ) {
679
  // doc didn't match all terms, so it's discarded.
680
  continue;
681
  }
 
 
 
 
 
 
 
 
 
 
 
 
682
 
683
  if ( ! empty( $fields ) ) {
684
  if ( 'ids' === $fields ) {
685
  $hits[ intval( $i ) ] = $doc;
686
  }
687
  if ( 'id=>parent' === $fields ) {
688
- $object = new StdClass();
689
- $object->ID = $doc;
690
- $object->post_parent = wp_get_post_parent_id( $doc );
691
-
692
- $hits[ intval( $i ) ] = $object;
693
  }
694
  } else {
695
  $hits[ intval( $i ) ] = relevanssi_get_post( $doc );
696
  $hits[ intval( $i ) ]->relevance_score = round( $weight, 2 );
 
 
 
 
697
  }
698
  $i++;
699
  }
@@ -710,21 +433,21 @@ function relevanssi_search( $args ) {
710
  $or_args['q'] = relevanssi_add_synonyms( $q );
711
  $return = relevanssi_search( $or_args );
712
 
713
- $hits = $return['hits'];
714
- $body_matches = $return['body_matches'];
715
- $title_matches = $return['title_matches'];
716
- $tag_matches = $return['tag_matches'];
717
- $category_matches = $return['category_matches'];
718
- $taxonomy_matches = $return['taxonomy_matches'];
719
- $comment_matches = $return['comment_matches'];
720
- $link_matches = $return['link_matches'];
721
- $author_matches = $return['author_matches'];
722
- $customfield_matches = $return['customfield_matches'];
723
- $mysqlcolumn_matches = $return['mysqlcolumn_matches'];
724
- $excerpt_matches = $return['excerpt_matches'];
725
- $term_hits = $return['term_hits'];
726
- $doc_weight = $return['doc_weights'];
727
- $q = $return['query'];
728
  }
729
  $params = array( 'args' => $args );
730
  /**
@@ -739,84 +462,45 @@ function relevanssi_search( $args ) {
739
  $params = apply_filters( 'relevanssi_fallback', $params );
740
  $args = $params['args'];
741
  if ( isset( $params['return'] ) ) {
742
- $return = $params['return'];
743
- $hits = $return['hits'];
744
- $body_matches = $return['body_matches'];
745
- $title_matches = $return['title_matches'];
746
- $tag_matches = $return['tag_matches'];
747
- $category_matches = $return['category_matches'];
748
- $taxonomy_matches = $return['taxonomy_matches'];
749
- $comment_matches = $return['comment_matches'];
750
- $link_matches = $return['link_matches'];
751
- $author_matches = $return['author_matches'];
752
- $customfield_matches = $return['customfield_matches'];
753
- $mysqlcolumn_matches = $return['mysqlcolumn_matches'];
754
- $excerpt_matches = $return['excerpt_matches'];
755
- $term_hits = $return['term_hits'];
756
- $doc_weight = $return['doc_weights'];
757
- $q = $return['query'];
758
  }
759
  }
760
 
761
- $default_order = get_option( 'relevanssi_default_orderby', 'relevance' );
762
- if ( empty( $orderby ) ) {
763
- $orderby = $default_order;
764
- }
765
- if ( is_array( $orderby ) ) {
766
- /**
767
- * Filters the 'orderby' value just before sorting.
768
- *
769
- * Relevanssi can use both array orderby ie. array( orderby => order )
770
- * with multiple orderby parameters, or a single pair of orderby and
771
- * order parameters. To avoid problems, try sticking to one and don't
772
- * use this filter to make surprising changes between different formats.
773
- *
774
- * @param string The 'orderby' parameter.
775
- */
776
- $orderby = apply_filters( 'relevanssi_orderby', $orderby );
777
- relevanssi_object_sort( $hits, $orderby, $meta_query );
778
- } else {
779
- if ( empty( $order ) ) {
780
- $order = 'desc';
781
- }
782
- $order = strtolower( $order );
783
-
784
- $order_accepted_values = array( 'asc', 'desc' );
785
- if ( ! in_array( $order, $order_accepted_values, true ) ) {
786
- $order = 'desc';
787
- }
788
-
789
- /** Documented in lib/search.php. */
790
- $orderby = apply_filters( 'relevanssi_orderby', $orderby );
791
- /**
792
- * Filters the 'order' value just before sorting.
793
- *
794
- * @param string The 'order' parameter.
795
- */
796
- $order = apply_filters( 'relevanssi_order', $order );
797
 
798
- if ( 'relevance' !== $orderby ) {
799
- $orderby_array = array( $orderby => $order );
800
- relevanssi_object_sort( $hits, $orderby_array, $meta_query );
801
- }
802
- }
803
  $return = array(
804
  'hits' => $hits,
805
- 'body_matches' => $body_matches,
806
- 'title_matches' => $title_matches,
807
- 'tag_matches' => $tag_matches,
808
- 'category_matches' => $category_matches,
809
- 'comment_matches' => $comment_matches,
810
- 'taxonomy_matches' => $taxonomy_matches,
811
- 'link_matches' => $link_matches,
812
- 'customfield_matches' => $customfield_matches,
813
- 'mysqlcolumn_matches' => $mysqlcolumn_matches,
814
- 'author_matches' => $author_matches,
815
- 'excerpt_matches' => $excerpt_matches,
816
  'term_hits' => $term_hits,
817
  'query' => $q,
818
  'doc_weights' => $doc_weight,
819
  'query_no_synonyms' => $q_no_synonyms,
 
820
  );
821
 
822
  return $return;
@@ -928,91 +612,40 @@ function relevanssi_do_query( &$query ) {
928
  relevanssi_update_log( $query_string, $hits_count );
929
  }
930
 
931
- $make_excerpts = get_option( 'relevanssi_excerpts' );
932
  if ( $relevanssi_test_admin || ( $query->is_admin && ! defined( 'DOING_AJAX' ) ) ) {
933
  $make_excerpts = false;
934
  }
935
 
936
- if ( isset( $query->query_vars['paged'] ) && $query->query_vars['paged'] > 0 ) {
937
- $search_low_boundary = ( $query->query_vars['paged'] - 1 ) * $query->query_vars['posts_per_page'];
938
- } else {
939
- $search_low_boundary = 0;
940
- }
941
-
942
- if ( ! isset( $query->query_vars['posts_per_page'] ) || -1 === $query->query_vars['posts_per_page'] ) {
943
- $search_high_boundary = $hits_count;
944
- } else {
945
- $search_high_boundary = $search_low_boundary + $query->query_vars['posts_per_page'] - 1;
946
- }
947
-
948
- if ( isset( $query->query_vars['offset'] ) && $query->query_vars['offset'] > 0 ) {
949
- $search_high_boundary += $query->query_vars['offset'];
950
- $search_low_boundary += $query->query_vars['offset'];
951
- }
952
-
953
- if ( $search_high_boundary > $hits_count ) {
954
- $search_high_boundary = $hits_count;
955
- }
956
-
957
- for ( $i = $search_low_boundary; $i <= $search_high_boundary; $i++ ) {
958
- if ( isset( $hits[ intval( $i ) ] ) ) {
959
- $post = $hits[ intval( $i ) ];
960
- } else {
961
- continue;
962
- }
963
 
964
- if ( null === $post ) {
965
- // @codeCoverageIgnoreStart
966
- // Sometimes you can get a null object.
967
- continue;
968
- // @codeCoverageIgnoreEnd
969
- }
970
 
971
- if ( 'on' === get_option( 'relevanssi_hilite_title' ) && empty( $search_params['fields'] ) ) {
972
- $post->post_highlighted_title = wp_strip_all_tags( $post->post_title );
973
- $highlight = get_option( 'relevanssi_highlight' );
974
- if ( 'none' !== $highlight ) {
975
- if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
976
- $q_for_highlight = 'on' === get_option( 'relevanssi_index_synonyms', 'off' )
977
- ? relevanssi_add_synonyms( $q )
978
- : $q;
979
-
980
- $post->post_highlighted_title = relevanssi_highlight_terms(
981
- $post->post_highlighted_title,
982
- $q_for_highlight
983
- );
984
- }
985
- }
986
  }
987
-
988
- /*
989
- * If you need to modify these on the go, use
990
- * 'pre_option_relevanssi_excerpt_length' and
991
- * pre_option_relevanssi_excerpt_type' filters.
992
- */
993
- $excerpt_length = get_option( 'relevanssi_excerpt_length' );
994
- $excerpt_type = get_option( 'relevanssi_excerpt_type' );
995
-
996
- if ( 'on' === $make_excerpts && empty( $search_params['fields'] ) ) {
997
- if ( isset( $post->blog_id ) ) {
998
- switch_to_blog( $post->blog_id );
999
- }
1000
- $post->original_excerpt = $post->post_excerpt;
1001
- $post->post_excerpt = relevanssi_do_excerpt(
1002
- $post,
1003
- $q,
1004
- $excerpt_length,
1005
- $excerpt_type
1006
- );
1007
-
1008
- if ( isset( $post->blog_id ) ) {
1009
- restore_current_blog();
1010
- }
1011
  }
1012
- if ( empty( $search_params['fields'] ) ) {
1013
  relevanssi_add_matches( $post, $return );
1014
  }
1015
- if ( 'on' === get_option( 'relevanssi_show_matches' ) && empty( $search_params['fields'] ) ) {
1016
  $post->post_excerpt .= relevanssi_show_matches( $post );
1017
  }
1018
 
@@ -1066,9 +699,8 @@ function relevanssi_limit_filter( $query ) {
1066
  /**
1067
  * Fetches the list of post types that are excluded from the search.
1068
  *
1069
- * Figures out the post types that are not included in the search.
1070
- *
1071
- * Tested.
1072
  *
1073
  * @param string $include_attachments Whether to include attachments or not.
1074
  *
@@ -1095,6 +727,12 @@ function relevanssi_get_negative_post_type( $include_attachments ) {
1095
  $negative_post_type_list = array_merge( $negative_post_type_list, $pt_1, $pt_2 );
1096
  }
1097
 
 
 
 
 
 
 
1098
  // Post types to exclude.
1099
  if ( count( $negative_post_type_list ) > 0 ) {
1100
  $negative_post_types = esc_sql( array_unique( $negative_post_type_list ) );
@@ -1241,6 +879,8 @@ function relevanssi_taxonomy_score( &$match, $post_type_weights ) {
1241
  function relevanssi_compile_search_args( $query, $q ) {
1242
  global $relevanssi_test_admin;
1243
 
 
 
1244
  $tax_query = array();
1245
  /**
1246
  * Filters the default tax_query relation.
@@ -1374,51 +1014,11 @@ function relevanssi_compile_search_args( $query, $q ) {
1374
  $parent_query = array( 'parent not in' => $query->query_vars['post_parent__not_in'] );
1375
  }
1376
 
1377
- $meta_query = relevanssi_meta_query_from_query_vars( $query );
1378
- $date_query = relevanssi_wp_date_query_from_query_vars( $query );
1379
-
1380
- $post_type = false;
1381
- if ( isset( $query->query_vars['post_type'] ) && 'any' !== $query->query_vars['post_type'] ) {
1382
- $post_type = $query->query_vars['post_type'];
1383
- }
1384
- if ( isset( $query->query_vars['post_types'] ) && 'any' !== $query->query_vars['post_types'] ) {
1385
- $post_type = $query->query_vars['post_types'];
1386
- }
1387
-
1388
- $post_status = false;
1389
- if ( isset( $query->query_vars['post_status'] ) && 'any' !== $query->query_vars['post_status'] ) {
1390
- $post_status = $query->query_vars['post_status'];
1391
- }
1392
-
1393
  $expost = get_option( 'relevanssi_exclude_posts' );
1394
  if ( $relevanssi_test_admin || ( is_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) ) {
1395
  $expost = null;
1396
  }
1397
 
1398
- $sentence = false;
1399
- if ( isset( $query->query_vars['sentence'] ) && ! empty( $query->query_vars['sentence'] ) ) {
1400
- $sentence = true;
1401
- }
1402
-
1403
- $operator = '';
1404
- if ( function_exists( 'relevanssi_set_operator' ) ) {
1405
- $operator = relevanssi_set_operator( $query );
1406
- $operator = strtoupper( $operator );
1407
- }
1408
- if ( ! in_array( $operator, array( 'OR', 'AND' ), true ) ) {
1409
- $operator = get_option( 'relevanssi_implicit_operator' );
1410
- }
1411
- $query->query_vars['operator'] = $operator;
1412
-
1413
- $orderby = null;
1414
- $order = null;
1415
- if ( isset( $query->query_vars['orderby'] ) ) {
1416
- $orderby = $query->query_vars['orderby'];
1417
- }
1418
- if ( isset( $query->query_vars['order'] ) ) {
1419
- $order = $query->query_vars['order'];
1420
- }
1421
-
1422
  $fields = '';
1423
  if ( ! empty( $query->query_vars['fields'] ) ) {
1424
  if ( 'ids' === $query->query_vars['fields'] ) {
@@ -1427,26 +1027,11 @@ function relevanssi_compile_search_args( $query, $q ) {
1427
  if ( 'id=>parent' === $query->query_vars['fields'] ) {
1428
  $fields = 'id=>parent';
1429
  }
1430
- }
1431
-
1432
- $by_date = '';
1433
- if ( ! empty( $query->query_vars['by_date'] ) ) {
1434
- if ( preg_match( '/\d+[hdmyw]/', $query->query_vars['by_date'] ) ) {
1435
- // Accepted format is digits followed by h, d, m, y, or w.
1436
- $by_date = $query->query_vars['by_date'];
1437
  }
1438
  }
1439
 
1440
- $admin_search = false;
1441
- if ( isset( $query->query_vars['relevanssi_admin_search'] ) ) {
1442
- $admin_search = true;
1443
- }
1444
-
1445
- $include_attachments = '';
1446
- if ( isset( $query->query_vars['include_attachments'] ) ) {
1447
- $include_attachments = $query->query_vars['include_attachments'];
1448
- }
1449
-
1450
  if ( function_exists( 'relevanssi_extract_specifier' ) ) {
1451
  $q = relevanssi_extract_specifier( $q );
1452
  }
@@ -1454,33 +1039,26 @@ function relevanssi_compile_search_args( $query, $q ) {
1454
  // Add synonyms.
1455
  // This is done here so the new terms will get highlighting.
1456
  $q_no_synonyms = $q;
1457
- if ( 'OR' === $operator ) {
1458
  // Synonyms are only used in OR queries.
1459
  $q = relevanssi_add_synonyms( $q );
1460
  }
1461
 
1462
- $search_params = array(
1463
- 'q' => $q,
1464
- 'q_no_synonyms' => $q_no_synonyms,
1465
- 'tax_query' => $tax_query,
1466
- 'tax_query_relation' => $tax_query_relation,
1467
- 'post_query' => $post_query,
1468
- 'parent_query' => $parent_query,
1469
- 'meta_query' => $meta_query,
1470
- 'date_query' => $date_query,
1471
- 'expost' => $expost,
1472
- 'post_type' => $post_type,
1473
- 'post_status' => $post_status,
1474
- 'operator' => $operator,
1475
- 'author' => $author,
1476
- 'orderby' => $orderby,
1477
- 'order' => $order,
1478
- 'fields' => $fields,
1479
- 'sentence' => $sentence,
1480
- 'by_date' => $by_date,
1481
- 'admin_search' => $admin_search,
1482
- 'include_attachments' => $include_attachments,
1483
- 'meta_query' => $meta_query,
1484
  );
1485
 
1486
  return $search_params;
@@ -1616,3 +1194,656 @@ function relevanssi_meta_query_from_query_vars( $query ) {
1616
  }
1617
  return $meta_query;
1618
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  $search_ok = false; // No search term.
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
53
  if ( $query->get( 'relevanssi' ) ) {
54
  $search_ok = true; // Manual override, always search.
55
  }
104
  * @return array An array of return values.
105
  */
106
  function relevanssi_search( $args ) {
107
+ global $wpdb;
 
108
 
109
  /**
110
  * Filters the search parameters.
139
  $terms = relevanssi_tokenize( $q, $remove_stopwords, $min_length );
140
  $terms = array_keys( $terms ); // Don't care about tf in query.
141
 
142
+ $terms_without_synonyms = relevanssi_tokenize( $q_no_synonyms, $remove_stopwords, $min_length );
143
+ $terms_without_synonyms = array_keys( $terms_without_synonyms );
144
+
145
  if ( function_exists( 'relevanssi_process_terms' ) ) {
146
  $process_terms_results = relevanssi_process_terms( $terms, $q );
147
  $query_restrictions .= $process_terms_results['query_restrictions'];
184
  }
185
  }
186
 
187
+ $match_arrays = relevanssi_initialize_match_arrays();
 
 
 
 
 
 
 
 
 
 
 
 
188
  $term_hits = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  $include_these_posts = array();
190
  $include_these_items = array();
 
191
  $doc_weight = array();
192
+ $total_hits = 0;
193
+ $no_matches = true;
194
+ $search_again = false;
195
+ $post_type_weights = get_option( 'relevanssi_post_type_weights' );
196
+ $fuzzy = get_option( 'relevanssi_fuzzy' );
197
 
198
  do {
199
+ $df_counts = relevanssi_generate_df_counts(
200
+ $terms,
201
+ array(
202
+ 'no_terms' => $no_terms,
203
+ 'operator' => $operator,
204
+ 'phrase_queries' => $phrase_queries,
205
+ 'query_join' => $query_join,
206
+ 'query_restrictions' => $query_restrictions,
207
+ 'search_again' => $search_again,
208
+ )
209
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
 
211
  foreach ( $df_counts as $term => $df ) {
 
 
212
  $this_query_restrictions = relevanssi_add_phrase_restrictions(
213
  $query_restrictions,
214
  $phrase_queries,
216
  $operator
217
  );
218
 
219
+ $query = relevanssi_generate_search_query( $term, $search_again, $no_terms, $query_join, $this_query_restrictions );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  $matches = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
221
+
222
  if ( count( $matches ) < 1 ) {
223
  continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  }
225
 
226
+ $no_matches = false;
227
+ relevanssi_add_include_matches(
228
+ $matches,
229
+ array(
230
+ 'posts' => $include_these_posts,
231
+ 'items' => $include_these_items,
232
+ ),
233
+ array(
234
+ 'term' => $term,
235
+ 'search_again' => $search_again,
236
+ 'no_terms' => $no_terms,
237
+ )
238
+ );
239
+
240
  relevanssi_populate_array( $matches );
241
 
242
  $total_hits += count( $matches );
248
  }
249
 
250
  foreach ( $matches as $match ) {
251
+ $match->doc = relevanssi_adjust_match_doc( $match );
252
+ $match->tf = relevanssi_calculate_tf( $match, $post_type_weights );
253
+ $match->weight = relevanssi_calculate_weight( $match, $idf, $post_type_weights, $q );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
 
255
  /**
256
+ * Filters the Relevanssi post matches.
257
+ *
258
+ * This powerful filter lets you modify the $match objects,
259
+ * which are used to calculate the weight of the documents. The
260
+ * object has attributes which contain the number of hits in
261
+ * different categories.
262
  *
263
+ * Post ID is $match->doc, term frequency (TF) is
264
+ * $match->tf and the total weight is in $match->weight. The
265
+ * filter is also passed $idf, which is the inverse document
266
+ * frequency (IDF). The weight is calculated as TF * IDF, which
267
+ * means you may need the IDF, if you wish to recalculate the
268
+ * weight for some reason. The third parameter, $term, contains
269
+ * the search term.
270
  *
271
+ * @param object $match The match object, with includes all
272
+ * the different categories of post matches.
273
+ * @param int $idf The inverse document frequency, in
274
+ * case you want to recalculate TF * IDF weights.
275
+ * @param string $term The search term.
276
  */
277
  $match = apply_filters( 'relevanssi_match', $match, $idf, $term );
278
  if ( $match->weight <= 0 ) {
293
  */
294
  $post_ok = apply_filters( 'relevanssi_post_ok', $post_ok, $match->doc );
295
  if ( $post_ok ) {
296
+ relevanssi_update_term_hits( $term_hits, $match_arrays, $match, $term );
297
+
298
  $doc_terms[ $match->doc ][ $term ] = true; // Count how many terms are matched to a doc.
299
  if ( ! isset( $doc_weight[ $match->doc ] ) ) {
300
  $doc_weight[ $match->doc ] = 0;
333
  /**
334
  * Filters the parameters for fallback search.
335
  *
336
+ * If you want to make Relevanssi search again with different
337
+ * parameters, you can use this filter hook to adjust the parameters.
338
+ * Set $params['search_again'] to true to make Relevanssi do a new search.
339
  *
340
  * @param array The search parameters.
341
  */
356
  }
357
  $total_terms = count( $terms_without_stops );
358
 
359
+ $temp_terms_without_stops_synonyms = array_keys( relevanssi_tokenize( implode( ' ', $terms_without_synonyms ), $strip_stops ) );
360
+ $terms_without_stops_synonyms = array();
361
+ foreach ( $temp_terms_without_stops_synonyms as $temp_term ) {
362
+ if ( relevanssi_strlen( $temp_term ) >= $min_length ) {
363
+ array_push( $terms_without_stops_synonyms, $temp_term );
364
+ }
365
+ }
366
+
367
  if ( isset( $doc_weight ) ) {
368
  /**
369
  * Filters the results Relevanssi finds.
376
  $doc_weight = apply_filters( 'relevanssi_results', $doc_weight );
377
  }
378
 
379
+ $missing_terms = array();
380
+
381
  if ( isset( $doc_weight ) && count( $doc_weight ) > 0 ) {
382
  arsort( $doc_weight );
383
  $i = 0;
387
  // doc didn't match all terms, so it's discarded.
388
  continue;
389
  }
390
+ if ( count( $doc_terms[ $doc ] ) < $total_terms ) {
391
+ $missing_terms[ $doc ] = array_diff(
392
+ array_values( $terms_without_stops_synonyms ),
393
+ array_keys( $doc_terms[ $doc ] )
394
+ );
395
+ if ( count( $missing_terms[ $doc ] ) === count( $terms_without_stops_synonyms ) ) {
396
+ $missing_terms[ $doc ] = array_diff(
397
+ array_values( $terms_without_stops_synonyms ),
398
+ relevanssi_replace_synonyms_in_terms( array_keys( $doc_terms[ $doc ] ) )
399
+ );
400
+ }
401
+ }
402
 
403
  if ( ! empty( $fields ) ) {
404
  if ( 'ids' === $fields ) {
405
  $hits[ intval( $i ) ] = $doc;
406
  }
407
  if ( 'id=>parent' === $fields ) {
408
+ $hits[ intval( $i ) ] = relevanssi_generate_post_parent( $doc );
409
+ }
410
+ if ( 'id=>type' === $fields ) {
411
+ $hits[ intval( $i ) ] = relevanssi_generate_id_type( $doc );
 
412
  }
413
  } else {
414
  $hits[ intval( $i ) ] = relevanssi_get_post( $doc );
415
  $hits[ intval( $i ) ]->relevance_score = round( $weight, 2 );
416
+
417
+ if ( isset( $missing_terms[ $doc ] ) ) {
418
+ $hits[ intval( $i ) ]->missing_terms = $missing_terms[ $doc ];
419
+ }
420
  }
421
  $i++;
422
  }
433
  $or_args['q'] = relevanssi_add_synonyms( $q );
434
  $return = relevanssi_search( $or_args );
435
 
436
+ $hits = $return['hits'];
437
+ $match_arrays['body'] = $return['body_matches'];
438
+ $match_arrays['title'] = $return['title_matches'];
439
+ $match_arrays['tag'] = $return['tag_matches'];
440
+ $match_arrays['category'] = $return['category_matches'];
441
+ $match_arrays['taxonomy'] = $return['taxonomy_matches'];
442
+ $match_arrays['comment'] = $return['comment_matches'];
443
+ $match_arrays['link'] = $return['link_matches'];
444
+ $match_arrays['author'] = $return['author_matches'];
445
+ $match_arrays['customfield'] = $return['customfield_matches'];
446
+ $match_arrays['mysqlcolumn'] = $return['mysqlcolumn_matches'];
447
+ $match_arrays['excerpt'] = $return['excerpt_matches'];
448
+ $term_hits = $return['term_hits'];
449
+ $doc_weight = $return['doc_weights'];
450
+ $q = $return['query'];
451
  }
452
  $params = array( 'args' => $args );
453
  /**
462
  $params = apply_filters( 'relevanssi_fallback', $params );
463
  $args = $params['args'];
464
  if ( isset( $params['return'] ) ) {
465
+ $return = $params['return'];
466
+ $hits = $return['hits'];
467
+ $match_arrays['body'] = $return['body_matches'];
468
+ $match_arrays['title'] = $return['title_matches'];
469
+ $match_arrays['tag'] = $return['tag_matches'];
470
+ $match_arrays['category'] = $return['category_matches'];
471
+ $match_arrays['taxonomy'] = $return['taxonomy_matches'];
472
+ $match_arrays['comment'] = $return['comment_matches'];
473
+ $match_arrays['link'] = $return['link_matches'];
474
+ $match_arrays['author'] = $return['author_matches'];
475
+ $match_arrays['customfield'] = $return['customfield_matches'];
476
+ $match_arrays['mysqlcolumn'] = $return['mysqlcolumn_matches'];
477
+ $match_arrays['excerpt'] = $return['excerpt_matches'];
478
+ $term_hits = $return['term_hits'];
479
+ $doc_weight = $return['doc_weights'];
480
+ $q = $return['query'];
481
  }
482
  }
483
 
484
+ relevanssi_sort_results( $hits, $orderby, $order, $meta_query );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
 
 
 
 
 
 
486
  $return = array(
487
  'hits' => $hits,
488
+ 'body_matches' => $match_arrays['body'],
489
+ 'title_matches' => $match_arrays['title'],
490
+ 'tag_matches' => $match_arrays['tag'],
491
+ 'category_matches' => $match_arrays['category'],
492
+ 'comment_matches' => $match_arrays['comment'],
493
+ 'taxonomy_matches' => $match_arrays['taxonomy'],
494
+ 'link_matches' => $match_arrays['link'],
495
+ 'customfield_matches' => $match_arrays['customfield'],
496
+ 'mysqlcolumn_matches' => $match_arrays['mysqlcolumn'],
497
+ 'author_matches' => $match_arrays['author'],
498
+ 'excerpt_matches' => $match_arrays['excerpt'],
499
  'term_hits' => $term_hits,
500
  'query' => $q,
501
  'doc_weights' => $doc_weight,
502
  'query_no_synonyms' => $q_no_synonyms,
503
+ 'missing_terms' => $missing_terms,
504
  );
505
 
506
  return $return;
612
  relevanssi_update_log( $query_string, $hits_count );
613
  }
614
 
615
+ $make_excerpts = 'on' === get_option( 'relevanssi_excerpts' ) ? true : false;
616
  if ( $relevanssi_test_admin || ( $query->is_admin && ! defined( 'DOING_AJAX' ) ) ) {
617
  $make_excerpts = false;
618
  }
619
 
620
+ list( $search_low_boundary, $search_high_boundary ) = relevanssi_get_boundaries( $query );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
621
 
622
+ $highlight_title = 'on' === get_option( 'relevanssi_hilite_title' ) ? true : false;
623
+ $show_matches = 'on' === get_option( 'relevanssi_show_matches' ) ? true : false;
624
+ $return_posts = empty( $search_params['fields'] );
 
 
 
625
 
626
+ $hits_to_show = array_slice( $hits, $search_low_boundary, $search_high_boundary - $search_low_boundary + 1 );
627
+ /**
628
+ * Filters the displayed hits.
629
+ *
630
+ * Similar to 'relevanssi_hits_filter', but only filters the posts that
631
+ * are displayed on the search results page. Don't make big changes here.
632
+ *
633
+ * @param array $hits_to_show An array of post objects.
634
+ * @param WP_Query $query The WP Query object.
635
+ *
636
+ * @return array An array of post objects.
637
+ */
638
+ foreach ( apply_filters( 'relevanssi_hits_to_show', $hits_to_show, $query ) as $post ) {
639
+ if ( $highlight_title && $return_posts ) {
640
+ relevanssi_highlight_post_title( $post, $q );
641
  }
642
+ if ( $make_excerpts && $return_posts ) {
643
+ relevanssi_add_excerpt( $post, $q );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
644
  }
645
+ if ( $return_posts ) {
646
  relevanssi_add_matches( $post, $return );
647
  }
648
+ if ( $show_matches && $return_posts ) {
649
  $post->post_excerpt .= relevanssi_show_matches( $post );
650
  }
651
 
699
  /**
700
  * Fetches the list of post types that are excluded from the search.
701
  *
702
+ * Figures out the post types that are not included in the search. Only includes
703
+ * the post types that are actually indexed.
 
704
  *
705
  * @param string $include_attachments Whether to include attachments or not.
706
  *
727
  $negative_post_type_list = array_merge( $negative_post_type_list, $pt_1, $pt_2 );
728
  }
729
 
730
+ $indexed_post_types = get_option( 'relevanssi_index_post_types', array() );
731
+ $negative_post_type_list = array_intersect(
732
+ $negative_post_type_list,
733
+ $indexed_post_types
734
+ );
735
+
736
  // Post types to exclude.
737
  if ( count( $negative_post_type_list ) > 0 ) {
738
  $negative_post_types = esc_sql( array_unique( $negative_post_type_list ) );
879
  function relevanssi_compile_search_args( $query, $q ) {
880
  global $relevanssi_test_admin;
881
 
882
+ $search_params = relevanssi_compile_common_args( $query );
883
+
884
  $tax_query = array();
885
  /**
886
  * Filters the default tax_query relation.
1014
  $parent_query = array( 'parent not in' => $query->query_vars['post_parent__not_in'] );
1015
  }
1016
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1017
  $expost = get_option( 'relevanssi_exclude_posts' );
1018
  if ( $relevanssi_test_admin || ( is_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) ) {
1019
  $expost = null;
1020
  }
1021
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1022
  $fields = '';
1023
  if ( ! empty( $query->query_vars['fields'] ) ) {
1024
  if ( 'ids' === $query->query_vars['fields'] ) {
1027
  if ( 'id=>parent' === $query->query_vars['fields'] ) {
1028
  $fields = 'id=>parent';
1029
  }
1030
+ if ( 'id=>type' === $query->query_vars['fields'] ) {
1031
+ $fields = 'id=>type';
 
 
 
 
 
1032
  }
1033
  }
1034
 
 
 
 
 
 
 
 
 
 
 
1035
  if ( function_exists( 'relevanssi_extract_specifier' ) ) {
1036
  $q = relevanssi_extract_specifier( $q );
1037
  }
1039
  // Add synonyms.
1040
  // This is done here so the new terms will get highlighting.
1041
  $q_no_synonyms = $q;
1042
+ if ( 'OR' === $search_params['operator'] ) {
1043
  // Synonyms are only used in OR queries.
1044
  $q = relevanssi_add_synonyms( $q );
1045
  }
1046
 
1047
+ $query->query_vars['operator'] = $search_params['operator'];
1048
+
1049
+ $search_params = array_merge(
1050
+ $search_params,
1051
+ array(
1052
+ 'q' => $q,
1053
+ 'q_no_synonyms' => $q_no_synonyms,
1054
+ 'tax_query' => $tax_query,
1055
+ 'tax_query_relation' => $tax_query_relation,
1056
+ 'post_query' => $post_query,
1057
+ 'parent_query' => $parent_query,
1058
+ 'expost' => $expost,
1059
+ 'author' => $author,
1060
+ 'fields' => $fields,
1061
+ )
 
 
 
 
 
 
 
1062
  );
1063
 
1064
  return $search_params;
1194
  }
1195
  return $meta_query;
1196
  }
1197
+
1198
+ /**
1199
+ * Checks whether Relevanssi can do a media search.
1200
+ *
1201
+ * Relevanssi does not work with the grid view of Media Gallery. This function
1202
+ * will disable Relevanssi a) if Relevanssi is not set to index attachments,
1203
+ * b) if Relevanssi is not set to index image attachments and c) if the Media
1204
+ * Library is in grid mode. Any of these will inactivate Relevanssi in the
1205
+ * Media Library search.
1206
+ *
1207
+ * @param boolean $search_ok If true, allow the search.
1208
+ * @param WP_Query $query The query object.
1209
+ *
1210
+ * @return boolean If true, allow the search.
1211
+ */
1212
+ function relevanssi_control_media_queries( bool $search_ok, WP_Query $query ) : bool {
1213
+ if ( ! $search_ok ) {
1214
+ // Something else has already disabled the search, this won't enable.
1215
+ return $search_ok;
1216
+ }
1217
+ if ( ! isset( $query->query_vars['post_type'] ) || ! isset( $query->query_vars['post_status'] ) ) {
1218
+ // Not a Media Library search.
1219
+ return $search_ok;
1220
+ }
1221
+ if (
1222
+ 'attachment' !== $query->query_vars['post_type'] &&
1223
+ 'inherit,private' !== $query->query_vars['post_status']
1224
+ ) {
1225
+ // Not a Media Library search.
1226
+ return $search_ok;
1227
+ }
1228
+ $indexed_post_types = array_flip(
1229
+ get_option( 'relevanssi_index_post_types', array() )
1230
+ );
1231
+ $images_indexed = get_option( 'relevanssi_index_image_files', 'off' );
1232
+ if ( false === isset( $indexed_post_types['attachment'] ) || 'off' === $images_indexed ) {
1233
+ // Attachments or images are not indexed, disable.
1234
+ $search_ok = false;
1235
+ }
1236
+
1237
+ if ( ! isset( $_REQUEST['mode'] ) || 'list' !== $_REQUEST['mode'] ) { // phpcs:ignore WordPress.Security.NonceVerification
1238
+ // Grid view, disable.
1239
+ $search_ok = false;
1240
+ }
1241
+
1242
+ return $search_ok;
1243
+ }
1244
+
1245
+ /**
1246
+ * Calculates the TF value.
1247
+ *
1248
+ * @param stdClass $match The match object.
1249
+ * @param array $post_type_weights An array of post type weights.
1250
+ *
1251
+ * @return float The TF value.
1252
+ */
1253
+ function relevanssi_calculate_tf( $match, $post_type_weights ) {
1254
+ $content_boost = floatval( get_option( 'relevanssi_content_boost', 1 ) );
1255
+ $title_boost = floatval( get_option( 'relevanssi_title_boost' ) );
1256
+ $link_boost = floatval( get_option( 'relevanssi_link_boost' ) );
1257
+ $comment_boost = floatval( get_option( 'relevanssi_comment_boost' ) );
1258
+
1259
+ if ( ! empty( $match->taxonomy_detail ) ) {
1260
+ relevanssi_taxonomy_score( $match, $post_type_weights );
1261
+ } else {
1262
+ $tag_weight = 1;
1263
+ if ( isset( $post_type_weights['post_tagged_with_post_tag'] ) && is_numeric( $post_type_weights['post_tagged_with_post_tag'] ) ) {
1264
+ $tag_weight = $post_type_weights['post_tagged_with_post_tag'];
1265
+ }
1266
+
1267
+ $category_weight = 1;
1268
+ if ( isset( $post_type_weights['post_tagged_with_category'] ) && is_numeric( $post_type_weights['post_tagged_with_category'] ) ) {
1269
+ $category_weight = $post_type_weights['post_tagged_with_category'];
1270
+ }
1271
+
1272
+ $taxonomy_weight = 1;
1273
+
1274
+ $match->taxonomy_score =
1275
+ $match->tag * $tag_weight +
1276
+ $match->category * $category_weight +
1277
+ $match->taxonomy * $taxonomy_weight;
1278
+ }
1279
+
1280
+ $tf =
1281
+ $match->title * $title_boost +
1282
+ $match->content * $content_boost +
1283
+ $match->comment * $comment_boost +
1284
+ $match->link * $link_boost +
1285
+ $match->author +
1286
+ $match->excerpt +
1287
+ $match->taxonomy_score +
1288
+ $match->customfield +
1289
+ $match->mysqlcolumn;
1290
+
1291
+ return $tf;
1292
+ }
1293
+
1294
+ /**
1295
+ * Calculates the match weight based on TF, IDF and bonus multipliers.
1296
+ *
1297
+ * @param stdClass $match The match object.
1298
+ * @param float $idf The inverse document frequency.
1299
+ * @param array $post_type_weights The post type weights.
1300
+ * @param string $query The search query.
1301
+ *
1302
+ * @return float The weight.
1303
+ */
1304
+ function relevanssi_calculate_weight( $match, $idf, $post_type_weights, $query ) {
1305
+ if ( $idf < 1 ) {
1306
+ $idf = 1;
1307
+ }
1308
+ $weight = $match->tf * $idf;
1309
+
1310
+ $type = relevanssi_get_post_type( $match->doc );
1311
+ if ( ! is_wp_error( $type ) && ! empty( $post_type_weights[ $type ] ) ) {
1312
+ $weight = $weight * $post_type_weights[ $type ];
1313
+ }
1314
+
1315
+ /* Weight boost for taxonomy terms based on taxonomy. */
1316
+ if ( ! empty( $post_type_weights[ 'taxonomy_term_' . $match->type ] ) ) {
1317
+ $weight = $weight * $post_type_weights[ 'taxonomy_term_' . $match->type ];
1318
+ }
1319
+
1320
+ if ( function_exists( 'relevanssi_get_recency_bonus' ) ) {
1321
+ $recency_details = relevanssi_get_recency_bonus();
1322
+ $recency_bonus = $recency_details['bonus'];
1323
+ $recency_cutoff_date = $recency_details['cutoff'];
1324
+ if ( $recency_bonus ) {
1325
+ $post = relevanssi_get_post( $match->doc );
1326
+ if ( strtotime( $post->post_date ) > $recency_cutoff_date ) {
1327
+ $weight = $weight * $recency_bonus;
1328
+ }
1329
+ }
1330
+ }
1331
+
1332
+ if ( $query && 'on' === get_option( 'relevanssi_exact_match_bonus' ) ) {
1333
+ /**
1334
+ * Filters the exact match bonus.
1335
+ *
1336
+ * @param array The title bonus under 'title' (default 5) and the content
1337
+ * bonus under 'content' (default 2).
1338
+ */
1339
+ $exact_match_boost = apply_filters(
1340
+ 'relevanssi_exact_match_bonus',
1341
+ array(
1342
+ 'title' => 5,
1343
+ 'content' => 2,
1344
+ )
1345
+ );
1346
+
1347
+ $post = relevanssi_get_post( $match->doc );
1348
+ $clean_query = str_replace( '"', '', $query );
1349
+ if ( stristr( $post->post_title, $clean_query ) !== false ) {
1350
+ $weight *= $exact_match_boost['title'];
1351
+ }
1352
+ if ( stristr( $post->post_content, $clean_query ) !== false ) {
1353
+ $weight *= $exact_match_boost['content'];
1354
+ }
1355
+ }
1356
+
1357
+ return $weight;
1358
+ }
1359
+
1360
+ /**
1361
+ * Updates the $term_hits array used for showing how many hits were found for
1362
+ * each term.
1363
+ *
1364
+ * @param array $term_hits The term hits array (passed as reference).
1365
+ * @param array $match_arrays The matches array (passed as reference).
1366
+ * @param stdClass $match The match object.
1367
+ * @param string $term The search term.
1368
+ */
1369
+ function relevanssi_update_term_hits( &$term_hits, &$match_arrays, $match, $term ) {
1370
+ $term_hits[ $match->doc ][ $term ] =
1371
+ $match->title +
1372
+ $match->content +
1373
+ $match->comment +
1374
+ $match->tag +
1375
+ $match->link +
1376
+ $match->author +
1377
+ $match->category +
1378
+ $match->excerpt +
1379
+ $match->taxonomy +
1380
+ $match->customfield;
1381
+
1382
+ relevanssi_increase_value( $match_arrays['body'][ $match->doc ], $match->content );
1383
+ relevanssi_increase_value( $match_arrays['title'][ $match->doc ], $match->title );
1384
+ relevanssi_increase_value( $match_arrays['link'][ $match->doc ], $match->link );
1385
+ relevanssi_increase_value( $match_arrays['tag'][ $match->doc ], $match->tag );
1386
+ relevanssi_increase_value( $match_arrays['category'][ $match->doc ], $match->category );
1387
+ relevanssi_increase_value( $match_arrays['taxonomy'][ $match->doc ], $match->taxonomy );
1388
+ relevanssi_increase_value( $match_arrays['comment'][ $match->doc ], $match->comment );
1389
+ relevanssi_increase_value( $match_arrays['customfield'][ $match->doc ], $match->customfield );
1390
+ relevanssi_increase_value( $match_arrays['author'][ $match->doc ], $match->author );
1391
+ relevanssi_increase_value( $match_arrays['excerpt'][ $match->doc ], $match->excerpt );
1392
+ relevanssi_increase_value( $match_arrays['mysqlcolumn'][ $match->doc ], $match->mysqlcolumn );
1393
+ }
1394
+
1395
+ /**
1396
+ * Increases a value. If it's not set, sets it first to the default value.
1397
+ *
1398
+ * @param int $value The value to increase (passed by reference).
1399
+ * @param int $increase The amount to increase the value.
1400
+ * @param int $default The default value, default 0.
1401
+ */
1402
+ function relevanssi_increase_value( &$value, $increase, $default = 0 ) {
1403
+ if ( ! isset( $value ) ) {
1404
+ $value = $default;
1405
+ }
1406
+ $value += $increase;
1407
+ }
1408
+
1409
+ /**
1410
+ * Initializes the matches array with empty arrays.
1411
+ *
1412
+ * @return array An array of empty arrays.
1413
+ */
1414
+ function relevanssi_initialize_match_arrays() {
1415
+ return array(
1416
+ 'author' => array(),
1417
+ 'body' => array(),
1418
+ 'category' => array(),
1419
+ 'comment' => array(),
1420
+ 'customfield' => array(),
1421
+ 'excerpt' => array(),
1422
+ 'link' => array(),
1423
+ 'mysqlcolumn' => array(),
1424
+ 'tag' => array(),
1425
+ 'taxonomy' => array(),
1426
+ 'title' => array(),
1427
+ );
1428
+ }
1429
+
1430
+ /**
1431
+ * Calculates the DF counts for each term.
1432
+ *
1433
+ * @param array $terms The list of terms.
1434
+ * @param array $args The rest of the parameters: bool 'no_terms' for whether
1435
+ * there's a search term or not; string 'operator' for the search operator,
1436
+ * array 'phrase_queries' for the phrase queries, string 'query_join' for the
1437
+ * MySQL query JOIN value, string 'query_restrictions' for the MySQL query
1438
+ * restrictions, bool 'search_again' to tell if this is a redone search.
1439
+ *
1440
+ * @return array An array of DF values for each term.
1441
+ */
1442
+ function relevanssi_generate_df_counts( array $terms, array $args ) : array {
1443
+ global $wpdb, $relevanssi_variables;
1444
+ $relevanssi_table = $relevanssi_variables['relevanssi_table'];
1445
+
1446
+ $fuzzy = get_option( 'relevanssi_fuzzy' );
1447
+
1448
+ $df_counts = array();
1449
+ foreach ( $terms as $term ) {
1450
+ $term_cond = relevanssi_generate_term_where( $term, $args['search_again'], $args['no_terms'] );
1451
+ if ( null === $term_cond ) {
1452
+ continue;
1453
+ }
1454
+
1455
+ $this_query_restrictions = relevanssi_add_phrase_restrictions(
1456
+ $args['query_restrictions'],
1457
+ $args['phrase_queries'],
1458
+ $term,
1459
+ $args['operator']
1460
+ );
1461
+
1462
+ $query = "SELECT COUNT(DISTINCT(relevanssi.doc)) FROM $relevanssi_table AS relevanssi
1463
+ {$args['query_join']} WHERE $term_cond $this_query_restrictions";
1464
+ // Clean: $this_query_restrictions is escaped, $term_cond is escaped.
1465
+ /**
1466
+ * Filters the DF query.
1467
+ *
1468
+ * This query is used to calculate the df for the tf * idf calculations.
1469
+ *
1470
+ * @param string MySQL query to filter.
1471
+ */
1472
+ $query = apply_filters( 'relevanssi_df_query_filter', $query );
1473
+
1474
+ $df = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1475
+
1476
+ if ( $df < 1 && 'sometimes' === $fuzzy ) {
1477
+ $term_cond = relevanssi_generate_term_where( $term, true, $args['no_terms'] );
1478
+ $query = "
1479
+ SELECT COUNT(DISTINCT(relevanssi.doc))
1480
+ FROM $relevanssi_table AS relevanssi
1481
+ {$args['query_join']} WHERE $term_cond {$args['query_restrictions']}";
1482
+ // Clean: $query_restrictions is escaped, $term is escaped.
1483
+ /** Documented in lib/search.php. */
1484
+ $query = apply_filters( 'relevanssi_df_query_filter', $query );
1485
+ $df = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1486
+ }
1487
+
1488
+ $df_counts[ $term ] = $df;
1489
+ }
1490
+
1491
+ // Sort the terms in ascending DF order, so that rarest terms are searched
1492
+ // for first. This is to make sure the throttle doesn't cut off posts with
1493
+ // rare search terms.
1494
+ asort( $df_counts );
1495
+
1496
+ return $df_counts;
1497
+ }
1498
+
1499
+ /**
1500
+ * Sorts the results Relevanssi finds.
1501
+ *
1502
+ * @param array $hits The results array (passed as reference).
1503
+ * @param string|array $orderby The orderby parameter, accepts both string
1504
+ * and array format.
1505
+ * @param string $order Either 'asc' or 'desc'.
1506
+ * @param array $meta_query The meta query parameters.
1507
+ */
1508
+ function relevanssi_sort_results( &$hits, $orderby, $order, $meta_query ) {
1509
+ if ( empty( $orderby ) ) {
1510
+ $orderby = get_option( 'relevanssi_default_orderby', 'relevance' );
1511
+ }
1512
+
1513
+ if ( is_array( $orderby ) ) {
1514
+ /**
1515
+ * Filters the orderby parameter before Relevanssi sorts posts.
1516
+ *
1517
+ * @param array|string $orderby The orderby parameter, accepts both
1518
+ * string and array format.
1519
+ */
1520
+ $orderby = apply_filters( 'relevanssi_orderby', $orderby );
1521
+ relevanssi_object_sort( $hits, $orderby, $meta_query );
1522
+ } else {
1523
+ if ( empty( $order ) ) {
1524
+ $order = 'desc';
1525
+ }
1526
+
1527
+ $order = strtolower( $order );
1528
+ $order_accepted_values = array( 'asc', 'desc' );
1529
+ if ( ! in_array( $order, $order_accepted_values, true ) ) {
1530
+ $order = 'desc';
1531
+ }
1532
+ /**
1533
+ * This filter is documented in lib/search.php.
1534
+ */
1535
+ $orderby = apply_filters( 'relevanssi_orderby', $orderby );
1536
+
1537
+ /**
1538
+ * Filters the order parameter before Relevanssi sorts posts.
1539
+ *
1540
+ * @param string $order The order parameter, either 'asc' or 'desc'.
1541
+ * Default 'desc'.
1542
+ */
1543
+ $order = apply_filters( 'relevanssi_order', $order );
1544
+
1545
+ if ( 'relevance' !== $orderby ) {
1546
+ // Results are by default sorted by relevance, so no need to sort
1547
+ // for that.
1548
+ $orderby_array = array( $orderby => $order );
1549
+ relevanssi_object_sort( $hits, $orderby_array, $meta_query );
1550
+ }
1551
+ }
1552
+ }
1553
+
1554
+ /**
1555
+ * Adjusts the $match->doc ID in case of users, post type archives and
1556
+ * taxonomy terms.
1557
+ *
1558
+ * @param stdClass $match The match object.
1559
+ *
1560
+ * @return int|string The doc ID, modified if necessary.
1561
+ */
1562
+ function relevanssi_adjust_match_doc( $match ) {
1563
+ $doc = $match->doc;
1564
+ if ( 'user' === $match->type ) {
1565
+ $doc = 'u_' . $match->item;
1566
+ } elseif ( 'post_type' === $match->type ) {
1567
+ $doc = 'p_' . $match->item;
1568
+ } elseif ( ! in_array( $match->type, array( 'post', 'attachment' ), true ) ) {
1569
+ $doc = '**' . $match->type . '**' . $match->item;
1570
+ }
1571
+ return $doc;
1572
+ }
1573
+
1574
+ /**
1575
+ * Generates the MySQL search query.
1576
+ *
1577
+ * @param string $term The search term.
1578
+ * @param bool $search_again If true, this is a repeat search (partial matching).
1579
+ * @param bool $no_terms If true, no search term is used.
1580
+ * @param string $query_join The MySQL JOIN clause.
1581
+ * @param string $query_restrictions The MySQL query restrictions.
1582
+ *
1583
+ * @return string The MySQL search query.
1584
+ */
1585
+ function relevanssi_generate_search_query( string $term, bool $search_again,
1586
+ bool $no_terms, string $query_join, string $query_restrictions ) : string {
1587
+ global $relevanssi_variables;
1588
+ $relevanssi_table = $relevanssi_variables['relevanssi_table'];
1589
+
1590
+ $term_cond = relevanssi_generate_term_where( $term, $search_again, $no_terms, get_option( 'relevanssi_fuzzy' ) );
1591
+
1592
+ $content_boost = floatval( get_option( 'relevanssi_content_boost', 1 ) );
1593
+ $title_boost = floatval( get_option( 'relevanssi_title_boost' ) );
1594
+ $link_boost = floatval( get_option( 'relevanssi_link_boost' ) );
1595
+ $comment_boost = floatval( get_option( 'relevanssi_comment_boost' ) );
1596
+
1597
+ $tag = ! empty( $post_type_weights['post_tag'] ) ? $post_type_weights['post_tag'] : $relevanssi_variables['post_type_weight_defaults']['post_tag'];
1598
+ $cat = ! empty( $post_type_weights['category'] ) ? $post_type_weights['category'] : $relevanssi_variables['post_type_weight_defaults']['category'];
1599
+
1600
+ // Clean: $term is escaped, as are $query_restrictions.
1601
+ $query = "SELECT DISTINCT(relevanssi.doc), relevanssi.*, relevanssi.title * $title_boost +
1602
+ relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
1603
+ relevanssi.tag * $tag + relevanssi.link * $link_boost +
1604
+ relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
1605
+ relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
1606
+ FROM $relevanssi_table AS relevanssi $query_join WHERE $term_cond $query_restrictions";
1607
+ /**
1608
+ * Filters the Relevanssi search query.
1609
+ *
1610
+ * @param string $query The Relevanssi search MySQL query.
1611
+ */
1612
+ return apply_filters( 'relevanssi_query_filter', $query );
1613
+ }
1614
+
1615
+ /**
1616
+ * Compiles search arguments that are shared between single site search and
1617
+ * multisite search.
1618
+ *
1619
+ * @param WP_Query $query The WP_Query that has the parameters.
1620
+ *
1621
+ * @return array The compiled search parameters.
1622
+ */
1623
+ function relevanssi_compile_common_args( $query ) {
1624
+ $admin_search = isset( $query->query_vars['relevanssi_admin_search'] ) ? true : false;
1625
+ $include_attachments = $query->query_vars['include_attachments'] ?? '';
1626
+
1627
+ $by_date = '';
1628
+ if ( ! empty( $query->query_vars['by_date'] ) ) {
1629
+ if ( preg_match( '/\d+[hdmyw]/', $query->query_vars['by_date'] ) ) {
1630
+ // Accepted format is digits followed by h, d, m, y, or w.
1631
+ $by_date = $query->query_vars['by_date'];
1632
+ }
1633
+ }
1634
+
1635
+ $order = $query->query_vars['order'] ?? null;
1636
+ $orderby = $query->query_vars['orderby'] ?? null;
1637
+
1638
+ $operator = '';
1639
+ if ( function_exists( 'relevanssi_set_operator' ) ) {
1640
+ $operator = relevanssi_set_operator( $query );
1641
+ $operator = strtoupper( $operator );
1642
+ }
1643
+ if ( ! in_array( $operator, array( 'OR', 'AND' ), true ) ) {
1644
+ $operator = get_option( 'relevanssi_implicit_operator' );
1645
+ }
1646
+
1647
+ $sentence = false;
1648
+ if ( isset( $query->query_vars['sentence'] ) && ! empty( $query->query_vars['sentence'] ) ) {
1649
+ $sentence = true;
1650
+ }
1651
+
1652
+ $meta_query = relevanssi_meta_query_from_query_vars( $query );
1653
+ $date_query = relevanssi_wp_date_query_from_query_vars( $query );
1654
+
1655
+ $post_type = false;
1656
+ if ( isset( $query->query_vars['post_type'] ) && 'any' !== $query->query_vars['post_type'] ) {
1657
+ $post_type = $query->query_vars['post_type'];
1658
+ }
1659
+ if ( isset( $query->query_vars['post_types'] ) && 'any' !== $query->query_vars['post_types'] ) {
1660
+ $post_type = $query->query_vars['post_types'];
1661
+ }
1662
+
1663
+ $post_status = false;
1664
+ if ( isset( $query->query_vars['post_status'] ) && 'any' !== $query->query_vars['post_status'] ) {
1665
+ $post_status = $query->query_vars['post_status'];
1666
+ }
1667
+
1668
+ return array(
1669
+ 'orderby' => $orderby,
1670
+ 'order' => $order,
1671
+ 'operator' => $operator,
1672
+ 'admin_search' => $admin_search,
1673
+ 'include_attachments' => $include_attachments,
1674
+ 'by_date' => $by_date,
1675
+ 'sentence' => $sentence,
1676
+ 'meta_query' => $meta_query,
1677
+ 'date_query' => $date_query,
1678
+ 'post_type' => $post_type,
1679
+ 'post_status' => $post_status,
1680
+ );
1681
+ }
1682
+
1683
+ function relevanssi_add_include_matches( &$matches, $include, $params ) {
1684
+ if ( count( $include['posts'] ) < 1 && count( $include['items'] ) < 1 ) {
1685
+ return;
1686
+ }
1687
+
1688
+ global $wpdb, $relevanssi_variables;
1689
+ $relevanssi_table = $relevanssi_variables['relevanssi_table'];
1690
+
1691
+ $term_cond = relevanssi_generate_term_where( $params['term'], $params['search_again'], $params['no_terms'] );
1692
+ $content_boost = floatval( get_option( 'relevanssi_content_boost', 1 ) ); // Default value, because this option was added late.
1693
+ $title_boost = floatval( get_option( 'relevanssi_title_boost' ) );
1694
+ $link_boost = floatval( get_option( 'relevanssi_link_boost' ) );
1695
+ $comment_boost = floatval( get_option( 'relevanssi_comment_boost' ) );
1696
+ $tag = $relevanssi_variables['post_type_weight_defaults']['post_tag'];
1697
+ $cat = $relevanssi_variables['post_type_weight_defaults']['category'];
1698
+
1699
+ if ( ! empty( $post_type_weights['post_tagged_with_post_tag'] ) ) {
1700
+ $tag = $post_type_weights['post_tagged_with_post_tag'];
1701
+ }
1702
+ if ( ! empty( $post_type_weights['post_tagged_with_category'] ) ) {
1703
+ $cat = $post_type_weights['post_tagged_with_category'];
1704
+ }
1705
+
1706
+ if ( count( $include['posts'] ) > 0 ) {
1707
+ $existing_ids = array();
1708
+ foreach ( $matches as $match ) {
1709
+ $existing_ids[] = $match->doc;
1710
+ }
1711
+ $existing_ids = array_keys( array_flip( $existing_ids ) );
1712
+ $added_post_ids = array_diff( array_keys( $include['posts'] ), $existing_ids );
1713
+ if ( count( $added_post_ids ) > 0 ) {
1714
+ $offset = 0;
1715
+ $slice_length = 20;
1716
+ $total_ids = count( $added_post_ids );
1717
+ do {
1718
+ $current_slice = array_slice( $added_post_ids, $offset, $slice_length );
1719
+ $post_ids_to_add = implode( ',', $current_slice );
1720
+ if ( ! empty( $post_ids_to_add ) ) {
1721
+ $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
1722
+ relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
1723
+ relevanssi.tag * $tag + relevanssi.link * $link_boost +
1724
+ relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
1725
+ relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
1726
+ FROM $relevanssi_table AS relevanssi WHERE relevanssi.doc IN ($post_ids_to_add)
1727
+ AND $term_cond";
1728
+
1729
+ // Clean: no unescaped user inputs.
1730
+ $matches_to_add = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1731
+ $matches = array_merge( $matches, $matches_to_add );
1732
+ }
1733
+ $offset += $slice_length;
1734
+ } while ( $offset <= $total_ids );
1735
+ }
1736
+ }
1737
+ if ( count( $include['items'] ) > 0 ) {
1738
+ $existing_items = array();
1739
+ foreach ( $matches as $match ) {
1740
+ if ( 0 !== intval( $match->item ) ) {
1741
+ $existing_items[] = $match->item;
1742
+ }
1743
+ }
1744
+ $existing_items = array_keys( array_flip( $existing_items ) );
1745
+ $items_to_add = implode( ',', array_diff( array_keys( $include['items'] ), $existing_items ) );
1746
+
1747
+ if ( ! empty( $items_to_add ) ) {
1748
+ $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
1749
+ relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
1750
+ relevanssi.tag * $tag + relevanssi.link * $link_boost +
1751
+ relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
1752
+ relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
1753
+ FROM $relevanssi_table AS relevanssi WHERE relevanssi.item IN ($items_to_add)
1754
+ AND $term_cond";
1755
+
1756
+ // Clean: no unescaped user inputs.
1757
+ $matches_to_add = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1758
+ $matches = array_merge( $matches, $matches_to_add );
1759
+ }
1760
+ }
1761
+ }
1762
+
1763
+ /**
1764
+ * Figures out the low and high boundaries for the search query.
1765
+ *
1766
+ * The low boundary defaults to 0. If the search is paged, the low boundary is
1767
+ * calculated from the page number and posts_per_page value.
1768
+ *
1769
+ * The high boundary defaults to the low boundary + post_per_page, but if no
1770
+ * posts_per_page is set or it's -1, the high boundary is the number of posts
1771
+ * found. Also if the high boundary is higher than the number of posts found,
1772
+ * it's set there.
1773
+ *
1774
+ * If an offset is defined, both boundaries are offset with the value.
1775
+ *
1776
+ * @param WP_Query $query The WP Query object.
1777
+ *
1778
+ * @return array An array with the low boundary first, the high boundary second.
1779
+ */
1780
+ function relevanssi_get_boundaries( $query ) : array {
1781
+ $hits_count = $query->found_posts;
1782
+
1783
+ if ( isset( $query->query_vars['paged'] ) && $query->query_vars['paged'] > 0 ) {
1784
+ $search_low_boundary = ( $query->query_vars['paged'] - 1 ) * $query->query_vars['posts_per_page'];
1785
+ } else {
1786
+ $search_low_boundary = 0;
1787
+ }
1788
+
1789
+ if ( ! isset( $query->query_vars['posts_per_page'] ) || -1 === $query->query_vars['posts_per_page'] ) {
1790
+ $search_high_boundary = $hits_count;
1791
+ } else {
1792
+ $search_high_boundary = $search_low_boundary + $query->query_vars['posts_per_page'] - 1;
1793
+ }
1794
+
1795
+ if ( isset( $query->query_vars['offset'] ) && $query->query_vars['offset'] > 0 ) {
1796
+ $search_high_boundary += $query->query_vars['offset'];
1797
+ $search_low_boundary += $query->query_vars['offset'];
1798
+ }
1799
+
1800
+ if ( $search_high_boundary > $hits_count ) {
1801
+ $search_high_boundary = $hits_count;
1802
+ }
1803
+
1804
+ return array( $search_low_boundary, $search_high_boundary );
1805
+ }
1806
+
1807
+ /**
1808
+ * Returns a ID=>parent object from post ID.
1809
+ *
1810
+ * @param int $post_id The post ID.
1811
+ *
1812
+ * @return object An object with the post ID in ->ID and post parent in
1813
+ * ->post_parent.
1814
+ */
1815
+ function relevanssi_generate_post_parent( int $post_id ) {
1816
+ $object = new StdClass();
1817
+ $object->ID = $post_id;
1818
+ $object->post_parent = wp_get_post_parent_id( $post_id );
1819
+ return $object;
1820
+ }
1821
+
1822
+ /**
1823
+ * Returns a ID=>type object from post ID.
1824
+ *
1825
+ * @param string $post_id The post ID.
1826
+ *
1827
+ * @return object An object with the post ID in ->ID, object type in ->type and
1828
+ * (possibly) term taxonomy in ->taxonomy and post type name in ->name.
1829
+ */
1830
+ function relevanssi_generate_id_type( string $post_id ) {
1831
+ $object = new StdClass();
1832
+ if ( 'u_' === substr( $post_id, 0, 2 ) ) {
1833
+ $object->ID = intval( substr( $post_id, 2 ) );
1834
+ $object->type = 'user';
1835
+ } elseif ( '**' === substr( $post_id, 0, 2 ) ) {
1836
+ list( , $taxonomy, $id ) = explode( '**', $post_id );
1837
+ $object->ID = $id;
1838
+ $object->type = 'term';
1839
+ $object->taxonomy = $taxonomy;
1840
+ } elseif ( 'p_' === substr( $post_id, 0, 2 ) ) {
1841
+ $object->ID = intval( substr( $post_id, 2 ) );
1842
+ $object->type = 'post_type';
1843
+ $object->name = relevanssi_get_post_type_by_id( $object->ID );
1844
+ } else {
1845
+ $object->ID = $post_id;
1846
+ $object->type = 'post';
1847
+ }
1848
+ return $object;
1849
+ }
lib/tabs/searching-tab.php CHANGED
@@ -57,7 +57,11 @@ function relevanssi_searching_tab() {
57
  $orfallback_visibility = '';
58
  }
59
 
60
- $docs_count = $wpdb->get_var( 'SELECT COUNT(DISTINCT doc) FROM ' . $relevanssi_variables['relevanssi_table'] . ' WHERE doc != -1' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
 
 
 
 
61
  ?>
62
 
63
  <table class="form-table" role="presentation">
@@ -322,7 +326,7 @@ function relevanssi_searching_tab() {
322
  <?php esc_html_e( 'Throttle searches.', 'relevanssi' ); ?>
323
  </label>
324
  </fieldset>
325
- <?php if ( $docs_count < 1000 ) { ?>
326
  <p class="description important"><?php esc_html_e( "Your database is so small that you don't need to enable this.", 'relevanssi' ); ?></p>
327
  <?php } ?>
328
  <p class="description"><?php esc_html_e( 'If this option is checked, Relevanssi will limit search results to at most 500 results per term. This will improve performance, but may cause some relevant documents to go unfound. See Help for more details.', 'relevanssi' ); ?></p>
57
  $orfallback_visibility = '';
58
  }
59
 
60
+ if ( ! $throttle ) {
61
+ $docs_count = $wpdb->get_var( 'SELECT COUNT(DISTINCT doc) FROM ' . $relevanssi_variables['relevanssi_table'] . ' WHERE doc != -1' ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
62
+ } else {
63
+ $docs_count = null;
64
+ }
65
  ?>
66
 
67
  <table class="form-table" role="presentation">
326
  <?php esc_html_e( 'Throttle searches.', 'relevanssi' ); ?>
327
  </label>
328
  </fieldset>
329
+ <?php if ( $docs_count && $docs_count < 1000 ) { ?>
330
  <p class="description important"><?php esc_html_e( "Your database is so small that you don't need to enable this.", 'relevanssi' ); ?></p>
331
  <?php } ?>
332
  <p class="description"><?php esc_html_e( 'If this option is checked, Relevanssi will limit search results to at most 500 results per term. This will improve performance, but may cause some relevant documents to go unfound. See Help for more details.', 'relevanssi' ); ?></p>
lib/utils.php CHANGED
@@ -20,6 +20,17 @@ function get_relevanssi_taxonomy_walker() {
20
  return new Relevanssi_Taxonomy_Walker();
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
23
  /**
24
  * Wraps the relevanssi_mb_trim() function so that it can be used as a callback
25
  * for array_walk().
@@ -155,6 +166,10 @@ function relevanssi_get_an_object( $source ) {
155
  // Convert from post ID to post.
156
  $object = relevanssi_get_post_object( $source );
157
  $format = 'id';
 
 
 
 
158
  } elseif ( ! isset( $source->post_content ) ) {
159
  // Convert from id=>parent to post.
160
  $object = relevanssi_get_post_object( $source->ID );
@@ -299,6 +314,44 @@ function relevanssi_get_post( $post_id, int $blog_id = -1 ) {
299
  return $post;
300
  }
301
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  /**
303
  * Returns an object based on ID.
304
  *
@@ -621,6 +674,36 @@ function relevanssi_return_id_parent( $post_object ) {
621
  return $id_parent_object;
622
  }
623
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
  /**
625
  * Returns "off".
626
  *
@@ -645,6 +728,8 @@ function relevanssi_return_off() {
645
  function relevanssi_return_value( $post, string $return_value ) {
646
  if ( 'id' === $return_value ) {
647
  return $post->ID;
 
 
648
  } elseif ( 'id=>parent' === $return_value ) {
649
  return relevanssi_return_id_parent( $post );
650
  }
@@ -747,8 +832,9 @@ function relevanssi_strip_invisibles( $text ) {
747
  * Strips tags from contents, keeping the allowed tags.
748
  *
749
  * The allowable tags are read from the relevanssi_excerpt_allowable_tags
750
- * option. Spaces are added between tags before removing the tags, so that
751
- * words don't get stuck together. The function also remove invisible content.
 
752
  *
753
  * @see relevanssi_strip_invisibles
754
  *
@@ -761,7 +847,19 @@ function relevanssi_strip_tags( $content ) {
761
  $content = strval( $content );
762
  }
763
  $content = relevanssi_strip_invisibles( $content );
764
- $content = preg_replace( '/(<\/[^>]+?>)(<[^>\/][^>]*?>)/', '$1 $2', $content );
 
 
 
 
 
 
 
 
 
 
 
 
765
  return strip_tags(
766
  $content,
767
  get_option( 'relevanssi_excerpt_allowable_tags', '' )
@@ -799,7 +897,7 @@ function relevanssi_stripos( $haystack, $needle, int $offset = 0 ) {
799
  $needle_regex = str_replace(
800
  array( '?', '*' ),
801
  array( '.', '.*' ),
802
- $needle
803
  );
804
  $pos_found = false;
805
  while ( ! $pos_found ) {
20
  return new Relevanssi_Taxonomy_Walker();
21
  }
22
 
23
+ /**
24
+ * Adds quotes around a string.
25
+ *
26
+ * @param string $string The string.
27
+ *
28
+ * @return string The string with quotes around it.
29
+ */
30
+ function relevanssi_add_quotes( $string ) {
31
+ return '"' . $string . '"';
32
+ }
33
+
34
  /**
35
  * Wraps the relevanssi_mb_trim() function so that it can be used as a callback
36
  * for array_walk().
166
  // Convert from post ID to post.
167
  $object = relevanssi_get_post_object( $source );
168
  $format = 'id';
169
+ } elseif ( isset( $source->type ) ) {
170
+ // Convert from id=>type to post.
171
+ $object = relevanssi_get_post_object( $source->ID );
172
+ $format = 'id=>type';
173
  } elseif ( ! isset( $source->post_content ) ) {
174
  // Convert from id=>parent to post.
175
  $object = relevanssi_get_post_object( $source->ID );
314
  return $post;
315
  }
316
 
317
+ /**
318
+ * Fetches post meta value for a large group of posts with just one query.
319
+ *
320
+ * This function can be used to reduce the number of database queries. Instead
321
+ * of looping through an array of posts and calling get_post_meta() for each
322
+ * individual post, you can get all the values with this function with just one
323
+ * database query.
324
+ *
325
+ * @param array $post_ids An array of post IDs.
326
+ * @param string $field The name of the field.
327
+ *
328
+ * @return array An array of post_id, meta_value pairs.
329
+ */
330
+ function relevanssi_get_post_meta_for_all_posts( array $post_ids, string $field ) : array {
331
+ global $wpdb;
332
+
333
+ $post_ids_string = implode( ',', $post_ids );
334
+ $meta_values = array();
335
+
336
+ if ( $post_ids_string ) {
337
+ $meta_values = $wpdb->get_results(
338
+ $wpdb->prepare(
339
+ "SELECT post_id, meta_value FROM $wpdb->postmeta
340
+ WHERE meta_key = %s
341
+ AND post_id IN ( $post_ids_string )", // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
342
+ $field
343
+ )
344
+ );
345
+ }
346
+
347
+ $results = array();
348
+ foreach ( $meta_values as $row ) {
349
+ $results[ $row->post_id ] = $row->meta_value;
350
+ }
351
+
352
+ return $results;
353
+ }
354
+
355
  /**
356
  * Returns an object based on ID.
357
  *
674
  return $id_parent_object;
675
  }
676
 
677
+ /**
678
+ * Returns an ID=>type object from a post (or a term, or a user).
679
+ *
680
+ * @param WP_Post|WP_Term|WP_User $post_object The source object.
681
+ *
682
+ * @return object An object with the attributes ID and type set. Type is
683
+ * 'post', 'user', 'term' or 'post_type'. For terms, also fills in 'taxonomy',
684
+ * for post types 'name'.
685
+ */
686
+ function relevanssi_return_id_type( $post_object ) {
687
+ $id_type_object = new stdClass();
688
+
689
+ if ( isset( $post_object->ID ) ) {
690
+ $id_type_object->ID = $post_object->ID;
691
+ $id_type_object->type = 'post';
692
+ } elseif ( isset( $post_object->term_id ) ) {
693
+ $id_type_object->ID = $post_object->term_id;
694
+ $id_type_object->type = 'term';
695
+ $id_type_object->taxonomy = $post_object->taxonomy;
696
+ } elseif ( isset( $post_object->user_id ) ) {
697
+ $id_type_object->ID = $post_object->user_id;
698
+ $id_type_object->type = 'user';
699
+ } else {
700
+ $id_type_object->ID = 0;
701
+ $id_type_object->post_parent = 0;
702
+ }
703
+
704
+ return $id_type_object;
705
+ }
706
+
707
  /**
708
  * Returns "off".
709
  *
728
  function relevanssi_return_value( $post, string $return_value ) {
729
  if ( 'id' === $return_value ) {
730
  return $post->ID;
731
+ } elseif ( 'id=>type' === $return_value ) {
732
+ return relevanssi_return_id_type( $post );
733
  } elseif ( 'id=>parent' === $return_value ) {
734
  return relevanssi_return_id_parent( $post );
735
  }
832
  * Strips tags from contents, keeping the allowed tags.
833
  *
834
  * The allowable tags are read from the relevanssi_excerpt_allowable_tags
835
+ * option. Relevanssi also adds extra spaces after some tags to make sure words
836
+ * are not stuck together after the tags are removed. The function also removes
837
+ * invisible content.
838
  *
839
  * @see relevanssi_strip_invisibles
840
  *
847
  $content = strval( $content );
848
  }
849
  $content = relevanssi_strip_invisibles( $content );
850
+
851
+ $space_tags = array(
852
+ '/(<\/?p.*?>)/',
853
+ '/(<\/?br.*?>)/',
854
+ '/(<\/?h[1-6].*?>)/',
855
+ '/(<\/?div.*?>)/',
856
+ '/(<\/?blockquote.*?>)/',
857
+ '/(<\/?hr.*?>)/',
858
+ '/(<\/?li.*?>)/',
859
+ '/(<img.*?>)/',
860
+ );
861
+
862
+ $content = preg_replace( $space_tags, '$1 ', $content );
863
  return strip_tags(
864
  $content,
865
  get_option( 'relevanssi_excerpt_allowable_tags', '' )
897
  $needle_regex = str_replace(
898
  array( '?', '*' ),
899
  array( '.', '.*' ),
900
+ preg_quote( $needle, '/' )
901
  );
902
  $pos_found = false;
903
  while ( ! $pos_found ) {
readme.txt CHANGED
@@ -3,9 +3,9 @@ Contributors: msaari
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search, product search, woocommerce search
5
  Requires at least: 4.9
6
- Tested up to: 5.7
7
  Requires PHP: 7.0
8
- Stable tag: 4.12.5
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -131,6 +131,21 @@ Each document database is full of useless words. All the little words that appea
131
  * John Calahan for extensive 4.0 beta testing.
132
 
133
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  = 4.12.5 =
135
  * Changed behaviour: `relevanssi_excerpt_custom_field_content` now gets the post ID and list of custom field names as a parameter.
136
  * Minor fix: Makes sure Relevanssi options are not wiped when the free version is deleted while Premium is active.
@@ -205,31 +220,10 @@ Each document database is full of useless words. All the little words that appea
205
  * Minor fix: Relevanssi had an unnecessary index on the `doc` column in the `wp_relevanssi` database table. It is now removed to save space. Thanks to Matthew Wang.
206
  * Minor fix: Improved Oxygen Builder support makes sure `ct_builder_shortcodes` custom field is always indexed.
207
 
208
- = 4.9.1 =
209
- * Changed behaviour: The `relevanssi_excerpt_part` filter hook now gets the post ID as a second parameter. The documentation for the filter has been fixed to match actual use: this filter is applied to the excerpt part after the highlighting and the ellipsis have been added.
210
- * Changed behaviour: The `relevanssi_index_custom_fields` filter hook is no longer used when determining which custom fields are used for phrase searching. If you have a use case where this change matters, please contact us.
211
- * Minor fix: The `relevanssi_excerpt` filter hook was removed in 4.9.0. It is now restored and behaves the way it did before.
212
- * Minor fix: Avoids undefined variable warnings from the Pretty Links compatibility code.
213
- * Minor fix: The Oxygen Builder compatibility has been improved. Now shortcodes in Oxygen Builder content are expanded, if that setting is enabled in Relevanssi settings.
214
-
215
- = 4.9.0 =
216
- * New feature: There's now a "Debugging" tab in the Relevanssi settings, letting you see how the Relevanssi index sees posts. This is familiar to Premium users, but is now available in the free version as well.
217
- * New feature: The SEO Framework plugin is now supported and posts set excluded from the search in SEO Framework settings will be excluded from the index.
218
- * New feature: There's a new option, "Expand highlights". Enabling it makes Relevanssi expand partial-word highlights to cover the full word. This is useful when doing partial matching and when using a stemmer.
219
- * New feature: New filter hook `relevanssi_excerpt_part` allows you to modify the excerpt parts before they are combined together. This doesn't do much in the free version.
220
- * New feature: Improved compatibility with Oxygen Builder. Relevanssi automatically indexes the Oxygen Builder content and cleans it up. New filter hooks `relevanssi_oxygen_section_filters` and `relevanssi_oxygen_section_content` allow easier filtering of Oxygen content to eg. remove unwanted sections.
221
- * Changed behaviour: The "Uncheck this for non-ASCII highlights" option has been removed. Highlights are now done in a slightly different way that should work in all cases, including for example Cyrillic text, thus this option is no longer necessary.
222
- * Minor fix: Fixes phrase searching using non-US alphabet.
223
- * Minor fix: Relevanssi would break admin searching for hierarchical post types. This is now fixed, Relevanssi won't do that anymore.
224
- * Minor fix: Relevanssi indexing now survives better shortcodes that change the global `$post`.
225
- * Minor fix: Warnings about missing `relevanssi_update_counts` function are now removed.
226
- * Minor fix: Paid Membership Pro support now takes notice of the "filter queries" setting.
227
- * Minor fix: OR logic didn't work correctly when two phrases both had the same word (for example "freedom of speech" and "free speech"). The search would always be an AND search in those cases. That has been fixed.
228
- * Minor fix: Relevanssi no longer blocks the Pretty Links admin page search.
229
- * Minor fix: The "Respect 'exclude_from_search'" setting did not work if no post type parameter was included in the search parameters.
230
- * Minor fix: The category inclusion and exclusion setting checkboxes on the Searching tab didn't work. The setting was saved, but the checkboxes wouldn't appear.
231
-
232
  == Upgrade notice ==
 
 
 
233
  = 4.12.5 =
234
  * Fixes minor bugs.
235
 
@@ -261,10 +255,4 @@ Each document database is full of useless words. All the little words that appea
261
  * Corrects the multilingual stopwords and synonyms.
262
 
263
  = 4.10.0 =
264
- * Adds support for multilingual stopwords and synonyms.
265
-
266
- = 4.9.1 =
267
- * Bug fixing, better Oxygen Builder compatibility.
268
-
269
- = 4.9.0 =
270
- * New debugging feature, lots of minor fixes.
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search, product search, woocommerce search
5
  Requires at least: 4.9
6
+ Tested up to: 5.7.1
7
  Requires PHP: 7.0
8
+ Stable tag: 4.13.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
131
  * John Calahan for extensive 4.0 beta testing.
132
 
133
  == Changelog ==
134
+ = 4.13.0 =
135
+ * New feature: New filter hook `relevanssi_phrase` filters each phrase before it's used in the MySQL query.
136
+ * New feature: Relevanssi can now add Google-style missing term lists to the search results. You can either use the `%missing%` tag in the search results breakdown settings, or you can create your own code: the missing terms are also in `$post->missing_terms`. Relevanssi Premium will also add "Must have" links when there's just one missing term.
137
+ * New feature: New filter hook `relevanssi_missing_terms_tag` controls which tag is used to wrap the missing terms.
138
+ * New feature: New filter hook `relevanssi_missing_terms_template` can be used to filter the template used to display the missing terms.
139
+ * New feature: New function `relevanssi_get_post_meta_for_all_posts()` can be used to fetch particular meta field for a number of posts with just one query.
140
+ * New feature: New filter hook `relevanssi_post_author` lets you filter the post author display_name before it is indexed.
141
+ * Changed behaviour: `relevanssi_strip_tags()` used to add spaces between HTML tags before stripping them. It no longer does that, but instead adds a space after specific list of tags (p, br, h1-h6, div, blockquote, hr, li, img) to avoid words being stuck to each other in excerpts.
142
+ * Changed behaviour: Relevanssi now indexes the contents of Oxygen Builder PHP & HTML code blocks.
143
+ * Changed behaviour: Relevanssi now handles synonyms inside phrases differently. If the new filter hook `relevanssi_phrase_synonyms` returns `true` (default value), synonyms create a new phrase (with synonym 'dog=hound', phrase `"dog biscuits"` becomes `"dog biscuits" "hound biscuits"`). If the value is `false`, synonyms inside phrases are ignored.
144
+ * Minor fix: Warnings when creating excerpts with search terms that contain a slash were removed.
145
+ * Minor fix: Better Ninja Tables compatibility to avoid problems with lightbox images.
146
+ * Minor fix: Relevanssi did not work well in the Media Library grid view. Relevanssi is now blocked there. If you need Relevanssi in Media Library searches, use the list view.
147
+ * Minor fix: Relevanssi excerpt creation didn't work correctly when numerical search terms were used.
148
+
149
  = 4.12.5 =
150
  * Changed behaviour: `relevanssi_excerpt_custom_field_content` now gets the post ID and list of custom field names as a parameter.
151
  * Minor fix: Makes sure Relevanssi options are not wiped when the free version is deleted while Premium is active.
220
  * Minor fix: Relevanssi had an unnecessary index on the `doc` column in the `wp_relevanssi` database table. It is now removed to save space. Thanks to Matthew Wang.
221
  * Minor fix: Improved Oxygen Builder support makes sure `ct_builder_shortcodes` custom field is always indexed.
222
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  == Upgrade notice ==
224
+ = 4.13.0 =
225
+ * Lots of new features and bug fixes.
226
+
227
  = 4.12.5 =
228
  * Fixes minor bugs.
229
 
255
  * Corrects the multilingual stopwords and synonyms.
256
 
257
  = 4.10.0 =
258
+ * Adds support for multilingual stopwords and synonyms.
 
 
 
 
 
 
relevanssi.php CHANGED
@@ -13,7 +13,7 @@
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
- * Version: 4.12.5
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
@@ -67,7 +67,7 @@ $relevanssi_variables['database_version'] = 6;
67
  $relevanssi_variables['file'] = __FILE__;
68
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
69
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
70
- $relevanssi_variables['plugin_version'] = '4.12.5';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
+ * Version: 4.13.0
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
67
  $relevanssi_variables['file'] = __FILE__;
68
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
69
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
70
+ $relevanssi_variables['plugin_version'] = '4.13.0';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';