Relevanssi – A Better Search - Version 4.2.0

Version Description

  • New feature: The search form shortcode has a new parameter dropdown which can be used to add a category dropdown, like this: [searchform dropdown="category"].
  • New feature: Relevanssi can now use the contents of the PDF files indexed with WP File Download.
  • New filter: relevanssi_indexing_tokens can be used to filter the tokens (individual words) before they are indexed.
  • Removed filter: relevanssi_default_meta_query_relation did not have any effect anymore.
  • Changed behaviour: The default taxonomy relation was set to AND in 4.1.4, but wasn't properly applied before. Now it is really switched.
  • Changed behaviour: New post types have been added to list of forbidden post types Relevanssi won't show as indexing options (ACF, TablePress and WooCommerce).
  • Major fix: Tax query processing has been completely refactored, eliminating all sorts of bugs, especially with various edge cases.
  • Major fix: Gutenberg block indexing only worked with the Gutenberg plugin enabled. It now works with WP 5.0 built-in Gutenberg as well. If you use Gutenberg blocks, reindex to get all the block content in the index.
  • Major fix: Excerpt-building and highlighting did not respect the "Keyword matching" setting. They do now, and the excerpts should be better now.
  • Major fix: AND searches needed queries that could get too long for the database to handle. This has been fixed and optimized.
  • Major fix: Taxonomy term subquery relations didn't work; now they are applied.
  • Minor fix: iOS uses curly quotes by default, and that didn't work as a phrase operator. Now phrase operator works with curly quotes and straight quotes.
  • Minor fix: The Did you mean broke with search terms longer than 255 characters.
  • Minor fix: Phrases with numbers and one word like "team 17" didn't work, because numbers weren't counted as words.
Download this release

Release Info

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

Code changes from version 4.1.4 to 4.2.0

lib/admin-ajax.php CHANGED
@@ -174,7 +174,7 @@ function relevanssi_admin_search() {
174
  if ( isset( $query->query_vars['offset'] ) ) {
175
  $offset = $query->query_vars['offset'];
176
  }
177
- $results .= relevanssi_admin_search_format_posts( $query->posts, $query->found_posts, $offset );
178
 
179
  echo wp_json_encode( $results );
180
  wp_die();
@@ -186,23 +186,24 @@ function relevanssi_admin_search() {
186
  * Results are presented as an ordered list of posts. The format is very basic, and
187
  * can be modified with the 'relevanssi_admin_search_element' filter hook.
188
  *
189
- * @param array $posts The posts array.
190
- * @param int $total The number of posts found in total.
191
- * @param int $offset Offset value.
 
192
  *
193
  * @return string The formatted posts.
194
  *
195
  * @since 2.2.0
196
  */
197
- function relevanssi_admin_search_format_posts( $posts, $total, $offset ) {
198
  $result = '<h3>' . __( 'Results', 'relevanssi' ) . '</h3>';
199
  // Translators: %1$d is the total number of posts found, %2$d is the current search result count, %3$d is the offset.
200
  $result .= '<p>' . sprintf( __( 'Found a total of %1$d posts, showing %2$d posts from offset %3$s.', 'relevanssi' ), $total, count( $posts ), '<span id="offset">' . $offset . '</span>' ) . '</p>';
201
  if ( $offset > 0 ) {
202
- $result .= '<button type="button" id="prev_page">Previous page</button>';
203
  }
204
  if ( count( $posts ) + $offset < $total ) {
205
- $result .= '<button type="button" id="next_page">Next page</button>';
206
  }
207
  $result .= '<ol>';
208
 
@@ -215,25 +216,31 @@ function relevanssi_admin_search_format_posts( $posts, $total, $offset ) {
215
  $blog_name = get_bloginfo( 'name' ) . ': ';
216
  }
217
  $permalink = get_permalink( $post->ID );
218
- $edit_link = get_edit_post_link( $post->ID );
219
  $post_type = $post->post_type;
220
  if ( isset( $post->relevanssi_link ) ) {
221
  $permalink = $post->relevanssi_link;
222
  }
223
  if ( 'user' === $post->post_type ) {
224
- $edit_link = get_edit_user_link( $post->ID );
225
  }
226
- if ( empty( $edit_link ) ) {
227
  if ( isset( $post->term_id ) ) {
228
- $edit_link = get_edit_term_link( $post->term_id, $post->post_type );
229
  }
230
  }
231
- $pinned = '';
232
- if ( isset( $post->relevanssi_pinned ) ) {
233
- $pinned = '<strong>(pinned)</strong>';
 
 
 
 
 
234
  }
 
235
  $post_element = <<<EOH
236
- <li>$blog_name <strong>$post->post_title</strong> (<a href="$permalink">View $post_type</a>) (<a href="$edit_link">Edit $post_type</a>) <br />
237
  $post->post_excerpt<br />
238
  $score_label $post->relevance_score $pinned</li>
239
  EOH;
@@ -309,7 +316,6 @@ function relevanssi_admin_search_debugging_info( $query ) {
309
  'relevanssi_orderby',
310
  'relevanssi_order',
311
  'relevanssi_default_tax_query_relation',
312
- 'relevanssi_default_meta_query_relation',
313
  'relevanssi_hits_filter',
314
  );
315
 
174
  if ( isset( $query->query_vars['offset'] ) ) {
175
  $offset = $query->query_vars['offset'];
176
  }
177
+ $results .= relevanssi_admin_search_format_posts( $query->posts, $query->found_posts, $offset, $args['s'] );
178
 
179
  echo wp_json_encode( $results );
180
  wp_die();
186
  * Results are presented as an ordered list of posts. The format is very basic, and
187
  * can be modified with the 'relevanssi_admin_search_element' filter hook.
188
  *
189
+ * @param array $posts The posts array.
190
+ * @param int $total The number of posts found in total.
191
+ * @param int $offset Offset value.
192
+ * @param string $query The search query.
193
  *
194
  * @return string The formatted posts.
195
  *
196
  * @since 2.2.0
197
  */
198
+ function relevanssi_admin_search_format_posts( $posts, $total, $offset, $query ) {
199
  $result = '<h3>' . __( 'Results', 'relevanssi' ) . '</h3>';
200
  // Translators: %1$d is the total number of posts found, %2$d is the current search result count, %3$d is the offset.
201
  $result .= '<p>' . sprintf( __( 'Found a total of %1$d posts, showing %2$d posts from offset %3$s.', 'relevanssi' ), $total, count( $posts ), '<span id="offset">' . $offset . '</span>' ) . '</p>';
202
  if ( $offset > 0 ) {
203
+ $result .= sprintf( '<button type="button" id="prev_page">%s</button>', __( 'Previous page', 'relevanssi' ) );
204
  }
205
  if ( count( $posts ) + $offset < $total ) {
206
+ $result .= sprintf( '<button type="button" id="next_page">%s</button>', __( 'Next page', 'relevanssi' ) );
207
  }
208
  $result .= '<ol>';
209
 
216
  $blog_name = get_bloginfo( 'name' ) . ': ';
217
  }
218
  $permalink = get_permalink( $post->ID );
219
+ $edit_url = get_edit_post_link( $post->ID );
220
  $post_type = $post->post_type;
221
  if ( isset( $post->relevanssi_link ) ) {
222
  $permalink = $post->relevanssi_link;
223
  }
224
  if ( 'user' === $post->post_type ) {
225
+ $edit_url = get_edit_user_link( $post->ID );
226
  }
227
+ if ( empty( $edit_url ) ) {
228
  if ( isset( $post->term_id ) ) {
229
+ $edit_url = get_edit_term_link( $post->term_id, $post->post_type );
230
  }
231
  }
232
+ $view_link = sprintf( '<a href="%1$s">%2$s %3$s</a>', $permalink, __( 'View', 'relevanssi' ), $post_type );
233
+ $edit_link = sprintf( '<a href="%1$s">%2$s %3$s</a>', $edit_url, __( 'Edit', 'relevanssi' ), $post_type );
234
+ $pinning_buttons = '';
235
+ $pinned = '';
236
+
237
+ if ( function_exists( 'relevanssi_admin_search_pinning' ) ) {
238
+ // Relevanssi Premium adds pinning features to the admin search.
239
+ list( $pinning_buttons, $pinned ) = relevanssi_admin_search_pinning( $post, $query );
240
  }
241
+
242
  $post_element = <<<EOH
243
+ <li>$blog_name <strong>$post->post_title</strong> ($view_link) ($edit_link) $pinning_buttons <br />
244
  $post->post_excerpt<br />
245
  $score_label $post->relevance_score $pinned</li>
246
  EOH;
316
  'relevanssi_orderby',
317
  'relevanssi_order',
318
  'relevanssi_default_tax_query_relation',
 
319
  'relevanssi_hits_filter',
320
  );
321
 
lib/admin_scripts.js CHANGED
@@ -1,403 +1,502 @@
1
  /* Confirmation for copying options between blogs */
2
 
3
  jQuery(document).ready(function($) {
4
- $('#copy_config').submit(function() {
5
- var c = confirm(relevanssi.confirm);
6
- return c; //you can just return c because it will be true or false
7
- });
8
-
9
- $('#removeallstopwords').click(function() {
10
- var c = confirm(relevanssi.confirm_stopwords);
11
- return c; //you can just return c because it will be true or false
12
- });
13
- });
14
-
15
- jQuery(document).ready(function($){
16
- $('.color-field').wpColorPicker();
17
-
18
- var txtcol_control = $("#tr_relevanssi_txt_col");
19
- var bgcol_control = $("#tr_relevanssi_bg_col");
20
- var class_control = $("#tr_relevanssi_class");
21
- var css_control = $("#tr_relevanssi_css");
22
-
23
- $("#relevanssi_highlight").change(function() {
24
- txtcol_control.addClass('screen-reader-text');
25
- bgcol_control.addClass('screen-reader-text');
26
- class_control.addClass('screen-reader-text');
27
- css_control.addClass('screen-reader-text');
28
-
29
- if (this.value == "col") txtcol_control.toggleClass('screen-reader-text');
30
- if (this.value == "bgcol") bgcol_control.toggleClass('screen-reader-text');
31
- if (this.value == "class") class_control.toggleClass('screen-reader-text');
32
- if (this.value == "css") css_control.toggleClass('screen-reader-text');
33
- });
34
-
35
- $("#relevanssi_hilite_title").click(function() {
36
- $("#title_description").toggleClass('screen-reader-text', !this.checked);
37
- });
38
-
39
- var or_fallback = $("#orfallback");
40
- $("#relevanssi_implicit_operator").change(function() {
41
- or_fallback.toggleClass('screen-reader-text');
42
- });
43
-
44
- var index_subscribers = $("#index_subscribers");
45
- var user_extra_fields = $("#user_extra_fields");
46
- $("#relevanssi_index_users").click(function() {
47
- $("#user_profile_notice").toggleClass('screen-reader-text', !this.checked);
48
- index_subscribers.toggleClass('screen-reader-text', !this.checked);
49
- user_extra_fields.toggleClass('screen-reader-text', !this.checked);
50
- });
51
-
52
- var taxonomies = $("#taxonomies");
53
- $("#relevanssi_index_taxonomies").click(function() {
54
- taxonomies.toggleClass('screen-reader-text', !this.checked);
55
- });
56
-
57
- var post_type_archives = $("#posttypearchives");
58
- $("#relevanssi_index_post_type_archives").click(function() {
59
- post_type_archives.toggleClass('screen-reader-text', !this.checked);
60
- });
61
-
62
- var fields_content = $("#index_field_input");
63
- var fields_select = $("#relevanssi_index_fields_select");
64
- fields_select.change(function() {
65
- if (this.value == "some") fields_content.show();
66
- if (this.value != "some") fields_content.hide();
67
- });
68
-
69
- $("#show_advanced_indexing").click(function(e) {
70
- $("#advanced_indexing").toggleClass('screen-reader-text');
71
- $("#hide_advanced_indexing").show();
72
- $("#show_advanced_indexing").hide();
73
- });
74
-
75
- $("#hide_advanced_indexing").click(function(e) {
76
- $("#advanced_indexing").toggleClass('screen-reader-text');
77
- $("#show_advanced_indexing").show();
78
- $("#hide_advanced_indexing").hide();
79
- });
80
 
81
  $("#indexing_tab :input").change(function(e) {
82
- $("#build_index").attr("disabled", "disabled");
83
- var relevanssi_note = $("#relevanssi-note");
84
- relevanssi_note.show();
85
- relevanssi_note.html(relevanssi.options_changed);
86
- });
87
 
88
  $("#relevanssi_default_orderby").change(function(e) {
89
  if (this.value == "post_date") {
90
- $("#relevanssi_throttle").prop("checked", false);
91
  }
92
- $("#throttle_disabled").toggleClass('screen-reader-text');
93
- $("#throttle_enabled").toggleClass('screen-reader-text');
94
- });
95
 
96
  $("#relevanssi_show_pdf_errors").click(function(e) {
97
- var error_box = $("#relevanssi_pdf_errors");
98
- error_box.toggle();
99
  var data = {
100
- 'action': 'relevanssi_get_pdf_errors',
101
- };
102
  jQuery.post(ajaxurl, data, function(response) {
103
- error_box.val(JSON.parse(response));
104
- });
105
- });
106
 
107
  $("#relevanssi_excerpts").click(function() {
108
- $("#relevanssi_breakdown").toggleClass('relevanssi_disabled', !this.checked);
109
- $("#relevanssi_highlighting").toggleClass('relevanssi_disabled', !this.checked);
110
- $("#tr_excerpt_custom_fields").toggleClass('relevanssi_disabled', !this.checked);
111
- $("#tr_excerpt_allowable_tags").toggleClass('relevanssi_disabled', !this.checked);
112
- $("#tr_excerpt_length").toggleClass('relevanssi_disabled', !this.checked);
113
- $("#relevanssi_excerpt_length").attr('disabled', !this.checked);
114
- $("#relevanssi_excerpt_type").attr('disabled', !this.checked);
115
- $("#relevanssi_excerpt_allowable_tags").attr('disabled', !this.checked);
116
- $("#relevanssi_excerpt_custom_fields").attr('disabled', !this.checked);
117
- $("#relevanssi_highlight").attr('disabled', !this.checked);
118
- $("#relevanssi_txt_col").attr('disabled', !this.checked);
119
- $("#relevanssi_bg_col").attr('disabled', !this.checked);
120
- $("#relevanssi_css").attr('disabled', !this.checked);
121
- $("#relevanssi_class").attr('disabled', !this.checked);
122
- $("#relevanssi_hilite_title").attr('disabled', !this.checked);
123
- $("#relevanssi_highlight_docs").attr('disabled', !this.checked);
124
- $("#relevanssi_highlight_comments").attr('disabled', !this.checked);
125
- $("#relevanssi_word_boundaries").attr('disabled', !this.checked);
126
- $("#relevanssi_show_matches").attr('disabled', !this.checked);
127
- $("#relevanssi_show_matches_text").attr('disabled', !this.checked);
128
- });
 
 
 
 
 
 
 
 
 
129
 
130
  $("#relevanssi_searchblogs_all").click(function() {
131
- $("#relevanssi_searchblogs").attr('disabled', this.checked);
132
- });
133
- });
134
 
135
- var time = 0;
136
- var intervalID = 0;
137
 
138
  function relevanssiUpdateClock() {
139
- time++;
140
- var time_formatted = rlv_format_time(Math.round(time));
141
- document.getElementById("relevanssi_elapsed").innerHTML = time_formatted;
142
  }
143
 
144
  jQuery(document).ready(function($) {
145
  $("#continue_indexing").click(function() {
146
- $("#relevanssi-progress").show();
147
- $("#results").show();
148
- $("#relevanssi-timer").show();
149
- $("#stateoftheindex").html(relevanssi.reload_state);
150
- $("#indexing_button_instructions").hide();
151
- var results = document.getElementById("results");
152
- results.value = "";
153
 
154
- intervalID = window.setInterval(relevanssiUpdateClock, 1000);
155
 
156
  var data = {
157
- 'action': 'relevanssi_count_missing_posts',
158
- };
159
- console.log("Counting posts.");
160
- results.value += relevanssi.counting_posts + " ";
161
  jQuery.post(ajaxurl, data, function(response) {
162
- count_response = JSON.parse(response);
163
- console.log("Counted " + count_response + " posts.");
164
- results.value += count_response + " " + relevanssi.posts_found + "\n";
165
 
166
  if (count_response > 0) {
167
  var args = {
168
- 'completed' : 0,
169
- 'total' : count_response,
170
- 'offset' : 0,
171
- 'total_seconds' : 0,
172
- 'limit' : 10,
173
- 'extend' : true,
174
- 'security' : nonce.indexing_nonce,
175
- };
176
- process_indexing_step(args);
177
- }
178
- else {
179
- clearInterval(intervalID);
180
  }
181
- });
182
- });
183
- });
184
 
185
  function process_indexing_step(args) {
186
  // console.log(args.completed + " / " + args.total);
187
- var t0 = performance.now();
188
  jQuery.ajax({
189
- type: 'POST',
190
  url: ajaxurl,
191
  data: {
192
- action: 'relevanssi_index_posts',
193
  completed: args.completed,
194
  total: args.total,
195
  offset: args.offset,
196
  limit: args.limit,
197
  extend: args.extend,
198
- security: args.security,
199
  },
200
- dataType: 'json',
201
  success: function(response) {
202
- console.log(response);
203
  if (response.completed == "done") {
204
  //console.log("response " + parseInt(response.total_posts));
205
- var results_textarea = document.getElementById("results");
206
- results_textarea.value += response.feedback;
207
-
208
- document.getElementById("relevanssi_estimated").innerHTML = relevanssi.notimeremaining;
209
-
210
- var hidden_posts = args.total - parseInt(response.total_posts);
211
- results_textarea.value += relevanssi.indexing_complete + " " + hidden_posts + " " + relevanssi.excluded_posts;
212
- results_textarea.scrollTop = results_textarea.scrollHeight;
213
- jQuery('.rpi-progress div').animate({
214
- width: response.percentage + '%',
215
- }, 50, function() {
216
- // Animation complete.
217
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
218
 
219
- clearInterval(intervalID);
220
- }
221
- else {
222
- var t1 = performance.now();
223
- var time_seconds = (t1 - t0) / 1000;
224
- time_seconds = Math.round(time_seconds * 100) / 100;
225
- args.total_seconds += time_seconds;
226
-
227
- var estimated_time = rlv_format_approximate_time(Math.round(args.total_seconds / response.percentage * 100 - args.total_seconds));
228
-
229
- document.getElementById("relevanssi_estimated").innerHTML = estimated_time;
230
-
231
  /*console.log("total time: " + total_seconds);
232
  console.log("estimated time: " + Math.round(total_seconds / response.percentage * 100));
233
  console.log("estimated remaining: " + Math.round((total_seconds / response.percentage * 100) - total_seconds));
234
  console.log("estimated formatted: " + estimated_time);
235
  */
236
  if (time_seconds < 2) {
237
- args.limit = args.limit * 2;
238
  // current limit can be indexed in less than two seconds; double the limit
239
- }
240
- else if (time_seconds < 5) {
241
- args.limit += 5;
242
  // current limit can be indexed in less than five seconds; up the limit
243
- }
244
- else if (time_seconds > 20) {
245
- args.limit = Math.round(args.limit / 2);
246
- if (args.limit < 1) args.limit = 1;
247
  // current limit takes more than twenty seconds; halve the limit
248
- }
249
- else if (time_seconds > 10) {
250
- args.limit -= 5;
251
- if (args.limit < 1) args.limit = 1;
252
  // current limit takes more than ten seconds; reduce the limit
253
  }
254
 
255
- var results_textarea = document.getElementById("results");
256
- results_textarea.value += response.feedback;
257
- results_textarea.scrollTop = results_textarea.scrollHeight;
258
- var percentage_rounded = Math.round(response.percentage);
259
-
260
- jQuery('.rpi-progress div').animate({
261
- width: percentage_rounded + '%',
262
- }, 50, function() {
263
- // Animation complete.
264
- });
 
 
 
 
265
  //console.log("Next step.");
266
  var new_args = {
267
- 'completed' : parseInt(response.completed),
268
- 'total' : args.total,
269
- 'offset' : response.offset,
270
- 'total_seconds' : args.total_seconds,
271
- 'limit' : args.limit,
272
- 'extend' : args.extend,
273
- 'security' : args.security,
274
- };
275
-
276
- process_indexing_step(new_args);
277
  }
278
  }
279
- })
280
  }
281
 
282
  function rlv_format_time(total_seconds) {
283
- var hours = Math.floor(total_seconds / 3600);
284
- var minutes = Math.floor((total_seconds - (hours * 3600)) / 60);
285
- var seconds = total_seconds - (hours * 3600) - (minutes * 60);
286
 
287
- if (minutes < 10) minutes = "0" + minutes;
288
- if (seconds < 10) seconds = "0" + seconds;
289
 
290
- return hours + ":" + minutes + ":" + seconds;
291
  }
292
 
293
  function rlv_format_approximate_time(total_seconds) {
294
- var hours = Math.floor(total_seconds / 3600);
295
- var minutes = Math.floor(total_seconds / 60);
296
- var seconds = total_seconds - (hours * 3600) - (minutes * 60);
297
-
298
  var time = ""
299
  if (minutes > 99) {
300
- hour_word = relevanssi.hours;
301
- if (hours == 1) hour_word = relevanssi.hour;
302
- time = relevanssi.about + " " + hours + " " + hour_word;
303
  }
304
- if (minutes > 79 && minutes < 100) time = relevanssi.ninety_min;
305
- if (minutes > 49 && minutes < 80) time = relevanssi.sixty_min;
306
  if (minutes < 50) {
307
- if (seconds > 30) minutes += 1;
308
- minute_word = relevanssi.minutes;
309
- if (minutes == 1) minute_word = relevanssi.minute;
310
- time = relevanssi.about + " " + minutes + " " + minute_word;
311
- }
312
- if (minutes < 1) time = relevanssi.underminute;
313
-
314
- return time;
315
  }
316
 
317
  jQuery(document).ready(function($) {
318
- $('#search').click(function(e) {
319
- var results = document.getElementById("results");
320
- results.innerHTML = 'Searching...';
321
- e.preventDefault();
322
  jQuery.ajax({
323
- type: 'POST',
324
  url: ajaxurl,
325
  data: {
326
- action: 'relevanssi_admin_search',
327
- args: document.getElementById('args').value,
328
- posts_per_page: document.getElementById('posts_per_page').value,
329
- s: document.getElementById('s').value,
330
- security : nonce.searching_nonce,
331
  },
332
- dataType: 'json',
333
  success: function(response) {
334
- results.innerHTML = response;
335
  }
336
- });
337
- });
338
 
339
  // Show the filters on the "Admin search" page.
340
- $(document).on('click', '#show_filters', function(e) {
341
- $('#relevanssi_filter_list').toggle();
342
- $('#show_filters').toggle();
343
- $('#hide_filters').toggle();
344
- });
345
 
346
  // Hide the filters on the "Admin search" page.
347
- $(document).on('click', '#hide_filters', function(e) {
348
- $('#relevanssi_filter_list').toggle();
349
- $('#show_filters').toggle();
350
- $('#hide_filters').toggle();
351
- });
352
-
353
- $(document).on('click', '#next_page', function(e) {
354
- var results = document.getElementById("results");
355
- e.preventDefault();
356
- var offset = parseInt(document.getElementById('offset').innerHTML);
357
- var posts = parseInt(document.getElementById('posts_per_page').value);
358
- offset = offset + posts;
359
- results.innerHTML = 'Searching...';
360
  jQuery.ajax({
361
- type: 'POST',
362
  url: ajaxurl,
363
  data: {
364
- action: 'relevanssi_admin_search',
365
- args: document.getElementById('args').value,
366
- posts_per_page: document.getElementById('posts_per_page').value,
367
- s: document.getElementById('s').value,
368
  offset: offset,
369
- security : nonce.searching_nonce,
370
  },
371
- dataType: 'json',
372
  success: function(response) {
373
- results.innerHTML = response;
374
  }
375
- });
376
- });
377
-
378
- $(document).on('click', '#prev_page', function(e) {
379
- var results = document.getElementById("results");
380
- e.preventDefault();
381
- var offset = parseInt(document.getElementById('offset').innerHTML);
382
- var posts = parseInt(document.getElementById('posts_per_page').value);
383
- offset = offset - posts;
384
- if ( offset < 0 ) offset = 0;
385
- results.innerHTML = 'Searching...';
386
  jQuery.ajax({
387
- type: 'POST',
388
  url: ajaxurl,
389
  data: {
390
- action: 'relevanssi_admin_search',
391
- args: document.getElementById('args').value,
392
- posts_per_page: document.getElementById('posts_per_page').value,
393
- s: document.getElementById('s').value,
394
  offset: offset,
395
- security : nonce.searching_nonce,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  },
397
- dataType: 'json',
398
  success: function(response) {
399
- results.innerHTML = response;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
  }
401
- });
402
- });
403
- });
1
  /* Confirmation for copying options between blogs */
2
 
3
  jQuery(document).ready(function($) {
4
+ $("#copy_config").submit(function() {
5
+ var c = confirm(relevanssi.confirm)
6
+ return c //you can just return c because it will be true or false
7
+ })
8
+
9
+ $("#removeallstopwords").click(function() {
10
+ var c = confirm(relevanssi.confirm_stopwords)
11
+ return c //you can just return c because it will be true or false
12
+ })
13
+ })
14
+
15
+ jQuery(document).ready(function($) {
16
+ $(".color-field").wpColorPicker()
17
+
18
+ var txtcol_control = $("#tr_relevanssi_txt_col")
19
+ var bgcol_control = $("#tr_relevanssi_bg_col")
20
+ var class_control = $("#tr_relevanssi_class")
21
+ var css_control = $("#tr_relevanssi_css")
22
+
23
+ $("#relevanssi_highlight").change(function() {
24
+ txtcol_control.addClass("screen-reader-text")
25
+ bgcol_control.addClass("screen-reader-text")
26
+ class_control.addClass("screen-reader-text")
27
+ css_control.addClass("screen-reader-text")
28
+
29
+ if (this.value == "col") txtcol_control.toggleClass("screen-reader-text")
30
+ if (this.value == "bgcol") bgcol_control.toggleClass("screen-reader-text")
31
+ if (this.value == "class") class_control.toggleClass("screen-reader-text")
32
+ if (this.value == "css") css_control.toggleClass("screen-reader-text")
33
+ })
34
+
35
+ $("#relevanssi_hilite_title").click(function() {
36
+ $("#title_description").toggleClass("screen-reader-text", !this.checked)
37
+ })
38
+
39
+ var or_fallback = $("#orfallback")
40
+ $("#relevanssi_implicit_operator").change(function() {
41
+ or_fallback.toggleClass("screen-reader-text")
42
+ })
43
+
44
+ var index_subscribers = $("#index_subscribers")
45
+ var user_extra_fields = $("#user_extra_fields")
46
+ $("#relevanssi_index_users").click(function() {
47
+ $("#user_profile_notice").toggleClass("screen-reader-text", !this.checked)
48
+ index_subscribers.toggleClass("screen-reader-text", !this.checked)
49
+ user_extra_fields.toggleClass("screen-reader-text", !this.checked)
50
+ })
51
+
52
+ var taxonomies = $("#taxonomies")
53
+ $("#relevanssi_index_taxonomies").click(function() {
54
+ taxonomies.toggleClass("screen-reader-text", !this.checked)
55
+ })
56
+
57
+ var post_type_archives = $("#posttypearchives")
58
+ $("#relevanssi_index_post_type_archives").click(function() {
59
+ post_type_archives.toggleClass("screen-reader-text", !this.checked)
60
+ })
61
+
62
+ var fields_content = $("#index_field_input")
63
+ var fields_select = $("#relevanssi_index_fields_select")
64
+ fields_select.change(function() {
65
+ if (this.value == "some") fields_content.show()
66
+ if (this.value != "some") fields_content.hide()
67
+ })
68
+
69
+ $("#show_advanced_indexing").click(function(e) {
70
+ $("#advanced_indexing").toggleClass("screen-reader-text")
71
+ $("#hide_advanced_indexing").show()
72
+ $("#show_advanced_indexing").hide()
73
+ })
74
+
75
+ $("#hide_advanced_indexing").click(function(e) {
76
+ $("#advanced_indexing").toggleClass("screen-reader-text")
77
+ $("#show_advanced_indexing").show()
78
+ $("#hide_advanced_indexing").hide()
79
+ })
80
 
81
  $("#indexing_tab :input").change(function(e) {
82
+ $("#build_index").attr("disabled", "disabled")
83
+ var relevanssi_note = $("#relevanssi-note")
84
+ relevanssi_note.show()
85
+ relevanssi_note.html(relevanssi.options_changed)
86
+ })
87
 
88
  $("#relevanssi_default_orderby").change(function(e) {
89
  if (this.value == "post_date") {
90
+ $("#relevanssi_throttle").prop("checked", false)
91
  }
92
+ $("#throttle_disabled").toggleClass("screen-reader-text")
93
+ $("#throttle_enabled").toggleClass("screen-reader-text")
94
+ })
95
 
96
  $("#relevanssi_show_pdf_errors").click(function(e) {
97
+ var error_box = $("#relevanssi_pdf_errors")
98
+ error_box.toggle()
99
  var data = {
100
+ action: "relevanssi_get_pdf_errors"
101
+ }
102
  jQuery.post(ajaxurl, data, function(response) {
103
+ error_box.val(JSON.parse(response))
104
+ })
105
+ })
106
 
107
  $("#relevanssi_excerpts").click(function() {
108
+ $("#relevanssi_breakdown").toggleClass("relevanssi_disabled", !this.checked)
109
+ $("#relevanssi_highlighting").toggleClass(
110
+ "relevanssi_disabled",
111
+ !this.checked
112
+ )
113
+ $("#tr_excerpt_custom_fields").toggleClass(
114
+ "relevanssi_disabled",
115
+ !this.checked
116
+ )
117
+ $("#tr_excerpt_allowable_tags").toggleClass(
118
+ "relevanssi_disabled",
119
+ !this.checked
120
+ )
121
+ $("#tr_excerpt_length").toggleClass("relevanssi_disabled", !this.checked)
122
+ $("#relevanssi_excerpt_length").attr("disabled", !this.checked)
123
+ $("#relevanssi_excerpt_type").attr("disabled", !this.checked)
124
+ $("#relevanssi_excerpt_allowable_tags").attr("disabled", !this.checked)
125
+ $("#relevanssi_excerpt_custom_fields").attr("disabled", !this.checked)
126
+ $("#relevanssi_highlight").attr("disabled", !this.checked)
127
+ $("#relevanssi_txt_col").attr("disabled", !this.checked)
128
+ $("#relevanssi_bg_col").attr("disabled", !this.checked)
129
+ $("#relevanssi_css").attr("disabled", !this.checked)
130
+ $("#relevanssi_class").attr("disabled", !this.checked)
131
+ $("#relevanssi_hilite_title").attr("disabled", !this.checked)
132
+ $("#relevanssi_highlight_docs").attr("disabled", !this.checked)
133
+ $("#relevanssi_highlight_comments").attr("disabled", !this.checked)
134
+ $("#relevanssi_word_boundaries").attr("disabled", !this.checked)
135
+ $("#relevanssi_show_matches").attr("disabled", !this.checked)
136
+ $("#relevanssi_show_matches_text").attr("disabled", !this.checked)
137
+ })
138
 
139
  $("#relevanssi_searchblogs_all").click(function() {
140
+ $("#relevanssi_searchblogs").attr("disabled", this.checked)
141
+ })
142
+ })
143
 
144
+ var time = 0
145
+ var intervalID = 0
146
 
147
  function relevanssiUpdateClock() {
148
+ time++
149
+ var time_formatted = rlv_format_time(Math.round(time))
150
+ document.getElementById("relevanssi_elapsed").innerHTML = time_formatted
151
  }
152
 
153
  jQuery(document).ready(function($) {
154
  $("#continue_indexing").click(function() {
155
+ $("#relevanssi-progress").show()
156
+ $("#results").show()
157
+ $("#relevanssi-timer").show()
158
+ $("#stateoftheindex").html(relevanssi.reload_state)
159
+ $("#indexing_button_instructions").hide()
160
+ var results = document.getElementById("results")
161
+ results.value = ""
162
 
163
+ intervalID = window.setInterval(relevanssiUpdateClock, 1000)
164
 
165
  var data = {
166
+ action: "relevanssi_count_missing_posts"
167
+ }
168
+ console.log("Counting posts.")
169
+ results.value += relevanssi.counting_posts + " "
170
  jQuery.post(ajaxurl, data, function(response) {
171
+ count_response = JSON.parse(response)
172
+ console.log("Counted " + count_response + " posts.")
173
+ results.value += count_response + " " + relevanssi.posts_found + "\n"
174
 
175
  if (count_response > 0) {
176
  var args = {
177
+ completed: 0,
178
+ total: count_response,
179
+ offset: 0,
180
+ total_seconds: 0,
181
+ limit: 10,
182
+ extend: true,
183
+ security: nonce.indexing_nonce
184
+ }
185
+ process_indexing_step(args)
186
+ } else {
187
+ clearInterval(intervalID)
 
188
  }
189
+ })
190
+ })
191
+ })
192
 
193
  function process_indexing_step(args) {
194
  // console.log(args.completed + " / " + args.total);
195
+ var t0 = performance.now()
196
  jQuery.ajax({
197
+ type: "POST",
198
  url: ajaxurl,
199
  data: {
200
+ action: "relevanssi_index_posts",
201
  completed: args.completed,
202
  total: args.total,
203
  offset: args.offset,
204
  limit: args.limit,
205
  extend: args.extend,
206
+ security: args.security
207
  },
208
+ dataType: "json",
209
  success: function(response) {
210
+ console.log(response)
211
  if (response.completed == "done") {
212
  //console.log("response " + parseInt(response.total_posts));
213
+ var results_textarea = document.getElementById("results")
214
+ results_textarea.value += response.feedback
215
+
216
+ document.getElementById("relevanssi_estimated").innerHTML =
217
+ relevanssi.notimeremaining
218
+
219
+ var hidden_posts = args.total - parseInt(response.total_posts)
220
+ results_textarea.value +=
221
+ relevanssi.indexing_complete +
222
+ " " +
223
+ hidden_posts +
224
+ " " +
225
+ relevanssi.excluded_posts
226
+ results_textarea.scrollTop = results_textarea.scrollHeight
227
+ jQuery(".rpi-progress div").animate(
228
+ {
229
+ width: response.percentage + "%"
230
+ },
231
+ 50,
232
+ function() {
233
+ // Animation complete.
234
+ }
235
+ )
236
+
237
+ clearInterval(intervalID)
238
+ } else {
239
+ var t1 = performance.now()
240
+ var time_seconds = (t1 - t0) / 1000
241
+ time_seconds = Math.round(time_seconds * 100) / 100
242
+ args.total_seconds += time_seconds
243
+
244
+ var estimated_time = rlv_format_approximate_time(
245
+ Math.round(
246
+ (args.total_seconds / response.percentage) * 100 -
247
+ args.total_seconds
248
+ )
249
+ )
250
+
251
+ document.getElementById(
252
+ "relevanssi_estimated"
253
+ ).innerHTML = estimated_time
254
 
 
 
 
 
 
 
 
 
 
 
 
 
255
  /*console.log("total time: " + total_seconds);
256
  console.log("estimated time: " + Math.round(total_seconds / response.percentage * 100));
257
  console.log("estimated remaining: " + Math.round((total_seconds / response.percentage * 100) - total_seconds));
258
  console.log("estimated formatted: " + estimated_time);
259
  */
260
  if (time_seconds < 2) {
261
+ args.limit = args.limit * 2
262
  // current limit can be indexed in less than two seconds; double the limit
263
+ } else if (time_seconds < 5) {
264
+ args.limit += 5
 
265
  // current limit can be indexed in less than five seconds; up the limit
266
+ } else if (time_seconds > 20) {
267
+ args.limit = Math.round(args.limit / 2)
268
+ if (args.limit < 1) args.limit = 1
 
269
  // current limit takes more than twenty seconds; halve the limit
270
+ } else if (time_seconds > 10) {
271
+ args.limit -= 5
272
+ if (args.limit < 1) args.limit = 1
 
273
  // current limit takes more than ten seconds; reduce the limit
274
  }
275
 
276
+ var results_textarea = document.getElementById("results")
277
+ results_textarea.value += response.feedback
278
+ results_textarea.scrollTop = results_textarea.scrollHeight
279
+ var percentage_rounded = Math.round(response.percentage)
280
+
281
+ jQuery(".rpi-progress div").animate(
282
+ {
283
+ width: percentage_rounded + "%"
284
+ },
285
+ 50,
286
+ function() {
287
+ // Animation complete.
288
+ }
289
+ )
290
  //console.log("Next step.");
291
  var new_args = {
292
+ completed: parseInt(response.completed),
293
+ total: args.total,
294
+ offset: response.offset,
295
+ total_seconds: args.total_seconds,
296
+ limit: args.limit,
297
+ extend: args.extend,
298
+ security: args.security
299
+ }
300
+
301
+ process_indexing_step(new_args)
302
  }
303
  }
304
+ })
305
  }
306
 
307
  function rlv_format_time(total_seconds) {
308
+ var hours = Math.floor(total_seconds / 3600)
309
+ var minutes = Math.floor((total_seconds - hours * 3600) / 60)
310
+ var seconds = total_seconds - hours * 3600 - minutes * 60
311
 
312
+ if (minutes < 10) minutes = "0" + minutes
313
+ if (seconds < 10) seconds = "0" + seconds
314
 
315
+ return hours + ":" + minutes + ":" + seconds
316
  }
317
 
318
  function rlv_format_approximate_time(total_seconds) {
319
+ var hours = Math.floor(total_seconds / 3600)
320
+ var minutes = Math.floor(total_seconds / 60)
321
+ var seconds = total_seconds - hours * 3600 - minutes * 60
322
+
323
  var time = ""
324
  if (minutes > 99) {
325
+ hour_word = relevanssi.hours
326
+ if (hours == 1) hour_word = relevanssi.hour
327
+ time = relevanssi.about + " " + hours + " " + hour_word
328
  }
329
+ if (minutes > 79 && minutes < 100) time = relevanssi.ninety_min
330
+ if (minutes > 49 && minutes < 80) time = relevanssi.sixty_min
331
  if (minutes < 50) {
332
+ if (seconds > 30) minutes += 1
333
+ minute_word = relevanssi.minutes
334
+ if (minutes == 1) minute_word = relevanssi.minute
335
+ time = relevanssi.about + " " + minutes + " " + minute_word
336
+ }
337
+ if (minutes < 1) time = relevanssi.underminute
338
+
339
+ return time
340
  }
341
 
342
  jQuery(document).ready(function($) {
343
+ $("#search").click(function(e) {
344
+ var results = document.getElementById("results")
345
+ results.innerHTML = "Searching..."
346
+ e.preventDefault()
347
  jQuery.ajax({
348
+ type: "POST",
349
  url: ajaxurl,
350
  data: {
351
+ action: "relevanssi_admin_search",
352
+ args: document.getElementById("args").value,
353
+ posts_per_page: document.getElementById("posts_per_page").value,
354
+ s: document.getElementById("s").value,
355
+ security: nonce.searching_nonce
356
  },
357
+ dataType: "json",
358
  success: function(response) {
359
+ results.innerHTML = response
360
  }
361
+ })
362
+ })
363
 
364
  // Show the filters on the "Admin search" page.
365
+ $(document).on("click", "#show_filters", function(e) {
366
+ $("#relevanssi_filter_list").toggle()
367
+ $("#show_filters").toggle()
368
+ $("#hide_filters").toggle()
369
+ })
370
 
371
  // Hide the filters on the "Admin search" page.
372
+ $(document).on("click", "#hide_filters", function(e) {
373
+ $("#relevanssi_filter_list").toggle()
374
+ $("#show_filters").toggle()
375
+ $("#hide_filters").toggle()
376
+ })
377
+
378
+ $(document).on("click", "#next_page", function(e) {
379
+ var results = document.getElementById("results")
380
+ e.preventDefault()
381
+ var offset = parseInt(document.getElementById("offset").innerHTML)
382
+ var posts = parseInt(document.getElementById("posts_per_page").value)
383
+ offset = offset + posts
384
+ results.innerHTML = "Searching..."
385
  jQuery.ajax({
386
+ type: "POST",
387
  url: ajaxurl,
388
  data: {
389
+ action: "relevanssi_admin_search",
390
+ args: document.getElementById("args").value,
391
+ posts_per_page: document.getElementById("posts_per_page").value,
392
+ s: document.getElementById("s").value,
393
  offset: offset,
394
+ security: nonce.searching_nonce
395
  },
396
+ dataType: "json",
397
  success: function(response) {
398
+ results.innerHTML = response
399
  }
400
+ })
401
+ })
402
+
403
+ $(document).on("click", "#prev_page", function(e) {
404
+ var results = document.getElementById("results")
405
+ e.preventDefault()
406
+ var offset = parseInt(document.getElementById("offset").innerHTML)
407
+ var posts = parseInt(document.getElementById("posts_per_page").value)
408
+ offset = offset - posts
409
+ if (offset < 0) offset = 0
410
+ results.innerHTML = "Searching..."
411
  jQuery.ajax({
412
+ type: "POST",
413
  url: ajaxurl,
414
  data: {
415
+ action: "relevanssi_admin_search",
416
+ args: document.getElementById("args").value,
417
+ posts_per_page: document.getElementById("posts_per_page").value,
418
+ s: document.getElementById("s").value,
419
  offset: offset,
420
+ security: nonce.searching_nonce
421
+ },
422
+ dataType: "json",
423
+ success: function(response) {
424
+ results.innerHTML = response
425
+ }
426
+ })
427
+ })
428
+
429
+ $(document).on("click", ".pin", function(e) {
430
+ e.preventDefault()
431
+ var keyword = e.target.dataset.keyword
432
+ var post_id = e.target.dataset.postid
433
+ jQuery.ajax({
434
+ type: "POST",
435
+ url: ajaxurl,
436
+ data: {
437
+ action: "relevanssi_pin_post",
438
+ keyword,
439
+ post_id,
440
+ security: nonce.searching_nonce
441
+ },
442
+ dataType: "json",
443
+ success: function(response) {
444
+ var results = document.getElementById("results")
445
+ results.innerHTML = "Searching..."
446
+ e.preventDefault()
447
+ jQuery.ajax({
448
+ type: "POST",
449
+ url: ajaxurl,
450
+ data: {
451
+ action: "relevanssi_admin_search",
452
+ args: document.getElementById("args").value,
453
+ posts_per_page: document.getElementById("posts_per_page").value,
454
+ s: document.getElementById("s").value,
455
+ security: nonce.searching_nonce
456
+ },
457
+ dataType: "json",
458
+ success: function(response) {
459
+ results.innerHTML = response
460
+ }
461
+ })
462
+ }
463
+ })
464
+ })
465
+
466
+ $(document).on("click", ".unpin", function(e) {
467
+ e.preventDefault()
468
+ var keyword = e.target.dataset.keyword
469
+ var post_id = e.target.dataset.postid
470
+ jQuery.ajax({
471
+ type: "POST",
472
+ url: ajaxurl,
473
+ data: {
474
+ action: "relevanssi_unpin_post",
475
+ keyword,
476
+ post_id,
477
+ security: nonce.searching_nonce
478
  },
479
+ dataType: "json",
480
  success: function(response) {
481
+ var results = document.getElementById("results")
482
+ results.innerHTML = "Searching..."
483
+ e.preventDefault()
484
+ jQuery.ajax({
485
+ type: "POST",
486
+ url: ajaxurl,
487
+ data: {
488
+ action: "relevanssi_admin_search",
489
+ args: document.getElementById("args").value,
490
+ posts_per_page: document.getElementById("posts_per_page").value,
491
+ s: document.getElementById("s").value,
492
+ security: nonce.searching_nonce
493
+ },
494
+ dataType: "json",
495
+ success: function(response) {
496
+ results.innerHTML = response
497
+ }
498
+ })
499
  }
500
+ })
501
+ })
502
+ })
lib/admin_scripts_free.js CHANGED
@@ -1,51 +1,51 @@
1
  jQuery(document).ready(function($) {
2
- $("#build_index").click(function() {
3
- $("#relevanssi-progress").show();
4
- $("#results").show();
5
- $("#relevanssi-timer").show();
6
- $("#relevanssi-indexing-instructions").show();
7
- $("#stateoftheindex").html(relevanssi.reload_state);
8
- $("#indexing_button_instructions").hide();
9
- var results = document.getElementById("results");
10
- results.value = "";
11
 
12
- var data = {
13
- 'action': 'relevanssi_truncate_index',
14
- };
15
 
16
- intervalID = window.setInterval(relevanssiUpdateClock, 1000);
17
 
18
- console.log("Truncating index.");
19
- results.value += relevanssi.truncating_index + " ";
20
- jQuery.post(ajaxurl, data, function(response) {
21
- truncate_response = JSON.parse(response);
22
- console.log("Truncate index: " + truncate_response);
23
- if (truncate_response == true) {
24
- results.value += relevanssi.done + "\n";
25
- }
26
 
27
- var data = {
28
- 'action': 'relevanssi_count_posts',
29
- };
30
- console.log("Counting posts.");
31
- results.value += relevanssi.counting_posts + " ";
32
- jQuery.post(ajaxurl, data, function(response) {
33
- count_response = JSON.parse(response);
34
- console.log("Counted " + count_response + " posts.");
35
- var post_total = parseInt(count_response);
36
- results.value += count_response + " " + relevanssi.posts_found + "\n";
37
 
38
- var args = {
39
- 'completed' : 0,
40
- 'total' : post_total,
41
- 'offset' : 0,
42
- 'total_seconds' : 0,
43
- 'limit' : 10,
44
- 'extend' : false,
45
- 'security' : nonce.indexing_nonce,
46
- };
47
- process_indexing_step(args);
48
- });
49
- });
50
- });
51
- });
1
  jQuery(document).ready(function($) {
2
+ $("#build_index").click(function() {
3
+ $("#relevanssi-progress").show()
4
+ $("#results").show()
5
+ $("#relevanssi-timer").show()
6
+ $("#relevanssi-indexing-instructions").show()
7
+ $("#stateoftheindex").html(relevanssi.reload_state)
8
+ $("#indexing_button_instructions").hide()
9
+ var results = document.getElementById("results")
10
+ results.value = ""
11
 
12
+ var data = {
13
+ action: "relevanssi_truncate_index"
14
+ }
15
 
16
+ intervalID = window.setInterval(relevanssiUpdateClock, 1000)
17
 
18
+ console.log("Truncating index.")
19
+ results.value += relevanssi.truncating_index + " "
20
+ jQuery.post(ajaxurl, data, function(response) {
21
+ truncate_response = JSON.parse(response)
22
+ console.log("Truncate index: " + truncate_response)
23
+ if (truncate_response == true) {
24
+ results.value += relevanssi.done + "\n"
25
+ }
26
 
27
+ var data = {
28
+ action: "relevanssi_count_posts"
29
+ }
30
+ console.log("Counting posts.")
31
+ results.value += relevanssi.counting_posts + " "
32
+ jQuery.post(ajaxurl, data, function(response) {
33
+ count_response = JSON.parse(response)
34
+ console.log("Counted " + count_response + " posts.")
35
+ var post_total = parseInt(count_response)
36
+ results.value += count_response + " " + relevanssi.posts_found + "\n"
37
 
38
+ var args = {
39
+ completed: 0,
40
+ total: post_total,
41
+ offset: 0,
42
+ total_seconds: 0,
43
+ limit: 10,
44
+ extend: false,
45
+ security: nonce.indexing_nonce
46
+ }
47
+ process_indexing_step(args)
48
+ })
49
+ })
50
+ })
51
+ })
lib/common.php CHANGED
@@ -167,9 +167,16 @@ function relevanssi_default_post_ok( $post_ok, $post_id ) {
167
  // Current user has the required capabilities and can see the page.
168
  $post_ok = true;
169
  }
170
-
 
 
 
 
 
 
 
171
  if ( function_exists( 'members_content_permissions_enabled' ) && function_exists( 'members_can_current_user_view_post' ) ) {
172
- // Members. Only use, if 'content permissions' feature is enabled.
173
  if ( members_content_permissions_enabled() ) {
174
  $post_ok = members_can_current_user_view_post( $post_id );
175
  }
@@ -201,6 +208,13 @@ function relevanssi_default_post_ok( $post_ok, $post_id ) {
201
  // Restrict Content Pro.
202
  $post_ok = rcp_user_can_access( get_current_user_id(), $post_id );
203
  }
 
 
 
 
 
 
 
204
  /**
205
  * Filters statuses allowed in admin searches.
206
  *
@@ -293,23 +307,26 @@ function relevanssi_extract_phrases( $query ) {
293
  $substr_function = 'mb_substr';
294
  }
295
 
296
- $pos = call_user_func( $strpos_function, $query, '"' );
 
 
 
297
 
298
  $phrases = array();
299
  while ( false !== $pos ) {
300
  $start = $pos;
301
- $end = call_user_func( $strpos_function, $query, '"', $start + 1 );
302
 
303
  if ( false === $end ) {
304
  // Just one " in the query.
305
  $pos = $end;
306
  continue;
307
  }
308
- $phrase = call_user_func( $substr_function, $query, $start + 1, $end - $start - 1 );
309
  $phrase = trim( $phrase );
310
 
311
  // Do not count single-word phrases as phrases.
312
- if ( ! empty( $phrase ) && str_word_count( $phrase ) > 1 ) {
313
  $phrases[] = $phrase;
314
  }
315
  $pos = $end;
@@ -705,9 +722,13 @@ function relevanssi_prevent_default_request( $request, $query ) {
705
  * source material. If the parameter is an array of string, each string is
706
  * tokenized separately and the resulting tokens are combined into one array.
707
  *
708
- * @param string|array $string The string, or an array of strings, to tokenize.
709
- * @param boolean $remove_stops If true, stopwords are removed. Default true.
710
- * @param int $min_word_length The minimum word length to include. Default -1.
 
 
 
 
711
  */
712
  function relevanssi_tokenize( $string, $remove_stops = true, $min_word_length = -1 ) {
713
  $tokens = array();
@@ -731,6 +752,9 @@ function relevanssi_tokenize( $string, $remove_stops = true, $min_word_length =
731
  if ( $remove_stops ) {
732
  $stopword_list = relevanssi_fetch_stopwords();
733
  }
 
 
 
734
 
735
  if ( function_exists( 'relevanssi_apply_thousands_separator' ) ) {
736
  // A Premium feature.
@@ -854,16 +878,21 @@ function relevanssi_get_post_status( $post_id ) {
854
  */
855
  function relevanssi_get_post_type( $post_id ) {
856
  global $relevanssi_post_array;
857
-
858
  if ( isset( $relevanssi_post_array[ $post_id ] ) ) {
859
  return $relevanssi_post_array[ $post_id ]->post_type;
860
  } else {
861
  // No hit from the cache; let's add this post to the cache.
862
- $post = get_post( $post_id );
863
-
864
- $relevanssi_post_array[ $post_id ] = $post;
865
-
866
- return $post->post_type;
 
 
 
 
 
 
867
  }
868
  }
869
 
@@ -1026,6 +1055,7 @@ function relevanssi_stripos( $haystack, $needle, $offset = 0 ) {
1026
  * @return string The HTML code, with tags closed.
1027
  */
1028
  function relevanssi_close_tags( $html ) {
 
1029
  preg_match_all( '#<(?!meta|img|br|hr|input\b)\b([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result );
1030
  $opened_tags = $result[1];
1031
  preg_match_all( '#</([a-z]+)>#iU', $html, $result );
@@ -1330,7 +1360,7 @@ function relevanssi_didyoumean( $query, $pre, $post, $n = 5, $echo = true ) {
1330
  * @return string The suggestion HTML code, null if nothing found.
1331
  */
1332
  function relevanssi_simple_didyoumean( $query, $pre, $post, $n = 5 ) {
1333
- global $wpdb, $relevanssi_variables, $wp_query;
1334
 
1335
  $total_results = $wp_query->found_posts;
1336
 
@@ -1425,13 +1455,16 @@ function relevanssi_simple_generate_suggestion( $query ) {
1425
  $closest = '';
1426
  break;
1427
  } else {
1428
- $lev = levenshtein( $token, $row->query );
1429
- if ( $lev < 3 && ( $lev < $distance || $distance < 0 ) ) {
1430
- if ( $row->a > 0 ) {
1431
- $distance = $lev;
1432
- $closest = $row->query;
1433
- if ( $lev < 2 ) {
1434
- break; // get the first with distance of 1 and go.
 
 
 
1435
  }
1436
  }
1437
  }
@@ -1555,11 +1588,6 @@ function relevanssi_sanitize_hex_color( $color ) {
1555
  function relevanssi_common_words( $limit = 25, $wp_cli = false ) {
1556
  global $wpdb, $relevanssi_variables;
1557
 
1558
- $plugin = 'relevanssi';
1559
- if ( RELEVANSSI_PREMIUM ) {
1560
- $plugin = 'relevanssi-premium';
1561
- }
1562
-
1563
  if ( ! is_numeric( $limit ) ) {
1564
  $limit = 25;
1565
  }
@@ -1578,11 +1606,14 @@ function relevanssi_common_words( $limit = 25, $wp_cli = false ) {
1578
  <td>
1579
  <ul>
1580
  <?php
1581
- $src = plugins_url( 'delete.png', $relevanssi_variables['file'] );
1582
-
1583
  foreach ( $words as $word ) {
1584
  $stop = __( 'Add to stopwords', 'relevanssi' );
1585
- printf( '<li><input style="padding: 0; margin: 0" type="submit" src="%1$s" name="term" value="%2$s"/> (%3$d)</li>', esc_attr( $src ), esc_attr( $word->term ), esc_html( $word->cnt ) );
 
 
 
 
 
1586
  }
1587
  ?>
1588
  </ul>
@@ -1605,6 +1636,7 @@ function relevanssi_get_forbidden_post_types() {
1605
  return array(
1606
  'nav_menu_item', // Navigation menu items.
1607
  'revision', // Never index revisions.
 
1608
  'acf-field', // Advanced Custom Fields.
1609
  'acf-field-group', // Advanced Custom Fields.
1610
  'oembed_cache', // Mysterious caches.
@@ -1617,6 +1649,10 @@ function relevanssi_get_forbidden_post_types() {
1617
  'amp_validated_url', // AMP.
1618
  'jp_pay_order', // Jetpack.
1619
  'jp_pay_product', // Jetpack.
 
 
 
 
1620
  );
1621
  }
1622
 
@@ -1630,5 +1666,6 @@ function relevanssi_get_forbidden_taxonomies() {
1630
  'nav_menu', // Navigation menus.
1631
  'link_category', // Link categories.
1632
  'amp_validation_error', // AMP.
 
1633
  );
1634
  }
167
  // Current user has the required capabilities and can see the page.
168
  $post_ok = true;
169
  }
170
+ $current_user = wp_get_current_user();
171
+ if ( ! $post_ok && $current_user->ID > 0 ) {
172
+ $post = relevanssi_get_post( $post_id );
173
+ if ( $current_user->ID === (int) $post->post_author ) {
174
+ // Allow authors to see their own private posts.
175
+ $post_ok = true;
176
+ }
177
+ }
178
  if ( function_exists( 'members_content_permissions_enabled' ) && function_exists( 'members_can_current_user_view_post' ) ) {
179
+ // Members. Only use if 'content permissions' feature is enabled.
180
  if ( members_content_permissions_enabled() ) {
181
  $post_ok = members_can_current_user_view_post( $post_id );
182
  }
208
  // Restrict Content Pro.
209
  $post_ok = rcp_user_can_access( get_current_user_id(), $post_id );
210
  }
211
+ // User Access Manager.
212
+ global $userAccessManager; // phpcs:ignore WordPress.NamingConventions.ValidVariableName
213
+ if ( isset( $userAccessManager ) ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
214
+ $type = relevanssi_get_post_type( $post_id );
215
+ $post_ok = $userAccessManager->getAccessHandler()->checkObjectAccess( $type, $post_id ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName
216
+ }
217
+
218
  /**
219
  * Filters statuses allowed in admin searches.
220
  *
307
  $substr_function = 'mb_substr';
308
  }
309
 
310
+ // iOS uses “” as the default quotes, so Relevanssi needs to understand that as
311
+ // well.
312
+ $normalized_query = str_replace( array( '”', '“' ), '"', $query );
313
+ $pos = call_user_func( $strpos_function, $normalized_query, '"' );
314
 
315
  $phrases = array();
316
  while ( false !== $pos ) {
317
  $start = $pos;
318
+ $end = call_user_func( $strpos_function, $normalized_query, '"', $start + 1 );
319
 
320
  if ( false === $end ) {
321
  // Just one " in the query.
322
  $pos = $end;
323
  continue;
324
  }
325
+ $phrase = call_user_func( $substr_function, $normalized_query, $start + 1, $end - $start - 1 );
326
  $phrase = trim( $phrase );
327
 
328
  // Do not count single-word phrases as phrases.
329
+ if ( ! empty( $phrase ) && count( explode( ' ', $phrase ) ) > 1 ) {
330
  $phrases[] = $phrase;
331
  }
332
  $pos = $end;
722
  * source material. If the parameter is an array of string, each string is
723
  * tokenized separately and the resulting tokens are combined into one array.
724
  *
725
+ * @param string|array $string The string, or an array of strings, to
726
+ * tokenize.
727
+ * @param boolean|string $remove_stops If true, stopwords are removed. If 'body',
728
+ * also removes the body stopwords. Default
729
+ * true.
730
+ * @param int $min_word_length The minimum word length to include.
731
+ * Default -1.
732
  */
733
  function relevanssi_tokenize( $string, $remove_stops = true, $min_word_length = -1 ) {
734
  $tokens = array();
752
  if ( $remove_stops ) {
753
  $stopword_list = relevanssi_fetch_stopwords();
754
  }
755
+ if ( 'body' === $remove_stops && function_exists( 'relevanssi_fetch_body_stopwords' ) ) {
756
+ $stopword_list = array_merge( $stopword_list, relevanssi_fetch_body_stopwords() );
757
+ }
758
 
759
  if ( function_exists( 'relevanssi_apply_thousands_separator' ) ) {
760
  // A Premium feature.
878
  */
879
  function relevanssi_get_post_type( $post_id ) {
880
  global $relevanssi_post_array;
 
881
  if ( isset( $relevanssi_post_array[ $post_id ] ) ) {
882
  return $relevanssi_post_array[ $post_id ]->post_type;
883
  } else {
884
  // No hit from the cache; let's add this post to the cache.
885
+ $post = relevanssi_get_post( $post_id );
886
+
887
+ if ( is_wp_error( $post ) ) {
888
+ $post->add_data( 'not_found', "relevanssi_get_post_type() didn't get a post, relevanssi_get_post() returned null." );
889
+ return $post;
890
+ } elseif ( $post ) {
891
+ $relevanssi_post_array[ $post_id ] = $post;
892
+ return $post->post_type;
893
+ } else {
894
+ return new WP_Error( 'not_found', 'Something went wrong.' );
895
+ }
896
  }
897
  }
898
 
1055
  * @return string The HTML code, with tags closed.
1056
  */
1057
  function relevanssi_close_tags( $html ) {
1058
+ $result = array();
1059
  preg_match_all( '#<(?!meta|img|br|hr|input\b)\b([a-z]+)(?: .*)?(?<![/|/ ])>#iU', $html, $result );
1060
  $opened_tags = $result[1];
1061
  preg_match_all( '#</([a-z]+)>#iU', $html, $result );
1360
  * @return string The suggestion HTML code, null if nothing found.
1361
  */
1362
  function relevanssi_simple_didyoumean( $query, $pre, $post, $n = 5 ) {
1363
+ global $wp_query;
1364
 
1365
  $total_results = $wp_query->found_posts;
1366
 
1455
  $closest = '';
1456
  break;
1457
  } else {
1458
+ if ( relevanssi_strlen( $token ) < 255 ) {
1459
+ // The levenshtein() function has a max length of 255 characters.
1460
+ $lev = levenshtein( $token, $row->query );
1461
+ if ( $lev < 3 && ( $lev < $distance || $distance < 0 ) ) {
1462
+ if ( $row->a > 0 ) {
1463
+ $distance = $lev;
1464
+ $closest = $row->query;
1465
+ if ( $lev < 2 ) {
1466
+ break; // get the first with distance of 1 and go.
1467
+ }
1468
  }
1469
  }
1470
  }
1588
  function relevanssi_common_words( $limit = 25, $wp_cli = false ) {
1589
  global $wpdb, $relevanssi_variables;
1590
 
 
 
 
 
 
1591
  if ( ! is_numeric( $limit ) ) {
1592
  $limit = 25;
1593
  }
1606
  <td>
1607
  <ul>
1608
  <?php
 
 
1609
  foreach ( $words as $word ) {
1610
  $stop = __( 'Add to stopwords', 'relevanssi' );
1611
+ printf( '<li>%1$s (%2$d) <button name="term" value="%1$s" />%3$s</button>', esc_attr( $word->term ), esc_html( $word->cnt ), esc_html( $stop ) );
1612
+ if ( RELEVANSSI_PREMIUM ) {
1613
+ $body = __( 'Add to content stopwords', 'relevanssi' );
1614
+ printf( ' <button name="body_term" value="%1$s" />%3$s</button>', esc_attr( $word->term ), esc_html( $word->cnt ), esc_html( $body ) );
1615
+ }
1616
+ echo '</li>';
1617
  }
1618
  ?>
1619
  </ul>
1636
  return array(
1637
  'nav_menu_item', // Navigation menu items.
1638
  'revision', // Never index revisions.
1639
+ 'acf', // Advanced Custom Fields.
1640
  'acf-field', // Advanced Custom Fields.
1641
  'acf-field-group', // Advanced Custom Fields.
1642
  'oembed_cache', // Mysterious caches.
1649
  'amp_validated_url', // AMP.
1650
  'jp_pay_order', // Jetpack.
1651
  'jp_pay_product', // Jetpack.
1652
+ 'tablepress_table', // TablePress.
1653
+ 'shop_order', // WooCommerce.
1654
+ 'shop_order_refund', // WooCommerce.
1655
+ 'shop_webhook', // WooCommerce.
1656
  );
1657
  }
1658
 
1666
  'nav_menu', // Navigation menus.
1667
  'link_category', // Link categories.
1668
  'amp_validation_error', // AMP.
1669
+ 'product_visibility', // WooCommerce.
1670
  );
1671
  }
lib/compatibility/gutenberg.php CHANGED
@@ -16,7 +16,7 @@ if ( RELEVANSSI_PREMIUM ) {
16
  add_action( 'save_post', 'relevanssi_remove_duplicate_postmeta', 100 );
17
  }
18
 
19
- add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 10, 2 );
20
 
21
  /**
22
  * Renders Gutenberg reusable blocks.
@@ -25,10 +25,9 @@ add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 1
25
  * picks up the comments and renders the blocks.
26
  *
27
  * @param string $content The post content.
28
- * @param object $post The post object.
29
  *
30
  * @return string The post content with the rendered content added.
31
  */
32
- function relevanssi_gutenberg_block_rendering( $content, $post ) {
33
  return do_blocks( $content );
34
  }
16
  add_action( 'save_post', 'relevanssi_remove_duplicate_postmeta', 100 );
17
  }
18
 
19
+ add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 10 );
20
 
21
  /**
22
  * Renders Gutenberg reusable blocks.
25
  * picks up the comments and renders the blocks.
26
  *
27
  * @param string $content The post content.
 
28
  *
29
  * @return string The post content with the rendered content added.
30
  */
31
+ function relevanssi_gutenberg_block_rendering( $content ) {
32
  return do_blocks( $content );
33
  }
lib/compatibility/wp-file-download.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/compatibility/wp-file-download.php
4
+ *
5
+ * WP File Download compatibility features. Compatibility with WPFD checked for
6
+ * version 4.5.4.
7
+ *
8
+ * @package Relevanssi
9
+ * @author Mikko Saari
10
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
11
+ * @see https://www.relevanssi.com/
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.
18
+ *
19
+ * Fetches the words from wpfd_words. wpfd_index.tid is the post ID, wpfd_index.id is
20
+ * then used to get the wpfd_docs.id, that is used to get the wpfd_vectors.did which
21
+ * can then be used to fetch the correct words from wpfd_words. This function is
22
+ * hooked onto relevanssi_content_to_index filter hook.
23
+ *
24
+ * @param string $content The post content as a string.
25
+ * @param object $post The post object.
26
+ *
27
+ * @return string The post content with the words added to the end.
28
+ */
29
+ function relevanssi_wpfd_content( $content, $post ) {
30
+ $wpfd_search_config = get_option( '_wpfd_global_search_config', null );
31
+ if ( 'wpfd_file' === $post->post_type ) {
32
+ if ( $wpfd_search_config && isset( $wpfd_search_config['plain_text_search'] ) && $wpfd_search_config['plain_text_search'] ) {
33
+ global $wpdb;
34
+ $words = $wpdb->get_col("SELECT word
35
+ FROM {$wpdb->prefix}wpfd_words, {$wpdb->prefix}wpfd_docs, {$wpdb->prefix}wpfd_index, {$wpdb->prefix}wpfd_vectors
36
+ WHERE {$wpdb->prefix}wpfd_index.tid = {$post->ID}
37
+ AND {$wpdb->prefix}wpfd_docs.index_id = {$wpdb->prefix}wpfd_index.id
38
+ AND {$wpdb->prefix}wpfd_docs.id = {$wpdb->prefix}wpfd_vectors.did
39
+ AND {$wpdb->prefix}wpfd_vectors.wid = {$wpdb->prefix}wpfd_words.id"); // WPCS: unprepared SQL ok, no user-generated inputs.
40
+ $content .= implode( ' ', $words );
41
+ }
42
+ }
43
+ return $content;
44
+ }
lib/contextual-help.php CHANGED
@@ -130,6 +130,8 @@ function rlv_index_filter( $block, $post_id ) {
130
  '<li>' . sprintf( __( 'If you need a search form on some page on your site, you can use the %s shortcode to print out a basic search form.', 'relevanssi' ), '<code>[searchform]</code>' ) . '</li>' .
131
  // Translators: %1$s is '[searchform post_types="page"]', %2$s is '[searchform cats="10,14,17"]'.
132
  '<li>' . sprintf( __( 'If you need to add query variables to the search form, the shortcode takes parameters, which are then printed out as hidden input fields. To get a search form with a post type restriction, you can use %1$s. To restrict the search to categories 10, 14 and 17, you can use %2$s and so on.', 'relevanssi' ), '<code>[searchform post_types="page"]</code>', '<code>[searchform cats="10,14,17"]</code>' ) . '</li>' .
 
 
133
  '</ul>',
134
  ));
135
  $screen->add_help_tab( array(
130
  '<li>' . sprintf( __( 'If you need a search form on some page on your site, you can use the %s shortcode to print out a basic search form.', 'relevanssi' ), '<code>[searchform]</code>' ) . '</li>' .
131
  // Translators: %1$s is '[searchform post_types="page"]', %2$s is '[searchform cats="10,14,17"]'.
132
  '<li>' . sprintf( __( 'If you need to add query variables to the search form, the shortcode takes parameters, which are then printed out as hidden input fields. To get a search form with a post type restriction, you can use %1$s. To restrict the search to categories 10, 14 and 17, you can use %2$s and so on.', 'relevanssi' ), '<code>[searchform post_types="page"]</code>', '<code>[searchform cats="10,14,17"]</code>' ) . '</li>' .
133
+ // Translators: %1$s is 'dropdown', %2$s is '[searchform dropdown="category"]'.
134
+ '<li>' . sprintf( __( 'You can use the %1$s parameter to add a taxonomy dropdown to the search form. Just use the name of the taxonomy, like %2$s. This works best with hierarchical taxonomies like categories with relatively few options available.', 'relevanssi' ), '<code>dropdown</code>', '<code>[searchform dropdown="category"]</code>' ) . '</li>' .
135
  '</ul>',
136
  ));
137
  $screen->add_help_tab( array(
lib/excerpts-highlights.php CHANGED
@@ -293,7 +293,6 @@ function relevanssi_create_excerpt( $content, $terms, $query ) {
293
  $excerpt_slice = array_slice( $words, $offset, $excerpt_length );
294
  $excerpt_slice = ' ' . implode( ' ', $excerpt_slice );
295
 
296
- $term_hits = 0;
297
  $count_matches = relevanssi_count_matches( array_keys( $terms ), $excerpt_slice );
298
  if ( $count_matches > 0 && $count_matches > $best_excerpt_term_hits ) {
299
  $best_excerpt_term_hits = $count_matches;
@@ -488,9 +487,9 @@ function relevanssi_highlight_terms( $content, $query, $in_docs = false ) {
488
 
489
  usort( $terms, 'relevanssi_strlen_sort' );
490
 
491
- $word_boundaries = false;
492
  if ( 'on' === get_option( 'relevanssi_word_boundaries', 'on' ) ) {
493
- $word_boundaries = true;
494
  }
495
 
496
  foreach ( $terms as $term ) {
@@ -500,7 +499,7 @@ function relevanssi_highlight_terms( $content, $query, $in_docs = false ) {
500
  $undecoded_content = $content;
501
  $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
502
 
503
- if ( $word_boundaries ) {
504
  $regex = "/(\b$pr_term\b)/iu";
505
  if ( 'never' !== get_option( 'relevanssi_fuzzy' ) ) {
506
  $regex = "/(\b$pr_term|$pr_term\b)/iu";
@@ -516,9 +515,6 @@ function relevanssi_highlight_terms( $content, $query, $in_docs = false ) {
516
  }
517
  }
518
 
519
- $preg_start = preg_quote( $start_emp_token );
520
- $preg_end = preg_quote( $end_emp_token );
521
-
522
  if ( preg_match_all( '/<.*>/U', $content, $matches ) > 0 ) {
523
  // Remove highlights from inside HTML tags.
524
  foreach ( $matches as $match ) {
@@ -821,10 +817,24 @@ function relevanssi_count_matches( $words, $complete_text ) {
821
  $lowercase_text = relevanssi_strtolower( $complete_text, 'UTF-8' );
822
  $text = '';
823
 
 
 
 
 
 
824
  $count_words = count( $words );
825
  for ( $t = 0; $t < $count_words; $t++ ) {
826
  $word_slice = relevanssi_strtolower( relevanssi_add_accent_variations( $words[ $t ] ), 'UTF-8' );
827
- $lines = preg_split( "/$word_slice/", $lowercase_text );
 
 
 
 
 
 
 
 
 
828
  if ( count( $lines ) > 1 ) {
829
  $count_lines = count( $lines );
830
  for ( $tt = 0; $tt < $count_lines; $tt++ ) {
293
  $excerpt_slice = array_slice( $words, $offset, $excerpt_length );
294
  $excerpt_slice = ' ' . implode( ' ', $excerpt_slice );
295
 
 
296
  $count_matches = relevanssi_count_matches( array_keys( $terms ), $excerpt_slice );
297
  if ( $count_matches > 0 && $count_matches > $best_excerpt_term_hits ) {
298
  $best_excerpt_term_hits = $count_matches;
487
 
488
  usort( $terms, 'relevanssi_strlen_sort' );
489
 
490
+ $word_boundaries_available = true;
491
  if ( 'on' === get_option( 'relevanssi_word_boundaries', 'on' ) ) {
492
+ $word_boundaries_available = false;
493
  }
494
 
495
  foreach ( $terms as $term ) {
499
  $undecoded_content = $content;
500
  $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
501
 
502
+ if ( $word_boundaries_available ) {
503
  $regex = "/(\b$pr_term\b)/iu";
504
  if ( 'never' !== get_option( 'relevanssi_fuzzy' ) ) {
505
  $regex = "/(\b$pr_term|$pr_term\b)/iu";
515
  }
516
  }
517
 
 
 
 
518
  if ( preg_match_all( '/<.*>/U', $content, $matches ) > 0 ) {
519
  // Remove highlights from inside HTML tags.
520
  foreach ( $matches as $match ) {
817
  $lowercase_text = relevanssi_strtolower( $complete_text, 'UTF-8' );
818
  $text = '';
819
 
820
+ $word_boundaries_available = true;
821
+ if ( 'on' === get_option( 'relevanssi_word_boundaries', 'on' ) ) {
822
+ $word_boundaries_available = false;
823
+ }
824
+
825
  $count_words = count( $words );
826
  for ( $t = 0; $t < $count_words; $t++ ) {
827
  $word_slice = relevanssi_strtolower( relevanssi_add_accent_variations( $words[ $t ] ), 'UTF-8' );
828
+ if ( $word_boundaries_available ) {
829
+ if ( 'never' !== get_option( 'relevanssi_fuzzy' ) ) {
830
+ $regex = "/\b$word_slice|$word_slice\b/";
831
+ } else {
832
+ $regex = "/\b$word_slice\b/";
833
+ }
834
+ } else {
835
+ $regex = "/$word_slice/";
836
+ }
837
+ $lines = preg_split( $regex, $lowercase_text );
838
  if ( count( $lines ) > 1 ) {
839
  $count_lines = count( $lines );
840
  for ( $tt = 0; $tt < $count_lines; $tt++ ) {
lib/indexing.php CHANGED
@@ -567,7 +567,15 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
567
  if ( $debug ) {
568
  relevanssi_debug_echo( "Comment content: $post_comments" );
569
  }
570
- $post_comments_tokens = relevanssi_tokenize( $post_comments, true, $min_word_length );
 
 
 
 
 
 
 
 
571
  if ( count( $post_comments_tokens ) > 0 ) {
572
  foreach ( $post_comments_tokens as $token => $count ) {
573
  $n++;
@@ -590,7 +598,8 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
590
  if ( 'on' === get_option( 'relevanssi_index_author' ) ) {
591
  $author_id = $post->post_author;
592
  $display_name = get_the_author_meta( 'display_name', $author_id );
593
- $name_tokens = relevanssi_tokenize( $display_name, false, $min_word_length );
 
594
  if ( $debug ) {
595
  relevanssi_debug_echo( 'Indexing post author as: ' . implode( ' ', array_keys( $name_tokens ) ) );
596
  }
@@ -671,8 +680,8 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
671
  if ( $debug ) {
672
  relevanssi_debug_echo( "\tKey: " . $field . ' – value: ' . $value );
673
  }
674
-
675
- $value_tokens = relevanssi_tokenize( $value, true, $min_word_length );
676
  foreach ( $value_tokens as $token => $count ) {
677
  if ( ! isset( $insert_data[ $token ]['customfield'] ) ) {
678
  $insert_data[ $token ]['customfield'] = 0;
@@ -694,7 +703,8 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
694
  if ( $debug ) {
695
  relevanssi_debug_echo( "Indexing post excerpt: $post->post_excerpt" );
696
  }
697
- $excerpt_tokens = relevanssi_tokenize( $post->post_excerpt, true, $min_word_length );
 
698
  foreach ( $excerpt_tokens as $token => $count ) {
699
  if ( ! isset( $insert_data[ $token ]['excerpt'] ) ) {
700
  $insert_data[ $token ]['excerpt'] = 0;
@@ -745,6 +755,8 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
745
  * @param boolean If true, remove stopwords. Default true.
746
  */
747
  $title_tokens = relevanssi_tokenize( $filtered_title, apply_filters( 'relevanssi_remove_stopwords_in_titles', true ), $min_word_length );
 
 
748
 
749
  if ( $debug ) {
750
  relevanssi_debug_echo( "\tTitle, tokenized: " . implode( ' ', array_keys( $title_tokens ) ) );
@@ -914,8 +926,9 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
914
  * @param string $contents The post content.
915
  * @param object $post The full post object.
916
  */
917
- $contents = apply_filters( 'relevanssi_post_content_before_tokenize', $contents, $post );
918
- $content_tokens = relevanssi_tokenize( $contents, true, $min_word_length );
 
919
  if ( $debug ) {
920
  relevanssi_debug_echo( "\tContent, tokenized:\n" . implode( ' ', array_keys( $content_tokens ) ) );
921
  }
567
  if ( $debug ) {
568
  relevanssi_debug_echo( "Comment content: $post_comments" );
569
  }
570
+ /**
571
+ * Filters the indexing tokens before they are added to the $insert_data.
572
+ *
573
+ * @param array An array of token-frequency pairs.
574
+ * @param string The context of the tokens (eg. 'content', 'title').
575
+ *
576
+ * @return array The filtered tokens.
577
+ */
578
+ $post_comments_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $post_comments, true, $min_word_length ), 'comments' );
579
  if ( count( $post_comments_tokens ) > 0 ) {
580
  foreach ( $post_comments_tokens as $token => $count ) {
581
  $n++;
598
  if ( 'on' === get_option( 'relevanssi_index_author' ) ) {
599
  $author_id = $post->post_author;
600
  $display_name = get_the_author_meta( 'display_name', $author_id );
601
+ /** This filter is documented in common/indexing.php */
602
+ $name_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $display_name, false, $min_word_length ), 'author' );
603
  if ( $debug ) {
604
  relevanssi_debug_echo( 'Indexing post author as: ' . implode( ' ', array_keys( $name_tokens ) ) );
605
  }
680
  if ( $debug ) {
681
  relevanssi_debug_echo( "\tKey: " . $field . ' – value: ' . $value );
682
  }
683
+ /** This filter is documented in common/indexing.php */
684
+ $value_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $value, true, $min_word_length ), 'custom_field' );
685
  foreach ( $value_tokens as $token => $count ) {
686
  if ( ! isset( $insert_data[ $token ]['customfield'] ) ) {
687
  $insert_data[ $token ]['customfield'] = 0;
703
  if ( $debug ) {
704
  relevanssi_debug_echo( "Indexing post excerpt: $post->post_excerpt" );
705
  }
706
+ /** This filter is documented in common/indexing.php */
707
+ $excerpt_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $post->post_excerpt, true, $min_word_length ), 'excerpt' );
708
  foreach ( $excerpt_tokens as $token => $count ) {
709
  if ( ! isset( $insert_data[ $token ]['excerpt'] ) ) {
710
  $insert_data[ $token ]['excerpt'] = 0;
755
  * @param boolean If true, remove stopwords. Default true.
756
  */
757
  $title_tokens = relevanssi_tokenize( $filtered_title, apply_filters( 'relevanssi_remove_stopwords_in_titles', true ), $min_word_length );
758
+ /** This filter is documented in common/indexing.php */
759
+ $title_tokens = apply_filters( 'relevanssi_indexing_tokens', $title_tokens, 'title' );
760
 
761
  if ( $debug ) {
762
  relevanssi_debug_echo( "\tTitle, tokenized: " . implode( ' ', array_keys( $title_tokens ) ) );
926
  * @param string $contents The post content.
927
  * @param object $post The full post object.
928
  */
929
+ $contents = apply_filters( 'relevanssi_post_content_before_tokenize', $contents, $post );
930
+ /** This filter is documented in common/indexing.php */
931
+ $content_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $contents, 'body', $min_word_length ), 'content' );
932
  if ( $debug ) {
933
  relevanssi_debug_echo( "\tContent, tokenized:\n" . implode( ' ', array_keys( $content_tokens ) ) );
934
  }
lib/init.php CHANGED
@@ -140,9 +140,13 @@ function relevanssi_init() {
140
  require_once 'compatibility/wp-search-suggest.php';
141
  }
142
 
143
- if ( defined( 'GUTENBERG_VERSION' ) ) {
144
  require_once 'compatibility/gutenberg.php';
145
  }
 
 
 
 
146
  }
147
 
148
  /**
140
  require_once 'compatibility/wp-search-suggest.php';
141
  }
142
 
143
+ if ( function_exists( 'do_blocks' ) ) {
144
  require_once 'compatibility/gutenberg.php';
145
  }
146
+
147
+ if ( defined( 'WPFD_VERSION' ) ) {
148
+ require_once 'compatibility/wp-file-download.php';
149
+ }
150
  }
151
 
152
  /**
lib/interface.php CHANGED
@@ -45,6 +45,10 @@ function relevanssi_options() {
45
  check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
46
  relevanssi_add_stopword( $_REQUEST['term'] );
47
  }
 
 
 
 
48
  }
49
  }
50
 
@@ -62,6 +66,21 @@ function relevanssi_options() {
62
  check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
63
  relevanssi_remove_all_stopwords();
64
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
 
67
  relevanssi_options_form();
45
  check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
46
  relevanssi_add_stopword( $_REQUEST['term'] );
47
  }
48
+ if ( isset( $_REQUEST['body_term'] ) ) {
49
+ check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
50
+ relevanssi_add_body_stopword( $_REQUEST['body_term'] );
51
+ }
52
  }
53
  }
54
 
66
  check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
67
  relevanssi_remove_all_stopwords();
68
  }
69
+
70
+ if ( isset( $_REQUEST['addbodystopword'] ) ) {
71
+ check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
72
+ relevanssi_add_body_stopword( $_REQUEST['addbodystopword'] );
73
+ }
74
+
75
+ if ( isset( $_REQUEST['removebodystopword'] ) ) {
76
+ check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
77
+ relevanssi_remove_body_stopword( $_REQUEST['removebodystopword'] );
78
+ }
79
+
80
+ if ( isset( $_REQUEST['removeallbodystopwords'] ) ) {
81
+ check_admin_referer( plugin_basename( $relevanssi_variables['file'] ), 'relevanssi_options' );
82
+ relevanssi_remove_all_body_stopwords();
83
+ }
84
  }
85
 
86
  relevanssi_options_form();
lib/search-query-restrictions.php ADDED
@@ -0,0 +1,515 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/search-query-restrictions.php
4
+ *
5
+ * Responsible for converting query parameters to MySQL query restrictions.
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
+ /**
14
+ * Processes the arguments to create the query restrictions.
15
+ *
16
+ * All individual parts are tested.
17
+ *
18
+ * @param array $args The query arguments.
19
+ *
20
+ * @return array An array containing `query_restriction` and `query_join`.
21
+ */
22
+ function relevanssi_process_query_args( $args ) {
23
+ $query_restrictions = '';
24
+ $query_join = '';
25
+ $query = '';
26
+
27
+ if ( function_exists( 'wp_encode_emoji' ) ) {
28
+ $query = wp_encode_emoji( $args['q'] );
29
+ }
30
+
31
+ if ( $args['sentence'] ) {
32
+ $query = str_replace( array( '"', '“', '”' ), '', $query );
33
+ $query = '"' . $query . '"';
34
+ }
35
+
36
+ if ( is_array( $args['tax_query'] ) ) {
37
+ $query_restrictions .= relevanssi_process_tax_query( $args['tax_query_relation'], $args['tax_query'] );
38
+ }
39
+
40
+ if ( is_array( $args['post_query'] ) ) {
41
+ $query_restrictions .= relevanssi_process_post_query( $args['post_query'] );
42
+ }
43
+
44
+ if ( is_array( $args['parent_query'] ) ) {
45
+ $query_restrictions .= relevanssi_process_parent_query( $args['parent_query'] );
46
+ }
47
+
48
+ if ( is_array( $args['meta_query'] ) ) {
49
+ $processed_meta = relevanssi_process_meta_query( $args['meta_query'] );
50
+ $query_restrictions .= $processed_meta['where'];
51
+ $query_join .= $processed_meta['join'];
52
+ }
53
+
54
+ if ( $args['date_query'] instanceof WP_Date_Query ) {
55
+ $query_restrictions .= relevanssi_process_date_query( $args['date_query'] );
56
+ }
57
+
58
+ if ( $args['expost'] ) {
59
+ $query_restrictions .= relevanssi_process_expost( $args['expost'] );
60
+ }
61
+
62
+ if ( $args['author'] ) {
63
+ $query_restrictions .= relevanssi_process_author( $args['author'] );
64
+ }
65
+
66
+ if ( $args['by_date'] ) {
67
+ $query_restrictions .= relevanssi_process_by_date( $args['by_date'] );
68
+ }
69
+
70
+ $phrases = relevanssi_recognize_phrases( $query );
71
+ if ( $phrases ) {
72
+ $query_restrictions .= " $phrases";
73
+ // Clean: $phrases is escaped earlier.
74
+ }
75
+
76
+ if ( $args['post_type'] || $args['include_attachments'] ) {
77
+ $query_restrictions .= relevanssi_process_post_type( $args['post_type'],
78
+ $args['admin_search'], $args['include_attachments'] );
79
+ }
80
+
81
+ if ( $args['post_status'] ) {
82
+ $query_restrictions .= relevanssi_process_post_status( $args['post_status'] );
83
+ }
84
+
85
+ return array(
86
+ 'query_restrictions' => $query_restrictions,
87
+ 'query_join' => $query_join,
88
+ 'query_query' => $query,
89
+ );
90
+ }
91
+
92
+ /**
93
+ * Processes the 'in' and 'not in' parameters to MySQL query restrictions.
94
+ *
95
+ * Checks that the parameters are integers and formulates a MySQL query restriction
96
+ * from them. If the same posts are both included and excluded, exclusion will take
97
+ * precedence.
98
+ *
99
+ * Tested.
100
+ *
101
+ * @param array $post_query An array where included posts are in $post_query['in']
102
+ * and excluded posts are in $post_query['not in'].
103
+ *
104
+ * @return string MySQL query restrictions matching the array.
105
+ */
106
+ function relevanssi_process_post_query( $post_query ) {
107
+ $query_restrictions = '';
108
+ $valid_exclude_values = array();
109
+ if ( ! empty( $post_query['not in'] ) ) {
110
+ foreach ( $post_query['not in'] as $post_not_in_id ) {
111
+ if ( is_numeric( $post_not_in_id ) ) {
112
+ $valid_exclude_values[] = $post_not_in_id;
113
+ }
114
+ }
115
+ $posts = implode( ',', $valid_exclude_values );
116
+ if ( ! empty( $posts ) ) {
117
+ $query_restrictions .= " AND relevanssi.doc NOT IN ($posts)";
118
+ // Clean: $posts is checked to be integers.
119
+ }
120
+ }
121
+ if ( ! empty( $post_query['in'] ) ) {
122
+ $valid_values = array();
123
+ foreach ( $post_query['in'] as $post_in_id ) {
124
+ if ( is_numeric( $post_in_id ) ) {
125
+ $valid_values[] = $post_in_id;
126
+ }
127
+ }
128
+ // If same values appear in both arrays, exclusion will override inclusion.
129
+ $valid_values = array_diff( $valid_values, $valid_exclude_values );
130
+ $posts = implode( ',', $valid_values );
131
+ if ( ! empty( $posts ) ) {
132
+ $query_restrictions .= " AND relevanssi.doc IN ($posts)";
133
+ // Clean: $posts is checked to be integers.
134
+ }
135
+ }
136
+ return $query_restrictions;
137
+ }
138
+
139
+ /**
140
+ * Processes the 'parent in' and 'parent not in' parameters to MySQL query
141
+ * restrictions.
142
+ *
143
+ * Checks that the parameters are integers and formulates a MySQL query restriction
144
+ * from them. If the same posts are both included and excluded, exclusion will take
145
+ * precedence.
146
+ *
147
+ * Tested.
148
+ *
149
+ * @param array $parent_query An array where included posts are in
150
+ * $post_query['parent in'] and excluded posts are in $post_query['parent not in'].
151
+ *
152
+ * @return string MySQL query restrictions matching the array.
153
+ */
154
+ function relevanssi_process_parent_query( $parent_query ) {
155
+ global $wpdb;
156
+
157
+ $query_restrictions = '';
158
+ $valid_exclude_values = array();
159
+ if ( isset( $parent_query['parent not in'] ) ) {
160
+ foreach ( $parent_query['parent not in'] as $post_not_in_id ) {
161
+ if ( is_int( $post_not_in_id ) ) {
162
+ $valid_exclude_values[] = $post_not_in_id;
163
+ }
164
+ }
165
+ $posts = implode( ',', $valid_exclude_values );
166
+ if ( isset( $posts ) ) {
167
+ $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
168
+ // Clean: $posts is checked to be integers.
169
+ }
170
+ }
171
+ if ( isset( $parent_query['parent in'] ) ) {
172
+ $valid_values = array();
173
+ foreach ( $parent_query['parent in'] as $post_in_id ) {
174
+ if ( is_int( $post_in_id ) ) {
175
+ $valid_values[] = $post_in_id;
176
+ }
177
+ }
178
+ $valid_values = array_diff( $valid_values, $valid_exclude_values );
179
+ $posts = implode( ',', $valid_values );
180
+ if ( strlen( $posts ) > 0 ) {
181
+ $query_restrictions .= " AND relevanssi.doc IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
182
+ // Clean: $posts is checked to be integers.
183
+ }
184
+ }
185
+
186
+ return $query_restrictions;
187
+ }
188
+
189
+ /**
190
+ * Processes the meta query parameter to MySQL query restrictions.
191
+ *
192
+ * Uses the WP_Meta_Query object to parse the query variables to create the MySQL
193
+ * JOIN and WHERE clauses.
194
+ *
195
+ * Tested.
196
+ *
197
+ * @see WP_Meta_Query
198
+ *
199
+ * @param array $meta_query A meta query array.
200
+ *
201
+ * @return array Index 'where' is the WHERE, index 'join' is the JOIN.
202
+ */
203
+ function relevanssi_process_meta_query( $meta_query ) {
204
+ $mq_vars = array( 'meta_query' => $meta_query );
205
+
206
+ $mq = new WP_Meta_Query();
207
+ $mq->parse_query_vars( $mq_vars );
208
+ $meta_sql = $mq->get_sql( 'post', 'relevanssi', 'doc' );
209
+ $meta_join = '';
210
+ $meta_where = '';
211
+ if ( $meta_sql ) {
212
+ $meta_join = $meta_sql['join'];
213
+ $meta_where = $meta_sql['where'];
214
+ }
215
+
216
+ return array(
217
+ 'where' => $meta_where,
218
+ 'join' => $meta_join,
219
+ );
220
+ }
221
+
222
+ /**
223
+ * Processes the date query parameter to MySQL query restrictions.
224
+ *
225
+ * Uses the WP_Date_Query object to parse the query variables to create the MySQL
226
+ * WHERE clause.
227
+ *
228
+ * Tested.
229
+ *
230
+ * @see WP_Date_Query
231
+ *
232
+ * @global object $wpdb The WP database interface.
233
+ *
234
+ * @param WP_Date_Query $date_query A date query object.
235
+ *
236
+ * @return string The MySQL query restriction.
237
+ */
238
+ function relevanssi_process_date_query( $date_query ) {
239
+ global $wpdb;
240
+
241
+ $query_restrictions = '';
242
+ if ( method_exists( $date_query, 'get_sql' ) ) {
243
+ $sql = $date_query->get_sql(); // AND ( the query itself ).
244
+ $query_restrictions = " AND relevanssi.doc IN ( SELECT DISTINCT(ID) FROM $wpdb->posts WHERE 1 $sql )";
245
+ // Clean: $sql generated by $date_query->get_sql() query.
246
+ }
247
+ return $query_restrictions;
248
+ }
249
+
250
+ /**
251
+ * Processes the post exclusion parameter to MySQL query restrictions.
252
+ *
253
+ * Takes a comma-separated list of post ID numbers and creates a MySQL query
254
+ * restriction from them.
255
+ *
256
+ * @param string $expost The post IDs to exclude, comma-separated.
257
+ *
258
+ * @return string The MySQL query restriction.
259
+ */
260
+ function relevanssi_process_expost( $expost ) {
261
+ $posts_to_exclude = '';
262
+ $excluded_post_ids_unchecked = explode( ',', $expost );
263
+ $excluded_post_ids = array();
264
+ foreach ( $excluded_post_ids_unchecked as $excluded_post_id ) {
265
+ $excluded_post_ids[] = intval( trim( $excluded_post_id, ' -' ) );
266
+ }
267
+ $excluded_post_ids_string = implode( ',', $excluded_post_ids );
268
+ $posts_to_exclude .= " AND relevanssi.doc NOT IN ($excluded_post_ids_string)";
269
+ // Clean: escaped.
270
+ return $posts_to_exclude;
271
+ }
272
+
273
+ /**
274
+ * Processes the author parameter to MySQL query restrictions.
275
+ *
276
+ * Takes an array of author ID numbers and creates the MySQL query restriction code
277
+ * from them. Negative values are counted as exclusion and positive values as
278
+ * inclusion.
279
+ *
280
+ * Tested.
281
+ *
282
+ * @global object $wpdb The WP database interface.
283
+ *
284
+ * @param array $author An array of authors. Positive values are inclusion,
285
+ * negative values are exclusion.
286
+ *
287
+ * @return string The MySQL query restriction.
288
+ */
289
+ function relevanssi_process_author( $author ) {
290
+ global $wpdb;
291
+
292
+ $query_restrictions = '';
293
+
294
+ $author_in = array();
295
+ $author_not_in = array();
296
+ foreach ( $author as $id ) {
297
+ if ( ! is_numeric( $id ) ) {
298
+ continue;
299
+ }
300
+ if ( $id > 0 ) {
301
+ $author_in[] = $id;
302
+ } else {
303
+ $author_not_in[] = abs( $id );
304
+ }
305
+ }
306
+ if ( count( $author_in ) > 0 ) {
307
+ $authors = implode( ',', $author_in );
308
+ $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
309
+ WHERE posts.post_author IN ($authors))";
310
+ // Clean: $authors is always just numbers.
311
+ }
312
+ if ( count( $author_not_in ) > 0 ) {
313
+ $authors = implode( ',', $author_not_in );
314
+ $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
315
+ WHERE posts.post_author IN ($authors))";
316
+ // Clean: $authors is always just numbers.
317
+ }
318
+
319
+ return $query_restrictions;
320
+ }
321
+
322
+ /**
323
+ * Processes the by_date parameter to MySQL query restrictions.
324
+ *
325
+ * The by_date parameter is a simple data parameter in the format '24h', that is a
326
+ * number followed by an unit (h, d, m, y, or w).
327
+ *
328
+ * Tested.
329
+ *
330
+ * @global object $wpdb The WP database interface.
331
+ *
332
+ * @param string $n The date parameter.
333
+ *
334
+ * @return string The MySQL query restriction.
335
+ */
336
+ function relevanssi_process_by_date( $n ) {
337
+ global $wpdb;
338
+ $query_restrictions = '';
339
+
340
+ $u = substr( $n, -1, 1 );
341
+ switch ( $u ) {
342
+ case 'h':
343
+ $unit = 'HOUR';
344
+ break;
345
+ case 'd':
346
+ $unit = 'DAY';
347
+ break;
348
+ case 'm':
349
+ $unit = 'MONTH';
350
+ break;
351
+ case 'y':
352
+ $unit = 'YEAR';
353
+ break;
354
+ case 'w':
355
+ $unit = 'WEEK';
356
+ break;
357
+ default:
358
+ $unit = 'DAY';
359
+ }
360
+
361
+ $n = preg_replace( '/[hdmyw]/', '', $n );
362
+
363
+ if ( is_numeric( $n ) ) {
364
+ $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
365
+ WHERE posts.post_date > DATE_SUB(NOW(), INTERVAL $n $unit))";
366
+ // Clean: $n is always numeric, $unit is Relevanssi-generated.
367
+ }
368
+
369
+ return $query_restrictions;
370
+ }
371
+
372
+ /**
373
+ * Extracts the post types from a comma-separated list or an array.
374
+ *
375
+ * Handles the non-post post types as well (user, taxonomies, etc.) and escapes the
376
+ * post types for SQL injections.
377
+ *
378
+ * Tested.
379
+ *
380
+ * @param string|array $post_type An array or a comma-separated list of
381
+ * post types.
382
+ * @param boolean $admin_search True if this is an admin search.
383
+ * @param boolean $include_attachments True if attachments are allowed in the
384
+ * search.
385
+ *
386
+ * @global object $wpdb The WP database interface.
387
+ *
388
+ * @return array Array containing the 'post_type' and 'non_post_post_type' (which
389
+ * defaults to null).
390
+ */
391
+ function relevanssi_process_post_type( $post_type, $admin_search, $include_attachments ) {
392
+ global $wpdb;
393
+
394
+ // If $post_type is not set, see if there are post types to exclude from the search.
395
+ // If $post_type is set, there's no need to exclude, as we only include.
396
+ $negative_post_type = null;
397
+ if ( ! $post_type && ! $admin_search ) {
398
+ $negative_post_type = relevanssi_get_negative_post_type( $include_attachments );
399
+ }
400
+
401
+ $non_post_post_type = null;
402
+ $non_post_post_types_array = array();
403
+ if ( function_exists( 'relevanssi_get_non_post_post_types' ) ) {
404
+ // Relevanssi Premium includes post types which are not actually posts.
405
+ $non_post_post_types_array = relevanssi_get_non_post_post_types();
406
+ }
407
+
408
+ if ( $post_type ) {
409
+ if ( ! is_array( $post_type ) ) {
410
+ $post_types = explode( ',', $post_type );
411
+ } else {
412
+ $post_types = $post_type;
413
+ }
414
+
415
+ // This array will contain all regular post types involved in the search parameters.
416
+ $post_post_types = array_diff( $post_types, $non_post_post_types_array );
417
+
418
+ // This array has the non-post post types involved.
419
+ $non_post_post_types = array_intersect( $post_types, $non_post_post_types_array );
420
+
421
+ // Escape both for SQL queries, just in case.
422
+ $non_post_post_types = esc_sql( $non_post_post_types );
423
+ $post_types = esc_sql( $post_post_types );
424
+
425
+ // Implode to a parameter string, or set to null if empty.
426
+ $non_post_post_type = null;
427
+ if ( count( $non_post_post_types ) > 0 ) {
428
+ $non_post_post_type = "'" . implode( "', '", $non_post_post_types ) . "'";
429
+ }
430
+ $post_type = null;
431
+ if ( count( $post_types ) > 0 ) {
432
+ $post_type = "'" . implode( "', '", $post_types ) . "'";
433
+ }
434
+ }
435
+
436
+ $query_restrictions = '';
437
+
438
+ if ( $post_type ) {
439
+ $restriction = " AND (
440
+ relevanssi.doc IN (
441
+ SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
442
+ WHERE posts.post_type IN ($post_type)
443
+ ) *np*
444
+ )"; // Clean: $post_type is escaped.
445
+
446
+ // There are post types involved that are taxonomies or users, so can't
447
+ // match to wp_posts. Add a relevanssi.type restriction.
448
+ if ( $non_post_post_type ) {
449
+ $restriction = str_replace( '*np*', "OR (relevanssi.type IN ($non_post_post_type))", $restriction );
450
+ // Clean: $non_post_post_types is escaped.
451
+ } else {
452
+ // No non-post post types, so remove the placeholder.
453
+ $restriction = str_replace( '*np*', '', $restriction );
454
+ }
455
+ $query_restrictions .= $restriction;
456
+ } else {
457
+ // No regular post types.
458
+ if ( $non_post_post_type ) {
459
+ // But there is a non-post post type restriction.
460
+ $query_restrictions .= " AND (relevanssi.type IN ($non_post_post_type))";
461
+ // Clean: $non_post_post_types is escaped.
462
+ }
463
+ }
464
+
465
+ if ( $negative_post_type ) {
466
+ $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
467
+ WHERE posts.post_type NOT IN ($negative_post_type))) OR (doc = -1))";
468
+ // Clean: $negative_post_type is escaped.
469
+ }
470
+
471
+ return $query_restrictions;
472
+ }
473
+
474
+ /**
475
+ * Processes the post status parameter.
476
+ *
477
+ * Takes the post status parameter and creates a MySQL query restriction from it.
478
+ * Checks if this is in admin context: if the query isn't, there's a catch added to
479
+ * capture user profiles and taxonomy terms.
480
+ *
481
+ * @param string $post_status A post status string.
482
+ *
483
+ * @global WP_Query $wp_query The WP Query object.
484
+ * @global object $wpdb The WP database interface.
485
+ *
486
+ * @return string The MySQL query restriction.
487
+ */
488
+ function relevanssi_process_post_status( $post_status ) {
489
+ global $wp_query, $wpdb;
490
+ $query_restrictions = '';
491
+
492
+ if ( ! is_array( $post_status ) ) {
493
+ $post_statuses = esc_sql( explode( ',', $post_status ) );
494
+ } else {
495
+ $post_statuses = esc_sql( $post_status );
496
+ }
497
+
498
+ $escaped_post_status = '';
499
+ if ( count( $post_statuses ) > 0 ) {
500
+ $escaped_post_status = "'" . implode( "', '", $post_statuses ) . "'";
501
+ }
502
+
503
+ if ( $escaped_post_status ) {
504
+ if ( $wp_query->is_admin ) {
505
+ $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
506
+ WHERE posts.post_status IN ($escaped_post_status))))";
507
+ } else {
508
+ // The -1 is there to get user profiles and category pages.
509
+ $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
510
+ WHERE posts.post_status IN ($escaped_post_status))) OR (doc = -1))";
511
+ }
512
+ }
513
+
514
+ return $query_restrictions;
515
+ }
lib/search-tax-query.php ADDED
@@ -0,0 +1,360 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/search-tax-query.php
4
+ *
5
+ * Responsible for converting tax_query parameters to MySQL query restrictions.
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
+ /**
14
+ * Processes the tax query to formulate a query restriction to the MySQL query.
15
+ *
16
+ * Tested.
17
+ *
18
+ * @uses relevanssi_process_tax_query_row()
19
+ *
20
+ * @global object $wpdb The WP database interface.
21
+ *
22
+ * @param string $tax_query_relation The base tax query relation. Default 'and'.
23
+ * @param array $tax_query The tax query array.
24
+ *
25
+ * @return string The query restrictions for the MySQL query.
26
+ */
27
+ function relevanssi_process_tax_query( $tax_query_relation, $tax_query ) {
28
+ global $wpdb;
29
+
30
+ $query_restrictions = '';
31
+
32
+ if ( ! isset( $tax_query_relation ) ) {
33
+ $tax_query_relation = 'and';
34
+ }
35
+ $tax_query_relation = relevanssi_strtolower( $tax_query_relation );
36
+ $term_tax_ids = array();
37
+ $not_term_tax_ids = array();
38
+ $and_term_tax_ids = array();
39
+
40
+ $is_sub_row = false;
41
+ foreach ( $tax_query as $row ) {
42
+ if ( isset( $row['terms'] ) || ( isset( $row['operator'] ) && ( 'not exists' === strtolower( $row['operator'] ) || 'exists' === strtolower( $row['operator'] ) ) ) ) {
43
+ list( $query_restrictions, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids ) =
44
+ relevanssi_process_tax_query_row( $row, $is_sub_row, $tax_query_relation, $query_restrictions, $tax_query_relation, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids );
45
+ } else {
46
+ $row_tax_query_relation = $tax_query_relation;
47
+ if ( isset( $row['relation'] ) ) {
48
+ $row_tax_query_relation = relevanssi_strtolower( $row['relation'] );
49
+ }
50
+ if ( is_array( $row ) ) {
51
+ foreach ( $row as $subrow ) {
52
+ $is_sub_row = true;
53
+ if ( isset( $subrow['terms'] ) ) {
54
+ list( $query_restrictions, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids ) =
55
+ relevanssi_process_tax_query_row( $subrow, $is_sub_row, $tax_query_relation, $query_restrictions, $row_tax_query_relation, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids );
56
+ }
57
+ }
58
+ if ( 'or' === $row_tax_query_relation ) {
59
+ $query_restrictions .= relevanssi_process_term_tax_ids(
60
+ $term_tax_ids,
61
+ $not_term_tax_ids,
62
+ $and_term_tax_ids
63
+ );
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ if ( 'or' === $tax_query_relation ) {
70
+ $query_restrictions .= relevanssi_process_term_tax_ids(
71
+ $term_tax_ids,
72
+ $not_term_tax_ids,
73
+ $and_term_tax_ids
74
+ );
75
+ }
76
+
77
+ return $query_restrictions;
78
+ }
79
+
80
+ /**
81
+ * Processes one tax_query row.
82
+ *
83
+ * Tested.
84
+ *
85
+ * @global object $wpdb The WordPress database interface.
86
+ *
87
+ * @param array $row The tax_query row array.
88
+ * @param boolean $is_sub_row True if this is a subrow.
89
+ * @param string $global_relation The global tax_query relation (AND or OR).
90
+ * @param string $query_restrictions The MySQL query restriction.
91
+ * @param string $tax_query_relation The tax_query relation.
92
+ * @param array $term_tax_ids Array of term taxonomy IDs.
93
+ * @param array $not_term_tax_ids Array of excluded term taxonomy IDs.
94
+ * @param array $and_term_tax_ids Array of AND term taxonomy IDs.
95
+ *
96
+ * @return array Returns an array where the first item is the updated
97
+ * $query_restrictions, then $term_tax_ids, $not_term_tax_ids, and $and_term_tax_ids.
98
+ */
99
+ function relevanssi_process_tax_query_row( $row, $is_sub_row, $global_relation, $query_restrictions, $tax_query_relation, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids ) {
100
+ global $wpdb;
101
+
102
+ $local_term_tax_ids = array();
103
+ $local_not_term_tax_ids = array();
104
+ $local_and_term_tax_ids = array();
105
+ $term_tax_id = array();
106
+
107
+ $exists_query = false;
108
+ if ( isset( $row['operator'] ) && ( 'exists' === strtolower( $row['operator'] ) || 'not exists' === strtolower( $row['operator'] ) ) ) {
109
+ $exists_query = true;
110
+ }
111
+
112
+ if ( $exists_query ) {
113
+ $row['field'] = 'exists';
114
+ }
115
+ if ( ! isset( $row['field'] ) ) {
116
+ $row['field'] = 'term_id'; // In case 'field' is not set, go with the WP default of 'term_id'.
117
+ }
118
+ $row['field'] = strtolower( $row['field'] ); // In some cases, you can get 'ID' instead of 'id'.
119
+
120
+ if ( in_array( $row['field'], array( 'slug', 'name', 'id', 'term_id' ), true ) ) {
121
+ $term_tax_id = relevanssi_term_tax_id_from_row( $row );
122
+ }
123
+
124
+ if ( 'term_taxonomy_id' === $row['field'] ) {
125
+ if ( ! is_array( $row['terms'] ) ) {
126
+ $row['terms'] = array( $row['terms'] );
127
+ }
128
+ $term_tax_id = array_filter( $row['terms'], 'is_numeric' );
129
+ }
130
+
131
+ if ( ! $exists_query && ( ! isset( $row['include_children'] ) || true === $row['include_children'] ) ) {
132
+ foreach ( $term_tax_id as $t_id ) {
133
+ $t_term = get_term_by( 'term_taxonomy_id', $t_id, $row['taxonomy'] );
134
+ $t_id = $t_term->term_id;
135
+ $kids = get_term_children( $t_id, $row['taxonomy'] );
136
+ foreach ( $kids as $kid ) {
137
+ $kid_term_tax_id = relevanssi_get_term_tax_id( $kid, $row['taxonomy'] );
138
+ if ( $kid_term_tax_id ) {
139
+ // In some weird cases, this may be null. See: https://wordpress.org/support/topic/childrens-of-chosen-product_cat-not-showing-up/.
140
+ $term_tax_id[] = $kid_term_tax_id;
141
+ }
142
+ }
143
+ }
144
+ }
145
+
146
+ $term_tax_id = array_unique( $term_tax_id );
147
+ if ( ! empty( $term_tax_id ) ) {
148
+ $n = count( $term_tax_id );
149
+ $term_tax_id = implode( ',', $term_tax_id );
150
+
151
+ $tq_operator = 'IN'; // Assuming the default operator "IN", unless something else is provided.
152
+ if ( isset( $row['operator'] ) ) {
153
+ $tq_operator = strtoupper( $row['operator'] );
154
+ }
155
+ if ( ! in_array( $tq_operator, array( 'IN', 'NOT IN', 'AND' ), true ) ) {
156
+ $tq_operator = 'IN';
157
+ }
158
+ if ( 'and' === $tax_query_relation ) {
159
+ if ( 'AND' === $tq_operator ) {
160
+ $query_restrictions .= " AND relevanssi.doc IN (
161
+ SELECT ID FROM $wpdb->posts WHERE 1=1
162
+ AND (
163
+ SELECT COUNT(1)
164
+ FROM $wpdb->term_relationships AS tr
165
+ WHERE tr.term_taxonomy_id IN ($term_tax_id)
166
+ AND tr.object_id = $wpdb->posts.ID ) = $n
167
+ )";
168
+ // Clean: $term_tax_id and $n are Relevanssi-generated.
169
+ } else {
170
+ $query_restrictions .= " AND relevanssi.doc $tq_operator (
171
+ SELECT DISTINCT(tr.object_id)
172
+ FROM $wpdb->term_relationships AS tr
173
+ WHERE tr.term_taxonomy_id IN ($term_tax_id))";
174
+ // Clean: all variables are Relevanssi-generated.
175
+ }
176
+ } else {
177
+ if ( 'IN' === $tq_operator ) {
178
+ $local_term_tax_ids[] = $term_tax_id;
179
+ }
180
+ if ( 'NOT IN' === $tq_operator ) {
181
+ $local_not_term_tax_ids[] = $term_tax_id;
182
+ }
183
+ if ( 'AND' === $tq_operator ) {
184
+ $local_and_term_tax_ids[] = $term_tax_id;
185
+ }
186
+ }
187
+ } else {
188
+ global $wp_query;
189
+ $wp_query->is_category = false;
190
+ }
191
+
192
+ $copy_term_tax_ids = false;
193
+ if ( ! $is_sub_row ) {
194
+ $copy_term_tax_ids = true;
195
+ }
196
+ if ( $is_sub_row && ( 'or' === $global_relation || 'or' === $tax_query_relation ) ) {
197
+ $copy_term_tax_ids = true;
198
+ }
199
+
200
+ if ( $copy_term_tax_ids ) {
201
+ $term_tax_ids = array_merge( $term_tax_ids, $local_term_tax_ids );
202
+ $not_term_tax_ids = array_merge( $not_term_tax_ids, $local_not_term_tax_ids );
203
+ $and_term_tax_ids = array_merge( $and_term_tax_ids, $local_and_term_tax_ids );
204
+ }
205
+
206
+ if ( $exists_query ) {
207
+ $taxonomy = $row['taxonomy'];
208
+ $operator = 'IN';
209
+ if ( 'not exists' === strtolower( $row['operator'] ) ) {
210
+ $operator = 'NOT IN';
211
+ }
212
+ $query_restrictions .= " AND relevanssi.doc $operator (
213
+ SELECT DISTINCT(tr.object_id)
214
+ FROM $wpdb->term_relationships AS tr, $wpdb->term_taxonomy AS tt
215
+ WHERE tr.term_taxonomy_id = tt.term_taxonomy_id
216
+ AND tt.taxonomy = '$taxonomy'
217
+ )";
218
+ }
219
+
220
+ return array( $query_restrictions, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids );
221
+ }
222
+
223
+ /**
224
+ * Generates query restrictions from the term taxonomy ids.
225
+ *
226
+ * Combines different term tax ID arrays into a set of query restrictions that can be
227
+ * used in an OR query.
228
+ *
229
+ * @global object $wpdb The WP database interface.
230
+ *
231
+ * @param array $term_tax_ids The regular terms.
232
+ * @param array $not_term_tax_ids The NOT terms.
233
+ * @param array $and_term_tax_ids The AND terms.
234
+ *
235
+ * @return string The MySQL query restrictions.
236
+ */
237
+ function relevanssi_process_term_tax_ids( $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids ) {
238
+ global $wpdb;
239
+
240
+ $query_restriction_parts = array();
241
+ $query_restrictions = '';
242
+
243
+ $term_tax_ids = array_unique( $term_tax_ids );
244
+ if ( count( $term_tax_ids ) > 0 ) {
245
+ $term_tax_ids = implode( ',', $term_tax_ids );
246
+ $query_restriction_parts[] = " relevanssi.doc IN (
247
+ SELECT DISTINCT(tr.object_id)
248
+ FROM $wpdb->term_relationships AS tr
249
+ WHERE tr.term_taxonomy_id IN ($term_tax_ids)
250
+ )";
251
+ // Clean: all variables are Relevanssi-generated.
252
+ }
253
+ if ( count( $not_term_tax_ids ) > 0 ) {
254
+ $not_term_tax_ids = implode( ',', $not_term_tax_ids );
255
+ $query_restriction_parts[] .= " relevanssi.doc NOT IN (
256
+ SELECT DISTINCT(tr.object_id)
257
+ FROM $wpdb->term_relationships AS tr
258
+ WHERE tr.term_taxonomy_id IN ($not_term_tax_ids)
259
+ )";
260
+ // Clean: all variables are Relevanssi-generated.
261
+ }
262
+ if ( count( $and_term_tax_ids ) > 0 ) {
263
+ $and_term_tax_ids = implode( ',', $and_term_tax_ids );
264
+ $n = count( explode( ',', $and_term_tax_ids ) );
265
+ $query_restriction_parts[] .= " relevanssi.doc IN (
266
+ SELECT ID FROM $wpdb->posts WHERE 1=1
267
+ AND (
268
+ SELECT COUNT(1)
269
+ FROM $wpdb->term_relationships AS tr
270
+ WHERE tr.term_taxonomy_id IN ($and_term_tax_ids)
271
+ AND tr.object_id = $wpdb->posts.ID ) = $n
272
+ )";
273
+ // Clean: all variables are Relevanssi-generated.
274
+ }
275
+ $query_restrictions .= ' AND ';
276
+ if ( count( $query_restriction_parts ) > 1 ) {
277
+ $query_restrictions .= '(';
278
+ }
279
+ $query_restrictions .= implode( ' OR', $query_restriction_parts );
280
+ if ( count( $query_restriction_parts ) > 1 ) {
281
+ $query_restrictions .= ')';
282
+ }
283
+
284
+ return $query_restrictions;
285
+ }
286
+
287
+ /**
288
+ * Gets and sanitizes the taxonomy name and slug parameters.
289
+ *
290
+ * Checks parameters: if they're numeric, pass them for term_id filtering, otherwise
291
+ * sanitize and create a comma-separated list.
292
+ *
293
+ * @param string $terms_parameter The 'terms' field from the tax_query row.
294
+ * @param string $taxonomy The taxonomy name.
295
+ *
296
+ * @return array An array containing numeric terms and the list of sanitized term
297
+ * names.
298
+ */
299
+ function relevanssi_get_term_in( $terms_parameter, $taxonomy ) {
300
+ $numeric_terms = array();
301
+ $names = array();
302
+
303
+ if ( ! is_array( $terms_parameter ) ) {
304
+ $terms_parameter = array( $terms_parameter );
305
+ }
306
+ foreach ( $terms_parameter as $name ) {
307
+ $term = get_term_by( 'name', $name, $taxonomy );
308
+ if ( ! $term && is_numeric( $name ) ) {
309
+ $numeric_terms[] = $name;
310
+ } else {
311
+ if ( isset( $term->term_id ) ) {
312
+ $name = sanitize_title( $name );
313
+ $names[] = "'$name'";
314
+ }
315
+ }
316
+ }
317
+
318
+ return array(
319
+ 'numeric_terms' => implode( ',', $numeric_terms ),
320
+ 'term_in' => implode( ',', $names ),
321
+ );
322
+ }
323
+
324
+ /**
325
+ * Gets the term_tax_id from a row with 'field' set to 'slug' or 'name'.
326
+ *
327
+ * If the slugs or names are all numeric values, will switch the 'field' parameter
328
+ * to 'term_id'.
329
+ *
330
+ * @param array $row The taxonomy query row.
331
+ *
332
+ * @return array An array of term taxonomy IDs.
333
+ */
334
+ function relevanssi_term_tax_id_from_row( $row ) {
335
+ global $wpdb;
336
+
337
+ $term_in_results = relevanssi_get_term_in( $row['terms'], $row['taxonomy'] );
338
+ $numeric_terms = $term_in_results['numeric_terms'];
339
+ $term_in = $term_in_results['term_in'];
340
+ $term_tax_id = array();
341
+
342
+ $type = $row['field'];
343
+ if ( ! empty( $numeric_terms ) ) {
344
+ $type = 'term_id';
345
+ $term_in = $numeric_terms;
346
+ }
347
+
348
+ if ( ! empty( $term_in ) ) {
349
+ $row_taxonomy = sanitize_text_field( $row['taxonomy'] );
350
+
351
+ $tt_q = "SELECT tt.term_taxonomy_id
352
+ FROM $wpdb->term_taxonomy AS tt
353
+ LEFT JOIN $wpdb->terms AS t ON (tt.term_id=t.term_id)
354
+ WHERE tt.taxonomy = '$row_taxonomy' AND t.$type IN ($term_in)";
355
+ // Clean: $row_taxonomy is sanitized, each term in $term_in is sanitized.
356
+ $term_tax_id = $wpdb->get_col( $tt_q ); // WPCS: unprepared SQL ok.
357
+ }
358
+
359
+ return $term_tax_id;
360
+ }
lib/search.php CHANGED
@@ -116,264 +116,20 @@ function relevanssi_search( $args ) {
116
  *
117
  * @param array The search parameters.
118
  */
119
- $filtered_args = apply_filters( 'relevanssi_search_filters', $args );
120
- $q = $filtered_args['q'];
121
- $tax_query = $filtered_args['tax_query'];
122
- $tax_query_relation = $filtered_args['tax_query_relation'];
123
- $post_query = $filtered_args['post_query'];
124
- $parent_query = $filtered_args['parent_query'];
125
- $meta_query = $filtered_args['meta_query'];
126
- $date_query = $filtered_args['date_query'];
127
- $expost = $filtered_args['expost'];
128
- $post_type = $filtered_args['post_type'];
129
- $post_status = $filtered_args['post_status'];
130
- $operator = $filtered_args['operator'];
131
- $search_blogs = $filtered_args['search_blogs'];
132
- $author = $filtered_args['author'];
133
- $orderby = $filtered_args['orderby'];
134
- $order = $filtered_args['order'];
135
- $fields = $filtered_args['fields'];
136
- $sentence = $filtered_args['sentence'];
137
- $by_date = $filtered_args['by_date'];
138
- $admin_search = $filtered_args['admin_search'];
139
- $include_attachments = $filtered_args['include_attachments'];
140
- $meta_query = $filtered_args['meta_query'];
141
-
142
- $hits = array();
143
- $query_restrictions = '';
144
-
145
- if ( ! isset( $tax_query_relation ) ) {
146
- $tax_query_relation = 'and';
147
- }
148
- $tax_query_relation = relevanssi_strtolower( $tax_query_relation );
149
- $term_tax_id = array();
150
- $term_tax_ids = array();
151
- $not_term_tax_ids = array();
152
- $and_term_tax_ids = array();
153
-
154
- if ( is_array( $tax_query ) ) {
155
- $is_sub_row = false;
156
- foreach ( $tax_query as $row ) {
157
- if ( isset( $row['terms'] ) || ( isset( $row['operator'] ) && ( 'not exists' === strtolower( $row['operator'] ) || 'exists' === strtolower( $row['operator'] ) ) ) ) {
158
- list( $query_restrictions, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids ) =
159
- relevanssi_process_tax_query_row( $row, $is_sub_row, $tax_query_relation, $query_restrictions, $tax_query_relation, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids );
160
- } else {
161
- $row_tax_query_relation = $tax_query_relation;
162
- if ( isset( $row['relation'] ) ) {
163
- $row_tax_query_relation = relevanssi_strtolower( $row['relation'] );
164
- }
165
- foreach ( $row as $subrow ) {
166
- $is_sub_row = true;
167
- if ( isset( $subrow['terms'] ) ) {
168
- list( $query_restrictions, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids ) =
169
- relevanssi_process_tax_query_row( $subrow, $is_sub_row, $tax_query_relation, $query_restrictions, $tax_query_relation, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids );
170
- }
171
- }
172
- }
173
- }
174
-
175
- if ( 'or' === $tax_query_relation ) {
176
- $term_tax_ids = array_unique( $term_tax_ids );
177
- if ( count( $term_tax_ids ) > 0 ) {
178
- $term_tax_ids = implode( ',', $term_tax_ids );
179
- $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr WHERE tr.term_taxonomy_id IN ($term_tax_ids))";
180
- // Clean: all variables are Relevanssi-generated.
181
- }
182
- if ( count( $not_term_tax_ids ) > 0 ) {
183
- $not_term_tax_ids = implode( ',', $not_term_tax_ids );
184
- $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr WHERE tr.term_taxonomy_id IN ($not_term_tax_ids))";
185
- // Clean: all variables are Relevanssi-generated.
186
- }
187
- if ( count( $and_term_tax_ids ) > 0 ) {
188
- $and_term_tax_ids = implode( ',', $and_term_tax_ids );
189
- $n = count( explode( ',', $and_term_tax_ids ) );
190
- $query_restrictions .= " AND relevanssi.doc IN (
191
- SELECT ID FROM $wpdb->posts WHERE 1=1
192
- AND (
193
- SELECT COUNT(1)
194
- FROM $wpdb->term_relationships AS tr
195
- WHERE tr.term_taxonomy_id IN ($and_term_tax_ids)
196
- AND tr.object_id = $wpdb->posts.ID ) = $n
197
- )";
198
- // Clean: all variables are Relevanssi-generated.
199
- }
200
- }
201
- }
202
-
203
- if ( is_array( $post_query ) ) {
204
- if ( ! empty( $post_query['in'] ) ) {
205
- $valid_values = array();
206
- foreach ( $post_query['in'] as $post_in_id ) {
207
- if ( is_numeric( $post_in_id ) ) {
208
- $valid_values[] = $post_in_id;
209
- }
210
- }
211
- $posts = implode( ',', $valid_values );
212
- if ( ! empty( $posts ) ) {
213
- $query_restrictions .= " AND relevanssi.doc IN ($posts)";
214
- // Clean: $posts is checked to be integers.
215
- }
216
- }
217
- if ( ! empty( $post_query['not in'] ) ) {
218
- $valid_values = array();
219
- foreach ( $post_query['not in'] as $post_not_in_id ) {
220
- if ( is_numeric( $post_not_in_id ) ) {
221
- $valid_values[] = $post_not_in_id;
222
- }
223
- }
224
- $posts = implode( ',', $valid_values );
225
- if ( ! empty( $posts ) ) {
226
- $query_restrictions .= " AND relevanssi.doc NOT IN ($posts)";
227
- // Clean: $posts is checked to be integers.
228
- }
229
- }
230
- }
231
-
232
- if ( is_array( $parent_query ) ) {
233
- if ( isset( $parent_query['parent in'] ) ) {
234
- $valid_values = array();
235
- foreach ( $parent_query['parent in'] as $post_in_id ) {
236
- if ( is_int( $post_in_id ) ) {
237
- $valid_values[] = $post_in_id;
238
- }
239
- }
240
- $posts = implode( ',', $valid_values );
241
- if ( strlen( $posts ) > 0 ) {
242
- $query_restrictions .= " AND relevanssi.doc IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
243
- // Clean: $posts is checked to be integers.
244
- }
245
- }
246
- if ( isset( $parent_query['parent not in'] ) ) {
247
- $valid_values = array();
248
- foreach ( $parent_query['parent not in'] as $post_not_in_id ) {
249
- if ( is_int( $post_not_in_id ) ) {
250
- $valid_values[] = $post_not_in_id;
251
- }
252
- }
253
- $posts = implode( ',', $valid_values );
254
- if ( isset( $posts ) ) {
255
- $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
256
- // Clean: $posts is checked to be integers.
257
- }
258
- }
259
- }
260
-
261
- if ( is_array( $meta_query ) ) {
262
- $meta_query_restrictions = '';
263
-
264
- $mq_vars = array( 'meta_query' => $meta_query );
265
-
266
- $mq = new WP_Meta_Query();
267
- $mq->parse_query_vars( $mq_vars );
268
- $meta_sql = $mq->get_sql( 'post', 'relevanssi', 'doc' );
269
- $meta_join = '';
270
- $meta_where = '';
271
- if ( $meta_sql ) {
272
- $meta_join = $meta_sql['join'];
273
- $meta_where = $meta_sql['where'];
274
- }
275
-
276
- $query_restrictions .= $meta_where;
277
- }
278
-
279
- if ( ! empty( $date_query ) ) {
280
- if ( is_object( $date_query ) && method_exists( $date_query, 'get_sql' ) ) {
281
- $sql = $date_query->get_sql(); // AND ( the query itself ).
282
- $query_restrictions .= " AND relevanssi.doc IN ( SELECT DISTINCT(ID) FROM $wpdb->posts WHERE 1 $sql )";
283
- // Clean: $sql generated by $date_query->get_sql() query.
284
- }
285
- }
286
-
287
- // If $post_type is not set, see if there are post types to exclude from the search.
288
- // If $post_type is set, there's no need to exclude, as we only include.
289
- $negative_post_type = null;
290
- if ( ! $post_type && ! $admin_search ) {
291
- $negative_post_type = relevanssi_get_negative_post_type( $include_attachments );
292
- }
293
-
294
- $non_post_post_type = null;
295
- $non_post_post_types_array = array();
296
- if ( function_exists( 'relevanssi_get_non_post_post_types' ) ) {
297
- // Relevanssi Premium includes post types which are not actually posts.
298
- $non_post_post_types_array = relevanssi_get_non_post_post_types();
299
- }
300
-
301
- if ( $post_type ) {
302
- if ( ! is_array( $post_type ) ) {
303
- $post_types = explode( ',', $post_type );
304
- } else {
305
- $post_types = $post_type;
306
- }
307
-
308
- // This array will contain all regular post types involved in the search parameters.
309
- $post_post_types = array_diff( $post_types, $non_post_post_types_array );
310
-
311
- // This array has the non-post post types involved.
312
- $non_post_post_types = array_intersect( $post_types, $non_post_post_types_array );
313
-
314
- // Escape both for SQL queries, just in case.
315
- $non_post_post_types = esc_sql( $non_post_post_types );
316
- $post_types = esc_sql( $post_post_types );
317
-
318
- // Implode to a parameter string, or set to null if empty.
319
- $non_post_post_type = null;
320
- if ( count( $non_post_post_types ) > 0 ) {
321
- $non_post_post_type = "'" . implode( "', '", $non_post_post_types ) . "'";
322
- }
323
- $post_type = null;
324
- if ( count( $post_types ) > 0 ) {
325
- $post_type = "'" . implode( "', '", $post_types ) . "'";
326
- }
327
- }
328
-
329
- if ( $post_status ) {
330
- if ( ! is_array( $post_status ) ) {
331
- $post_statuses = esc_sql( explode( ',', $post_status ) );
332
- } else {
333
- $post_statuses = esc_sql( $post_status );
334
- }
335
-
336
- $post_status = null;
337
- if ( count( $post_statuses ) > 0 ) {
338
- $post_status = "'" . implode( "', '", $post_statuses ) . "'";
339
- }
340
- }
341
 
342
- $posts_to_exclude = '';
343
- if ( ! empty( $expost ) ) {
344
- $excluded_post_ids = explode( ',', $expost );
345
- foreach ( $excluded_post_ids as $excluded_post_id ) {
346
- $excluded_post_id = intval( trim( $excluded_post_id, ' -' ) );
347
- $posts_to_exclude .= " AND relevanssi.doc != $excluded_post_id";
348
- // Clean: escaped.
349
- }
350
- $query_restrictions .= $posts_to_exclude;
351
- }
352
-
353
- if ( function_exists( 'wp_encode_emoji' ) ) {
354
- $q = wp_encode_emoji( $q );
355
- }
356
-
357
- if ( $sentence ) {
358
- $q = str_replace( '"', '', $q );
359
- $q = '"' . $q . '"';
360
- }
361
-
362
- $phrases = relevanssi_recognize_phrases( $q );
363
-
364
- if ( function_exists( 'relevanssi_recognize_negatives' ) ) {
365
- // Relevanssi Premium supports negative minus operator.
366
- $negative_terms = relevanssi_recognize_negatives( $q );
367
- } else {
368
- $negative_terms = false;
369
- }
370
 
371
- if ( function_exists( 'relevanssi_recognize_positives' ) ) {
372
- // Relevanssi Premium supports a plus operator.
373
- $positive_terms = relevanssi_recognize_positives( $q );
374
- } else {
375
- $positive_terms = false;
376
- }
377
 
378
  /**
379
  * Filters whether stopwords are removed from titles.
@@ -383,17 +139,38 @@ function relevanssi_search( $args ) {
383
  $remove_stopwords = apply_filters( 'relevanssi_remove_stopwords_in_titles', true );
384
 
385
  $terms = relevanssi_tokenize( $q, $remove_stopwords );
386
-
387
  if ( count( $terms ) < 1 ) {
388
  // Tokenizer killed all the search terms.
389
  return $hits;
390
  }
391
  $terms = array_keys( $terms ); // Don't care about tf in query.
392
 
393
- if ( $negative_terms ) {
394
- $terms = array_diff( $terms, $negative_terms );
 
 
395
  }
396
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  // Go get the count from the options, but run the full query if it's not available.
398
  $doc_count = get_option( 'relevanssi_doc_count' );
399
  if ( ! $doc_count || $doc_count < 1 ) {
@@ -414,146 +191,6 @@ function relevanssi_search( $args ) {
414
 
415
  $fuzzy = get_option( 'relevanssi_fuzzy' );
416
 
417
- if ( function_exists( 'relevanssi_negatives_positives' ) ) {
418
- $query_restrictions .= relevanssi_negatives_positives( $negative_terms, $positive_terms, $relevanssi_table );
419
- // Clean: escaped in the function.
420
- }
421
-
422
- if ( ! empty( $author ) ) {
423
- $author_in = array();
424
- $author_not_in = array();
425
- foreach ( $author as $id ) {
426
- if ( ! is_numeric( $id ) ) {
427
- continue;
428
- }
429
- if ( $id > 0 ) {
430
- $author_in[] = $id;
431
- } else {
432
- $author_not_in[] = abs( $id );
433
- }
434
- }
435
- if ( count( $author_in ) > 0 ) {
436
- $authors = implode( ',', $author_in );
437
- $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
438
- WHERE posts.post_author IN ($authors))";
439
- // Clean: $authors is always just numbers.
440
- }
441
- if ( count( $author_not_in ) > 0 ) {
442
- $authors = implode( ',', $author_not_in );
443
- $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
444
- WHERE posts.post_author IN ($authors))";
445
- // Clean: $authors is always just numbers.
446
- }
447
- }
448
-
449
- if ( $post_type ) {
450
- // A post type is set: add a restriction.
451
- $restriction = " AND (
452
- relevanssi.doc IN (
453
- SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
454
- WHERE posts.post_type IN ($post_type)
455
- ) *np*
456
- )"; // Clean: $post_type is escaped.
457
-
458
- // There are post types involved that are taxonomies or users, so can't
459
- // match to wp_posts. Add a relevanssi.type restriction.
460
- if ( $non_post_post_type ) {
461
- $restriction = str_replace( '*np*', "OR (relevanssi.type IN ($non_post_post_type))", $restriction );
462
- // Clean: $non_post_post_types is escaped.
463
- } else {
464
- // No non-post post types, so remove the placeholder.
465
- $restriction = str_replace( '*np*', '', $restriction );
466
- }
467
- $query_restrictions .= $restriction;
468
- } else {
469
- // No regular post types.
470
- if ( $non_post_post_type ) {
471
- // But there is a non-post post type restriction.
472
- $query_restrictions .= " AND (relevanssi.type IN ($non_post_post_type))";
473
- // Clean: $non_post_post_types is escaped.
474
- }
475
- }
476
-
477
- if ( $negative_post_type ) {
478
- $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
479
- WHERE posts.post_type NOT IN ($negative_post_type))) OR (doc = -1))";
480
- // Clean: $negative_post_type is escaped.
481
- }
482
-
483
- if ( $post_status ) {
484
- global $wp_query;
485
- if ( $wp_query->is_admin ) {
486
- $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
487
- WHERE posts.post_status IN ($post_status))))";
488
- } else {
489
- // The -1 is there to get user profiles and category pages.
490
- $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
491
- WHERE posts.post_status IN ($post_status))) OR (doc = -1))";
492
- }
493
- // Clean: $post_status is escaped.
494
- }
495
-
496
- if ( $phrases ) {
497
- $query_restrictions .= " $phrases";
498
- // Clean: $phrases is escaped earlier.
499
- }
500
-
501
- if ( isset( $by_date ) ) {
502
- $n = $by_date;
503
-
504
- $u = substr( $n, -1, 1 );
505
- switch ( $u ) {
506
- case 'h':
507
- $unit = 'HOUR';
508
- break;
509
- case 'd':
510
- $unit = 'DAY';
511
- break;
512
- case 'm':
513
- $unit = 'MONTH';
514
- break;
515
- case 'y':
516
- $unit = 'YEAR';
517
- break;
518
- case 'w':
519
- $unit = 'WEEK';
520
- break;
521
- default:
522
- $unit = 'DAY';
523
- }
524
-
525
- $n = preg_replace( '/[hdmyw]/', '', $n );
526
-
527
- if ( is_numeric( $n ) ) {
528
- $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
529
- WHERE posts.post_date > DATE_SUB(NOW(), INTERVAL $n $unit))";
530
- // Clean: $n is always numeric, $unit is Relevanssi-generated.
531
- }
532
- }
533
-
534
- /**
535
- * Filters the query restrictions for the Relevanssi query.
536
- *
537
- * Equivalent to the 'posts_where' filter.
538
- *
539
- * @author Charles St-Pierre
540
- *
541
- * @param string The MySQL code that restricts the query.
542
- */
543
- $query_restrictions = apply_filters( 'relevanssi_where', $query_restrictions );
544
- $query_join = '';
545
- if ( ! empty( $meta_join ) ) {
546
- $query_join = $meta_join;
547
- }
548
- /**
549
- * Filters the meta query JOIN for the Relevanssi search query.
550
- *
551
- * Somewhat equivalent to the 'posts_join' filter.
552
- *
553
- * @param string The JOINed query.
554
- */
555
- $query_join = apply_filters( 'relevanssi_join', $query_join );
556
-
557
  $no_matches = true;
558
  if ( 'always' === $fuzzy ) {
559
  /**
@@ -580,7 +217,9 @@ function relevanssi_search( $args ) {
580
  $recency_bonus = false;
581
  $recency_cutoff_date = false;
582
  if ( function_exists( 'relevanssi_get_recency_bonus' ) ) {
583
- list( $recency_bonus, $recency_cutoff_date ) = relevanssi_get_recency_bonus();
 
 
584
  }
585
 
586
  $exact_match_bonus = false;
@@ -698,27 +337,37 @@ function relevanssi_search( $args ) {
698
  } else {
699
  $no_matches = false;
700
  if ( count( $include_these_posts ) > 0 ) {
701
- $post_ids_to_add = implode( ',', array_keys( $include_these_posts ) );
702
- $existing_ids = array();
703
  foreach ( $matches as $match ) {
704
  $existing_ids[] = $match->doc;
705
  }
706
- $existing_ids = array_keys( array_flip( $existing_ids ) );
707
- $existing_ids = implode( ',', $existing_ids );
708
- $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
709
- relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
710
- relevanssi.tag * $tag + relevanssi.link * $link_boost +
711
- relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
712
- relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
713
- FROM $relevanssi_table AS relevanssi WHERE relevanssi.doc IN ($post_ids_to_add)
714
- AND relevanssi.doc NOT IN ($existing_ids) AND $term_cond";
715
-
716
- // Clean: no unescaped user inputs.
717
- $matches_to_add = $wpdb->get_results( $query ); // WPCS: unprepared SQL ok.
718
- $matches = array_merge( $matches, $matches_to_add );
 
 
 
 
 
 
 
 
 
 
 
 
719
  }
720
  if ( count( $include_these_items ) > 0 ) {
721
- $items_to_add = implode( ',', array_keys( $include_these_items ) );
722
  $existing_items = array();
723
  foreach ( $matches as $match ) {
724
  if ( 0 !== intval( $match->item ) ) {
@@ -726,21 +375,21 @@ function relevanssi_search( $args ) {
726
  }
727
  }
728
  $existing_items = array_keys( array_flip( $existing_items ) );
729
- $existing_items = implode( ',', $existing_items );
730
- if ( ! empty( $existing_items ) ) {
731
- $existing_items = "AND relevanssi.item NOT IN ($existing_items) ";
 
 
 
 
 
 
 
 
 
 
 
732
  }
733
- $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
734
- relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
735
- relevanssi.tag * $tag + relevanssi.link * $link_boost +
736
- relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
737
- relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
738
- FROM $relevanssi_table AS relevanssi WHERE relevanssi.item IN ($items_to_add)
739
- $existing_items AND $term_cond";
740
-
741
- // Clean: no unescaped user inputs.
742
- $matches_to_add = $wpdb->get_results( $query ); // WPCS: unprepared SQL ok.
743
- $matches = array_merge( $matches, $matches_to_add );
744
  }
745
  }
746
 
@@ -754,6 +403,7 @@ function relevanssi_search( $args ) {
754
  if ( $idf < 1 ) {
755
  $idf = 1;
756
  }
 
757
  foreach ( $matches as $match ) {
758
  if ( 'user' === $match->type ) {
759
  $match->doc = 'u_' . $match->item;
@@ -1290,9 +940,9 @@ function relevanssi_do_query( &$query ) {
1290
  /**
1291
  * Filters the default tax_query relation.
1292
  *
1293
- * @param string The default relation, default 'OR'.
1294
  */
1295
- $tax_query_relation = apply_filters( 'relevanssi_default_tax_query_relation', 'OR' );
1296
  if ( isset( $query->tax_query ) && empty( $query->tax_query->queries ) ) {
1297
  // Tax query is empty, let's get rid of it.
1298
  $query->tax_query = null;
@@ -1517,13 +1167,7 @@ function relevanssi_do_query( &$query ) {
1517
  $parent_query = array( 'parent not in' => $query->query_vars['post_parent__not_in'] );
1518
  }
1519
 
1520
- /**
1521
- * Filters the default meta_query relation.
1522
- *
1523
- * @param string The meta_query relation, default 'AND'.
1524
- */
1525
- $meta_query_relation = apply_filters( 'relevanssi_default_meta_query_relation', 'AND' );
1526
- $meta_query = array();
1527
  if ( ! empty( $query->query_vars['meta_query'] ) ) {
1528
  $meta_query = $query->query_vars['meta_query'];
1529
  }
@@ -1584,6 +1228,10 @@ function relevanssi_do_query( &$query ) {
1584
  } else {
1585
  $date_query = new WP_Date_Query( $query->date_query );
1586
  }
 
 
 
 
1587
  }
1588
 
1589
  $search_blogs = false;
@@ -1842,6 +1490,8 @@ function relevanssi_do_query( &$query ) {
1842
  /**
1843
  * Limits the search queries to restrict the number of posts handled.
1844
  *
 
 
1845
  * @param string $query The MySQL query.
1846
  *
1847
  * @return string The query with the LIMIT parameter added, if necessary.
@@ -1866,6 +1516,8 @@ function relevanssi_limit_filter( $query ) {
1866
  *
1867
  * Figures out the post types that are not included in the search.
1868
  *
 
 
1869
  * @param string $include_attachments Whether to include attachments or not.
1870
  *
1871
  * @return string SQL escaped list of excluded post types.
@@ -1874,7 +1526,7 @@ function relevanssi_get_negative_post_type( $include_attachments ) {
1874
  $negative_post_type = null;
1875
  $negative_post_type_list = array();
1876
 
1877
- if ( isset( $include_attachments ) && in_array( $include_attachments, array( '0', 'off', 'false' ), true ) ) {
1878
  $negative_post_type_list[] = 'attachment';
1879
  }
1880
 
@@ -1903,325 +1555,13 @@ function relevanssi_get_negative_post_type( $include_attachments ) {
1903
  return $negative_post_type;
1904
  }
1905
 
1906
- /**
1907
- * Processes one tax_query row.
1908
- *
1909
- * @global object $wpdb The WordPress database interface.
1910
- *
1911
- * @param array $row The tax_query row array.
1912
- * @param boolean $is_sub_row True if this is a subrow.
1913
- * @param string $global_relation The global tax_query relation (AND or OR).
1914
- * @param string $query_restrictions The MySQL query restriction.
1915
- * @param string $tax_query_relation The tax_query relation.
1916
- * @param array $term_tax_ids Array of term taxonomy IDs.
1917
- * @param array $not_term_tax_ids Array of excluded term taxonomy IDs.
1918
- * @param array $and_term_tax_ids Array of AND term taxonomy IDs.
1919
- *
1920
- * @return array Returns an array where the first item is the updated
1921
- * $query_restrictions, then $term_tax_ids, $not_term_tax_ids, and $and_term_tax_ids.
1922
- */
1923
- function relevanssi_process_tax_query_row( $row, $is_sub_row, $global_relation, $query_restrictions, $tax_query_relation, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids ) {
1924
- global $wpdb;
1925
-
1926
- $local_term_tax_ids = array();
1927
- $local_not_term_tax_ids = array();
1928
- $local_and_term_tax_ids = array();
1929
- $term_tax_id = array();
1930
-
1931
- $exists_query = false;
1932
- if ( isset( $row['operator'] ) && ( 'exists' === strtolower( $row['operator'] ) || 'not exists' === strtolower( $row['operator'] ) ) ) {
1933
- $exists_query = true;
1934
- }
1935
-
1936
- $using_term_tax_id = false;
1937
- if ( $exists_query ) {
1938
- $row['field'] = 'exists';
1939
- }
1940
- if ( ! isset( $row['field'] ) ) {
1941
- $row['field'] = 'term_id'; // In case 'field' is not set, go with the WP default of 'term_id'.
1942
- }
1943
- $row['field'] = strtolower( $row['field'] ); // In some cases, you can get 'ID' instead of 'id'.
1944
- if ( 'slug' === $row['field'] ) {
1945
- $slug = $row['terms'];
1946
- $numeric_slugs = array();
1947
- $slug_in = null;
1948
- if ( is_array( $slug ) ) {
1949
- $slugs = array();
1950
- $term_id = array();
1951
- foreach ( $slug as $t_slug ) {
1952
- $term = get_term_by( 'slug', $t_slug, $row['taxonomy'] );
1953
- if ( ! $term && is_numeric( $t_slug ) ) {
1954
- $numeric_slugs[] = "'$t_slug'";
1955
- } else {
1956
- if ( isset( $term->term_id ) ) {
1957
- $t_slug = sanitize_title( $t_slug );
1958
- $term_id[] = $term->term_id;
1959
- $slugs[] = "'$t_slug'";
1960
- }
1961
- }
1962
- }
1963
- if ( ! empty( $slugs ) ) {
1964
- $slug_in = implode( ',', $slugs );
1965
- }
1966
- } else {
1967
- $term = get_term_by( 'slug', $slug, $row['taxonomy'], OBJECT );
1968
- if ( ! $term && is_numeric( $slug ) ) {
1969
- $numeric_slugs[] = $slug;
1970
- } else {
1971
- if ( isset( $term->term_id ) ) {
1972
- $slug = sanitize_title( $slug );
1973
- $term_id = $term->term_id;
1974
- $slug_in = "'$slug'";
1975
- }
1976
- }
1977
- }
1978
- if ( ! empty( $slug_in ) ) {
1979
- $row_taxonomy = sanitize_text_field( $row['taxonomy'] );
1980
-
1981
- $tt_q = "SELECT tt.term_taxonomy_id
1982
- FROM $wpdb->term_taxonomy AS tt
1983
- LEFT JOIN $wpdb->terms AS t ON (tt.term_id=t.term_id)
1984
- WHERE tt.taxonomy = '$row_taxonomy' AND t.slug IN ($slug_in)";
1985
- // Clean: $row_taxonomy is sanitized, each slug in $slug_in is sanitized.
1986
- $term_tax_id = $wpdb->get_col( $tt_q ); // WPCS: unprepared SQL ok.
1987
- }
1988
- if ( ! empty( $numeric_slugs ) ) {
1989
- $row['field'] = 'term_id';
1990
- }
1991
- }
1992
- if ( 'name' === $row['field'] ) {
1993
- $name = $row['terms'];
1994
- $numeric_names = array();
1995
- $name_in = null;
1996
- if ( is_array( $name ) ) {
1997
- $names = array();
1998
- $term_id = array();
1999
- foreach ( $name as $t_name ) {
2000
- $term = get_term_by( 'name', $t_name, $row['taxonomy'] );
2001
- if ( ! $term && is_numeric( $t_name ) ) {
2002
- $numeric_names[] = "'$t_name'";
2003
- } else {
2004
- if ( isset( $term->term_id ) ) {
2005
- $t_name = sanitize_title( $t_name );
2006
- $term_id[] = $term->term_id;
2007
- $names[] = "'$t_name'";
2008
- }
2009
- }
2010
- }
2011
- if ( ! empty( $names ) ) {
2012
- $name_in = implode( ',', $names );
2013
- }
2014
- } else {
2015
- $term = get_term_by( 'name', $name, $row['taxonomy'] );
2016
- if ( ! $term && is_numeric( $name ) ) {
2017
- $numeric_slugs[] = $name;
2018
- } else {
2019
- if ( isset( $term->term_id ) ) {
2020
- $name = sanitize_title( $name );
2021
- $term_id = $term->term_id;
2022
- $name_in = "'$name'";
2023
- }
2024
- }
2025
- }
2026
- if ( ! empty( $name_in ) ) {
2027
- $row_taxonomy = sanitize_text_field( $row['taxonomy'] );
2028
-
2029
- $tt_q = "SELECT tt.term_taxonomy_id
2030
- FROM $wpdb->term_taxonomy AS tt
2031
- LEFT JOIN $wpdb->terms AS t ON (tt.term_id=t.term_id)
2032
- WHERE tt.taxonomy = '$row_taxonomy' AND t.name IN ($name_in)";
2033
- // Clean: $row_taxonomy is sanitized, each name in $name_in is sanitized.
2034
- $term_tax_id = $wpdb->get_col( $tt_q ); // WPCS: unprepared SQL ok.
2035
- }
2036
- if ( ! empty( $numeric_names ) ) {
2037
- $row['field'] = 'term_id';
2038
- }
2039
- }
2040
- if ( 'id' === $row['field'] || 'term_id' === $row['field'] ) {
2041
- $id = $row['terms'];
2042
- $term_id = $id;
2043
- if ( is_array( $id ) ) {
2044
- $numeric_values = array();
2045
- foreach ( $id as $t_id ) {
2046
- if ( is_numeric( $t_id ) ) {
2047
- $numeric_values[] = $t_id;
2048
- }
2049
- }
2050
- $id = implode( ',', $numeric_values );
2051
- }
2052
- $row_taxonomy = sanitize_text_field( $row['taxonomy'] );
2053
-
2054
- if ( ! empty( $id ) ) {
2055
- $tt_q = "SELECT tt.term_taxonomy_id
2056
- FROM $wpdb->term_taxonomy AS tt
2057
- LEFT JOIN $wpdb->terms AS t ON (tt.term_id=t.term_id)
2058
- WHERE tt.taxonomy = '$row_taxonomy' AND t.term_id IN ($id)";
2059
- // Clean: $row_taxonomy is sanitized, $id is checked to be numeric.
2060
- $id_term_tax_id = $wpdb->get_col( $tt_q ); // WPCS: unprepared SQL ok.
2061
- if ( ! empty( $term_tax_id ) && is_array( $term_tax_id ) ) {
2062
- $term_tax_id = array_unique( array_merge( $term_tax_id, $id_term_tax_id ) );
2063
- } else {
2064
- $term_tax_id = $id_term_tax_id;
2065
- }
2066
- }
2067
- }
2068
- if ( 'term_taxonomy_id' === $row['field'] ) {
2069
- $using_term_tax_id = true;
2070
- $id = $row['terms'];
2071
- $term_tax_id = $id;
2072
- if ( is_array( $id ) ) {
2073
- $numeric_values = array();
2074
- foreach ( $id as $t_id ) {
2075
- if ( is_numeric( $t_id ) ) {
2076
- $numeric_values[] = $t_id;
2077
- }
2078
- }
2079
- $term_tax_id = implode( ',', $numeric_values );
2080
- }
2081
- }
2082
-
2083
- if ( ! $exists_query && ( ! isset( $row['include_children'] ) || true === $row['include_children'] ) ) {
2084
- if ( ! $using_term_tax_id && isset( $term_id ) ) {
2085
- if ( ! is_array( $term_id ) ) {
2086
- $term_id = array( $term_id );
2087
- }
2088
- } else {
2089
- if ( ! is_array( $term_tax_id ) ) {
2090
- $term_tax_id = array( $term_tax_id );
2091
- $term_id = $term_tax_id;
2092
- }
2093
- }
2094
- if ( empty( $term_tax_id ) ) {
2095
- $term_tax_id = array();
2096
- }
2097
- if ( ! is_array( $term_tax_id ) ) {
2098
- $term_tax_id = array( $term_tax_id );
2099
- }
2100
- if ( isset( $term_id ) && is_array( $term_id ) ) {
2101
- foreach ( $term_id as $t_id ) {
2102
- if ( $using_term_tax_id ) {
2103
- $t_term = get_term_by( 'term_taxonomy_id', $t_id, $row['taxonomy'] );
2104
- $t_id = $t_term->ID;
2105
- }
2106
- $kids = get_term_children( $t_id, $row['taxonomy'] );
2107
- foreach ( $kids as $kid ) {
2108
- $kid_term_tax_id = relevanssi_get_term_tax_id( $kid, $row['taxonomy'] );
2109
- if ( $kid_term_tax_id ) {
2110
- // In some weird cases, this may be null. See: https://wordpress.org/support/topic/childrens-of-chosen-product_cat-not-showing-up/.
2111
- $term_tax_id[] = $kid_term_tax_id;
2112
- }
2113
- }
2114
- }
2115
- }
2116
- }
2117
-
2118
- $term_tax_id = array_unique( $term_tax_id );
2119
- if ( ! empty( $term_tax_id ) ) {
2120
- $n = count( $term_tax_id );
2121
- $term_tax_id = implode( ',', $term_tax_id );
2122
-
2123
- $tq_operator = 'IN'; // Assuming the default operator "IN", unless something else is provided.
2124
- if ( isset( $row['operator'] ) ) {
2125
- $tq_operator = strtoupper( $row['operator'] );
2126
- }
2127
- if ( ! in_array( $tq_operator, array( 'IN', 'NOT IN', 'AND' ), true ) ) {
2128
- $tq_operator = 'IN';
2129
- }
2130
- if ( 'and' === $tax_query_relation ) {
2131
- if ( 'AND' === $tq_operator ) {
2132
- $query_restrictions .= " AND relevanssi.doc IN (
2133
- SELECT ID FROM $wpdb->posts WHERE 1=1
2134
- AND (
2135
- SELECT COUNT(1)
2136
- FROM $wpdb->term_relationships AS tr
2137
- WHERE tr.term_taxonomy_id IN ($term_tax_id)
2138
- AND tr.object_id = $wpdb->posts.ID ) = $n
2139
- )";
2140
- // Clean: $term_tax_id and $n are Relevanssi-generated.
2141
- } else {
2142
- $query_restrictions .= " AND relevanssi.doc $tq_operator (SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr
2143
- WHERE tr.term_taxonomy_id IN ($term_tax_id))";
2144
- // Clean: all variables are Relevanssi-generated.
2145
- }
2146
- } else {
2147
- if ( 'IN' === $tq_operator ) {
2148
- $local_term_tax_ids[] = $term_tax_id;
2149
- }
2150
- if ( 'NOT IN' === $tq_operator ) {
2151
- $local_not_term_tax_ids[] = $term_tax_id;
2152
- }
2153
- if ( 'AND' === $tq_operator ) {
2154
- $local_and_term_tax_ids[] = $term_tax_id;
2155
- }
2156
- }
2157
- } else {
2158
- global $wp_query;
2159
- $wp_query->is_category = false;
2160
- }
2161
-
2162
- if ( $is_sub_row && 'and' === $global_relation && 'or' === $tax_query_relation ) {
2163
- $local_term_tax_ids = array_unique( $local_term_tax_ids );
2164
- $local_not_term_tax_ids = array_unique( $local_not_term_tax_ids );
2165
- $local_and_term_tax_ids = array_unique( $local_and_term_tax_ids );
2166
- if ( count( $local_term_tax_ids ) > 0 ) {
2167
- $local_term_tax_ids = implode( ',', $local_term_tax_ids );
2168
- $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr
2169
- WHERE tr.term_taxonomy_id IN ($local_term_tax_ids))";
2170
- // Clean: all variables are Relevanssi-generated.
2171
- }
2172
- if ( count( $local_not_term_tax_ids ) > 0 ) {
2173
- $local_not_term_tax_ids = implode( ',', $local_not_term_tax_ids );
2174
- $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr
2175
- WHERE tr.term_taxonomy_id IN ($local_not_term_tax_ids))";
2176
- // Clean: all variables are Relevanssi-generated.
2177
- }
2178
- if ( count( $local_and_term_tax_ids ) > 0 ) {
2179
- $local_and_term_tax_ids = implode( ',', $local_and_term_tax_ids );
2180
- $n = count( explode( ',', $local_and_term_tax_ids ) );
2181
- $query_restrictions .= " AND relevanssi.doc IN (
2182
- SELECT ID FROM $wpdb->posts WHERE 1=1
2183
- AND (
2184
- SELECT COUNT(1)
2185
- FROM $wpdb->term_relationships AS tr
2186
- WHERE tr.term_taxonomy_id IN ($local_and_term_tax_ids)
2187
- AND tr.object_id = $wpdb->posts.ID ) = $n
2188
- )";
2189
- // Clean: all variables are Relevanssi-generated.
2190
- }
2191
- }
2192
-
2193
- $copy_term_tax_ids = false;
2194
- if ( ! $is_sub_row ) {
2195
- $copy_term_tax_ids = true;
2196
- }
2197
- if ( $is_sub_row && 'or' === $global_relation ) {
2198
- $copy_term_tax_ids = true;
2199
- }
2200
-
2201
- if ( $copy_term_tax_ids ) {
2202
- $term_tax_ids = array_merge( $term_tax_ids, $local_term_tax_ids );
2203
- $not_term_tax_ids = array_merge( $not_term_tax_ids, $local_not_term_tax_ids );
2204
- $and_term_tax_ids = array_merge( $and_term_tax_ids, $local_and_term_tax_ids );
2205
- }
2206
-
2207
- if ( $exists_query ) {
2208
- $taxonomy = $row['taxonomy'];
2209
- $operator = 'IN';
2210
- if ( 'not exists' === strtolower( $row['operator'] ) ) {
2211
- $operator = 'NOT IN';
2212
- }
2213
- $query_restrictions .= " AND relevanssi.doc $operator (SELECT DISTINCT(tr.object_id) FROM $wpdb->term_relationships AS tr,
2214
- $wpdb->term_taxonomy AS tt WHERE tr.term_taxonomy_id = tt.term_taxonomy_id AND tt.taxonomy = '$taxonomy')";
2215
- }
2216
-
2217
- return array( $query_restrictions, $term_tax_ids, $not_term_tax_ids, $and_term_tax_ids );
2218
- }
2219
-
2220
  /**
2221
  * Generates the WHERE condition for terms.
2222
  *
2223
  * Trims the term, escapes it and places it in the template.
2224
  *
 
 
2225
  * @param string $term The search term.
2226
  * @param string $o_term_cond The search condition template.
2227
  *
@@ -2259,6 +1599,8 @@ function relevanssi_generate_term_cond( $term, $o_term_cond ) {
2259
  * If there's a taxonomy weight in $post_type_weights, that is used, otherwise
2260
  * assume weight 1.
2261
  *
 
 
2262
  * @since 2.1.5
2263
  *
2264
  * @param object $match The match object, used as a reference.
116
  *
117
  * @param array The search parameters.
118
  */
119
+ $filtered_args = apply_filters( 'relevanssi_search_filters', $args );
120
+ $meta_query = $filtered_args['meta_query'];
121
+ $operator = $filtered_args['operator'];
122
+ $orderby = $filtered_args['orderby'];
123
+ $order = $filtered_args['order'];
124
+ $fields = $filtered_args['fields'];
125
+ $meta_query = $filtered_args['meta_query'];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ $hits = array();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
+ $query_data = relevanssi_process_query_args( $filtered_args );
130
+ $query_restrictions = $query_data['query_restrictions'];
131
+ $query_join = $query_data['query_join'];
132
+ $q = $query_data['query_query'];
 
 
133
 
134
  /**
135
  * Filters whether stopwords are removed from titles.
139
  $remove_stopwords = apply_filters( 'relevanssi_remove_stopwords_in_titles', true );
140
 
141
  $terms = relevanssi_tokenize( $q, $remove_stopwords );
 
142
  if ( count( $terms ) < 1 ) {
143
  // Tokenizer killed all the search terms.
144
  return $hits;
145
  }
146
  $terms = array_keys( $terms ); // Don't care about tf in query.
147
 
148
+ if ( function_exists( 'relevanssi_process_terms' ) ) {
149
+ $process_terms_results = relevanssi_process_terms( $terms, $q );
150
+ $query_restrictions .= $process_terms_results['query_restrictions'];
151
+ $terms = $process_terms_results['terms'];
152
  }
153
 
154
+ /**
155
+ * Filters the query restrictions for the Relevanssi query.
156
+ *
157
+ * Equivalent to the 'posts_where' filter.
158
+ *
159
+ * @author Charles St-Pierre
160
+ *
161
+ * @param string The MySQL code that restricts the query.
162
+ */
163
+ $query_restrictions = apply_filters( 'relevanssi_where', $query_restrictions );
164
+
165
+ /**
166
+ * Filters the meta query JOIN for the Relevanssi search query.
167
+ *
168
+ * Somewhat equivalent to the 'posts_join' filter.
169
+ *
170
+ * @param string The JOINed query.
171
+ */
172
+ $query_join = apply_filters( 'relevanssi_join', $query_join );
173
+
174
  // Go get the count from the options, but run the full query if it's not available.
175
  $doc_count = get_option( 'relevanssi_doc_count' );
176
  if ( ! $doc_count || $doc_count < 1 ) {
191
 
192
  $fuzzy = get_option( 'relevanssi_fuzzy' );
193
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  $no_matches = true;
195
  if ( 'always' === $fuzzy ) {
196
  /**
217
  $recency_bonus = false;
218
  $recency_cutoff_date = false;
219
  if ( function_exists( 'relevanssi_get_recency_bonus' ) ) {
220
+ $recency_details = relevanssi_get_recency_bonus();
221
+ $recency_bonus = $recency_details['bonus'];
222
+ $recency_cutoff_date = $recency_details['cutoff'];
223
  }
224
 
225
  $exact_match_bonus = false;
337
  } else {
338
  $no_matches = false;
339
  if ( count( $include_these_posts ) > 0 ) {
340
+ $existing_ids = array();
 
341
  foreach ( $matches as $match ) {
342
  $existing_ids[] = $match->doc;
343
  }
344
+ $existing_ids = array_keys( array_flip( $existing_ids ) );
345
+ $added_post_ids = array_diff( array_keys( $include_these_posts ), $existing_ids );
346
+ if ( count( $added_post_ids ) > 0 ) {
347
+ $offset = 0;
348
+ $slice_length = 1;
349
+ $total_ids = count( $added_post_ids );
350
+ do {
351
+ $current_slice = array_slice( $added_post_ids, $offset, $slice_length );
352
+ $post_ids_to_add = implode( ',', $current_slice );
353
+ if ( ! empty( $post_ids_to_add ) ) {
354
+ $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
355
+ relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
356
+ relevanssi.tag * $tag + relevanssi.link * $link_boost +
357
+ relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
358
+ relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
359
+ FROM $relevanssi_table AS relevanssi WHERE relevanssi.doc IN ($post_ids_to_add)
360
+ AND $term_cond";
361
+
362
+ // Clean: no unescaped user inputs.
363
+ $matches_to_add = $wpdb->get_results( $query ); // WPCS: unprepared SQL ok.
364
+ $matches = array_merge( $matches, $matches_to_add );
365
+ }
366
+ $offset += $slice_length;
367
+ } while ( $offset <= $total_ids );
368
+ }
369
  }
370
  if ( count( $include_these_items ) > 0 ) {
 
371
  $existing_items = array();
372
  foreach ( $matches as $match ) {
373
  if ( 0 !== intval( $match->item ) ) {
375
  }
376
  }
377
  $existing_items = array_keys( array_flip( $existing_items ) );
378
+ $items_to_add = implode( ',', array_diff( array_keys( $include_these_items ), $existing_items ) );
379
+
380
+ if ( ! empty( $items_to_add ) ) {
381
+ $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
382
+ relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
383
+ relevanssi.tag * $tag + relevanssi.link * $link_boost +
384
+ relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
385
+ relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
386
+ FROM $relevanssi_table AS relevanssi WHERE relevanssi.item IN ($items_to_add)
387
+ AND $term_cond";
388
+
389
+ // Clean: no unescaped user inputs.
390
+ $matches_to_add = $wpdb->get_results( $query ); // WPCS: unprepared SQL ok.
391
+ $matches = array_merge( $matches, $matches_to_add );
392
  }
 
 
 
 
 
 
 
 
 
 
 
393
  }
394
  }
395
 
403
  if ( $idf < 1 ) {
404
  $idf = 1;
405
  }
406
+
407
  foreach ( $matches as $match ) {
408
  if ( 'user' === $match->type ) {
409
  $match->doc = 'u_' . $match->item;
940
  /**
941
  * Filters the default tax_query relation.
942
  *
943
+ * @param string The default relation, default 'AND'.
944
  */
945
+ $tax_query_relation = apply_filters( 'relevanssi_default_tax_query_relation', 'AND' );
946
  if ( isset( $query->tax_query ) && empty( $query->tax_query->queries ) ) {
947
  // Tax query is empty, let's get rid of it.
948
  $query->tax_query = null;
1167
  $parent_query = array( 'parent not in' => $query->query_vars['post_parent__not_in'] );
1168
  }
1169
 
1170
+ $meta_query = array();
 
 
 
 
 
 
1171
  if ( ! empty( $query->query_vars['meta_query'] ) ) {
1172
  $meta_query = $query->query_vars['meta_query'];
1173
  }
1228
  } else {
1229
  $date_query = new WP_Date_Query( $query->date_query );
1230
  }
1231
+ } elseif ( ! empty( $query->query_vars['date_query'] ) ) {
1232
+ // The official date query is in $query->date_query, but this allows
1233
+ // users to set the date query from query variables.
1234
+ $date_query = new WP_Date_Query( $query->query_vars['date_query'] );
1235
  }
1236
 
1237
  $search_blogs = false;
1490
  /**
1491
  * Limits the search queries to restrict the number of posts handled.
1492
  *
1493
+ * Tested.
1494
+ *
1495
  * @param string $query The MySQL query.
1496
  *
1497
  * @return string The query with the LIMIT parameter added, if necessary.
1516
  *
1517
  * Figures out the post types that are not included in the search.
1518
  *
1519
+ * Tested.
1520
+ *
1521
  * @param string $include_attachments Whether to include attachments or not.
1522
  *
1523
  * @return string SQL escaped list of excluded post types.
1526
  $negative_post_type = null;
1527
  $negative_post_type_list = array();
1528
 
1529
+ if ( isset( $include_attachments ) && in_array( $include_attachments, array( '0', 'off', 'false', false ), true ) ) {
1530
  $negative_post_type_list[] = 'attachment';
1531
  }
1532
 
1555
  return $negative_post_type;
1556
  }
1557
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1558
  /**
1559
  * Generates the WHERE condition for terms.
1560
  *
1561
  * Trims the term, escapes it and places it in the template.
1562
  *
1563
+ * Tested.
1564
+ *
1565
  * @param string $term The search term.
1566
  * @param string $o_term_cond The search condition template.
1567
  *
1599
  * If there's a taxonomy weight in $post_type_weights, that is used, otherwise
1600
  * assume weight 1.
1601
  *
1602
+ * Tested.
1603
+ *
1604
  * @since 2.1.5
1605
  *
1606
  * @param object $match The match object, used as a reference.
lib/shortcodes.php CHANGED
@@ -110,12 +110,33 @@ function relevanssi_search_form( $atts ) {
110
  if ( is_array( $atts ) ) {
111
  $additional_fields = array();
112
  foreach ( $atts as $key => $value ) {
113
- $key = esc_attr( $key );
114
- $value = esc_attr( $value );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- $additional_fields[] = "<input type='hidden' name='$key' value='$value' />";
 
117
  }
118
- $form = str_replace( '</form>', implode( "\n", $additional_fields ) . '</form>', $form );
119
  }
120
  /**
121
  * Filters the Relevanssi shortcode search form before it's used.
110
  if ( is_array( $atts ) ) {
111
  $additional_fields = array();
112
  foreach ( $atts as $key => $value ) {
113
+ if ( 'dropdown' === $key ) {
114
+ switch ( $value ) {
115
+ case 'category':
116
+ $name = 'cat';
117
+ break;
118
+ case 'post_tag':
119
+ $name = 'tag';
120
+ break;
121
+ default:
122
+ $name = $value;
123
+ }
124
+ $args = array(
125
+ 'taxonomy' => $value,
126
+ 'echo' => 0,
127
+ 'hide_if_empty' => true,
128
+ 'show_option_none' => __( 'None' ),
129
+ 'name' => $name,
130
+ );
131
+ $additional_fields[] = wp_dropdown_categories( $args );
132
+ } else {
133
+ $key = esc_attr( $key );
134
+ $value = esc_attr( $value );
135
 
136
+ $additional_fields[] = "<input type='hidden' name='$key' value='$value' />";
137
+ }
138
  }
139
+ $form = str_replace( '<input type="submit"', implode( "\n", $additional_fields ) . '<input type="submit"', $form );
140
  }
141
  /**
142
  * Filters the Relevanssi shortcode search form before it's used.
lib/sorting.php CHANGED
@@ -198,7 +198,7 @@ function relevanssi_get_compare_values( $key, $item_1, $item_2 ) {
198
  $key2 = get_post_meta( $item_2->ID, $key, true );
199
  if ( empty( $key2 ) ) {
200
  /**
201
- * Documented in lib/common.php.
202
  */
203
  $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
204
  }
@@ -211,7 +211,7 @@ function relevanssi_get_compare_values( $key, $item_1, $item_2 ) {
211
  $key1 = get_post_meta( $item_1->ID, $relevanssi_meta_query[ $key ]['key'], true );
212
  } else {
213
  /**
214
- * Documented in lib/common.php.
215
  */
216
  $key1 = apply_filters( 'relevanssi_missing_sort_key', $key1, $key );
217
  }
@@ -222,7 +222,7 @@ function relevanssi_get_compare_values( $key, $item_1, $item_2 ) {
222
  $key2 = get_post_meta( $item_2->ID, $relevanssi_meta_query[ $key ]['key'], true );
223
  } else {
224
  /**
225
- * Documented in lib/common.php.
226
  */
227
  $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
228
  }
198
  $key2 = get_post_meta( $item_2->ID, $key, true );
199
  if ( empty( $key2 ) ) {
200
  /**
201
+ * Documented in lib/sorting.php.
202
  */
203
  $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
204
  }
211
  $key1 = get_post_meta( $item_1->ID, $relevanssi_meta_query[ $key ]['key'], true );
212
  } else {
213
  /**
214
+ * Documented in lib/sorting.php.
215
  */
216
  $key1 = apply_filters( 'relevanssi_missing_sort_key', $key1, $key );
217
  }
222
  $key2 = get_post_meta( $item_2->ID, $relevanssi_meta_query[ $key ]['key'], true );
223
  } else {
224
  /**
225
+ * Documented in lib/sorting.php.
226
  */
227
  $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
228
  }
lib/stopwords.php CHANGED
@@ -67,8 +67,6 @@ function relevanssi_fetch_stopwords() {
67
  /**
68
  * Adds a stopword to the list of stopwords.
69
  *
70
- * @global object $wpdb The WP database interface.
71
- *
72
  * @param string $term The stopword that is added.
73
  * @param boolean $verbose If true, print out notice. If false, be silent. Default
74
  * true.
@@ -76,7 +74,6 @@ function relevanssi_fetch_stopwords() {
76
  * @return boolean True, if success; false otherwise.
77
  */
78
  function relevanssi_add_stopword( $term, $verbose = true ) {
79
- global $wpdb;
80
  if ( empty( $term ) ) {
81
  return;
82
  }
67
  /**
68
  * Adds a stopword to the list of stopwords.
69
  *
 
 
70
  * @param string $term The stopword that is added.
71
  * @param boolean $verbose If true, print out notice. If false, be silent. Default
72
  * true.
74
  * @return boolean True, if success; false otherwise.
75
  */
76
  function relevanssi_add_stopword( $term, $verbose = true ) {
 
77
  if ( empty( $term ) ) {
78
  return;
79
  }
lib/tabs/searching-tab.php CHANGED
@@ -120,6 +120,10 @@ function relevanssi_searching_tab() {
120
  <option value='always' <?php echo esc_html( $fuzzy_always ); ?>><?php esc_html_e( 'Partial words', 'relevanssi' ); ?></option>
121
  <option value='sometimes' <?php echo esc_html( $fuzzy_sometimes ); ?>><?php esc_html_e( 'Partial words if no hits for whole words', 'relevanssi' ); ?></option>
122
  </select>
 
 
 
 
123
  <p class="description"><?php esc_html_e( 'Whole words means Relevanssi only finds posts that include the whole search term.', 'relevanssi' ); ?></p>
124
  <p class="description"><?php esc_html_e( "Partial words also includes cases where the word in the index begins or ends with the search term (searching for 'ana' will match 'anaconda' or 'banana', but not 'banal'). See Help, if you want to make Relevanssi match also inside words.", 'relevanssi' ); ?></p>
125
  </td>
120
  <option value='always' <?php echo esc_html( $fuzzy_always ); ?>><?php esc_html_e( 'Partial words', 'relevanssi' ); ?></option>
121
  <option value='sometimes' <?php echo esc_html( $fuzzy_sometimes ); ?>><?php esc_html_e( 'Partial words if no hits for whole words', 'relevanssi' ); ?></option>
122
  </select>
123
+ <?php if ( $fuzzy_sometimes ) : ?>
124
+ <?php // Translators: %1$s is the "partial words if no hits" option and %2$s is the "partial words" option. ?>
125
+ <p class="description important"><?php printf( esc_html__( 'Choosing the "%1$s" option may lead to unexpected results. Most of the time the "%2$s" option is the better choice.', 'relevanssi' ), esc_html__( 'Partial words if not hits for whole words', 'relevanssi' ), esc_html__( 'Partial words', 'relevanssi' ) ); ?></p>
126
+ <?php endif; ?>
127
  <p class="description"><?php esc_html_e( 'Whole words means Relevanssi only finds posts that include the whole search term.', 'relevanssi' ); ?></p>
128
  <p class="description"><?php esc_html_e( "Partial words also includes cases where the word in the index begins or ends with the search term (searching for 'ana' will match 'anaconda' or 'banana', but not 'banal'). See Help, if you want to make Relevanssi match also inside words.", 'relevanssi' ); ?></p>
129
  </td>
lib/tabs/stopwords-tab.php CHANGED
@@ -20,6 +20,17 @@ function relevanssi_stopwords_tab() {
20
 
21
  relevanssi_show_stopwords();
22
 
 
 
 
 
 
 
 
 
 
 
 
23
  /**
24
  * Filters whether the common words list is displayed or not.
25
  *
@@ -45,11 +56,6 @@ function relevanssi_stopwords_tab() {
45
  function relevanssi_show_stopwords() {
46
  global $wpdb, $relevanssi_variables;
47
 
48
- $plugin = 'relevanssi';
49
- if ( RELEVANSSI_PREMIUM ) {
50
- $plugin = 'relevanssi-premium';
51
- }
52
-
53
  printf( '<p>%s</p>', esc_html__( 'Enter a word here to add it to the list of stopwords. The word will automatically be removed from the index, so re-indexing is not necessary. You can enter many words at the same time, separate words with commas.', 'relevanssi' ) );
54
  ?>
55
  <table class="form-table">
20
 
21
  relevanssi_show_stopwords();
22
 
23
+ ?>
24
+
25
+ <h3 id="bodystopwords"><?php esc_html_e( 'Content stopwords', 'relevanssi' ); ?></h3>
26
+
27
+ <?php
28
+ if ( function_exists( 'relevanssi_show_body_stopwords' ) ) {
29
+ relevanssi_show_body_stopwords();
30
+ } else {
31
+ printf( '<p>%s</p>', esc_html__( 'Content stopwords are a premium feature where you can set stopwords that only apply to the post content. Those stopwords will still be indexed if they appear in post titles, tags, categories, custom fields or other parts of the post. To use content stopwords, you need Relevanssi Premium.', 'relevanssi' ) );
32
+ }
33
+
34
  /**
35
  * Filters whether the common words list is displayed or not.
36
  *
56
  function relevanssi_show_stopwords() {
57
  global $wpdb, $relevanssi_variables;
58
 
 
 
 
 
 
59
  printf( '<p>%s</p>', esc_html__( 'Enter a word here to add it to the list of stopwords. The word will automatically be removed from the index, so re-indexing is not necessary. You can enter many words at the same time, separate words with commas.', 'relevanssi' ) );
60
  ?>
61
  <table class="form-table">
readme.txt CHANGED
@@ -3,9 +3,9 @@ Contributors: msaari
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search
5
  Requires at least: 4.8.3
6
- Tested up to: 5.2
7
  Requires PHP: 5.6
8
- Stable tag: 4.1.4
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -129,6 +129,22 @@ Each document database is full of useless words. All the little words that appea
129
  * John Calahan for extensive 4.0 beta testing.
130
 
131
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  = 4.1.4 =
133
  * `EXISTS` and `NOT EXISTS` didn’t work for taxonomy terms in searches.
134
  * WPML post type handling has been improved. If post type allows fallback for default language, Relevanssi will support that.
@@ -193,6 +209,9 @@ Each document database is full of useless words. All the little words that appea
193
  * WP Search Suggest compatibility added.
194
 
195
  == Upgrade notice ==
 
 
 
196
  = 4.1.4 =
197
  * Restrict Content Pro support, bug fixes and small improvements.
198
 
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search
5
  Requires at least: 4.8.3
6
+ Tested up to: 5.2.1
7
  Requires PHP: 5.6
8
+ Stable tag: 4.2.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
129
  * John Calahan for extensive 4.0 beta testing.
130
 
131
  == Changelog ==
132
+ = 4.2.0 =
133
+ * New feature: The search form shortcode has a new parameter `dropdown` which can be used to add a category dropdown, like this: `[searchform dropdown="category"]`.
134
+ * New feature: Relevanssi can now use the contents of the PDF files indexed with WP File Download.
135
+ * New filter: `relevanssi_indexing_tokens` can be used to filter the tokens (individual words) before they are indexed.
136
+ * Removed filter: `relevanssi_default_meta_query_relation` did not have any effect anymore.
137
+ * Changed behaviour: The default taxonomy relation was set to AND in 4.1.4, but wasn't properly applied before. Now it is really switched.
138
+ * Changed behaviour: New post types have been added to list of forbidden post types Relevanssi won't show as indexing options (ACF, TablePress and WooCommerce).
139
+ * Major fix: Tax query processing has been completely refactored, eliminating all sorts of bugs, especially with various edge cases.
140
+ * Major fix: Gutenberg block indexing only worked with the Gutenberg plugin enabled. It now works with WP 5.0 built-in Gutenberg as well. If you use Gutenberg blocks, reindex to get all the block content in the index.
141
+ * Major fix: Excerpt-building and highlighting did not respect the "Keyword matching" setting. They do now, and the excerpts should be better now.
142
+ * Major fix: AND searches needed queries that could get too long for the database to handle. This has been fixed and optimized.
143
+ * Major fix: Taxonomy term subquery relations didn't work; now they are applied.
144
+ * Minor fix: iOS uses curly quotes by default, and that didn't work as a phrase operator. Now phrase operator works with curly quotes and straight quotes.
145
+ * Minor fix: The Did you mean broke with search terms longer than 255 characters.
146
+ * Minor fix: Phrases with numbers and one word like "team 17" didn't work, because numbers weren't counted as words.
147
+
148
  = 4.1.4 =
149
  * `EXISTS` and `NOT EXISTS` didn’t work for taxonomy terms in searches.
150
  * WPML post type handling has been improved. If post type allows fallback for default language, Relevanssi will support that.
209
  * WP Search Suggest compatibility added.
210
 
211
  == Upgrade notice ==
212
+ = 4.2.0 =
213
+ * New features, bug fixes, smaller improvements.
214
+
215
  = 4.1.4 =
216
  * Restrict Content Pro support, bug fixes and small improvements.
217
 
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.1.4
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
@@ -58,15 +58,17 @@ $relevanssi_variables['database_version'] = 5;
58
  $relevanssi_variables['file'] = __FILE__;
59
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
60
 
61
- require_once 'lib/install.php';
 
 
 
62
  require_once 'lib/init.php';
 
63
  require_once 'lib/interface.php';
64
- require_once 'lib/indexing.php';
65
  require_once 'lib/log.php';
66
- require_once 'lib/sorting.php';
67
- require_once 'lib/stopwords.php';
68
  require_once 'lib/search.php';
69
- require_once 'lib/excerpts-highlights.php';
 
70
  require_once 'lib/shortcodes.php';
71
- require_once 'lib/common.php';
72
- require_once 'lib/admin-ajax.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.2.0
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
58
  $relevanssi_variables['file'] = __FILE__;
59
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
60
 
61
+ require_once 'lib/admin-ajax.php';
62
+ require_once 'lib/common.php';
63
+ require_once 'lib/excerpts-highlights.php';
64
+ require_once 'lib/indexing.php';
65
  require_once 'lib/init.php';
66
+ require_once 'lib/install.php';
67
  require_once 'lib/interface.php';
 
68
  require_once 'lib/log.php';
 
 
69
  require_once 'lib/search.php';
70
+ require_once 'lib/search-tax-query.php';
71
+ require_once 'lib/search-query-restrictions.php';
72
  require_once 'lib/shortcodes.php';
73
+ require_once 'lib/sorting.php';
74
+ require_once 'lib/stopwords.php';
relevanssi.po CHANGED
@@ -2,7 +2,7 @@ msgid ""
2
  msgstr ""
3
  "Project-Id-Version: Relevanssi\n"
4
  "Report-Msgid-Bugs-To: \n"
5
- "POT-Creation-Date: 2019-02-12 14:31+0200\n"
6
  "PO-Revision-Date: \n"
7
  "Last-Translator: Mikko Saari <mikko@mikkosaari.fi>\n"
8
  "Language-Team: \n"
@@ -12,7 +12,7 @@ msgstr ""
12
  "Content-Transfer-Encoding: 8bit\n"
13
  "X-Poedit-KeywordsList: _e;__;esc_html__;esc_html_e;_n\n"
14
  "X-Poedit-Basepath: .\n"
15
- "X-Generator: Poedit 2.2.1\n"
16
  "X-Poedit-SearchPath-0: .\n"
17
  "X-Poedit-SearchPath-1: lib\n"
18
 
@@ -21,40 +21,56 @@ msgstr ""
21
  msgid "Indexed %1$d post (total %2$d), processed %3$d / %4$d."
22
  msgstr ""
23
 
24
- #: lib/admin-ajax.php:198
25
  msgid "Results"
26
  msgstr ""
27
 
28
- #: lib/admin-ajax.php:200
29
  #, php-format
30
  msgid "Found a total of %1$d posts, showing %2$d posts from offset %3$s."
31
  msgstr ""
32
 
33
- #: lib/admin-ajax.php:209
 
 
 
 
 
 
 
 
34
  msgid "Score:"
35
  msgstr ""
36
 
37
- #: lib/admin-ajax.php:269
 
 
 
 
 
 
 
 
38
  msgid "Query variables"
39
  msgstr ""
40
 
41
- #: lib/admin-ajax.php:316
42
  msgid "Filters"
43
  msgstr ""
44
 
45
- #: lib/admin-ajax.php:317
46
  msgid "show"
47
  msgstr ""
48
 
49
- #: lib/admin-ajax.php:318
50
  msgid "hide"
51
  msgstr ""
52
 
53
- #: lib/common.php:1566
54
  msgid "25 most common words in the index"
55
  msgstr ""
56
 
57
- #: lib/common.php:1567
58
  msgid ""
59
  "These words are excellent stopword material. A word that appears in most of "
60
  "the posts in the database is quite pointless when searching. This is also an "
@@ -64,15 +80,19 @@ msgid ""
64
  "necessary."
65
  msgstr ""
66
 
67
- #: lib/common.php:1573
68
  msgid "Stopword Candidates"
69
  msgstr ""
70
 
71
- #: lib/common.php:1580
72
  msgid "Add to stopwords"
73
  msgstr ""
74
 
75
- #: lib/contextual-help.php:24 lib/interface.php:791
 
 
 
 
76
  #: lib/tabs/overview-tab.php:56
77
  msgid "Searching"
78
  msgstr ""
@@ -298,56 +318,65 @@ msgid ""
298
  "search to categories 10, 14 and 17, you can use %2$s and so on."
299
  msgstr ""
300
 
301
- #: lib/contextual-help.php:137
302
- msgid "WooCommerce"
 
 
 
 
 
303
  msgstr ""
304
 
305
  #: lib/contextual-help.php:139
 
 
 
 
306
  msgid ""
307
  "If your SKUs include hyphens or other punctuation, do note that Relevanssi "
308
  "replaces most punctuation with spaces. That's going to cause issues with SKU "
309
  "searches."
310
  msgstr ""
311
 
312
- #: lib/contextual-help.php:141
313
  #, php-format
314
  msgid ""
315
  "For more details how to fix that issue, see <a href='%s'>WooCommerce tips in "
316
  "Relevanssi user manual</a>."
317
  msgstr ""
318
 
319
- #: lib/contextual-help.php:142
320
  msgid ""
321
  "If you don't want to index products that are out of stock, excluded from the "
322
  "catalog or excluded from the search, there's a product visibility filtering "
323
  "method that is described in the user manual (see link above)."
324
  msgstr ""
325
 
326
- #: lib/contextual-help.php:147
327
  msgid "Exact match bonus"
328
  msgstr ""
329
 
330
- #: lib/contextual-help.php:150
331
  #, php-format
332
  msgid ""
333
  "To adjust the amount of the exact match bonus, you can use the %s filter "
334
  "hook. It works like this:"
335
  msgstr ""
336
 
337
- #: lib/contextual-help.php:156
338
  #, php-format
339
  msgid "The default values are %1$s for titles and %2$s for content."
340
  msgstr ""
341
 
342
- #: lib/contextual-help.php:159
343
  msgid "For more information:"
344
  msgstr ""
345
 
346
- #: lib/contextual-help.php:160
347
  msgid "Plugin knowledge base"
348
  msgstr ""
349
 
350
- #: lib/contextual-help.php:161
351
  msgid "WordPress.org forum"
352
  msgstr ""
353
 
@@ -376,20 +405,20 @@ msgid ""
376
  "extension."
377
  msgstr ""
378
 
379
- #: lib/init.php:186 lib/init.php:187 lib/interface.php:453
380
  #: lib/tabs/logging-tab.php:47
381
  msgid "User searches"
382
  msgstr ""
383
 
384
- #: lib/init.php:198 lib/init.php:199 lib/tabs/searching-tab.php:244
385
  msgid "Admin search"
386
  msgstr ""
387
 
388
- #: lib/init.php:389
389
  msgid "Settings"
390
  msgstr ""
391
 
392
- #: lib/init.php:392
393
  msgid "Go Premium!"
394
  msgstr ""
395
 
@@ -401,286 +430,286 @@ msgstr ""
401
  msgid "Relevanssi Premium Search Options"
402
  msgstr ""
403
 
404
- #: lib/interface.php:455
405
  msgid "Relevanssi User Searches"
406
  msgstr ""
407
 
408
- #: lib/interface.php:478
409
  msgid "Enable query logging to see stats here."
410
  msgstr ""
411
 
412
- #: lib/interface.php:490
413
  msgid "Admin Search"
414
  msgstr ""
415
 
416
- #: lib/interface.php:520
417
  msgid "Logs clear!"
418
  msgstr ""
419
 
420
- #: lib/interface.php:522
421
  msgid "Clearing the logs failed."
422
  msgstr ""
423
 
424
- #: lib/interface.php:556
425
  msgid "Total Searches"
426
  msgstr ""
427
 
428
- #: lib/interface.php:558
429
  msgid "Totals"
430
  msgstr ""
431
 
432
- #: lib/interface.php:562
433
  msgid "Common Queries"
434
  msgstr ""
435
 
436
- #: lib/interface.php:572
437
  #, php-format
438
  msgid ""
439
  "Here you can see the %d most common user search queries, how many times "
440
  "those queries were made and how many results were found for those queries."
441
  msgstr ""
442
 
443
- #: lib/interface.php:576 lib/interface.php:598 lib/interface.php:639
444
  msgid "Today and yesterday"
445
  msgstr ""
446
 
447
- #: lib/interface.php:579 lib/interface.php:585 lib/interface.php:590
448
- #: lib/interface.php:607
449
  #, php-format
450
  msgid "Last %d days"
451
  msgstr ""
452
 
453
- #: lib/interface.php:595
454
  msgid "Unsuccessful Queries"
455
  msgstr ""
456
 
457
- #: lib/interface.php:602 lib/interface.php:640
458
  msgid "Last 7 days"
459
  msgstr ""
460
 
461
- #: lib/interface.php:613
462
  msgid "Reset Logs"
463
  msgstr ""
464
 
465
- #: lib/interface.php:617
466
  #, php-format
467
  msgid "To reset the logs, type \"reset\" into the box here %1$s and click %2$s"
468
  msgstr ""
469
 
470
- #: lib/interface.php:641
471
  msgid "Last 30 days"
472
  msgstr ""
473
 
474
- #: lib/interface.php:642
475
  msgid "Forever"
476
  msgstr ""
477
 
478
- #: lib/interface.php:650
479
  msgid "When"
480
  msgstr ""
481
 
482
- #: lib/interface.php:650
483
  msgid "Searches"
484
  msgstr ""
485
 
486
- #: lib/interface.php:707 lib/log.php:163
487
  msgid "Query"
488
  msgstr ""
489
 
490
- #: lib/interface.php:707
491
  msgid "Hits"
492
  msgstr ""
493
 
494
- #: lib/interface.php:788
495
  msgid "Overview"
496
  msgstr ""
497
 
498
- #: lib/interface.php:789 lib/tabs/overview-tab.php:44
499
  msgid "Indexing"
500
  msgstr ""
501
 
502
- #: lib/interface.php:790
503
  msgid "Attachments"
504
  msgstr ""
505
 
506
- #: lib/interface.php:792
507
  msgid "Logging"
508
  msgstr ""
509
 
510
- #: lib/interface.php:793 lib/tabs/overview-tab.php:60
511
  msgid "Excerpts and highlights"
512
  msgstr ""
513
 
514
- #: lib/interface.php:794 lib/tabs/synonyms-tab.php:25
515
  #: lib/tabs/synonyms-tab.php:30
516
  msgid "Synonyms"
517
  msgstr ""
518
 
519
- #: lib/interface.php:795 lib/tabs/stopwords-tab.php:18
520
  msgid "Stopwords"
521
  msgstr ""
522
 
523
- #: lib/interface.php:796 lib/tabs/redirects-tab.php:18
524
  msgid "Redirects"
525
  msgstr ""
526
 
527
- #: lib/interface.php:798
528
  msgid "Related"
529
  msgstr ""
530
 
531
- #: lib/interface.php:799
532
  msgid "Import / Export options"
533
  msgstr ""
534
 
535
- #: lib/interface.php:918
536
  msgid "Click OK to copy Relevanssi options to all subsites"
537
  msgstr ""
538
 
539
- #: lib/interface.php:919
540
  msgid "Are you sure you want to remove all stopwords?"
541
  msgstr ""
542
 
543
- #: lib/interface.php:920
544
  msgid "Wiping out the index..."
545
  msgstr ""
546
 
547
- #: lib/interface.php:921
548
  msgid "Done."
549
  msgstr ""
550
 
551
- #: lib/interface.php:922
552
  msgid "Indexing users..."
553
  msgstr ""
554
 
555
- #: lib/interface.php:923
556
  msgid "Indexing the following taxonomies:"
557
  msgstr ""
558
 
559
- #: lib/interface.php:924
560
  msgid "Indexing attachments..."
561
  msgstr ""
562
 
563
- #: lib/interface.php:925
564
  msgid "Counting posts..."
565
  msgstr ""
566
 
567
- #: lib/interface.php:926
568
  msgid "Counting taxonomy terms..."
569
  msgstr ""
570
 
571
- #: lib/interface.php:927
572
  msgid "Counting users..."
573
  msgstr ""
574
 
575
- #: lib/interface.php:928
576
  msgid "Counting attachments..."
577
  msgstr ""
578
 
579
- #: lib/interface.php:929
580
  msgid "posts found."
581
  msgstr ""
582
 
583
- #: lib/interface.php:930
584
  msgid "taxonomy terms found."
585
  msgstr ""
586
 
587
- #: lib/interface.php:931
588
  msgid "users found."
589
  msgstr ""
590
 
591
- #: lib/interface.php:932
592
  msgid "attachments found."
593
  msgstr ""
594
 
595
- #: lib/interface.php:933
596
  msgid "Taxonomy term indexing is disabled."
597
  msgstr ""
598
 
599
- #: lib/interface.php:934
600
  msgid "User indexing is disabled."
601
  msgstr ""
602
 
603
- #: lib/interface.php:935
604
  msgid "Indexing complete."
605
  msgstr ""
606
 
607
- #: lib/interface.php:936
608
  msgid "posts excluded."
609
  msgstr ""
610
 
611
- #: lib/interface.php:937
612
  msgid "Settings have changed, please save the options before indexing."
613
  msgstr ""
614
 
615
- #: lib/interface.php:938
616
  msgid "Reload the page to refresh the state of the index."
617
  msgstr ""
618
 
619
- #: lib/interface.php:939
620
  msgid "Are you sure you want to delete all attachment content from the index?"
621
  msgstr ""
622
 
623
- #: lib/interface.php:940
624
  msgid "Relevanssi attachment data wiped clean."
625
  msgstr ""
626
 
627
- #: lib/interface.php:941
628
  msgid "hour"
629
  msgstr ""
630
 
631
- #: lib/interface.php:942
632
  msgid "hours"
633
  msgstr ""
634
 
635
- #: lib/interface.php:943
636
  msgid "about"
637
  msgstr ""
638
 
639
- #: lib/interface.php:944
640
  msgid "about an hour"
641
  msgstr ""
642
 
643
- #: lib/interface.php:945
644
  msgid "about an hour and a half"
645
  msgstr ""
646
 
647
- #: lib/interface.php:946
648
  msgid "minute"
649
  msgstr ""
650
 
651
- #: lib/interface.php:947
652
  msgid "minutes"
653
  msgstr ""
654
 
655
- #: lib/interface.php:948
656
  msgid "less than a minute"
657
  msgstr ""
658
 
659
- #: lib/interface.php:949
660
  msgid "we're done!"
661
  msgstr ""
662
 
663
- #: lib/interface.php:981
664
  msgid "Tag weight"
665
  msgstr ""
666
 
667
- #: lib/interface.php:989
668
  msgid "Category weight"
669
  msgstr ""
670
 
671
- #: lib/log.php:156
672
  msgid "Logged seaches"
673
  msgstr ""
674
 
675
- #: lib/log.php:159
676
  msgid "Time"
677
  msgstr ""
678
 
679
- #: lib/log.php:167
680
  msgid "Hits found"
681
  msgstr ""
682
 
683
- #: lib/log.php:171
684
  msgid "IP address"
685
  msgstr ""
686
 
@@ -725,35 +754,39 @@ msgstr ""
725
  msgid "Relevanssi Search Logs"
726
  msgstr ""
727
 
728
- #: lib/stopwords.php:99
 
 
 
 
729
  #, php-format
730
  msgid "Successfully added %1$d/%2$d terms to stopwords!"
731
  msgstr ""
732
 
733
- #: lib/stopwords.php:110
734
  #, php-format
735
  msgid "Term '%s' added to stopwords!"
736
  msgstr ""
737
 
738
- #: lib/stopwords.php:113
739
  #, php-format
740
  msgid "Couldn't add term '%s' to stopwords!"
741
  msgstr ""
742
 
743
- #: lib/stopwords.php:165
744
  msgid "All stopwords removed! Remember to re-index."
745
  msgstr ""
746
 
747
- #: lib/stopwords.php:167
748
  msgid "There was a problem, and stopwords couldn't be removed."
749
  msgstr ""
750
 
751
- #: lib/stopwords.php:190
752
  #, php-format
753
  msgid "Term '%s' removed from stopwords! Re-index to get it back to index."
754
  msgstr ""
755
 
756
- #: lib/stopwords.php:196
757
  #, php-format
758
  msgid "Couldn't remove term '%s' from stopwords!"
759
  msgstr ""
@@ -1421,20 +1454,26 @@ msgstr ""
1421
  msgid "How many days of logs to keep in the database."
1422
  msgstr ""
1423
 
1424
- #: lib/tabs/logging-tab.php:89
 
 
 
 
 
 
1425
  #, php-format
1426
  msgid " Set to %d for no trimming."
1427
  msgstr ""
1428
 
1429
- #: lib/tabs/logging-tab.php:95
1430
  msgid "Export logs"
1431
  msgstr ""
1432
 
1433
- #: lib/tabs/logging-tab.php:98
1434
  msgid "Export the log as a CSV file"
1435
  msgstr ""
1436
 
1437
- #: lib/tabs/logging-tab.php:99
1438
  msgid "Push the button to export the search log as a CSV file."
1439
  msgstr ""
1440
 
@@ -1747,7 +1786,7 @@ msgstr ""
1747
  msgid "Whole words"
1748
  msgstr ""
1749
 
1750
- #: lib/tabs/searching-tab.php:120
1751
  msgid "Partial words"
1752
  msgstr ""
1753
 
@@ -1755,13 +1794,24 @@ msgstr ""
1755
  msgid "Partial words if no hits for whole words"
1756
  msgstr ""
1757
 
1758
- #: lib/tabs/searching-tab.php:123
 
 
 
 
 
 
 
 
 
 
 
1759
  msgid ""
1760
  "Whole words means Relevanssi only finds posts that include the whole search "
1761
  "term."
1762
  msgstr ""
1763
 
1764
- #: lib/tabs/searching-tab.php:124
1765
  msgid ""
1766
  "Partial words also includes cases where the word in the index begins or ends "
1767
  "with the search term (searching for 'ana' will match 'anaconda' or 'banana', "
@@ -1769,46 +1819,46 @@ msgid ""
1769
  "words."
1770
  msgstr ""
1771
 
1772
- #: lib/tabs/searching-tab.php:129
1773
  msgid "Weights"
1774
  msgstr ""
1775
 
1776
- #: lib/tabs/searching-tab.php:132
1777
  msgid ""
1778
  "All the weights in the table are multipliers. To increase the weight of an "
1779
  "element, use a higher number. To make an element less significant, use a "
1780
  "number lower than 1."
1781
  msgstr ""
1782
 
1783
- #: lib/tabs/searching-tab.php:136
1784
  msgid "Element"
1785
  msgstr ""
1786
 
1787
- #: lib/tabs/searching-tab.php:137
1788
  msgid "Weight"
1789
  msgstr ""
1790
 
1791
- #: lib/tabs/searching-tab.php:142
1792
  msgid "Content"
1793
  msgstr ""
1794
 
1795
- #: lib/tabs/searching-tab.php:150
1796
  msgid "Titles"
1797
  msgstr ""
1798
 
1799
- #: lib/tabs/searching-tab.php:163
1800
  msgid "Comment text"
1801
  msgstr ""
1802
 
1803
- #: lib/tabs/searching-tab.php:192
1804
  msgid "Boost exact matches"
1805
  msgstr ""
1806
 
1807
- #: lib/tabs/searching-tab.php:196 lib/tabs/searching-tab.php:199
1808
  msgid "Give boost to exact matches."
1809
  msgstr ""
1810
 
1811
- #: lib/tabs/searching-tab.php:203
1812
  #, php-format
1813
  msgid ""
1814
  "If you enable this option, matches where the search query appears in title "
@@ -1816,144 +1866,156 @@ msgid ""
1816
  "use the %s filter hook. See Help for more details."
1817
  msgstr ""
1818
 
1819
- #: lib/tabs/searching-tab.php:211
1820
  msgid "WPML"
1821
  msgstr ""
1822
 
1823
- #: lib/tabs/searching-tab.php:215 lib/tabs/searching-tab.php:218
1824
  msgid "Limit results to current language."
1825
  msgstr ""
1826
 
1827
- #: lib/tabs/searching-tab.php:221
1828
  msgid ""
1829
  "Enabling this option will restrict the results to the currently active "
1830
  "language. If the option is disabled, results will include posts in all "
1831
  "languages."
1832
  msgstr ""
1833
 
1834
- #: lib/tabs/searching-tab.php:228
1835
  msgid "Polylang"
1836
  msgstr ""
1837
 
1838
- #: lib/tabs/searching-tab.php:232 lib/tabs/searching-tab.php:235
1839
  msgid "Allow results from all languages."
1840
  msgstr ""
1841
 
1842
- #: lib/tabs/searching-tab.php:238
1843
  msgid ""
1844
  "By default Polylang restricts the search to the current language. Enabling "
1845
  "this option will lift this restriction."
1846
  msgstr ""
1847
 
1848
- #: lib/tabs/searching-tab.php:248 lib/tabs/searching-tab.php:251
1849
  msgid "Use Relevanssi for admin searches."
1850
  msgstr ""
1851
 
1852
- #: lib/tabs/searching-tab.php:254
1853
  msgid ""
1854
  "If checked, Relevanssi will be used for searches in the admin interface. The "
1855
  "page search doesn't use Relevanssi, because WordPress works like that."
1856
  msgstr ""
1857
 
1858
- #: lib/tabs/searching-tab.php:260
1859
  #, php-format
1860
  msgid "Respect %s"
1861
  msgstr ""
1862
 
1863
- #: lib/tabs/searching-tab.php:264
1864
  msgid "Respect exclude_from_search for custom post types"
1865
  msgstr ""
1866
 
1867
- #: lib/tabs/searching-tab.php:268
1868
  #, php-format
1869
  msgid "Respect %s for custom post types"
1870
  msgstr ""
1871
 
1872
- #: lib/tabs/searching-tab.php:270
1873
  msgid ""
1874
  "If checked, Relevanssi won't display posts of custom post types that have "
1875
  "'exclude_from_search' set to true."
1876
  msgstr ""
1877
 
1878
- #: lib/tabs/searching-tab.php:282
1879
  msgid ""
1880
  "You probably should uncheck this option, because you've set Relevanssi to "
1881
  "index the following non-public post types:"
1882
  msgstr ""
1883
 
1884
- #: lib/tabs/searching-tab.php:295
1885
  msgid "Throttle searches"
1886
  msgstr ""
1887
 
1888
- #: lib/tabs/searching-tab.php:305
1889
  msgid "Throttling the search does not work when sorting the posts by date."
1890
  msgstr ""
1891
 
1892
- #: lib/tabs/searching-tab.php:315 lib/tabs/searching-tab.php:318
1893
  msgid "Throttle searches."
1894
  msgstr ""
1895
 
1896
- #: lib/tabs/searching-tab.php:322
1897
  msgid "Your database is so small that you don't need to enable this."
1898
  msgstr ""
1899
 
1900
- #: lib/tabs/searching-tab.php:324
1901
  msgid ""
1902
  "If this option is checked, Relevanssi will limit search results to at most "
1903
  "500 results per term. This will improve performance, but may cause some "
1904
  "relevant documents to go unfound. See Help for more details."
1905
  msgstr ""
1906
 
1907
- #: lib/tabs/searching-tab.php:330
1908
  msgid "Category restriction"
1909
  msgstr ""
1910
 
1911
- #: lib/tabs/searching-tab.php:350
1912
  msgid ""
1913
  "You can restrict search results to a category for all searches. For "
1914
  "restricting on a per-search basis and more options (eg. tag restrictions), "
1915
  "see Help."
1916
  msgstr ""
1917
 
1918
- #: lib/tabs/searching-tab.php:355
1919
  msgid "Category exclusion"
1920
  msgstr ""
1921
 
1922
- #: lib/tabs/searching-tab.php:375
1923
  msgid ""
1924
  "Posts in these categories are not included in search results. To exclude the "
1925
  "posts completely from the index, see Help."
1926
  msgstr ""
1927
 
1928
- #: lib/tabs/searching-tab.php:380
1929
  msgid "Post exclusion"
1930
  msgstr ""
1931
 
1932
- #: lib/tabs/searching-tab.php:384
1933
  msgid ""
1934
  "Enter a comma-separated list of post or page ID's to exclude those pages "
1935
  "from the search results."
1936
  msgstr ""
1937
 
1938
- #: lib/tabs/searching-tab.php:386
1939
  msgid ""
1940
  "With Relevanssi Premium, it's better to use the check box on post edit "
1941
  "pages. That will remove the posts completely from the index, and will work "
1942
  "with multisite searches unlike this setting."
1943
  msgstr ""
1944
 
1945
- #: lib/tabs/stopwords-tab.php:53
 
 
 
 
 
 
 
 
 
 
 
 
1946
  msgid ""
1947
  "Enter a word here to add it to the list of stopwords. The word will "
1948
  "automatically be removed from the index, so re-indexing is not necessary. "
1949
  "You can enter many words at the same time, separate words with commas."
1950
  msgstr ""
1951
 
1952
- #: lib/tabs/stopwords-tab.php:58
1953
  msgid "Stopword(s) to add"
1954
  msgstr ""
1955
 
1956
- #: lib/tabs/stopwords-tab.php:66
1957
  msgid ""
1958
  "Here's a list of stopwords in the database. Click a word to remove it from "
1959
  "stopwords. Removing stopwords won't automatically return them to index, so "
@@ -1961,15 +2023,15 @@ msgid ""
1961
  "back to index."
1962
  msgstr ""
1963
 
1964
- #: lib/tabs/stopwords-tab.php:71
1965
  msgid "Current stopwords"
1966
  msgstr ""
1967
 
1968
- #: lib/tabs/stopwords-tab.php:92
1969
  msgid "Exportable list of stopwords"
1970
  msgstr ""
1971
 
1972
- #: lib/tabs/stopwords-tab.php:96
1973
  msgid ""
1974
  "You can copy the list of stopwords here if you want to back up the list, "
1975
  "copy it to a different blog or otherwise need the list."
2
  msgstr ""
3
  "Project-Id-Version: Relevanssi\n"
4
  "Report-Msgid-Bugs-To: \n"
5
+ "POT-Creation-Date: 2019-05-27 11:38+0300\n"
6
  "PO-Revision-Date: \n"
7
  "Last-Translator: Mikko Saari <mikko@mikkosaari.fi>\n"
8
  "Language-Team: \n"
12
  "Content-Transfer-Encoding: 8bit\n"
13
  "X-Poedit-KeywordsList: _e;__;esc_html__;esc_html_e;_n\n"
14
  "X-Poedit-Basepath: .\n"
15
+ "X-Generator: Poedit 2.2.3\n"
16
  "X-Poedit-SearchPath-0: .\n"
17
  "X-Poedit-SearchPath-1: lib\n"
18
 
21
  msgid "Indexed %1$d post (total %2$d), processed %3$d / %4$d."
22
  msgstr ""
23
 
24
+ #: lib/admin-ajax.php:199
25
  msgid "Results"
26
  msgstr ""
27
 
28
+ #: lib/admin-ajax.php:201
29
  #, php-format
30
  msgid "Found a total of %1$d posts, showing %2$d posts from offset %3$s."
31
  msgstr ""
32
 
33
+ #: lib/admin-ajax.php:203
34
+ msgid "Previous page"
35
+ msgstr ""
36
+
37
+ #: lib/admin-ajax.php:206
38
+ msgid "Next page"
39
+ msgstr ""
40
+
41
+ #: lib/admin-ajax.php:210
42
  msgid "Score:"
43
  msgstr ""
44
 
45
+ #: lib/admin-ajax.php:232
46
+ msgid "View"
47
+ msgstr ""
48
+
49
+ #: lib/admin-ajax.php:233
50
+ msgid "Edit"
51
+ msgstr ""
52
+
53
+ #: lib/admin-ajax.php:276
54
  msgid "Query variables"
55
  msgstr ""
56
 
57
+ #: lib/admin-ajax.php:322
58
  msgid "Filters"
59
  msgstr ""
60
 
61
+ #: lib/admin-ajax.php:323
62
  msgid "show"
63
  msgstr ""
64
 
65
+ #: lib/admin-ajax.php:324
66
  msgid "hide"
67
  msgstr ""
68
 
69
+ #: lib/common.php:1598
70
  msgid "25 most common words in the index"
71
  msgstr ""
72
 
73
+ #: lib/common.php:1599
74
  msgid ""
75
  "These words are excellent stopword material. A word that appears in most of "
76
  "the posts in the database is quite pointless when searching. This is also an "
80
  "necessary."
81
  msgstr ""
82
 
83
+ #: lib/common.php:1605
84
  msgid "Stopword Candidates"
85
  msgstr ""
86
 
87
+ #: lib/common.php:1610
88
  msgid "Add to stopwords"
89
  msgstr ""
90
 
91
+ #: lib/common.php:1613
92
+ msgid "Add to content stopwords"
93
+ msgstr ""
94
+
95
+ #: lib/contextual-help.php:24 lib/interface.php:810
96
  #: lib/tabs/overview-tab.php:56
97
  msgid "Searching"
98
  msgstr ""
318
  "search to categories 10, 14 and 17, you can use %2$s and so on."
319
  msgstr ""
320
 
321
+ #: lib/contextual-help.php:134
322
+ #, php-format
323
+ msgid ""
324
+ "You can use the %1$s parameter to add a taxonomy dropdown to the search "
325
+ "form. Just use the name of the taxonomy, like %2$s. This works best with "
326
+ "hierarchical taxonomies like categories with relatively few options "
327
+ "available."
328
  msgstr ""
329
 
330
  #: lib/contextual-help.php:139
331
+ msgid "WooCommerce"
332
+ msgstr ""
333
+
334
+ #: lib/contextual-help.php:141
335
  msgid ""
336
  "If your SKUs include hyphens or other punctuation, do note that Relevanssi "
337
  "replaces most punctuation with spaces. That's going to cause issues with SKU "
338
  "searches."
339
  msgstr ""
340
 
341
+ #: lib/contextual-help.php:143
342
  #, php-format
343
  msgid ""
344
  "For more details how to fix that issue, see <a href='%s'>WooCommerce tips in "
345
  "Relevanssi user manual</a>."
346
  msgstr ""
347
 
348
+ #: lib/contextual-help.php:144
349
  msgid ""
350
  "If you don't want to index products that are out of stock, excluded from the "
351
  "catalog or excluded from the search, there's a product visibility filtering "
352
  "method that is described in the user manual (see link above)."
353
  msgstr ""
354
 
355
+ #: lib/contextual-help.php:149
356
  msgid "Exact match bonus"
357
  msgstr ""
358
 
359
+ #: lib/contextual-help.php:152
360
  #, php-format
361
  msgid ""
362
  "To adjust the amount of the exact match bonus, you can use the %s filter "
363
  "hook. It works like this:"
364
  msgstr ""
365
 
366
+ #: lib/contextual-help.php:158
367
  #, php-format
368
  msgid "The default values are %1$s for titles and %2$s for content."
369
  msgstr ""
370
 
371
+ #: lib/contextual-help.php:161
372
  msgid "For more information:"
373
  msgstr ""
374
 
375
+ #: lib/contextual-help.php:162
376
  msgid "Plugin knowledge base"
377
  msgstr ""
378
 
379
+ #: lib/contextual-help.php:163
380
  msgid "WordPress.org forum"
381
  msgstr ""
382
 
405
  "extension."
406
  msgstr ""
407
 
408
+ #: lib/init.php:190 lib/init.php:191 lib/interface.php:472
409
  #: lib/tabs/logging-tab.php:47
410
  msgid "User searches"
411
  msgstr ""
412
 
413
+ #: lib/init.php:202 lib/init.php:203 lib/tabs/searching-tab.php:248
414
  msgid "Admin search"
415
  msgstr ""
416
 
417
+ #: lib/init.php:394
418
  msgid "Settings"
419
  msgstr ""
420
 
421
+ #: lib/init.php:397
422
  msgid "Go Premium!"
423
  msgstr ""
424
 
430
  msgid "Relevanssi Premium Search Options"
431
  msgstr ""
432
 
433
+ #: lib/interface.php:474
434
  msgid "Relevanssi User Searches"
435
  msgstr ""
436
 
437
+ #: lib/interface.php:497
438
  msgid "Enable query logging to see stats here."
439
  msgstr ""
440
 
441
+ #: lib/interface.php:509
442
  msgid "Admin Search"
443
  msgstr ""
444
 
445
+ #: lib/interface.php:539
446
  msgid "Logs clear!"
447
  msgstr ""
448
 
449
+ #: lib/interface.php:541
450
  msgid "Clearing the logs failed."
451
  msgstr ""
452
 
453
+ #: lib/interface.php:575
454
  msgid "Total Searches"
455
  msgstr ""
456
 
457
+ #: lib/interface.php:577
458
  msgid "Totals"
459
  msgstr ""
460
 
461
+ #: lib/interface.php:581
462
  msgid "Common Queries"
463
  msgstr ""
464
 
465
+ #: lib/interface.php:591
466
  #, php-format
467
  msgid ""
468
  "Here you can see the %d most common user search queries, how many times "
469
  "those queries were made and how many results were found for those queries."
470
  msgstr ""
471
 
472
+ #: lib/interface.php:595 lib/interface.php:617 lib/interface.php:658
473
  msgid "Today and yesterday"
474
  msgstr ""
475
 
476
+ #: lib/interface.php:598 lib/interface.php:604 lib/interface.php:609
477
+ #: lib/interface.php:626
478
  #, php-format
479
  msgid "Last %d days"
480
  msgstr ""
481
 
482
+ #: lib/interface.php:614
483
  msgid "Unsuccessful Queries"
484
  msgstr ""
485
 
486
+ #: lib/interface.php:621 lib/interface.php:659
487
  msgid "Last 7 days"
488
  msgstr ""
489
 
490
+ #: lib/interface.php:632
491
  msgid "Reset Logs"
492
  msgstr ""
493
 
494
+ #: lib/interface.php:636
495
  #, php-format
496
  msgid "To reset the logs, type \"reset\" into the box here %1$s and click %2$s"
497
  msgstr ""
498
 
499
+ #: lib/interface.php:660
500
  msgid "Last 30 days"
501
  msgstr ""
502
 
503
+ #: lib/interface.php:661
504
  msgid "Forever"
505
  msgstr ""
506
 
507
+ #: lib/interface.php:669
508
  msgid "When"
509
  msgstr ""
510
 
511
+ #: lib/interface.php:669
512
  msgid "Searches"
513
  msgstr ""
514
 
515
+ #: lib/interface.php:726 lib/log.php:167
516
  msgid "Query"
517
  msgstr ""
518
 
519
+ #: lib/interface.php:726
520
  msgid "Hits"
521
  msgstr ""
522
 
523
+ #: lib/interface.php:807
524
  msgid "Overview"
525
  msgstr ""
526
 
527
+ #: lib/interface.php:808 lib/tabs/overview-tab.php:44
528
  msgid "Indexing"
529
  msgstr ""
530
 
531
+ #: lib/interface.php:809
532
  msgid "Attachments"
533
  msgstr ""
534
 
535
+ #: lib/interface.php:811
536
  msgid "Logging"
537
  msgstr ""
538
 
539
+ #: lib/interface.php:812 lib/tabs/overview-tab.php:60
540
  msgid "Excerpts and highlights"
541
  msgstr ""
542
 
543
+ #: lib/interface.php:813 lib/tabs/synonyms-tab.php:25
544
  #: lib/tabs/synonyms-tab.php:30
545
  msgid "Synonyms"
546
  msgstr ""
547
 
548
+ #: lib/interface.php:814 lib/tabs/stopwords-tab.php:18
549
  msgid "Stopwords"
550
  msgstr ""
551
 
552
+ #: lib/interface.php:815 lib/tabs/redirects-tab.php:18
553
  msgid "Redirects"
554
  msgstr ""
555
 
556
+ #: lib/interface.php:817
557
  msgid "Related"
558
  msgstr ""
559
 
560
+ #: lib/interface.php:818
561
  msgid "Import / Export options"
562
  msgstr ""
563
 
564
+ #: lib/interface.php:937
565
  msgid "Click OK to copy Relevanssi options to all subsites"
566
  msgstr ""
567
 
568
+ #: lib/interface.php:938
569
  msgid "Are you sure you want to remove all stopwords?"
570
  msgstr ""
571
 
572
+ #: lib/interface.php:939
573
  msgid "Wiping out the index..."
574
  msgstr ""
575
 
576
+ #: lib/interface.php:940
577
  msgid "Done."
578
  msgstr ""
579
 
580
+ #: lib/interface.php:941
581
  msgid "Indexing users..."
582
  msgstr ""
583
 
584
+ #: lib/interface.php:942
585
  msgid "Indexing the following taxonomies:"
586
  msgstr ""
587
 
588
+ #: lib/interface.php:943
589
  msgid "Indexing attachments..."
590
  msgstr ""
591
 
592
+ #: lib/interface.php:944
593
  msgid "Counting posts..."
594
  msgstr ""
595
 
596
+ #: lib/interface.php:945
597
  msgid "Counting taxonomy terms..."
598
  msgstr ""
599
 
600
+ #: lib/interface.php:946
601
  msgid "Counting users..."
602
  msgstr ""
603
 
604
+ #: lib/interface.php:947
605
  msgid "Counting attachments..."
606
  msgstr ""
607
 
608
+ #: lib/interface.php:948
609
  msgid "posts found."
610
  msgstr ""
611
 
612
+ #: lib/interface.php:949
613
  msgid "taxonomy terms found."
614
  msgstr ""
615
 
616
+ #: lib/interface.php:950
617
  msgid "users found."
618
  msgstr ""
619
 
620
+ #: lib/interface.php:951
621
  msgid "attachments found."
622
  msgstr ""
623
 
624
+ #: lib/interface.php:952
625
  msgid "Taxonomy term indexing is disabled."
626
  msgstr ""
627
 
628
+ #: lib/interface.php:953
629
  msgid "User indexing is disabled."
630
  msgstr ""
631
 
632
+ #: lib/interface.php:954
633
  msgid "Indexing complete."
634
  msgstr ""
635
 
636
+ #: lib/interface.php:955
637
  msgid "posts excluded."
638
  msgstr ""
639
 
640
+ #: lib/interface.php:956
641
  msgid "Settings have changed, please save the options before indexing."
642
  msgstr ""
643
 
644
+ #: lib/interface.php:957
645
  msgid "Reload the page to refresh the state of the index."
646
  msgstr ""
647
 
648
+ #: lib/interface.php:958
649
  msgid "Are you sure you want to delete all attachment content from the index?"
650
  msgstr ""
651
 
652
+ #: lib/interface.php:959
653
  msgid "Relevanssi attachment data wiped clean."
654
  msgstr ""
655
 
656
+ #: lib/interface.php:960
657
  msgid "hour"
658
  msgstr ""
659
 
660
+ #: lib/interface.php:961
661
  msgid "hours"
662
  msgstr ""
663
 
664
+ #: lib/interface.php:962
665
  msgid "about"
666
  msgstr ""
667
 
668
+ #: lib/interface.php:963
669
  msgid "about an hour"
670
  msgstr ""
671
 
672
+ #: lib/interface.php:964
673
  msgid "about an hour and a half"
674
  msgstr ""
675
 
676
+ #: lib/interface.php:965
677
  msgid "minute"
678
  msgstr ""
679
 
680
+ #: lib/interface.php:966
681
  msgid "minutes"
682
  msgstr ""
683
 
684
+ #: lib/interface.php:967
685
  msgid "less than a minute"
686
  msgstr ""
687
 
688
+ #: lib/interface.php:968
689
  msgid "we're done!"
690
  msgstr ""
691
 
692
+ #: lib/interface.php:1000
693
  msgid "Tag weight"
694
  msgstr ""
695
 
696
+ #: lib/interface.php:1008
697
  msgid "Category weight"
698
  msgstr ""
699
 
700
+ #: lib/log.php:160
701
  msgid "Logged seaches"
702
  msgstr ""
703
 
704
+ #: lib/log.php:163
705
  msgid "Time"
706
  msgstr ""
707
 
708
+ #: lib/log.php:171
709
  msgid "Hits found"
710
  msgstr ""
711
 
712
+ #: lib/log.php:175
713
  msgid "IP address"
714
  msgstr ""
715
 
754
  msgid "Relevanssi Search Logs"
755
  msgstr ""
756
 
757
+ #: lib/shortcodes.php:128
758
+ msgid "None"
759
+ msgstr ""
760
+
761
+ #: lib/stopwords.php:96
762
  #, php-format
763
  msgid "Successfully added %1$d/%2$d terms to stopwords!"
764
  msgstr ""
765
 
766
+ #: lib/stopwords.php:107
767
  #, php-format
768
  msgid "Term '%s' added to stopwords!"
769
  msgstr ""
770
 
771
+ #: lib/stopwords.php:110
772
  #, php-format
773
  msgid "Couldn't add term '%s' to stopwords!"
774
  msgstr ""
775
 
776
+ #: lib/stopwords.php:162
777
  msgid "All stopwords removed! Remember to re-index."
778
  msgstr ""
779
 
780
+ #: lib/stopwords.php:164
781
  msgid "There was a problem, and stopwords couldn't be removed."
782
  msgstr ""
783
 
784
+ #: lib/stopwords.php:187
785
  #, php-format
786
  msgid "Term '%s' removed from stopwords! Re-index to get it back to index."
787
  msgstr ""
788
 
789
+ #: lib/stopwords.php:193
790
  #, php-format
791
  msgid "Couldn't remove term '%s' from stopwords!"
792
  msgstr ""
1454
  msgid "How many days of logs to keep in the database."
1455
  msgstr ""
1456
 
1457
+ #: lib/tabs/logging-tab.php:91
1458
+ msgid ""
1459
+ "Big log database table will eventually start to slow down the search, so "
1460
+ "it's a good idea to use some level of automatic log trimming."
1461
+ msgstr ""
1462
+
1463
+ #: lib/tabs/logging-tab.php:96
1464
  #, php-format
1465
  msgid " Set to %d for no trimming."
1466
  msgstr ""
1467
 
1468
+ #: lib/tabs/logging-tab.php:105
1469
  msgid "Export logs"
1470
  msgstr ""
1471
 
1472
+ #: lib/tabs/logging-tab.php:108
1473
  msgid "Export the log as a CSV file"
1474
  msgstr ""
1475
 
1476
+ #: lib/tabs/logging-tab.php:109
1477
  msgid "Push the button to export the search log as a CSV file."
1478
  msgstr ""
1479
 
1786
  msgid "Whole words"
1787
  msgstr ""
1788
 
1789
+ #: lib/tabs/searching-tab.php:120 lib/tabs/searching-tab.php:125
1790
  msgid "Partial words"
1791
  msgstr ""
1792
 
1794
  msgid "Partial words if no hits for whole words"
1795
  msgstr ""
1796
 
1797
+ #: lib/tabs/searching-tab.php:125
1798
+ #, php-format
1799
+ msgid ""
1800
+ "Choosing the \"%1$s\" option may lead to unexpected results. Most of the "
1801
+ "time the \"%2$s\" option is the better choice."
1802
+ msgstr ""
1803
+
1804
+ #: lib/tabs/searching-tab.php:125
1805
+ msgid "Partial words if not hits for whole words"
1806
+ msgstr ""
1807
+
1808
+ #: lib/tabs/searching-tab.php:127
1809
  msgid ""
1810
  "Whole words means Relevanssi only finds posts that include the whole search "
1811
  "term."
1812
  msgstr ""
1813
 
1814
+ #: lib/tabs/searching-tab.php:128
1815
  msgid ""
1816
  "Partial words also includes cases where the word in the index begins or ends "
1817
  "with the search term (searching for 'ana' will match 'anaconda' or 'banana', "
1819
  "words."
1820
  msgstr ""
1821
 
1822
+ #: lib/tabs/searching-tab.php:133
1823
  msgid "Weights"
1824
  msgstr ""
1825
 
1826
+ #: lib/tabs/searching-tab.php:136
1827
  msgid ""
1828
  "All the weights in the table are multipliers. To increase the weight of an "
1829
  "element, use a higher number. To make an element less significant, use a "
1830
  "number lower than 1."
1831
  msgstr ""
1832
 
1833
+ #: lib/tabs/searching-tab.php:140
1834
  msgid "Element"
1835
  msgstr ""
1836
 
1837
+ #: lib/tabs/searching-tab.php:141
1838
  msgid "Weight"
1839
  msgstr ""
1840
 
1841
+ #: lib/tabs/searching-tab.php:146
1842
  msgid "Content"
1843
  msgstr ""
1844
 
1845
+ #: lib/tabs/searching-tab.php:154
1846
  msgid "Titles"
1847
  msgstr ""
1848
 
1849
+ #: lib/tabs/searching-tab.php:167
1850
  msgid "Comment text"
1851
  msgstr ""
1852
 
1853
+ #: lib/tabs/searching-tab.php:196
1854
  msgid "Boost exact matches"
1855
  msgstr ""
1856
 
1857
+ #: lib/tabs/searching-tab.php:200 lib/tabs/searching-tab.php:203
1858
  msgid "Give boost to exact matches."
1859
  msgstr ""
1860
 
1861
+ #: lib/tabs/searching-tab.php:207
1862
  #, php-format
1863
  msgid ""
1864
  "If you enable this option, matches where the search query appears in title "
1866
  "use the %s filter hook. See Help for more details."
1867
  msgstr ""
1868
 
1869
+ #: lib/tabs/searching-tab.php:215
1870
  msgid "WPML"
1871
  msgstr ""
1872
 
1873
+ #: lib/tabs/searching-tab.php:219 lib/tabs/searching-tab.php:222
1874
  msgid "Limit results to current language."
1875
  msgstr ""
1876
 
1877
+ #: lib/tabs/searching-tab.php:225
1878
  msgid ""
1879
  "Enabling this option will restrict the results to the currently active "
1880
  "language. If the option is disabled, results will include posts in all "
1881
  "languages."
1882
  msgstr ""
1883
 
1884
+ #: lib/tabs/searching-tab.php:232
1885
  msgid "Polylang"
1886
  msgstr ""
1887
 
1888
+ #: lib/tabs/searching-tab.php:236 lib/tabs/searching-tab.php:239
1889
  msgid "Allow results from all languages."
1890
  msgstr ""
1891
 
1892
+ #: lib/tabs/searching-tab.php:242
1893
  msgid ""
1894
  "By default Polylang restricts the search to the current language. Enabling "
1895
  "this option will lift this restriction."
1896
  msgstr ""
1897
 
1898
+ #: lib/tabs/searching-tab.php:252 lib/tabs/searching-tab.php:255
1899
  msgid "Use Relevanssi for admin searches."
1900
  msgstr ""
1901
 
1902
+ #: lib/tabs/searching-tab.php:258
1903
  msgid ""
1904
  "If checked, Relevanssi will be used for searches in the admin interface. The "
1905
  "page search doesn't use Relevanssi, because WordPress works like that."
1906
  msgstr ""
1907
 
1908
+ #: lib/tabs/searching-tab.php:264
1909
  #, php-format
1910
  msgid "Respect %s"
1911
  msgstr ""
1912
 
1913
+ #: lib/tabs/searching-tab.php:268
1914
  msgid "Respect exclude_from_search for custom post types"
1915
  msgstr ""
1916
 
1917
+ #: lib/tabs/searching-tab.php:272
1918
  #, php-format
1919
  msgid "Respect %s for custom post types"
1920
  msgstr ""
1921
 
1922
+ #: lib/tabs/searching-tab.php:274
1923
  msgid ""
1924
  "If checked, Relevanssi won't display posts of custom post types that have "
1925
  "'exclude_from_search' set to true."
1926
  msgstr ""
1927
 
1928
+ #: lib/tabs/searching-tab.php:286
1929
  msgid ""
1930
  "You probably should uncheck this option, because you've set Relevanssi to "
1931
  "index the following non-public post types:"
1932
  msgstr ""
1933
 
1934
+ #: lib/tabs/searching-tab.php:299
1935
  msgid "Throttle searches"
1936
  msgstr ""
1937
 
1938
+ #: lib/tabs/searching-tab.php:309
1939
  msgid "Throttling the search does not work when sorting the posts by date."
1940
  msgstr ""
1941
 
1942
+ #: lib/tabs/searching-tab.php:319 lib/tabs/searching-tab.php:322
1943
  msgid "Throttle searches."
1944
  msgstr ""
1945
 
1946
+ #: lib/tabs/searching-tab.php:326
1947
  msgid "Your database is so small that you don't need to enable this."
1948
  msgstr ""
1949
 
1950
+ #: lib/tabs/searching-tab.php:328
1951
  msgid ""
1952
  "If this option is checked, Relevanssi will limit search results to at most "
1953
  "500 results per term. This will improve performance, but may cause some "
1954
  "relevant documents to go unfound. See Help for more details."
1955
  msgstr ""
1956
 
1957
+ #: lib/tabs/searching-tab.php:334
1958
  msgid "Category restriction"
1959
  msgstr ""
1960
 
1961
+ #: lib/tabs/searching-tab.php:354
1962
  msgid ""
1963
  "You can restrict search results to a category for all searches. For "
1964
  "restricting on a per-search basis and more options (eg. tag restrictions), "
1965
  "see Help."
1966
  msgstr ""
1967
 
1968
+ #: lib/tabs/searching-tab.php:359
1969
  msgid "Category exclusion"
1970
  msgstr ""
1971
 
1972
+ #: lib/tabs/searching-tab.php:379
1973
  msgid ""
1974
  "Posts in these categories are not included in search results. To exclude the "
1975
  "posts completely from the index, see Help."
1976
  msgstr ""
1977
 
1978
+ #: lib/tabs/searching-tab.php:384
1979
  msgid "Post exclusion"
1980
  msgstr ""
1981
 
1982
+ #: lib/tabs/searching-tab.php:388
1983
  msgid ""
1984
  "Enter a comma-separated list of post or page ID's to exclude those pages "
1985
  "from the search results."
1986
  msgstr ""
1987
 
1988
+ #: lib/tabs/searching-tab.php:390
1989
  msgid ""
1990
  "With Relevanssi Premium, it's better to use the check box on post edit "
1991
  "pages. That will remove the posts completely from the index, and will work "
1992
  "with multisite searches unlike this setting."
1993
  msgstr ""
1994
 
1995
+ #: lib/tabs/stopwords-tab.php:25
1996
+ msgid "Content stopwords"
1997
+ msgstr ""
1998
+
1999
+ #: lib/tabs/stopwords-tab.php:31
2000
+ msgid ""
2001
+ "Content stopwords are a premium feature where you can set stopwords that "
2002
+ "only apply to the post content. Those stopwords will still be indexed if "
2003
+ "they appear in post titles, tags, categories, custom fields or other parts "
2004
+ "of the post. To use content stopwords, you need Relevanssi Premium."
2005
+ msgstr ""
2006
+
2007
+ #: lib/tabs/stopwords-tab.php:59
2008
  msgid ""
2009
  "Enter a word here to add it to the list of stopwords. The word will "
2010
  "automatically be removed from the index, so re-indexing is not necessary. "
2011
  "You can enter many words at the same time, separate words with commas."
2012
  msgstr ""
2013
 
2014
+ #: lib/tabs/stopwords-tab.php:64
2015
  msgid "Stopword(s) to add"
2016
  msgstr ""
2017
 
2018
+ #: lib/tabs/stopwords-tab.php:72
2019
  msgid ""
2020
  "Here's a list of stopwords in the database. Click a word to remove it from "
2021
  "stopwords. Removing stopwords won't automatically return them to index, so "
2023
  "back to index."
2024
  msgstr ""
2025
 
2026
+ #: lib/tabs/stopwords-tab.php:77
2027
  msgid "Current stopwords"
2028
  msgstr ""
2029
 
2030
+ #: lib/tabs/stopwords-tab.php:98
2031
  msgid "Exportable list of stopwords"
2032
  msgstr ""
2033
 
2034
+ #: lib/tabs/stopwords-tab.php:102
2035
  msgid ""
2036
  "You can copy the list of stopwords here if you want to back up the list, "
2037
  "copy it to a different blog or otherwise need the list."