Relevanssi – A Better Search - Version 4.17.0

Version Description

  • New feature: You can now look at how the posts appear in the database from the Debugging tab.
  • New feature: Relevanssi now works with WooCommerce layered navigation filters. The filter post counts should now match the Relevanssi search results.
  • New feature: New function relevanssi_count_term_occurrances() can be used to display how many times search terms appear in the database.
  • Changed behaviour: Relevanssi post update trigger is now on wp_after_insert_post instead of wp_insert_post. This makes the indexing more reliable and better compatible with other plugins.
  • Changed behaviour: Previously, throttling searches has been impossible when results are sorted by date. Now if you set Relevanssi to sort by post date from the searching settings, you can enable the throttle and the throttling will make sure to keep the most recent posts. This does not work if you set the orderby to post_date elsewhere.
  • Minor fix: Prevents Relevanssi from interfering in fringe cases (including The Event Calendar event search).
  • Minor fix: Relevanssi added the highlight parameter to home page URLs, even though it shouldn't.
  • Minor fix: Indexing nav_menu_item posts is stopped earlier in the process to avoid problems with big menus.
  • Minor fix: If the sentence query variable is used to enable phrase searching, Relevanssi now adds quotes to the highlight parameter.
  • Minor fix: Add support for JetSmartFilters.
  • Minor fix: Add support for WooCommerce products attribute lookup table filtering.
  • Minor fix: Improve excerpts to avoid breaking HTML tags when tags are allowed.
  • Minor fix: Fix broken tag and category weight settings.
  • Minor fix: Improve Polylang language detection.
  • Minor fix: Relevanssi now hyphenates long search terms in the User searches page. This prevents long search terms from messing up the display.
  • Minor fix: Improve WPFD file content indexing support. Relevanssi indexing now happens after the WPFD indexing is done.
  • Minor fix: Add support for TablePress table_filter shortcodes.
  • Minor fix: Stopped some problems with Did you mean suggestions suggesting the same word if a hyphen was included.
  • Minor fix: Paging didn't work in admin searches for hierarchical post types (like pages).
  • Minor fix: In-document highlighting could break certain elements thanks to Relevanssi messing up data attributes.
  • Minor fix: Relevanssi now recursively runs relevanssi_block_to_render and the CSS relevanssi_noindex filtering for inner blocks.
Download this release

Release Info

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

Code changes from version 4.15.2 to 4.17.0

lib/admin_styles.css CHANGED
@@ -109,9 +109,10 @@ table.form-table table.widefat th {
109
  vertical-align: top;
110
  }
111
 
112
- #relevanssi_sees_container {
113
  width: 80%;
114
  background: white;
115
  padding: 5px 20px;
116
  border: thin solid black;
 
117
  }
109
  vertical-align: top;
110
  }
111
 
112
+ #relevanssi_sees_container, #relevanssi_db_view_container {
113
  width: 80%;
114
  background: white;
115
  padding: 5px 20px;
116
  border: thin solid black;
117
+ overflow: scroll;
118
  }
lib/class-relevanssi-taxonomy-walker.php CHANGED
@@ -57,18 +57,18 @@ class Relevanssi_Taxonomy_Walker extends Walker_Category_Checklist {
57
  $aria_checked = 'false';
58
  $inner_class = 'category';
59
 
60
- /** This filter is documented in wp-includes/category-template.php */
61
  $output .= "\n" . '<li' . $class . '>' .
62
  '<div class="' . $inner_class . '" data-term-id=' . $category->term_id .
63
  ' tabindex="0" role="checkbox" aria-checked="' . $aria_checked . '">' .
64
- esc_html( apply_filters( 'the_category', $category->name ) ) . '</div>';
65
- } else {
66
  /** This filter is documented in wp-includes/category-template.php */
 
 
67
  $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
68
  '<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="' . $name . '[]" id="in-' . $taxonomy . '-' . $category->term_id . '"' .
69
  checked( in_array( intval( $category->term_id ), $args['selected_cats'], true ), true, false ) .
70
  disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
71
- esc_html( apply_filters( 'the_category', $category->name ) ) . '</label>';
 
72
  }
73
  }
74
  }
57
  $aria_checked = 'false';
58
  $inner_class = 'category';
59
 
 
60
  $output .= "\n" . '<li' . $class . '>' .
61
  '<div class="' . $inner_class . '" data-term-id=' . $category->term_id .
62
  ' tabindex="0" role="checkbox" aria-checked="' . $aria_checked . '">' .
 
 
63
  /** This filter is documented in wp-includes/category-template.php */
64
+ esc_html( apply_filters( 'the_category', $category->name, '', '' ) ) . '</div>';
65
+ } else {
66
  $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
67
  '<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="' . $name . '[]" id="in-' . $taxonomy . '-' . $category->term_id . '"' .
68
  checked( in_array( intval( $category->term_id ), $args['selected_cats'], true ), true, false ) .
69
  disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
70
+ /** This filter is documented in wp-includes/category-template.php */
71
+ esc_html( apply_filters( 'the_category', $category->name, '', '' ) ) . '</label>';
72
  }
73
  }
74
  }
lib/common.php CHANGED
@@ -224,7 +224,7 @@ function relevanssi_default_post_ok( $post_ok, $post_id ) {
224
  apply_filters( 'relevanssi_valid_admin_status', array( 'draft', 'pending', 'future' ) ),
225
  true
226
  )
227
- && is_admin() ) {
228
  // Only show drafts, pending and future posts in admin search.
229
  $post_ok = true;
230
  }
@@ -535,7 +535,8 @@ function relevanssi_prevent_default_request( $request, $query ) {
535
 
536
  if ( ! is_admin() && $prevent ) {
537
  $request = "SELECT * FROM $wpdb->posts WHERE 1=2";
538
- } elseif ( 'on' === get_option( 'relevanssi_admin_search' ) && $admin_search_ok ) {
 
539
  $request = "SELECT * FROM $wpdb->posts WHERE 1=2";
540
  }
541
  }
@@ -981,7 +982,11 @@ function relevanssi_add_highlight( $permalink, $link_post = null ) {
981
  $highlight_docs = get_option( 'relevanssi_highlight_docs', 'off' );
982
  $query = get_search_query();
983
  if ( isset( $highlight_docs ) && 'off' !== $highlight_docs && ! empty( $query ) ) {
984
- if ( ! relevanssi_is_front_page_id( isset( $link_post->ID ) ?? null ) ) {
 
 
 
 
985
  $query = str_replace( '&quot;', '"', $query );
986
  $permalink = esc_attr( add_query_arg( array( 'highlight' => rawurlencode( $query ) ), $permalink ) );
987
  }
@@ -1046,11 +1051,23 @@ function relevanssi_permalink( $link, $link_post = null ) {
1046
  }
1047
  }
1048
 
1049
- if ( is_search() && is_object( $link_post ) && property_exists( $link_post, 'relevance_score' ) ) {
 
 
 
 
 
 
 
 
 
 
 
 
1050
  $link = relevanssi_add_highlight( $link, $link_post );
1051
  }
1052
 
1053
- if ( function_exists( 'relevanssi_add_tracking' ) ) {
1054
  $link = relevanssi_add_tracking( $link, $link_post );
1055
  }
1056
 
@@ -1226,6 +1243,16 @@ function relevanssi_get_forbidden_post_types() {
1226
  'astra_adv_header', // Astra.
1227
  'udb_widgets', // Ultimate Dashboard.
1228
  'udb_admin_page', // Ultimate Dashboard.
 
 
 
 
 
 
 
 
 
 
1229
  );
1230
  }
1231
 
224
  apply_filters( 'relevanssi_valid_admin_status', array( 'draft', 'pending', 'future' ) ),
225
  true
226
  )
227
+ && is_admin() && ! relevanssi_is_live_search() ) {
228
  // Only show drafts, pending and future posts in admin search.
229
  $post_ok = true;
230
  }
535
 
536
  if ( ! is_admin() && $prevent ) {
537
  $request = "SELECT * FROM $wpdb->posts WHERE 1=2";
538
+ }
539
+ if ( is_admin() && 'on' === get_option( 'relevanssi_admin_search' ) && $admin_search_ok ) {
540
  $request = "SELECT * FROM $wpdb->posts WHERE 1=2";
541
  }
542
  }
982
  $highlight_docs = get_option( 'relevanssi_highlight_docs', 'off' );
983
  $query = get_search_query();
984
  if ( isset( $highlight_docs ) && 'off' !== $highlight_docs && ! empty( $query ) ) {
985
+ if ( ! relevanssi_is_front_page_id( $link_post->ID ?? null ) ) {
986
+ global $wp_query;
987
+ if ( isset( $wp_query->query_vars['sentence'] ) && '&quot;' !== substr( $query, 0, 6 ) ) {
988
+ $query = relevanssi_add_quotes( $query );
989
+ }
990
  $query = str_replace( '&quot;', '"', $query );
991
  $permalink = esc_attr( add_query_arg( array( 'highlight' => rawurlencode( $query ) ), $permalink ) );
992
  }
1051
  }
1052
  }
1053
 
1054
+ global $wp_query;
1055
+
1056
+ $add_highlight_and_tracking = false;
1057
+ if ( is_search() && ! is_admin() ) {
1058
+ $add_highlight_and_tracking = true;
1059
+ }
1060
+ if ( is_search() && is_admin() &&
1061
+ ( isset( $wp_query->query_vars['relevanssi'] ) || isset( $wp_query->query_vars['rlvquery'] ) )
1062
+ ) {
1063
+ $add_highlight_and_tracking = true;
1064
+ }
1065
+
1066
+ if ( $add_highlight_and_tracking && is_object( $link_post ) && property_exists( $link_post, 'relevance_score' ) ) {
1067
  $link = relevanssi_add_highlight( $link, $link_post );
1068
  }
1069
 
1070
+ if ( $add_highlight_and_tracking && function_exists( 'relevanssi_add_tracking' ) ) {
1071
  $link = relevanssi_add_tracking( $link, $link_post );
1072
  }
1073
 
1243
  'astra_adv_header', // Astra.
1244
  'udb_widgets', // Ultimate Dashboard.
1245
  'udb_admin_page', // Ultimate Dashboard.
1246
+ 'oxy_user_library', // Oxygen.
1247
+ 'aw_workflow', // AutomateWoo.
1248
+ 'paypal_transaction', // PayPal for WooCommerce.
1249
+ 'scheduled-action',
1250
+ 'divi_bars', // Divi Bars.
1251
+ 'br_product_filter', // BeRocket Product Filters.
1252
+ 'br_filters_group', // BeRocket Product Filters.
1253
+ 'wfob_bump', // WooFunnel.
1254
+ 'wfocu_funnel', // WooFunnel.
1255
+ 'wfocu_offer', // WooFunnel.
1256
  );
1257
  }
1258
 
lib/compatibility/gutenberg.php CHANGED
@@ -89,24 +89,29 @@ function relevanssi_gutenberg_block_rendering( $content, $post_object ) {
89
  }
90
 
91
  if (
92
- ! isset( $block['attrs']['className'] )
93
- || false === strstr( $block['attrs']['className'], 'relevanssi_noindex' )
94
  ) {
95
- /**
96
- * Filters the Gutenberg block after it is rendered.
97
- *
98
- * The value is the output from render_block( $block ). Feel free to
99
- * modify it as you wish.
100
- *
101
- * @see render_block
102
- *
103
- * @param string The rendered block content.
104
- * @param array $block The Gutenberg block being rendered.
105
- *
106
- * @return string The filtered block content.
107
- */
108
- $output .= apply_filters( 'relevanssi_rendered_block', render_block( $block ), $block );
109
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  }
111
 
112
  // If there are blocks in this content, we shouldn't run wpautop() on it later.
@@ -118,3 +123,52 @@ function relevanssi_gutenberg_block_rendering( $content, $post_object ) {
118
 
119
  return $output;
120
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
  }
90
 
91
  if (
92
+ isset( $block['attrs']['className'] )
93
+ && false !== strstr( $block['attrs']['className'], 'relevanssi_noindex' )
94
  ) {
95
+ continue;
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  }
97
+
98
+ $block = relevanssi_process_inner_blocks( $block );
99
+
100
+ /**
101
+ * Filters the Gutenberg block after it is rendered.
102
+ *
103
+ * The value is the output from render_block( $block ). Feel free to
104
+ * modify it as you wish.
105
+ *
106
+ * @see render_block
107
+ *
108
+ * @param string The rendered block content.
109
+ * @param array $block The Gutenberg block being rendered.
110
+ *
111
+ * @return string The filtered block content.
112
+ */
113
+ $output .= apply_filters( 'relevanssi_rendered_block', render_block( $block ), $block );
114
+
115
  }
116
 
117
  // If there are blocks in this content, we shouldn't run wpautop() on it later.
123
 
124
  return $output;
125
  }
126
+
127
+ /**
128
+ * Runs recursively through inner blocks to filter them.
129
+ *
130
+ * Runs relevanssi_block_to_render and the relevanssi_noindex CSS class check
131
+ * on all inner blocks. If inner blocks are filtered out, they will be removed
132
+ * with empty blocks of the type "core/fake". Removing the inner blocks causes
133
+ * problems; that's why they are replaced. The blocks are rendered here;
134
+ * everything will be rendered once at the top level.
135
+ *
136
+ * @param array $block A Gutenberg block.
137
+ *
138
+ * @return array The filtered block.
139
+ */
140
+ function relevanssi_process_inner_blocks( $block ) {
141
+ $innerblocks_to_keep = array();
142
+
143
+ $empty_block = array(
144
+ 'blockName' => 'core/fake',
145
+ 'attrs' => array(),
146
+ 'innerHTML' => '',
147
+ 'innerBlocks' => array(),
148
+ );
149
+
150
+ foreach ( $block['innerBlocks'] as $inner_block ) {
151
+ /* Filter documented in /lib/compatibility/gutenberg.php. */
152
+ $inner_block = apply_filters( 'relevanssi_block_to_render', $inner_block );
153
+
154
+ if ( ! $inner_block ) {
155
+ $innerblocks_to_keep[] = $empty_block;
156
+ continue;
157
+ }
158
+
159
+ if (
160
+ isset( $inner_block['attrs']['className'] )
161
+ && false !== strstr( $inner_block['attrs']['className'], 'relevanssi_noindex' )
162
+ ) {
163
+ $innerblocks_to_keep[] = $empty_block;
164
+ continue;
165
+ }
166
+
167
+ $inner_block = relevanssi_process_inner_blocks( $inner_block );
168
+
169
+ $innerblocks_to_keep[] = $inner_block;
170
+ }
171
+
172
+ $block['innerBlocks'] = $innerblocks_to_keep;
173
+ return $block;
174
+ }
lib/compatibility/jetsmartfilters.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/compatibility/jetsmartfilters.php
4
+ *
5
+ * JetSmartFilters compatibility features.
6
+ *
7
+ * @package Relevanssi
8
+ * @author Mikko Saari
9
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
10
+ * @see https://www.relevanssi.com/
11
+ */
12
+
13
+ add_action( 'pre_get_posts', 'relevanssi_jetsmartfilters', 9999 );
14
+
15
+ /**
16
+ * Makes JetSmartFilters use posts from Relevanssi.
17
+ *
18
+ * @param WP_Query $wp_query The wp_query object.
19
+ */
20
+ function relevanssi_jetsmartfilters( $wp_query ) {
21
+ if (
22
+ ! isset( $wp_query->query['jet_smart_filters'] )
23
+ || empty( $wp_query->query['s'] )
24
+ ) {
25
+ return;
26
+ }
27
+
28
+ $args = array(
29
+ 's' => $wp_query->query['s'],
30
+ 'fields' => 'ids',
31
+ 'posts_per_page' => -1,
32
+ 'relevanssi' => true,
33
+ );
34
+
35
+ $relevanssi_query = new WP_Query( $args );
36
+
37
+ $results = ! empty( $relevanssi_query->posts )
38
+ ? $relevanssi_query->posts
39
+ : array( 0 );
40
+
41
+ $wp_query->set( 'post__in', $results );
42
+ $wp_query->set( 'post_type', 'any' );
43
+ $wp_query->set( 'post_status', 'any' );
44
+ $wp_query->set( 'orderby', 'post__in' );
45
+ $wp_query->set( 'order', 'DESC' );
46
+ $wp_query->set( 's', false );
47
+ }
lib/compatibility/oxygen.php CHANGED
@@ -35,6 +35,23 @@ add_action( 'save_post', 'relevanssi_insert_edit', 99, 1 );
35
  * @return array|null An array of custom field values, null if no value exists.
36
  */
37
  function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  if ( 'ct_builder_shortcodes_revisions_dates' === $field ) {
39
  return '';
40
  }
@@ -42,6 +59,9 @@ function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
42
  return '';
43
  }
44
  if ( 'ct_builder_shortcodes' === $field ) {
 
 
 
45
  if ( empty( $value ) ) {
46
  return null;
47
  }
@@ -118,18 +138,66 @@ function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
118
  }
119
 
120
  /**
121
- * Adds the `ct_builder_shortcodes` to the list of indexed custom fields.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  *
123
  * @param array|boolean $fields An array of custom fields to index, or false.
124
  *
125
- * @return array An array of custom fields, including `ct_builder_shortcodes`.
 
126
  */
127
  function relevanssi_add_oxygen( $fields ) {
 
 
 
128
  if ( ! is_array( $fields ) ) {
129
  $fields = array();
130
  }
131
- if ( ! in_array( 'ct_builder_shortcodes', $fields, true ) ) {
132
- $fields[] = 'ct_builder_shortcodes';
133
  }
134
 
135
  return $fields;
@@ -143,11 +211,14 @@ function relevanssi_add_oxygen( $fields ) {
143
  * is ignored, Relevanssi disables this filter and then checks the option to
144
  * see what the value is.
145
  *
146
- * @return string If value is undefined, it's set to 'ct_builder_shortcodes'.
 
147
  */
148
  function relevanssi_oxygen_fix_none_setting( $value ) {
149
  if ( ! $value ) {
150
- $value = 'ct_builder_shortcodes';
 
 
151
  }
152
 
153
  return $value;
35
  * @return array|null An array of custom field values, null if no value exists.
36
  */
37
  function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
38
+ if ( 'ct_builder_json' === $field ) {
39
+ $json = array();
40
+ foreach ( $value as $row ) {
41
+ $json[] = json_decode( $row );
42
+ }
43
+
44
+ $content = '';
45
+ if ( isset( $json[0]->children ) ) {
46
+ foreach ( $json[0]->children as $child ) {
47
+ $content .= relevanssi_process_oxygen_child( $child );
48
+ }
49
+ }
50
+
51
+ $value[0] = $content;
52
+ return $value;
53
+ }
54
+
55
  if ( 'ct_builder_shortcodes_revisions_dates' === $field ) {
56
  return '';
57
  }
59
  return '';
60
  }
61
  if ( 'ct_builder_shortcodes' === $field ) {
62
+ if ( version_compare( CT_VERSION, '4.0', '>=' ) ) {
63
+ return null;
64
+ }
65
  if ( empty( $value ) ) {
66
  return null;
67
  }
138
  }
139
 
140
  /**
141
+ * Recursively processes the Oxygen JSON data.
142
+ *
143
+ * This function extracts all the ct_content data from the JSON. All elements
144
+ * are run through the relevanssi_oxygen_element filter hook. You can use that
145
+ * filter hook to modify or to eliminate elements from the JSON.
146
+ *
147
+ * @param array $child The child element array.
148
+ *
149
+ * @return string The content from the child and the grandchildren.
150
+ */
151
+ function relevanssi_process_oxygen_child( $child ) : string {
152
+ /**
153
+ * Filters the Oxygen JSON child element.
154
+ *
155
+ * If the filter returns an empty value, the child element and all its
156
+ * children will be ignored.
157
+ *
158
+ * @param array $child The JSON child element.
159
+ */
160
+ $child = apply_filters( 'relevanssi_oxygen_element', $child );
161
+ if ( empty( $child ) ) {
162
+ return '';
163
+ }
164
+
165
+ $child_content = ' ';
166
+ if ( isset( $child->options->ct_content ) ) {
167
+ $child_content .= $child->options->ct_content;
168
+ }
169
+
170
+ if ( isset( $child->options->original->{'code-php'} ) ) {
171
+ // For code and HTML blocks, strip all tags.
172
+ $child_content .= wp_strip_all_tags( $child->options->original->{'code-php'} );
173
+ }
174
+
175
+ if ( isset( $child->children ) ) {
176
+ foreach ( $child->children as $grandchild ) {
177
+ $child_content .= relevanssi_process_oxygen_child( $grandchild );
178
+ }
179
+ }
180
+
181
+ return $child_content;
182
+ }
183
+
184
+ /**
185
+ * Adds the Oxygen custom field to the list of indexed custom fields.
186
  *
187
  * @param array|boolean $fields An array of custom fields to index, or false.
188
  *
189
+ * @return array An array of custom fields, including `ct_builder_json` or
190
+ * `ct_builder_shortcodes`.
191
  */
192
  function relevanssi_add_oxygen( $fields ) {
193
+ $oxygen_field = version_compare( CT_VERSION, '4.0', '>=' )
194
+ ? 'ct_builder_json'
195
+ : 'ct_builder_shortcodes';
196
  if ( ! is_array( $fields ) ) {
197
  $fields = array();
198
  }
199
+ if ( ! in_array( $oxygen_field, $fields, true ) ) {
200
+ $fields[] = $oxygen_field;
201
  }
202
 
203
  return $fields;
211
  * is ignored, Relevanssi disables this filter and then checks the option to
212
  * see what the value is.
213
  *
214
+ * @return string If value is undefined, it's set to 'ct_builder_json' or
215
+ * 'ct_builder_shortcodes'.
216
  */
217
  function relevanssi_oxygen_fix_none_setting( $value ) {
218
  if ( ! $value ) {
219
+ $value = version_compare( CT_VERSION, '4.0', '>=' )
220
+ ? 'ct_builder_json'
221
+ : 'ct_builder_shortcodes';
222
  }
223
 
224
  return $value;
lib/compatibility/tablepress.php CHANGED
@@ -28,3 +28,21 @@ function relevanssi_enable_tablepress_shortcodes() {
28
  }
29
  return $my_tablepress_controller;
30
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
29
  return $my_tablepress_controller;
30
  }
31
+
32
+ add_filter( 'relevanssi_post_content', 'relevanssi_table_filter' );
33
+
34
+ /**
35
+ * Replaces the [table_filter] shortcodes with [table].
36
+ *
37
+ * The shortcode filter extension adds a [table_filter] shortcode which is not
38
+ * compatible with Relevanssi. This function switches those to the normal
39
+ * [table] shortcode which works better.
40
+ *
41
+ * @param string $content The post content.
42
+ *
43
+ * @return string The fixed post content.
44
+ */
45
+ function relevanssi_table_filter( $content ) {
46
+ $content = str_replace( '[table_filter', '[table', $content );
47
+ return $content;
48
+ }
lib/compatibility/woocommerce.php CHANGED
@@ -12,6 +12,8 @@
12
 
13
  add_filter( 'relevanssi_indexing_restriction', 'relevanssi_woocommerce_restriction' );
14
  add_filter( 'relevanssi_admin_search_blocked_post_types', 'relevanssi_woocommerce_admin_search_blocked_post_types' );
 
 
15
 
16
  /**
17
  * This action solves the problems introduced by adjust_posts_count() in
@@ -152,3 +154,126 @@ function relevanssi_woocommerce_admin_search_blocked_post_types( array $post_typ
152
  );
153
  return array_merge( $post_types, $woo_post_types );
154
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  add_filter( 'relevanssi_indexing_restriction', 'relevanssi_woocommerce_restriction' );
14
  add_filter( 'relevanssi_admin_search_blocked_post_types', 'relevanssi_woocommerce_admin_search_blocked_post_types' );
15
+ add_filter( 'relevanssi_modify_wp_query', 'relevanssi_woocommerce_filters' );
16
+ add_filter( 'woocommerce_get_filtered_term_product_counts_query', 'relevanssi_filtered_term_product_counts_query' );
17
 
18
  /**
19
  * This action solves the problems introduced by adjust_posts_count() in
154
  );
155
  return array_merge( $post_types, $woo_post_types );
156
  }
157
+
158
+ /**
159
+ * Relevanssi support for WooCommerce filtering.
160
+ *
161
+ * @param WP_Query $query The WP_Query object.
162
+ * @return WP_Query The WP_Query object.
163
+ */
164
+ function relevanssi_woocommerce_filters( $query ) {
165
+ // phpcs:disable WordPress.Security.NonceVerification.Recommended
166
+
167
+ $min_price = isset( $_REQUEST['min_price'] ) ? intval( $_REQUEST['min_price'] ) : false;
168
+ $max_price = isset( $_REQUEST['max_price'] ) ? intval( $_REQUEST['max_price'] ) : false;
169
+
170
+ $meta_query = $query->get( 'meta_query' );
171
+ if ( $min_price ) {
172
+ $meta_query[] = array(
173
+ 'key' => '_price',
174
+ 'value' => $min_price,
175
+ 'compare' => '>=',
176
+ 'type' => 'NUMERIC',
177
+ );
178
+ }
179
+ if ( $max_price ) {
180
+ $meta_query[] = array(
181
+ 'key' => '_price',
182
+ 'value' => $max_price,
183
+ 'compare' => '<=',
184
+ 'type' => 'NUMERIC',
185
+ );
186
+ }
187
+ if ( $meta_query ) {
188
+ $query->set( 'meta_query', $meta_query );
189
+ }
190
+
191
+ foreach ( array( 'product_tag', 'product_cat', 'product_brand' ) as $taxonomy ) {
192
+ $value = isset( $_REQUEST[ $taxonomy ] ) ? intval( $_REQUEST[ $taxonomy ] ) : false;
193
+ if ( $value ) {
194
+ $tax_query = $query->get( 'tax_query' );
195
+ if ( ! is_array( $tax_query ) ) {
196
+ $tax_query = array();
197
+ }
198
+ $tax_query[] = array(
199
+ 'taxonomy' => $taxonomy,
200
+ 'field' => 'term_id',
201
+ 'terms' => $value,
202
+ );
203
+ $query->set( 'tax_query', $tax_query );
204
+ }
205
+ }
206
+
207
+ if ( 'no' === get_option( 'woocommerce_attribute_lookup_enabled' ) ) {
208
+ return $query;
209
+ }
210
+
211
+ $chosen_attributes = array();
212
+
213
+ if ( ! empty( $_GET ) ) {
214
+ foreach ( $_GET as $key => $value ) {
215
+ if ( 0 === strpos( $key, 'filter_' ) ) {
216
+ $attribute = wc_sanitize_taxonomy_name( str_replace( 'filter_', '', $key ) );
217
+ $taxonomy = wc_attribute_taxonomy_name( $attribute );
218
+ $filter_terms = ! empty( $value ) ? explode( ',', wc_clean( wp_unslash( $value ) ) ) : array();
219
+
220
+ if ( empty( $filter_terms ) || ! taxonomy_exists( $taxonomy ) || ! wc_attribute_taxonomy_id_by_name( $attribute ) ) {
221
+ continue;
222
+ }
223
+
224
+ $query_type = ! empty( $_GET[ 'query_type_' . $attribute ] ) && in_array( $_GET[ 'query_type_' . $attribute ], array( 'and', 'or' ), true )
225
+ ? wc_clean( wp_unslash( $_GET[ 'query_type_' . $attribute ] ) )
226
+ : '';
227
+
228
+ $chosen_attributes[ $taxonomy ]['terms'] = array_map( 'sanitize_title', $filter_terms );
229
+ $chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : apply_filters( 'woocommerce_layered_nav_default_query_type', 'and' );
230
+ }
231
+ }
232
+ }
233
+
234
+ $tax_query = $query->get( 'tax_query' );
235
+ if ( ! is_array( $tax_query ) ) {
236
+ $tax_query = array();
237
+ }
238
+ foreach ( $chosen_attributes as $taxonomy => $data ) {
239
+ $tax_query[] = array(
240
+ 'taxonomy' => $taxonomy,
241
+ 'field' => 'slug',
242
+ 'terms' => $data['terms'],
243
+ 'operator' => 'and' === $data['query_type'] ? 'AND' : 'IN',
244
+ 'include_children' => false,
245
+ );
246
+ }
247
+ $query->set( 'tax_query', $tax_query );
248
+
249
+ return $query;
250
+ }
251
+
252
+ /**
253
+ * Provides layered navigation term counts based on Relevanssi searches.
254
+ *
255
+ * Hooks onto woocommerce_get_filtered_term_product_counts_query to provide
256
+ * accurate term counts.
257
+ *
258
+ * @param array $query The MySQL query parts.
259
+ *
260
+ * @return array The modified query.
261
+ */
262
+ function relevanssi_filtered_term_product_counts_query( $query ) {
263
+ global $relevanssi_variables, $wpdb;
264
+
265
+ if ( false !== stripos( $query['select'], 'product_or_parent_id' ) ) {
266
+ $query['from'] = str_replace( 'FROM ', "FROM {$relevanssi_variables['relevanssi_table']} AS relevanssi, ", $query['from'] );
267
+ $query['where'] = str_replace( 'WHERE ', " WHERE relevanssi.doc = $wpdb->posts.ID AND ", $query['where'] );
268
+ $query['where'] = preg_replace( '/\(\w+posts.post_title LIKE(.*?)\)\)/', 'relevanssi.term LIKE\1)', $query['where'] );
269
+ $query['where'] = preg_replace( array( '/OR \(\w+posts.post_excerpt LIKE .*?\)/', '/OR \(\w+posts.post_content LIKE .*?\)/' ), '', $query['where'] );
270
+ } else {
271
+ $query['select'] = 'SELECT COUNT( DISTINCT( relevanssi.doc ) ) AS term_count, terms.term_id AS term_count_id';
272
+ $query['from'] = "FROM {$relevanssi_variables['relevanssi_table']} AS relevanssi, $wpdb->posts";
273
+ $query['where'] = str_replace( 'WHERE ', " WHERE relevanssi.doc = $wpdb->posts.ID AND ", $query['where'] );
274
+ $query['where'] = preg_replace( '/\(\w+posts.post_title LIKE(.*?)\)\)/', 'relevanssi.term LIKE\1)', $query['where'] );
275
+ $query['where'] = preg_replace( array( '/OR \(\w+posts.post_excerpt LIKE .*?\)/', '/OR \(\w+posts.post_content LIKE .*?\)/' ), '', $query['where'] );
276
+ }
277
+
278
+ return $query;
279
+ }
lib/compatibility/wp-file-download.php CHANGED
@@ -12,6 +12,7 @@
12
  */
13
 
14
  add_filter( 'relevanssi_content_to_index', 'relevanssi_wpfd_content', 10, 2 );
 
15
 
16
  /**
17
  * Adds the WPFD indexed content to wpfd_file posts.
@@ -44,3 +45,14 @@ function relevanssi_wpfd_content( $content, $post ) {
44
  }
45
  return $content;
46
  }
 
 
 
 
 
 
 
 
 
 
 
12
  */
13
 
14
  add_filter( 'relevanssi_content_to_index', 'relevanssi_wpfd_content', 10, 2 );
15
+ add_action( 'wpfd_file_indexed', 'relevanssi_wpfd_index' );
16
 
17
  /**
18
  * Adds the WPFD indexed content to wpfd_file posts.
45
  }
46
  return $content;
47
  }
48
+
49
+ /**
50
+ * Runs Relevanssi indexing after WPFD indexing is done.
51
+ *
52
+ * @param int $wpfd_id The WPFD post index.
53
+ */
54
+ function relevanssi_wpfd_index( $wpfd_id ) {
55
+ global $wpdb;
56
+ $post_id = $wpdb->get_var( $wpdb->prepare( "SELECT tid FROM {$wpdb->prefix}wpfd_index WHERE id=%d", $wpfd_id ) );
57
+ relevanssi_insert_edit( $post_id );
58
+ }
lib/didyoumean.php CHANGED
@@ -206,8 +206,10 @@ function relevanssi_simple_generate_suggestion( $query ) {
206
  }
207
  }
208
  if ( ! empty( $closest ) ) {
209
- $query = str_ireplace( $token, $closest, $query );
210
- $suggestions_made = true;
 
 
211
  }
212
  }
213
 
206
  }
207
  }
208
  if ( ! empty( $closest ) ) {
209
+ $query = str_ireplace( $token, $closest, $query, $replacement_count );
210
+ if ( $replacement_count > 0 ) {
211
+ $suggestions_made = true;
212
+ }
213
  }
214
  }
215
 
lib/excerpts-highlights.php CHANGED
@@ -158,6 +158,16 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
158
  // Replace linefeeds and carriage returns with spaces.
159
  $content = preg_replace( "/\n\r|\r\n|\n|\r/", ' ', $content );
160
 
 
 
 
 
 
 
 
 
 
 
161
  if ( 'OR' === get_option( 'relevanssi_implicit_operator' ) || 'on' === get_option( 'relevanssi_index_synonyms' ) ) {
162
  $query = relevanssi_add_synonyms( $query );
163
  }
@@ -438,6 +448,12 @@ function relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length =
438
  }
439
  }
440
 
 
 
 
 
 
 
441
  return $excerpts;
442
  }
443
 
@@ -621,7 +637,8 @@ function relevanssi_highlight_terms( $content, $query, $convert_entities = false
621
  usort( $terms, 'relevanssi_strlen_sort' );
622
 
623
  $content = strtr( $content, array( "\xC2\xAD" => '' ) );
624
- $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
 
625
  if ( ! $convert_entities ) {
626
  $content = str_replace( "\n", ' ', $content );
627
  }
@@ -691,7 +708,7 @@ function relevanssi_highlight_terms( $content, $query, $convert_entities = false
691
  $content
692
  );
693
 
694
- if ( preg_match_all( '/<.*>/Us', $content, $matches ) > 0 ) {
695
  // Remove highlights from inside HTML tags.
696
  foreach ( $matches as $match ) {
697
  $new_match = str_replace( $start_emp_token, '', $match );
@@ -1535,3 +1552,30 @@ function relevanssi_add_excerpt( &$post, $query ) {
1535
  restore_current_blog();
1536
  }
1537
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
158
  // Replace linefeeds and carriage returns with spaces.
159
  $content = preg_replace( "/\n\r|\r\n|\n|\r/", ' ', $content );
160
 
161
+ // Replace spaces inside HTML tags to avoid splitting tags when doing
162
+ // word-based excerpts.
163
+ $content = preg_replace_callback(
164
+ '~<([!a-zA-Z\/][^>].*?)>~s',
165
+ function( $match ) {
166
+ return '<' . str_replace( ' ', '*VÄLILYÖNTI*', $match[1] ) . '>';
167
+ },
168
+ $content
169
+ );
170
+
171
  if ( 'OR' === get_option( 'relevanssi_implicit_operator' ) || 'on' === get_option( 'relevanssi_index_synonyms' ) ) {
172
  $query = relevanssi_add_synonyms( $query );
173
  }
448
  }
449
  }
450
 
451
+ array_walk(
452
+ $excerpts,
453
+ function( &$excerpt ) {
454
+ $excerpt['text'] = str_replace( '*VÄLILYÖNTI*', ' ', $excerpt['text'] );
455
+ }
456
+ );
457
  return $excerpts;
458
  }
459
 
637
  usort( $terms, 'relevanssi_strlen_sort' );
638
 
639
  $content = strtr( $content, array( "\xC2\xAD" => '' ) );
640
+ $content = relevanssi_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
641
+
642
  if ( ! $convert_entities ) {
643
  $content = str_replace( "\n", ' ', $content );
644
  }
708
  $content
709
  );
710
 
711
+ if ( preg_match_all( '/<.*>/Usm', $content, $matches ) > 0 ) {
712
  // Remove highlights from inside HTML tags.
713
  foreach ( $matches as $match ) {
714
  $new_match = str_replace( $start_emp_token, '', $match );
1552
  restore_current_blog();
1553
  }
1554
  }
1555
+
1556
+ /**
1557
+ * Runs html_entity_decode(), then restores entities inside data attributes.
1558
+ *
1559
+ * @uses html_entity_decode
1560
+ *
1561
+ * @param string $content The content to decode.
1562
+ * @param int $flags The flags for html_entity_decode, default ENT_QUOTES.
1563
+ * @param string $charset The charset for html_entity_decode, default 'UTF-8'.
1564
+ *
1565
+ * @return string The decoded content.
1566
+ */
1567
+ function relevanssi_entity_decode( $content, $flags = ENT_QUOTES, $charset = 'UTF-8' ) {
1568
+ $content = html_entity_decode( $content, $flags, $charset );
1569
+
1570
+ if ( preg_match_all( '/data-.+?="(.*?)"[ >]/sm', $content, $matches ) ) {
1571
+ $source = array();
1572
+ $replace = array();
1573
+ foreach ( $matches[1] as $match ) {
1574
+ $source[] = $match;
1575
+ $replace[] = htmlentities( $match );
1576
+ }
1577
+ $content = str_replace( $source, $replace, $content );
1578
+ }
1579
+
1580
+ return $content;
1581
+ }
lib/indexing.php CHANGED
@@ -838,8 +838,9 @@ function relevanssi_publish( $post_id, $bypass_global_post = false ) {
838
  * @param int $post_id The post ID.
839
  *
840
  * @return string|int Returns 'auto-draft' if the post is an auto draft and
841
- * thus skipped, 'revision' for revisions, 'removed' if the post is removed or
842
- * the relevanssi_index_doc() return value from relevanssi_publish().
 
843
  *
844
  * @see relevanssi_publish()
845
  */
@@ -849,6 +850,10 @@ function relevanssi_insert_edit( $post_id ) {
849
  return 'revision';
850
  }
851
 
 
 
 
 
852
  $post_status = get_post_status( $post_id );
853
  if ( 'auto-draft' === $post_status ) {
854
  return 'auto-draft';
838
  * @param int $post_id The post ID.
839
  *
840
  * @return string|int Returns 'auto-draft' if the post is an auto draft and
841
+ * thus skipped, 'revision' for revisions, 'nav_menu_item' for navigation menu
842
+ * items, 'removed' if the post is removed or the relevanssi_index_doc() return
843
+ * value from relevanssi_publish().
844
  *
845
  * @see relevanssi_publish()
846
  */
850
  return 'revision';
851
  }
852
 
853
+ if ( 'nav_menu_item' === relevanssi_get_post_type( $post_id ) ) {
854
+ return 'nav_menu_item';
855
+ }
856
+
857
  $post_status = get_post_status( $post_id );
858
  if ( 'auto-draft' === $post_status ) {
859
  return 'auto-draft';
lib/init.php CHANGED
@@ -25,7 +25,12 @@ add_filter( 'relevanssi_prevent_default_request', 'relevanssi_block_on_admin_sea
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 );
 
 
 
 
 
29
  add_action( 'delete_post', 'relevanssi_remove_doc' );
30
 
31
  // Comment indexing.
@@ -43,6 +48,7 @@ add_action( 'edit_attachment', 'relevanssi_insert_edit' );
43
  add_action( 'transition_post_status', 'relevanssi_update_child_posts', 99, 3 );
44
 
45
  // Relevanssi features.
 
46
  add_filter( 'relevanssi_remove_punctuation', 'relevanssi_remove_punct' );
47
  add_filter( 'relevanssi_post_ok', 'relevanssi_default_post_ok', 9, 2 );
48
  add_filter( 'relevanssi_query_filter', 'relevanssi_limit_filter' );
@@ -50,6 +56,8 @@ add_action( 'relevanssi_trim_logs', 'relevanssi_trim_logs' );
50
  add_action( 'relevanssi_update_counts', 'relevanssi_update_counts' );
51
  add_action( 'relevanssi_custom_field_value', 'relevanssi_filter_custom_fields', 10, 2 );
52
  add_filter( 'relevanssi_index_custom_fields', 'relevanssi_remove_metadata_fields' );
 
 
53
 
54
  // Excerpts and highlights.
55
  add_action( 'relevanssi_pre_the_content', 'relevanssi_kill_autoembed' );
@@ -471,15 +479,21 @@ function relevanssi_rest_api_disable() {
471
  /**
472
  * Checks if a log export is requested.
473
  *
474
- * If the 'relevanssi_export' query variable is set, a log export has been requested
475
- * and one will be provided by relevanssi_export_log().
 
 
476
  *
477
  * @see relevanssi_export_log
 
478
  */
479
  function relevanssi_export_log_check() {
480
  if ( isset( $_REQUEST['relevanssi_export'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification, just checking the parameter exists.
481
  relevanssi_export_log();
482
  }
 
 
 
483
  }
484
 
485
  /**
@@ -488,6 +502,7 @@ function relevanssi_export_log_check() {
488
  function relevanssi_load_compatibility_code() {
489
  class_exists( 'acf', false ) && require_once 'compatibility/acf.php';
490
  class_exists( 'DGWT_WC_Ajax_Search', false ) && require_once 'compatibility/fibosearch.php';
 
491
  class_exists( 'MeprUpdateCtrl', false ) && MeprUpdateCtrl::is_activated() && require_once 'compatibility/memberpress.php';
492
  class_exists( 'Obenland_Wp_Search_Suggest', false ) && require_once 'compatibility/wp-search-suggest.php';
493
  class_exists( 'Polylang', false ) && require_once 'compatibility/polylang.php';
25
  add_filter( 'relevanssi_search_ok', 'relevanssi_control_media_queries', 11, 2 );
26
 
27
  // Post indexing.
28
+ global $wp_version;
29
+ if ( version_compare( $wp_version, '5.6', '>=' ) ) {
30
+ add_action( 'wp_after_insert_post', 'relevanssi_insert_edit', 99, 1 );
31
+ } else {
32
+ add_action( 'wp_insert_post', 'relevanssi_insert_edit', 99, 1 );
33
+ }
34
  add_action( 'delete_post', 'relevanssi_remove_doc' );
35
 
36
  // Comment indexing.
48
  add_action( 'transition_post_status', 'relevanssi_update_child_posts', 99, 3 );
49
 
50
  // Relevanssi features.
51
+ add_filter( 'relevanssi_remove_punctuation', 'remove_accents', 9 );
52
  add_filter( 'relevanssi_remove_punctuation', 'relevanssi_remove_punct' );
53
  add_filter( 'relevanssi_post_ok', 'relevanssi_default_post_ok', 9, 2 );
54
  add_filter( 'relevanssi_query_filter', 'relevanssi_limit_filter' );
56
  add_action( 'relevanssi_update_counts', 'relevanssi_update_counts' );
57
  add_action( 'relevanssi_custom_field_value', 'relevanssi_filter_custom_fields', 10, 2 );
58
  add_filter( 'relevanssi_index_custom_fields', 'relevanssi_remove_metadata_fields' );
59
+ add_filter( 'relevanssi_join', 'relevanssi_post_date_throttle_join', 1 );
60
+ add_filter( 'relevanssi_where', 'relevanssi_post_date_throttle_where', 1 );
61
 
62
  // Excerpts and highlights.
63
  add_action( 'relevanssi_pre_the_content', 'relevanssi_kill_autoembed' );
479
  /**
480
  * Checks if a log export is requested.
481
  *
482
+ * If the 'relevanssi_export' query variable is set, a log export has been
483
+ * requested and one will be provided by relevanssi_export_log(). The click
484
+ * tracking log export checks 'relevanssi_export_clicks' and uses the function
485
+ * relevanssi_export_click_log().
486
  *
487
  * @see relevanssi_export_log
488
+ * @see relevanssi_export_click_log
489
  */
490
  function relevanssi_export_log_check() {
491
  if ( isset( $_REQUEST['relevanssi_export'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification, just checking the parameter exists.
492
  relevanssi_export_log();
493
  }
494
+ if ( isset( $_REQUEST['relevanssi_export_clicks'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification, just checking the parameter exists.
495
+ function_exists( 'relevanssi_export_click_log' ) && relevanssi_export_click_log();
496
+ }
497
  }
498
 
499
  /**
502
  function relevanssi_load_compatibility_code() {
503
  class_exists( 'acf', false ) && require_once 'compatibility/acf.php';
504
  class_exists( 'DGWT_WC_Ajax_Search', false ) && require_once 'compatibility/fibosearch.php';
505
+ class_exists( 'Jet_Smart_Filters', false ) && require_once 'compatibility/jetsmartfilters.php';
506
  class_exists( 'MeprUpdateCtrl', false ) && MeprUpdateCtrl::is_activated() && require_once 'compatibility/memberpress.php';
507
  class_exists( 'Obenland_Wp_Search_Suggest', false ) && require_once 'compatibility/wp-search-suggest.php';
508
  class_exists( 'Polylang', false ) && require_once 'compatibility/polylang.php';
lib/install.php CHANGED
@@ -76,7 +76,7 @@ function _relevanssi_install() {
76
  add_option( 'relevanssi_class', 'relevanssi-query-term' );
77
  add_option( 'relevanssi_comment_boost', $relevanssi_variables['comment_boost_default'] );
78
  add_option( 'relevanssi_content_boost', $relevanssi_variables['content_boost_default'] );
79
- add_option( 'relevanssi_css', 'text-decoration: underline; text-color: #ff0000' );
80
  add_option( 'relevanssi_db_version', '0' );
81
  add_option( 'relevanssi_default_orderby', 'relevance' );
82
  add_option( 'relevanssi_disable_or_fallback', 'off' );
76
  add_option( 'relevanssi_class', 'relevanssi-query-term' );
77
  add_option( 'relevanssi_comment_boost', $relevanssi_variables['comment_boost_default'] );
78
  add_option( 'relevanssi_content_boost', $relevanssi_variables['content_boost_default'] );
79
+ add_option( 'relevanssi_css', 'text-decoration: underline; color: #ff0000' );
80
  add_option( 'relevanssi_db_version', '0' );
81
  add_option( 'relevanssi_default_orderby', 'relevance' );
82
  add_option( 'relevanssi_disable_or_fallback', 'off' );
lib/interface.php CHANGED
@@ -127,6 +127,10 @@ function relevanssi_truncate_logs( $verbose = true ) {
127
  global $wpdb, $relevanssi_variables;
128
 
129
  $result = $wpdb->query( 'TRUNCATE ' . $relevanssi_variables['log_table'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
 
 
 
 
130
 
131
  if ( $verbose ) {
132
  if ( false !== $result ) {
127
  global $wpdb, $relevanssi_variables;
128
 
129
  $result = $wpdb->query( 'TRUNCATE ' . $relevanssi_variables['log_table'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
130
+ if ( isset( $relevanssi_variables['tracking_table'] ) ) {
131
+ $tracking_result = $wpdb->query( 'TRUNCATE ' . $relevanssi_variables['tracking_table'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
132
+ $results = $result && $tracking_result;
133
+ }
134
 
135
  if ( $verbose ) {
136
  if ( false !== $result ) {
lib/log.php CHANGED
@@ -237,13 +237,33 @@ function relevanssi_erase_log_data( $user_id, $page ) {
237
  *
238
  * Exports the whole Relevanssi search log as a CSV file.
239
  *
 
 
240
  * @since 2.2
241
  */
242
  function relevanssi_export_log() {
243
  global $wpdb, $relevanssi_variables;
244
 
245
- $now = gmdate( 'D, d M Y H:i:s' );
246
- $filename = 'relevanssi_log.csv';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
  header( 'Expires: Tue, 03 Jul 2001 06:00:00 GMT' );
249
  header( 'Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate' );
@@ -254,11 +274,10 @@ function relevanssi_export_log() {
254
  header( "Content-Disposition: attachment;filename={$filename}" );
255
  header( 'Content-Transfer-Encoding: binary' );
256
 
257
- $data = $wpdb->get_results( 'SELECT * FROM ' . $relevanssi_variables['log_table'], ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
258
  ob_start();
259
  $df = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions
260
  if ( empty( $data ) ) {
261
- fputcsv( $df, array( __( 'No search keywords logged.', 'relevanssi' ) ) );
262
  die();
263
  }
264
  fputcsv( $df, array_keys( reset( $data ) ) );
237
  *
238
  * Exports the whole Relevanssi search log as a CSV file.
239
  *
240
+ * @uses relevanssi_output_exported_log
241
+ *
242
  * @since 2.2
243
  */
244
  function relevanssi_export_log() {
245
  global $wpdb, $relevanssi_variables;
246
 
247
+ $data = $wpdb->get_results( 'SELECT * FROM ' . $relevanssi_variables['log_table'], ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
248
+
249
+ relevanssi_output_exported_log(
250
+ 'relevanssi_log.csv',
251
+ $data,
252
+ __( 'No search keywords logged.', 'relevanssi' )
253
+ );
254
+ }
255
+
256
+ /**
257
+ * Prints out the log.
258
+ *
259
+ * Does the exporting work for log exports.
260
+ *
261
+ * @param string $filename The filename to use.
262
+ * @param array $data The data to export.
263
+ * @param string $message The message to print if there is no data.
264
+ */
265
+ function relevanssi_output_exported_log( string $filename, array $data, string $message ) {
266
+ $now = gmdate( 'D, d M Y H:i:s' );
267
 
268
  header( 'Expires: Tue, 03 Jul 2001 06:00:00 GMT' );
269
  header( 'Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate' );
274
  header( "Content-Disposition: attachment;filename={$filename}" );
275
  header( 'Content-Transfer-Encoding: binary' );
276
 
 
277
  ob_start();
278
  $df = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions
279
  if ( empty( $data ) ) {
280
+ fputcsv( $df, array( $message ) );
281
  die();
282
  }
283
  fputcsv( $df, array_keys( reset( $data ) ) );
lib/phrases.php CHANGED
@@ -48,7 +48,7 @@ function relevanssi_extract_phrases( string $query ) {
48
  $phrase = trim( $phrase );
49
 
50
  // Do not count single-word phrases as phrases.
51
- if ( ! empty( $phrase ) && count( explode( ' ', $phrase ) ) > 1 ) {
52
  $phrases[] = $phrase;
53
  }
54
  $pos = $end + 1;
48
  $phrase = trim( $phrase );
49
 
50
  // Do not count single-word phrases as phrases.
51
+ if ( relevanssi_is_multiple_words( $phrase ) ) {
52
  $phrases[] = $phrase;
53
  }
54
  $pos = $end + 1;
lib/search.php CHANGED
@@ -719,8 +719,11 @@ function relevanssi_limit_filter( $query ) {
719
  if ( $limit < 0 ) {
720
  $limit = 500;
721
  }
 
722
  if ( $termless_search ) {
723
  $query = $query . " GROUP BY doc, item, type ORDER BY doc ASC LIMIT $limit";
 
 
724
  } else {
725
  $query = $query . " ORDER BY tf DESC LIMIT $limit";
726
  }
@@ -881,11 +884,11 @@ function relevanssi_taxonomy_score( &$match, $post_type_weights ) {
881
  $match->taxonomy_detail = json_decode( $match->taxonomy_detail );
882
  if ( is_object( $match->taxonomy_detail ) ) {
883
  foreach ( $match->taxonomy_detail as $tax => $count ) {
884
- if ( empty( $post_type_weights[ 'post_tagged_with_' . $tax ] ) ) {
885
- $match->taxonomy_score += $count * 1;
886
- } else {
887
- $match->taxonomy_score += $count * $post_type_weights[ 'post_tagged_with_' . $tax ];
888
  }
 
889
  }
890
  }
891
  }
@@ -1314,13 +1317,13 @@ function relevanssi_calculate_tf( $match, $post_type_weights ) {
1314
  relevanssi_taxonomy_score( $match, $post_type_weights );
1315
  } else {
1316
  $tag_weight = 1;
1317
- if ( isset( $post_type_weights['post_tagged_with_post_tag'] ) && is_numeric( $post_type_weights['post_tagged_with_post_tag'] ) ) {
1318
- $tag_weight = $post_type_weights['post_tagged_with_post_tag'];
1319
  }
1320
 
1321
  $category_weight = 1;
1322
- if ( isset( $post_type_weights['post_tagged_with_category'] ) && is_numeric( $post_type_weights['post_tagged_with_category'] ) ) {
1323
- $category_weight = $post_type_weights['post_tagged_with_category'];
1324
  }
1325
 
1326
  $taxonomy_weight = 1;
@@ -1645,10 +1648,11 @@ bool $no_terms, string $query_join = '', string $query_restrictions = '' ) : str
1645
  } else {
1646
  $term_cond = relevanssi_generate_term_where( $term, $search_again, $no_terms, get_option( 'relevanssi_fuzzy' ) );
1647
 
1648
- $content_boost = floatval( get_option( 'relevanssi_content_boost', 1 ) );
1649
- $title_boost = floatval( get_option( 'relevanssi_title_boost' ) );
1650
- $link_boost = floatval( get_option( 'relevanssi_link_boost' ) );
1651
- $comment_boost = floatval( get_option( 'relevanssi_comment_boost' ) );
 
1652
 
1653
  $tag = ! empty( $post_type_weights['post_tag'] ) ? $post_type_weights['post_tag'] : $relevanssi_variables['post_type_weight_defaults']['post_tag'];
1654
  $cat = ! empty( $post_type_weights['category'] ) ? $post_type_weights['category'] : $relevanssi_variables['post_type_weight_defaults']['category'];
@@ -1870,6 +1874,9 @@ function relevanssi_get_boundaries( $query ) : array {
1870
  $search_low_boundary += $query->query_vars['offset'];
1871
  }
1872
 
 
 
 
1873
  if ( $search_high_boundary > $hits_count ) {
1874
  $search_high_boundary = $hits_count;
1875
  }
@@ -1920,3 +1927,40 @@ function relevanssi_generate_id_type( string $post_id ) {
1920
  }
1921
  return $object;
1922
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
719
  if ( $limit < 0 ) {
720
  $limit = 500;
721
  }
722
+
723
  if ( $termless_search ) {
724
  $query = $query . " GROUP BY doc, item, type ORDER BY doc ASC LIMIT $limit";
725
+ } elseif ( 'post_date' === get_option( 'relevanssi_default_orderby' ) ) {
726
+ $query = $query . " ORDER BY p.post_date DESC LIMIT $limit";
727
  } else {
728
  $query = $query . " ORDER BY tf DESC LIMIT $limit";
729
  }
884
  $match->taxonomy_detail = json_decode( $match->taxonomy_detail );
885
  if ( is_object( $match->taxonomy_detail ) ) {
886
  foreach ( $match->taxonomy_detail as $tax => $count ) {
887
+ $weight = $post_type_weights[ 'post_tagged_with_' . $tax ] ?? null;
888
+ if ( ! $weight ) {
889
+ $weight = $post_type_weights[ $tax ] ?? 1;
 
890
  }
891
+ $match->taxonomy_score += $count * $weight;
892
  }
893
  }
894
  }
1317
  relevanssi_taxonomy_score( $match, $post_type_weights );
1318
  } else {
1319
  $tag_weight = 1;
1320
+ if ( isset( $post_type_weights['post_tag'] ) && is_numeric( $post_type_weights['post_tag'] ) ) {
1321
+ $tag_weight = $post_type_weights['post_tag'];
1322
  }
1323
 
1324
  $category_weight = 1;
1325
+ if ( isset( $post_type_weights['category'] ) && is_numeric( $post_type_weights['category'] ) ) {
1326
+ $category_weight = $post_type_weights['category'];
1327
  }
1328
 
1329
  $taxonomy_weight = 1;
1648
  } else {
1649
  $term_cond = relevanssi_generate_term_where( $term, $search_again, $no_terms, get_option( 'relevanssi_fuzzy' ) );
1650
 
1651
+ $content_boost = floatval( get_option( 'relevanssi_content_boost', 1 ) );
1652
+ $title_boost = floatval( get_option( 'relevanssi_title_boost' ) );
1653
+ $link_boost = floatval( get_option( 'relevanssi_link_boost' ) );
1654
+ $comment_boost = floatval( get_option( 'relevanssi_comment_boost' ) );
1655
+ $post_type_weights = get_option( 'relevanssi_post_type_weights' );
1656
 
1657
  $tag = ! empty( $post_type_weights['post_tag'] ) ? $post_type_weights['post_tag'] : $relevanssi_variables['post_type_weight_defaults']['post_tag'];
1658
  $cat = ! empty( $post_type_weights['category'] ) ? $post_type_weights['category'] : $relevanssi_variables['post_type_weight_defaults']['category'];
1874
  $search_low_boundary += $query->query_vars['offset'];
1875
  }
1876
 
1877
+ if ( $search_low_boundary < 0 ) {
1878
+ $search_low_boundary = 0;
1879
+ }
1880
  if ( $search_high_boundary > $hits_count ) {
1881
  $search_high_boundary = $hits_count;
1882
  }
1927
  }
1928
  return $object;
1929
  }
1930
+
1931
+ /**
1932
+ * Adds a join for wp_posts for post_date searches.
1933
+ *
1934
+ * If the default orderby is post_date, this function adds a wp_posts join to
1935
+ * the search query.
1936
+ *
1937
+ * @param string $query_join The join query.
1938
+ *
1939
+ * @return string The modified join query.
1940
+ */
1941
+ function relevanssi_post_date_throttle_join( $query_join ) {
1942
+ if ( 'post_date' === get_option( 'relevanssi_default_orderby' ) &&
1943
+ 'on' === get_option( 'relevanssi_throttle', 'on' ) ) {
1944
+ global $wpdb;
1945
+ $query_join = ', ' . $wpdb->posts . ' AS p';
1946
+ }
1947
+ return $query_join;
1948
+ }
1949
+
1950
+ /**
1951
+ * Adds a join for wp_posts for post_date searches.
1952
+ *
1953
+ * If the default orderby is post_date, this function connects the wp_posts
1954
+ * table joined in another filter function.
1955
+ *
1956
+ * @param string $query_restrictions The where query restrictions.
1957
+ *
1958
+ * @return string The modified query restrictions.
1959
+ */
1960
+ function relevanssi_post_date_throttle_where( $query_restrictions ) {
1961
+ if ( 'post_date' === get_option( 'relevanssi_default_orderby' ) &&
1962
+ 'on' === get_option( 'relevanssi_throttle', 'on' ) ) {
1963
+ $query_restrictions .= ' AND p.ID = relevanssi.doc';
1964
+ }
1965
+ return $query_restrictions;
1966
+ }
lib/tabs/debugging-tab.php CHANGED
@@ -15,7 +15,9 @@
15
  */
16
  function relevanssi_debugging_tab() {
17
  $how_relevanssi_sees = '';
 
18
  $current_post_id = 0;
 
19
  $selected = 'post';
20
  if ( isset( $_REQUEST['post_id'] ) ) {
21
  wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
@@ -39,6 +41,14 @@ function relevanssi_debugging_tab() {
39
  );
40
  }
41
  }
 
 
 
 
 
 
 
 
42
  wp_nonce_field( 'relevanssi_how_relevanssi_sees', '_relevanssi_nonce', true, true );
43
  ?>
44
  <h2><?php esc_html_e( 'Debugging', 'relevanssi' ); ?></h2>
@@ -90,9 +100,58 @@ function relevanssi_debugging_tab() {
90
  value='<?php esc_attr_e( 'Check the post', 'relevanssi' ); ?>'
91
  class='button button-primary' />
92
  </p>
 
93
  <?php echo $how_relevanssi_sees; // phpcs:ignore WordPress.Security.EscapeOutput ?>
94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  <?php do_action( 'relevanssi_debugging_tab' ); ?>
96
 
97
  <?php
98
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  */
16
  function relevanssi_debugging_tab() {
17
  $how_relevanssi_sees = '';
18
+ $db_post_view = '';
19
  $current_post_id = 0;
20
+ $current_db_post_id = 0;
21
  $selected = 'post';
22
  if ( isset( $_REQUEST['post_id'] ) ) {
23
  wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
41
  );
42
  }
43
  }
44
+
45
+ if ( isset( $_REQUEST['db_post_id'] ) ) {
46
+ wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
47
+ if ( intval( $_REQUEST['db_post_id'] ) > 0 ) {
48
+ $current_db_post_id = intval( $_REQUEST['db_post_id'] );
49
+ $db_post_view = relevanssi_generate_db_post_view( $current_db_post_id );
50
+ }
51
+ }
52
  wp_nonce_field( 'relevanssi_how_relevanssi_sees', '_relevanssi_nonce', true, true );
53
  ?>
54
  <h2><?php esc_html_e( 'Debugging', 'relevanssi' ); ?></h2>
100
  value='<?php esc_attr_e( 'Check the post', 'relevanssi' ); ?>'
101
  class='button button-primary' />
102
  </p>
103
+
104
  <?php echo $how_relevanssi_sees; // phpcs:ignore WordPress.Security.EscapeOutput ?>
105
 
106
+ <h2><?php esc_html_e( 'How does the post look like in the database?', 'relevanssi' ); ?></h2>
107
+
108
+ <p><?php esc_html_e( "This feature will show you how the post looks like in the database. It can sometimes be very helpful for debugging why a post isn't indexed the way you expect it to be.", 'relevanssi' ); ?></p>
109
+
110
+ <p><label for="db_post_id"><?php esc_html_e( 'The ID', 'relevanssi' ); ?></label>:
111
+ <input type="text" name="db_post_id" id="db_post_id"
112
+ <?php
113
+ if ( $current_db_post_id > 0 ) {
114
+ echo 'value="' . esc_attr( $current_db_post_id ) . '"';
115
+ }
116
+ ?>
117
+ />
118
+ </p>
119
+ <p>
120
+ <input
121
+ type='submit' name='submit'
122
+ value='<?php esc_attr_e( 'Check the post', 'relevanssi' ); ?>'
123
+ class='button button-primary' />
124
+ </p>
125
+
126
+ <?php echo $db_post_view; // phpcs:ignore WordPress.Security.EscapeOutput ?>
127
+
128
  <?php do_action( 'relevanssi_debugging_tab' ); ?>
129
 
130
  <?php
131
  }
132
+
133
+ /**
134
+ * Generates the debugging view for a post.
135
+ *
136
+ * @param int $post_id ID of the post.
137
+ *
138
+ * @return string The debugging view in a div container.
139
+ */
140
+ function relevanssi_generate_db_post_view( int $post_id ) {
141
+ global $wpdb;
142
+
143
+ $element = '<div id="relevanssi_db_view_container">';
144
+
145
+ $post_object = get_post( $post_id );
146
+
147
+ if ( ! $post_object ) {
148
+ $element .= '<p>' . esc_html__( 'Post not found', 'relevanssi' ) . '</p>';
149
+ $element .= '</div>';
150
+ return $element;
151
+ }
152
+
153
+ $element .= '<p>' . esc_html( $post_object->post_content ) . '</p>';
154
+
155
+ $element .= '</div>';
156
+ return $element;
157
+ }
lib/tabs/searching-tab.php CHANGED
@@ -36,6 +36,8 @@ function relevanssi_searching_tab() {
36
  $excat = get_option( 'relevanssi_excat' );
37
  $exclude_posts = get_option( 'relevanssi_exclude_posts' );
38
  $index_post_types = get_option( 'relevanssi_index_post_types' );
 
 
39
 
40
  $throttle = relevanssi_check( $throttle );
41
  $respect_exclude = relevanssi_check( $respect_exclude );
@@ -303,22 +305,7 @@ function relevanssi_searching_tab() {
303
  <?php esc_html_e( 'Throttle searches', 'relevanssi' ); ?>
304
  </th>
305
  <td id="throttlesearches">
306
- <div id="throttle_disabled"
307
- <?php
308
- if ( ! $orderby_date ) {
309
- echo "class='screen-reader-text'";
310
- }
311
- ?>
312
- >
313
- <p class="description"><?php esc_html_e( 'Throttling the search does not work when sorting the posts by date.', 'relevanssi' ); ?></p>
314
- </div>
315
- <div id="throttle_enabled"
316
- <?php
317
- if ( ! $orderby_relevance ) {
318
- echo "class='screen-reader-text'";
319
- }
320
- ?>
321
- >
322
  <fieldset>
323
  <legend class="screen-reader-text"><?php esc_html_e( 'Throttle searches.', 'relevanssi' ); ?></legend>
324
  <label for='relevanssi_throttle'>
@@ -330,6 +317,9 @@ function relevanssi_searching_tab() {
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>
 
 
 
333
  </div>
334
  </td>
335
  </tr>
36
  $excat = get_option( 'relevanssi_excat' );
37
  $exclude_posts = get_option( 'relevanssi_exclude_posts' );
38
  $index_post_types = get_option( 'relevanssi_index_post_types' );
39
+ $index_users = get_option( 'relevanssi_index_users' );
40
+ $index_terms = get_option( 'relevanssi_index_taxonomies' );
41
 
42
  $throttle = relevanssi_check( $throttle );
43
  $respect_exclude = relevanssi_check( $respect_exclude );
305
  <?php esc_html_e( 'Throttle searches', 'relevanssi' ); ?>
306
  </th>
307
  <td id="throttlesearches">
308
+ <div id="throttle_enabled">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  <fieldset>
310
  <legend class="screen-reader-text"><?php esc_html_e( 'Throttle searches.', 'relevanssi' ); ?></legend>
311
  <label for='relevanssi_throttle'>
317
  <p class="description important"><?php esc_html_e( "Your database is so small that you don't need to enable this.", 'relevanssi' ); ?></p>
318
  <?php } ?>
319
  <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>
320
+ <?php if ( 'post_date' === $orderby && ( 'on' === $index_users || 'on' === $index_terms ) ) { ?>
321
+ <p class="important"><?php esc_html_e( 'You have the default ordering set to post date and have enabled user or taxonomy term indexing. If you enable the throttle, the search results will only include posts. Users and taxonomy terms will be excluded. Either keep the throttle disabled or set the post ordering to relevance.', 'relevanssi' ); ?></p>
322
+ <?php } ?>
323
  </div>
324
  </td>
325
  </tr>
lib/user-searches.php CHANGED
@@ -163,15 +163,26 @@ function relevanssi_query_log() {
163
  printf( '<h3>%s</h3>', esc_html__( 'Reset Logs', 'relevanssi' ) );
164
  print( "<form method='post'>" );
165
  wp_nonce_field( 'relevanssi_reset_logs', '_relresnonce', true, true );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
  printf(
167
  '<p><label for="relevanssi_reset_code">%s</label>
168
  <input type="text" id="relevanssi_reset_code" name="relevanssi_reset_code" />
169
  <input type="submit" name="relevanssi_reset" value="%s" class="button" /></p></form>',
170
- // Translators: do not translate "reset".
171
- esc_html__(
172
- 'To reset the logs, type "reset" into the box here and click the Reset button',
173
- 'relevanssi'
174
- ),
175
  esc_html__( 'Reset', 'relevanssi' )
176
  );
177
  }
@@ -340,7 +351,7 @@ function relevanssi_date_queries( string $from, string $to, string $version = 'g
340
  if ( function_exists( 'relevanssi_insights_link' ) ) {
341
  $query_link = relevanssi_insights_link( $query );
342
  } else {
343
- $query_link = wp_kses( $query->query, 'strip' );
344
  }
345
 
346
  if ( 'good' === $version ) {
163
  printf( '<h3>%s</h3>', esc_html__( 'Reset Logs', 'relevanssi' ) );
164
  print( "<form method='post'>" );
165
  wp_nonce_field( 'relevanssi_reset_logs', '_relresnonce', true, true );
166
+
167
+ // Translators: do not translate "reset".
168
+ $message = esc_html__(
169
+ 'To reset the logs, type "reset" into the box here and click the Reset button',
170
+ 'relevanssi'
171
+ );
172
+
173
+ if ( RELEVANSSI_PREMIUM ) {
174
+ // Translators: do not translate "reset".
175
+ $message = esc_html__(
176
+ 'To reset the logs, type "reset" into the box here and click the Reset button. This will reset both the search log and the click tracking log.',
177
+ 'relevanssi'
178
+ );
179
+ }
180
+
181
  printf(
182
  '<p><label for="relevanssi_reset_code">%s</label>
183
  <input type="text" id="relevanssi_reset_code" name="relevanssi_reset_code" />
184
  <input type="submit" name="relevanssi_reset" value="%s" class="button" /></p></form>',
185
+ $message, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- already escaped.
 
 
 
 
186
  esc_html__( 'Reset', 'relevanssi' )
187
  );
188
  }
351
  if ( function_exists( 'relevanssi_insights_link' ) ) {
352
  $query_link = relevanssi_insights_link( $query );
353
  } else {
354
+ $query_link = wp_kses( relevanssi_hyphenate( $query->query ), 'strip' );
355
  }
356
 
357
  if ( 'good' === $version ) {
lib/utils.php CHANGED
@@ -128,6 +128,51 @@ function relevanssi_close_tags( string $html ) {
128
  return $html;
129
  }
130
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  /**
132
  * Prints out debugging notices.
133
  *
@@ -387,7 +432,8 @@ function relevanssi_get_current_language( bool $locale = true ) {
387
  $current_language = pll_get_post_language( $post->ID, $locale ? 'locale' : 'slug' );
388
  }
389
  } elseif ( function_exists( 'pll_current_language' ) ) {
390
- $current_language = pll_current_language( $locale ? 'locale' : 'slug' );
 
391
  }
392
  }
393
  if ( function_exists( 'icl_object_id' ) && ! function_exists( 'pll_is_translated_post_type' ) ) {
@@ -430,7 +476,6 @@ function relevanssi_get_current_language( bool $locale = true ) {
430
  }
431
  }
432
  }
433
-
434
  return $current_language;
435
  }
436
 
@@ -648,6 +693,18 @@ function relevanssi_get_the_title( $post ) {
648
  return $post->post_highlighted_title;
649
  }
650
 
 
 
 
 
 
 
 
 
 
 
 
 
651
  /**
652
  * Returns an imploded string if the option exists and is an array, an empty
653
  * string otherwise.
@@ -698,6 +755,85 @@ function relevanssi_intval( array $request, string $option ) {
698
  return null;
699
  }
700
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
701
  /**
702
  * Launches an asynchronous Ajax action.
703
  *
@@ -1007,9 +1143,10 @@ function relevanssi_strip_all_tags( $content ) : string {
1007
  if ( ! is_string( $content ) ) {
1008
  $content = '';
1009
  }
1010
- $content = preg_replace( '/<!--.*?-->/ms', '', $content );
1011
- $content = preg_replace( '/<[!a-zA-Z\/][^>].*?>/ms', ' ', $content );
1012
- return $content;
 
1013
  }
1014
 
1015
  /**
128
  return $html;
129
  }
130
 
131
+ /**
132
+ * Counts search term occurrances in the Relevanssi index.
133
+ *
134
+ * @param string $query The search query. Will be split at spaces.
135
+ * @param string $mode Output mode. Possible values 'array' or 'string'.
136
+ * Default is 'array'.
137
+ *
138
+ * @return array|string An array of search term occurrances, or a string with
139
+ * the number of occurrances.
140
+ */
141
+ function relevanssi_count_term_occurrances( string $query, string $mode = 'array' ) {
142
+ global $wpdb, $relevanssi_variables;
143
+
144
+ $terms = explode( ' ', $query );
145
+ $counts = array();
146
+
147
+ foreach ( $terms as $term ) {
148
+ $term = trim( $term );
149
+ if ( empty( $term ) ) {
150
+ continue;
151
+ }
152
+ $counts[ $term ] = $wpdb->get_var(
153
+ $wpdb->prepare(
154
+ 'SELECT SUM(content + title + comment + tag +
155
+ link + author + category + excerpt + taxonomy + customfield
156
+ + mysqlcolumn) AS total FROM ' .
157
+ $relevanssi_variables['relevanssi_table'] . // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
158
+ ' WHERE term = %s
159
+ GROUP BY term',
160
+ $term
161
+ )
162
+ );
163
+ }
164
+
165
+ if ( 'array' === $mode ) {
166
+ return $counts;
167
+ } elseif ( 'string' === $mode ) {
168
+ $strings = array();
169
+ foreach ( $counts as $term => $count ) {
170
+ $strings[] = "<span class='search_term'>$term</span>: <span class='count'>$count</span>";
171
+ }
172
+ return implode( ', ', $strings );
173
+ }
174
+ }
175
+
176
  /**
177
  * Prints out debugging notices.
178
  *
432
  $current_language = pll_get_post_language( $post->ID, $locale ? 'locale' : 'slug' );
433
  }
434
  } elseif ( function_exists( 'pll_current_language' ) ) {
435
+ $pll_language = pll_current_language( $locale ? 'locale' : 'slug' );
436
+ $current_language = $pll_language ? $pll_language : $current_language;
437
  }
438
  }
439
  if ( function_exists( 'icl_object_id' ) && ! function_exists( 'pll_is_translated_post_type' ) ) {
476
  }
477
  }
478
  }
 
479
  return $current_language;
480
  }
481
 
693
  return $post->post_highlighted_title;
694
  }
695
 
696
+ /**
697
+ * Adds a soft hyphen to a string at every five characters.
698
+ *
699
+ * @param string $string The string to hyphenate.
700
+ *
701
+ * @return string The hyphenated string.
702
+ */
703
+ function relevanssi_hyphenate( $string ) {
704
+ $string = preg_replace( '/([^\s]{8})([^\s])/u', '$1&shy;$2', html_entity_decode( $string ) );
705
+ return $string;
706
+ }
707
+
708
  /**
709
  * Returns an imploded string if the option exists and is an array, an empty
710
  * string otherwise.
755
  return null;
756
  }
757
 
758
+ /**
759
+ * Returns true if the search is from Relevanssi Live Ajax Search.
760
+ *
761
+ * Checks if $wp_query->query_vars['action'] is set to "relevanssi_live_search".
762
+ *
763
+ * @return bool True if the search is from Relevanssi Live Ajax Search, false
764
+ * otherwise.
765
+ */
766
+ function relevanssi_is_live_search() {
767
+ global $wp_query;
768
+ $relevanssi_live_search = false;
769
+ if ( isset( $wp_query->query_vars['action'] ) && 'relevanssi_live_search' === $wp_query->query_vars['action'] ) {
770
+ $relevanssi_live_search = true;
771
+ }
772
+ return $relevanssi_live_search;
773
+ }
774
+
775
+ /**
776
+ * Checks if a string is a multiple-word phrase.
777
+ *
778
+ * Replaces hyphens, quotes and ampersands with spaces if necessary based on
779
+ * the Relevanssi advanced indexing settings.
780
+ *
781
+ * @param string $string The string to check.
782
+ *
783
+ * @return boolean True if the string is a multiple-word phrase, false otherwise.
784
+ */
785
+ function relevanssi_is_multiple_words( string $string ) : bool {
786
+ if ( empty( $string ) ) {
787
+ return false;
788
+ }
789
+ $punctuation = get_option( 'relevanssi_punctuation' );
790
+ if ( 'replace' === $punctuation['hyphens'] ) {
791
+ $string = str_replace(
792
+ array(
793
+ '-',
794
+ '–',
795
+ '—',
796
+ ),
797
+ ' ',
798
+ $string
799
+ );
800
+ }
801
+ if ( 'replace' === $punctuation['quotes'] ) {
802
+ $string = str_replace(
803
+ array(
804
+ '&#8217;',
805
+ "'",
806
+ '’',
807
+ '‘',
808
+ '”',
809
+ '“',
810
+ '„',
811
+ '´',
812
+ '″',
813
+ ),
814
+ ' ',
815
+ $string
816
+ );
817
+ }
818
+ if ( 'replace' === $punctuation['ampersands'] ) {
819
+ $string = str_replace(
820
+ array(
821
+ '&#038;',
822
+ '&amp;',
823
+ '&',
824
+ ),
825
+ ' ',
826
+ $string
827
+ );
828
+ }
829
+
830
+ if ( count( explode( ' ', $string ) ) > 1 ) {
831
+ return true;
832
+ }
833
+
834
+ return false;
835
+ }
836
+
837
  /**
838
  * Launches an asynchronous Ajax action.
839
  *
1143
  if ( ! is_string( $content ) ) {
1144
  $content = '';
1145
  }
1146
+ $content = preg_replace( '/<!--.*?-->/ums', '', $content );
1147
+ $content = preg_replace( '/<[!a-zA-Z\/][^>].*?>/ums', ' ', $content );
1148
+
1149
+ return $content ?? '';
1150
  }
1151
 
1152
  /**
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.9
7
  Requires PHP: 7.0
8
- Stable tag: 4.15.2
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -128,6 +128,38 @@ Each document database is full of useless words. All the little words that appea
128
  * John Calahan for extensive 4.0 beta testing.
129
 
130
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
  = 4.15.2 =
132
  * New feature: New filter hook `relevanssi_didyoumean_token` lets you filter Did you mean words before correction. You can use this filter hook to exclude words from being corrected.
133
  * Minor fix: Phrase search couldn't find phrases that include an ampersand if they matched the post title. This works now.
@@ -152,11 +184,8 @@ Each document database is full of useless words. All the little words that appea
152
  * Minor fix: The Bricks compatibility was improved, Relevanssi now notices changes to Bricks posts more often. Relevanssi also only reads the text from the `_bricks_page_content_2` custom field.
153
 
154
  == Upgrade notice ==
155
- = 4.15.2 =
156
- * New filter hook for Did you mean words, small bug fixes for excerpts and phrases.
157
 
158
- = 4.15.1 =
159
- * Fixes warnings, improves permalink functions and relevanssi_hits_filter.
160
-
161
- = 4.15.0 =
162
- * New filter hooks and improved Bricks compatibility.
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: 6.0
7
  Requires PHP: 7.0
8
+ Stable tag: 4.17.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
128
  * John Calahan for extensive 4.0 beta testing.
129
 
130
  == Changelog ==
131
+ = 4.17.0 =
132
+ * New feature: You can now look at how the posts appear in the database from the Debugging tab.
133
+ * New feature: Relevanssi now works with WooCommerce layered navigation filters. The filter post counts should now match the Relevanssi search results.
134
+ * New feature: New function `relevanssi_count_term_occurrances()` can be used to display how many times search terms appear in the database.
135
+ * Changed behaviour: Relevanssi post update trigger is now on `wp_after_insert_post` instead of `wp_insert_post`. This makes the indexing more reliable and better compatible with other plugins.
136
+ * Changed behaviour: Previously, throttling searches has been impossible when results are sorted by date. Now if you set Relevanssi to sort by post date from the searching settings, you can enable the throttle and the throttling will make sure to keep the most recent posts. This does not work if you set the `orderby` to `post_date` elsewhere.
137
+ * Minor fix: Prevents Relevanssi from interfering in fringe cases (including The Event Calendar event search).
138
+ * Minor fix: Relevanssi added the `highlight` parameter to home page URLs, even though it shouldn't.
139
+ * Minor fix: Indexing `nav_menu_item` posts is stopped earlier in the process to avoid problems with big menus.
140
+ * Minor fix: If the `sentence` query variable is used to enable phrase searching, Relevanssi now adds quotes to the `highlight` parameter.
141
+ * Minor fix: Add support for JetSmartFilters.
142
+ * Minor fix: Add support for WooCommerce products attribute lookup table filtering.
143
+ * Minor fix: Improve excerpts to avoid breaking HTML tags when tags are allowed.
144
+ * Minor fix: Fix broken tag and category weight settings.
145
+ * Minor fix: Improve Polylang language detection.
146
+ * Minor fix: Relevanssi now hyphenates long search terms in the User searches page. This prevents long search terms from messing up the display.
147
+ * Minor fix: Improve WPFD file content indexing support. Relevanssi indexing now happens after the WPFD indexing is done.
148
+ * Minor fix: Add support for TablePress `table_filter` shortcodes.
149
+ * Minor fix: Stopped some problems with Did you mean suggestions suggesting the same word if a hyphen was included.
150
+ * Minor fix: Paging didn't work in admin searches for hierarchical post types (like pages).
151
+ * Minor fix: In-document highlighting could break certain elements thanks to Relevanssi messing up data attributes.
152
+ * Minor fix: Relevanssi now recursively runs `relevanssi_block_to_render` and the CSS `relevanssi_noindex` filtering for inner blocks.
153
+
154
+ = 4.16.0 =
155
+ * New feature: Oxygen compatibility has been upgraded to support JSON data from Oxygen 4. This is still in early stages, so feedback from Oxygen users is welcome.
156
+ * New feature: New filter hook `relevanssi_oxygen_element` is used to filter Oxygen JSON elements. The earlier `relevanssi_oxygen_section_filters` and `relevanssi_oxygen_section_content` filters are no longer used with Oxygen 4; this hook is the only way to filter Oxygen elements.
157
+ * Changed behaviour: Relevanssi now applies `remove_accents()` to all strings. This is because default database collations do not care for accents and having accents may cause missing information in indexing. If you use a database collation that doesn't ignore accents, make sure you disable this filter.
158
+ * Minor fix: Relevanssi used `the_category` filter with too few parameters. The missing parameters have been added.
159
+ * Minor fix: Stops drafts and pending posts from showing up in Relevanssi Live Ajax Searches.
160
+ * Minor fix: Phrases weren't used in some cases where a multiple-word phrase looked like a single-word phrase.
161
+ * Minor fix: Prevents fatal errors from `relevanssi_strip_all_tags()`.
162
+
163
  = 4.15.2 =
164
  * New feature: New filter hook `relevanssi_didyoumean_token` lets you filter Did you mean words before correction. You can use this filter hook to exclude words from being corrected.
165
  * Minor fix: Phrase search couldn't find phrases that include an ampersand if they matched the post title. This works now.
184
  * Minor fix: The Bricks compatibility was improved, Relevanssi now notices changes to Bricks posts more often. Relevanssi also only reads the text from the `_bricks_page_content_2` custom field.
185
 
186
  == Upgrade notice ==
187
+ = 4.17.0 =
188
+ * Large number of bug fixes and general improvements.
189
 
190
+ = 4.16.0 =
191
+ * Indexing update; please reindex after the upgrade! Oxygen 4 compatibility.
 
 
 
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.15.2
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.15.2';
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.17.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.17.0';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';