Relevanssi – A Better Search - Version 4.1

Version Description

  • New feature: You can now export the search log as a CSV file.
  • New feature: Admin Search page allows you to perform searches in WP admin using Relevanssi.
  • New filter: relevanssi_admin_search_capability can be used to adjust who sees the admin search page.
  • New filter: relevanssi_entities_inside_pre and relevanssi_entities_inside_code adjust how HTML entities are handled inside pre and code tags.
  • Numeric meta values (meta_value_num) are now sorted as numbers and not strings.
  • Pinned posts have $post->relevanssi_pinned set to 1 for debugging purposes, but you can also use this for styling the posts in the search results templates.
  • The Did you mean feature has been toned down a bit, to make the suggestions slightly less weird in some cases.
  • Post parent parameters now accept 0 as a value, making it easier to search for children of any post or posts without a parent.
  • Polylang compatibility has been improved.
  • Phrases with apostrophes inside work better.
  • The relevanssi_excerpt filter hook got a second parameter that holds the post ID.
  • Custom field sorting actually works now.
  • WP Search Suggest compatibility added.
Download this release

Release Info

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

Code changes from version 4.0.11 to 4.1

changelog.txt CHANGED
@@ -1,3 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  = 4.0.4 =
2
  * Fixed shortcode: `searchform` shortcode didn't work properly.
3
  * Setting post type or post content weight to 0 didn't work.
1
+ = 4.0.9 =
2
+ * Fixes broken tag and category indexing and searching. If you use tags and categories, rebuild the index after updating.
3
+ * Phrases were not highlighted correctly on documents. This is now fixed.
4
+ * Shortcode fix: 'wp_show_posts' shouldn't cause problems anymore.
5
+ * New filter: `relevanssi_indexing_restriction` allows filtering posts before indexing.
6
+ * New WooCommerce product visibility filtering tool makes WooCommerce product indexing faster.
7
+ * MemberPress post controls were loose and showed drafts to searchers. That is now fixed.
8
+ * Highlighting was too loose, even if matching was set to whole words.
9
+ * Highlighting now works better in cases where there's a hyphen or an apostrophe inside a word.
10
+
11
+ = 4.0.8 =
12
+ * Fixed cases where Relevanssi added an ellipsis even if the excerpt was from the start of the post.
13
+ * Highlighting now works with numeric search strings.
14
+ * Improved highlighting for accented words. Thanks to Paul Ryan.
15
+ * A surplus comma at the end of post exclusion setting won't break the search anymore.
16
+ * Fixed instructions for adjusting the throttle limit.
17
+
18
+ = 4.0.7 =
19
+ * Recent post bonus is now applied to searches.
20
+ * Exact term setting can now be disabled.
21
+ * Users of Members plugin would have drafts appear in search results. This is now fixed.
22
+
23
+ = 4.0.6 =
24
+ * Indexing bugs squashed.
25
+ * Missing tag and category weight settings returned.
26
+ * Fusion builder shortcodes are removed from excerpts.
27
+ * MemberPress post control was backwards.
28
+ * User searches page reset buttons fixed.
29
+ * WPML language filter fix.
30
+
31
  = 4.0.4 =
32
  * Fixed shortcode: `searchform` shortcode didn't work properly.
33
  * Setting post type or post content weight to 0 didn't work.
lib/admin-ajax.php CHANGED
@@ -130,3 +130,205 @@ function relevanssi_list_categories() {
130
  echo wp_json_encode( $categories );
131
  wp_die();
132
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  echo wp_json_encode( $categories );
131
  wp_die();
132
  }
133
+
134
+ /**
135
+ * Performs an admin search.
136
+ *
137
+ * Performs an admin dashboard search.
138
+ *
139
+ * @since 2.2.0
140
+ */
141
+ function relevanssi_admin_search() {
142
+ check_ajax_referer( 'relevanssi_admin_search_nonce', 'security' );
143
+
144
+ $args = array();
145
+ if ( isset( $_POST['args'] ) ) {
146
+ parse_str( $_POST['args'], $args );
147
+ }
148
+ if ( isset( $_POST['posts_per_page'] ) ) {
149
+ $posts_per_page = intval( $_POST['posts_per_page'] );
150
+ if ( $posts_per_page > 0 ) {
151
+ $args['posts_per_page'] = $posts_per_page;
152
+ }
153
+ }
154
+ if ( isset( $_POST['offset'] ) ) {
155
+ $offset = intval( $_POST['offset'] );
156
+ if ( $offset > 0 ) {
157
+ $args['offset'] = $offset;
158
+ }
159
+ }
160
+ if ( isset( $_POST['s'] ) ) {
161
+ $args['s'] = $_POST['s'];
162
+ }
163
+
164
+ $query = new WP_Query();
165
+ $query->parse_query( $args );
166
+ $query->set( 'relevanssi_admin_search', true );
167
+ relevanssi_do_query( $query );
168
+
169
+ $results = relevanssi_admin_search_debugging_info( $query );
170
+
171
+ // Take the posts array and create a string out of it.
172
+ $offset = 0;
173
+ if ( isset( $query->query_vars['offset'] ) ) {
174
+ $offset = $query->query_vars['offset'];
175
+ }
176
+ $results .= relevanssi_admin_search_format_posts( $query->posts, $query->found_posts, $offset );
177
+
178
+ echo wp_json_encode( $results );
179
+ wp_die();
180
+ }
181
+
182
+ /**
183
+ * Formats the posts for admin search.
184
+ *
185
+ * Results are presented as an ordered list of posts. The format is very basic, and
186
+ * can be modified with the 'relevanssi_admin_search_element' filter hook.
187
+ *
188
+ * @param array $posts The posts array.
189
+ * @param int $total The number of posts found in total.
190
+ * @param int $offset Offset value.
191
+ *
192
+ * @return string The formatted posts.
193
+ *
194
+ * @since 2.2.0
195
+ */
196
+ function relevanssi_admin_search_format_posts( $posts, $total, $offset ) {
197
+ $result = '<h3>' . __( 'Results', 'relevanssi' ) . '</h3>';
198
+ // Translators: %1$d is the total number of posts found, %2$d is the current search result count, %3$d is the offset.
199
+ $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>';
200
+ if ( $offset > 0 ) {
201
+ $result .= '<button type="button" id="prev_page">Previous page</button>';
202
+ }
203
+ if ( count( $posts ) + $offset < $total ) {
204
+ $result .= '<button type="button" id="next_page">Next page</button>';
205
+ }
206
+ $result .= '<ol>';
207
+
208
+ $score_label = __( 'Score:', 'relevanssi' );
209
+
210
+ foreach ( $posts as $post ) {
211
+ $blog_name = '';
212
+ if ( isset( $post->blog_id ) ) {
213
+ switch_to_blog( $post->blog_id );
214
+ $blog_name = get_bloginfo( 'name' ) . ': ';
215
+ }
216
+ $permalink = get_permalink( $post->ID );
217
+ $edit_link = get_edit_post_link( $post->ID );
218
+ $post_type = $post->post_type;
219
+ if ( isset( $post->relevanssi_link ) ) {
220
+ $permalink = $post->relevanssi_link;
221
+ }
222
+ if ( 'user' === $post->post_type ) {
223
+ $edit_link = get_edit_user_link( $post->ID );
224
+ }
225
+ if ( empty( $edit_link ) ) {
226
+ if ( isset( $post->term_id ) ) {
227
+ $edit_link = get_edit_term_link( $post->term_id, $post->post_type );
228
+ }
229
+ }
230
+ $pinned = '';
231
+ if ( isset( $post->relevanssi_pinned ) ) {
232
+ $pinned = '<strong>(pinned)</strong>';
233
+ }
234
+ $post_element = <<<EOH
235
+ <li>$blog_name <strong>$post->post_title</strong> (<a href="$permalink">View $post_type</a>) (<a href="$edit_link">Edit $post_type</a>) <br />
236
+ $post->post_excerpt<br />
237
+ $score_label $post->relevance_score $pinned</li>
238
+ EOH;
239
+ /**
240
+ * Filters the admin search results element.
241
+ *
242
+ * The post element is a <li> element. Feel free to edit the element any way you want to.
243
+ *
244
+ * @param string $post_element The post element.
245
+ */
246
+ $result .= apply_filters( 'relevanssi_admin_search_element', $post_element );
247
+ if ( isset( $post->blog_id ) ) {
248
+ restore_current_blog();
249
+ }
250
+ }
251
+ $result .= '</ol>';
252
+ return $result;
253
+ }
254
+
255
+ /**
256
+ * Shows debugging information about the search.
257
+ *
258
+ * Formats the WP_Query parameters, looks at some filter hooks and presents the
259
+ * information in an easy-to-read format.
260
+ *
261
+ * @param array $query The WP_Query object.
262
+ *
263
+ * @return string The formatted debugging information.
264
+ *
265
+ * @since 2.2.0
266
+ */
267
+ function relevanssi_admin_search_debugging_info( $query ) {
268
+ $result = '<h3>' . __( 'Query variables', 'relevanssi' ) . '</h3>';
269
+ $result .= '<ul style="list-style: disc; margin-left: 1.5em">';
270
+ foreach ( $query->query_vars as $key => $value ) {
271
+ if ( is_array( $value ) ) {
272
+ $value = implode( ', ', $value );
273
+ }
274
+ if ( empty( $value ) ) {
275
+ continue;
276
+ }
277
+ $result .= "<li>$key: $value</li>";
278
+ }
279
+ if ( ! empty( $query->tax_query ) ) {
280
+ $result .= '<li><strong>tax_query</strong>:<ul style="list-style: disc; margin-left: 1.5em">';
281
+ foreach ( $query->tax_query as $tax_query ) {
282
+ foreach ( $tax_query as $key => $value ) {
283
+ if ( is_array( $value ) ) {
284
+ $value = implode( ', ', $value );
285
+ }
286
+ $result .= "<li>$key: $value</li>";
287
+ }
288
+ }
289
+ $result .= '</ul></li>';
290
+ }
291
+ $result .= '</ul>';
292
+
293
+ global $wp_filter;
294
+
295
+ $filters = array(
296
+ 'relevanssi_search_ok',
297
+ 'relevanssi_modify_wp_query',
298
+ 'relevanssi_search_filters',
299
+ 'relevanssi_where',
300
+ 'relevanssi_join',
301
+ 'relevanssi_fuzzy_query',
302
+ 'relevanssi_exact_match_bonus',
303
+ 'relevanssi_query_filter',
304
+ 'relevanssi_match',
305
+ 'relevanssi_post_ok',
306
+ 'relevanssi_search_again',
307
+ 'relevanssi_results',
308
+ 'relevanssi_orderby',
309
+ 'relevanssi_order',
310
+ 'relevanssi_default_tax_query_relation',
311
+ 'relevanssi_default_meta_query_relation',
312
+ 'relevanssi_hits_filter',
313
+ );
314
+
315
+ $result .= '<h3>' . __( 'Filters', 'relevanssi' ) . '</h3>';
316
+ $result .= '<button type="button" id="show_filters">' . __( 'show', 'relevanssi' ) . '</button>';
317
+ $result .= '<button type="button" id="hide_filters" style="display: none">' . __( 'hide', 'relevanssi' ) . '</button>';
318
+ $result .= '<div id="relevanssi_filter_list">';
319
+ foreach ( $filters as $filter ) {
320
+ if ( isset( $wp_filter[ $filter ] ) ) {
321
+ $result .= '<h4>' . $filter . '</h4>';
322
+ $result .= '<ul style="list-style: disc; margin-left: 1.5em">';
323
+ foreach ( $wp_filter[ $filter ] as $priority => $functions ) {
324
+ foreach ( $functions as $function ) {
325
+ $result .= "<li>$priority: " . $function['function'] . '</li>';
326
+ }
327
+ }
328
+ $result .= '</ul>';
329
+ }
330
+ }
331
+ $result .= '</div>';
332
+
333
+ return $result;
334
+ }
lib/admin_scripts.js CHANGED
@@ -53,7 +53,12 @@ jQuery(document).ready(function($){
53
  $("#relevanssi_index_taxonomies").click(function() {
54
  taxonomies.toggleClass('screen-reader-text', !this.checked);
55
  });
56
-
 
 
 
 
 
57
  var fields_content = $("#index_field_input");
58
  var fields_select = $("#relevanssi_index_fields_select");
59
  fields_select.change(function() {
@@ -307,4 +312,90 @@ function rlv_format_approximate_time(total_seconds) {
307
  if (minutes < 1) time = relevanssi.underminute;
308
 
309
  return time;
310
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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() {
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
+ $(document).on('click', '#show_filters', function(e) {
340
+ $('#relevanssi_filter_list').toggle();
341
+ $('#show_filters').toggle();
342
+ $('#hide_filters').toggle();
343
+ });
344
+
345
+ $(document).on('click', '#hide_filters', function(e) {
346
+ $('#relevanssi_filter_list').toggle();
347
+ $('#show_filters').toggle();
348
+ $('#hide_filters').toggle();
349
+ });
350
+
351
+ $(document).on('click', '#next_page', function(e) {
352
+ var results = document.getElementById("results");
353
+ e.preventDefault();
354
+ var offset = parseInt(document.getElementById('offset').innerHTML);
355
+ var posts = parseInt(document.getElementById('posts_per_page').value);
356
+ offset = offset + posts;
357
+ results.innerHTML = 'Searching...';
358
+ jQuery.ajax({
359
+ type: 'POST',
360
+ url: ajaxurl,
361
+ data: {
362
+ action: 'relevanssi_admin_search',
363
+ args: document.getElementById('args').value,
364
+ posts_per_page: document.getElementById('posts_per_page').value,
365
+ s: document.getElementById('s').value,
366
+ offset: offset,
367
+ security : nonce.searching_nonce,
368
+ },
369
+ dataType: 'json',
370
+ success: function(response) {
371
+ results.innerHTML = response;
372
+ }
373
+ });
374
+ });
375
+
376
+ $(document).on('click', '#prev_page', function(e) {
377
+ var results = document.getElementById("results");
378
+ e.preventDefault();
379
+ var offset = parseInt(document.getElementById('offset').innerHTML);
380
+ var posts = parseInt(document.getElementById('posts_per_page').value);
381
+ offset = offset - posts;
382
+ if ( offset < 0 ) offset = 0;
383
+ results.innerHTML = 'Searching...';
384
+ jQuery.ajax({
385
+ type: 'POST',
386
+ url: ajaxurl,
387
+ data: {
388
+ action: 'relevanssi_admin_search',
389
+ args: document.getElementById('args').value,
390
+ posts_per_page: document.getElementById('posts_per_page').value,
391
+ s: document.getElementById('s').value,
392
+ offset: offset,
393
+ security : nonce.searching_nonce,
394
+ },
395
+ dataType: 'json',
396
+ success: function(response) {
397
+ results.innerHTML = response;
398
+ }
399
+ });
400
+ });
401
+ });
lib/admin_styles.css CHANGED
@@ -95,4 +95,12 @@ table.form-table table.widefat th {
95
 
96
  #relevanssi-timer {
97
  display: none;
98
- }
 
 
 
 
 
 
 
 
95
 
96
  #relevanssi-timer {
97
  display: none;
98
+ }
99
+
100
+ #category_inclusion_checklist ul.children, #category_exclusion_checklist ul.children {
101
+ margin-left: 1.5em;
102
+ }
103
+
104
+ #relevanssi_filter_list {
105
+ display: none;
106
+ }
lib/common.php CHANGED
@@ -103,7 +103,6 @@ function relevanssi_show_matches( $data, $hit ) {
103
  } else {
104
  $term_hits_array = array();
105
  }
106
-
107
  $term_hits = '';
108
  $total_hits = 0;
109
  foreach ( $term_hits_array as $term => $hits ) {
@@ -197,7 +196,13 @@ function relevanssi_default_post_ok( $post_ok, $post_id ) {
197
  // WP JV Post Reading Groups.
198
  $post_ok = wp_jv_prg_user_can_see_a_post( get_current_user_id(), $post_id );
199
  }
200
-
 
 
 
 
 
 
201
  /**
202
  * Filters statuses allowed in admin searches.
203
  *
@@ -337,6 +342,7 @@ function relevanssi_recognize_phrases( $search_query ) {
337
  if ( count( $phrases ) > 0 ) {
338
  foreach ( $phrases as $phrase ) {
339
  $queries = array();
 
340
  $phrase = str_replace( '‘', '_', $phrase );
341
  $phrase = str_replace( '’', '_', $phrase );
342
  $phrase = str_replace( "'", '_', $phrase );
@@ -345,7 +351,6 @@ function relevanssi_recognize_phrases( $search_query ) {
345
  $phrase = str_replace( '“', '_', $phrase );
346
  $phrase = str_replace( '„', '_', $phrase );
347
  $phrase = str_replace( '´', '_', $phrase );
348
- $phrase = $wpdb->esc_like( $phrase );
349
  $phrase = esc_sql( $phrase );
350
  $excerpt = '';
351
  if ( 'on' === get_option( 'relevanssi_index_excerpt' ) ) {
@@ -804,7 +809,7 @@ function relevanssi_tokenize( $string, $remove_stops = true, $min_word_length =
804
  function relevanssi_get_post_status( $post_id ) {
805
  global $relevanssi_post_array;
806
  $type = substr( $post_id, 0, 2 );
807
- if ( '**' === $type || 'u_' === $type ) {
808
  // Taxonomy term or user (a Premium feature).
809
  return 'publish';
810
  }
@@ -1406,8 +1411,7 @@ function relevanssi_simple_generate_suggestion( $query ) {
1406
  break;
1407
  } else {
1408
  $lev = levenshtein( $token, $row->query );
1409
-
1410
- if ( $lev < $distance || $distance < 0 ) {
1411
  if ( $row->a > 0 ) {
1412
  $distance = $lev;
1413
  $closest = $row->query;
103
  } else {
104
  $term_hits_array = array();
105
  }
 
106
  $term_hits = '';
107
  $total_hits = 0;
108
  foreach ( $term_hits_array as $term => $hits ) {
196
  // WP JV Post Reading Groups.
197
  $post_ok = wp_jv_prg_user_can_see_a_post( get_current_user_id(), $post_id );
198
  }
199
+ if ( class_exists( 'AAM', false ) ) {
200
+ $object = AAM::api()->getUser()->getObject( 'post', $post_id );
201
+ if ( $object->has( 'frontend.read' ) ) {
202
+ // Current user is not allowed to see this post.
203
+ $post_ok = false;
204
+ }
205
+ }
206
  /**
207
  * Filters statuses allowed in admin searches.
208
  *
342
  if ( count( $phrases ) > 0 ) {
343
  foreach ( $phrases as $phrase ) {
344
  $queries = array();
345
+ $phrase = $wpdb->esc_like( $phrase );
346
  $phrase = str_replace( '‘', '_', $phrase );
347
  $phrase = str_replace( '’', '_', $phrase );
348
  $phrase = str_replace( "'", '_', $phrase );
351
  $phrase = str_replace( '“', '_', $phrase );
352
  $phrase = str_replace( '„', '_', $phrase );
353
  $phrase = str_replace( '´', '_', $phrase );
 
354
  $phrase = esc_sql( $phrase );
355
  $excerpt = '';
356
  if ( 'on' === get_option( 'relevanssi_index_excerpt' ) ) {
809
  function relevanssi_get_post_status( $post_id ) {
810
  global $relevanssi_post_array;
811
  $type = substr( $post_id, 0, 2 );
812
+ if ( '**' === $type || 'u_' === $type || 'p_' === $type ) {
813
  // Taxonomy term or user (a Premium feature).
814
  return 'publish';
815
  }
1411
  break;
1412
  } else {
1413
  $lev = levenshtein( $token, $row->query );
1414
+ if ( $lev < 3 && ( $lev < $distance || $distance < 0 ) ) {
 
1415
  if ( $row->a > 0 ) {
1416
  $distance = $lev;
1417
  $closest = $row->query;
lib/compatibility/polylang.php CHANGED
@@ -86,7 +86,7 @@ function relevanssi_polylang_where_include_terms( $where ) {
86
  $languages = get_terms( array( 'taxonomy' => 'language' ) );
87
  $language_id = 0;
88
  foreach ( $languages as $language ) {
89
- if ( $language->slug === $current_language ) {
90
  $language_id = intval( $language->term_id );
91
  break;
92
  }
86
  $languages = get_terms( array( 'taxonomy' => 'language' ) );
87
  $language_id = 0;
88
  foreach ( $languages as $language ) {
89
+ if ( ! is_wp_error( $language ) && $language instanceof WP_Term && $language->slug === $current_language ) {
90
  $language_id = intval( $language->term_id );
91
  break;
92
  }
lib/compatibility/wp-search-suggest.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/compatibility/wp-search-suggest.php
4
+ *
5
+ * WP Search Suggest compatibility features.
6
+ *
7
+ * @package Relevanssi
8
+ * @author Mikko Saari
9
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
10
+ * @see https://www.relevanssi.com/
11
+ */
12
+
13
+ add_filter( 'wpss_search_results', 'relevanssi_wpss_support', 10, 2 );
14
+ /**
15
+ * Adds Relevanssi results to WP Search Suggest dropdown.
16
+ *
17
+ * @param array $list List of post titles.
18
+ * @param object $query The WP_Query object.
19
+ *
20
+ * @return array List of post titles.
21
+ */
22
+ function relevanssi_wpss_support( $list, $query ) {
23
+ $query = relevanssi_do_query( $query );
24
+ return wp_list_pluck( $query->posts, 'post_title' );
25
+ }
lib/compatibility/wpml.php CHANGED
@@ -84,8 +84,9 @@ function relevanssi_wpml_filter( $data ) {
84
  return $data;
85
  }
86
 
87
- // add_action( 'wp_ajax_relevanssi_index_posts', 'relevanssi_wpml_remove_filters', 5 );
88
- // add_action( 'wp_ajax_relevanssi_index_taxonomies', 'relevanssi_wpml_remove_filters', 5 );
 
89
  /**
90
  * Removes WPML filters from get_term().
91
  *
84
  return $data;
85
  }
86
 
87
+ add_action( 'wp_ajax_relevanssi_index_posts', 'relevanssi_wpml_remove_filters', 5 );
88
+ add_action( 'wp_ajax_relevanssi_index_taxonomies', 'relevanssi_wpml_remove_filters', 5 );
89
+
90
  /**
91
  * Removes WPML filters from get_term().
92
  *
lib/excerpts-highlights.php CHANGED
@@ -170,9 +170,10 @@ function relevanssi_do_excerpt( $t_post, $query ) {
170
  * Filters the post excerpt generated by Relevanssi before the highlighting is
171
  * applied.
172
  *
173
- * @param string $excerpt The excerpt.
 
174
  */
175
- $excerpt = apply_filters( 'relevanssi_excerpt', $excerpt );
176
 
177
  $whole_post_excerpted = false;
178
  if ( $excerpt === $post->post_content ) {
@@ -535,7 +536,7 @@ function relevanssi_highlight_terms( $content, $query, $in_docs = false ) {
535
  }
536
  }
537
 
538
- if ( preg_match_all( '/<(style|script|object|embed|pre|code)>.*<\/(style|script|object|embed|pre|code)>/U', $content, $matches ) > 0 ) {
539
  // Remove highlights in style, object, embed, script and pre tags.
540
  foreach ( $matches as $match ) {
541
  $new_match = str_replace( $start_emp_token, '', $match );
@@ -638,10 +639,26 @@ function relevanssi_fix_entities( $excerpt, $in_docs ) {
638
  $excerpt = str_replace( '&#039;', "'", $excerpt );
639
  } else {
640
  // Running htmlentities() for whole posts tends to ruin things.
641
- // However, we want to run htmlentities() for anything inside
642
  // <pre> and <code> tags.
643
- $excerpt = relevanssi_entities_inside( $excerpt, 'pre' );
644
- $excerpt = relevanssi_entities_inside( $excerpt, 'code' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
645
  }
646
  return $excerpt;
647
  }
@@ -995,7 +1012,7 @@ function relevanssi_get_custom_field_content( $post_id ) {
995
  $remove_underscore_fields = true;
996
  }
997
  /* Documented in lib/indexing.php. */
998
- $custom_fields = apply_filters( 'relevanssi_index_custom_fields', $custom_fields );
999
 
1000
  if ( function_exists( 'relevanssi_get_child_pdf_content' ) ) {
1001
  $custom_field_content .= ' ' . relevanssi_get_child_pdf_content( $post_id );
170
  * Filters the post excerpt generated by Relevanssi before the highlighting is
171
  * applied.
172
  *
173
+ * @param string $excerpt The excerpt.
174
+ * @param int $post->ID The post ID.
175
  */
176
+ $excerpt = apply_filters( 'relevanssi_excerpt', $excerpt, $post->ID );
177
 
178
  $whole_post_excerpted = false;
179
  if ( $excerpt === $post->post_content ) {
536
  }
537
  }
538
 
539
+ if ( preg_match_all( '/<(style|script|object|embed|pre|code).*<\/(style|script|object|embed|pre|code)>/U', $content, $matches ) > 0 ) {
540
  // Remove highlights in style, object, embed, script and pre tags.
541
  foreach ( $matches as $match ) {
542
  $new_match = str_replace( $start_emp_token, '', $match );
639
  $excerpt = str_replace( '&#039;', "'", $excerpt );
640
  } else {
641
  // Running htmlentities() for whole posts tends to ruin things.
642
+ // However, we may want to run htmlentities() for anything inside
643
  // <pre> and <code> tags.
644
+ /**
645
+ * Choose whether htmlentities() is run inside <pre> tags or not. If your
646
+ * pages have HTML code inside <pre> tags, set this to false.
647
+ *
648
+ * @param boolean If true, htmlentities() will be used inside <pre> tags.
649
+ */
650
+ if ( apply_filters( 'relevanssi_entities_inside_pre', true ) ) {
651
+ $excerpt = relevanssi_entities_inside( $excerpt, 'pre' );
652
+ }
653
+ /**
654
+ * Choose whether htmlentities() is run inside <code> tags or not. If your
655
+ * pages have HTML code inside <code> tags, set this to false.
656
+ *
657
+ * @param boolean If true, htmlentities() will be used inside <code> tags.
658
+ */
659
+ if ( apply_filters( 'relevanssi_entities_inside_code', true ) ) {
660
+ $excerpt = relevanssi_entities_inside( $excerpt, 'code' );
661
+ }
662
  }
663
  return $excerpt;
664
  }
1012
  $remove_underscore_fields = true;
1013
  }
1014
  /* Documented in lib/indexing.php. */
1015
+ $custom_fields = apply_filters( 'relevanssi_index_custom_fields', $custom_fields, $post_id );
1016
 
1017
  if ( function_exists( 'relevanssi_get_child_pdf_content' ) ) {
1018
  $custom_field_content .= ' ' . relevanssi_get_child_pdf_content( $post_id );
lib/indexing.php CHANGED
@@ -179,6 +179,9 @@ function relevanssi_post_type_restriction() {
179
  }
180
  }
181
 
 
 
 
182
  if ( count( $post_types ) > 0 ) {
183
  $restriction = ' AND post.post_type IN (' . implode( ', ', $post_types ) . ') ';
184
  }
179
  }
180
  }
181
 
182
+ if ( empty( $post_types ) ) {
183
+ $post_types[] = "'no_post_types_chosen_so_index_no_posts'";
184
+ }
185
  if ( count( $post_types ) > 0 ) {
186
  $restriction = ' AND post.post_type IN (' . implode( ', ', $post_types ) . ') ';
187
  }
lib/init.php CHANGED
@@ -52,6 +52,9 @@ add_filter( 'post_link', 'relevanssi_permalink', 10, 2 );
52
  add_filter( 'page_link', 'relevanssi_permalink', 10, 2 );
53
  add_filter( 'relevanssi_permalink', 'relevanssi_permalink' );
54
 
 
 
 
55
  global $relevanssi_variables;
56
  register_activation_hook( $relevanssi_variables['file'], 'relevanssi_install' );
57
 
@@ -121,17 +124,21 @@ function relevanssi_init() {
121
  require_once 'compatibility/wpml.php';
122
  }
123
 
124
- if ( function_exists( 'pll_get_post' ) ) {
125
  require_once 'compatibility/polylang.php';
126
  }
127
 
128
- if ( class_exists( 'WooCommerce' ) ) {
129
  require_once 'compatibility/woocommerce.php';
130
  }
131
 
132
- if ( class_exists( 'acf' ) ) {
133
  require_once 'compatibility/acf.php';
134
  }
 
 
 
 
135
  }
136
 
137
  /**
@@ -183,6 +190,18 @@ function relevanssi_menu() {
183
  $relevanssi_variables['file'],
184
  'relevanssi_search_stats'
185
  );
 
 
 
 
 
 
 
 
 
 
 
 
186
  require_once 'contextual-help.php';
187
  add_action( 'load-' . $plugin_page, 'relevanssi_admin_help' );
188
  if ( function_exists( 'relevanssi_premium_plugin_page_actions' ) ) {
@@ -381,3 +400,17 @@ function relevanssi_rest_api_disable() {
381
  remove_filter( 'posts_request', 'relevanssi_prevent_default_request' );
382
  remove_filter( 'the_posts', 'relevanssi_query', 99 );
383
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  add_filter( 'page_link', 'relevanssi_permalink', 10, 2 );
53
  add_filter( 'relevanssi_permalink', 'relevanssi_permalink' );
54
 
55
+ // Log exports.
56
+ add_action( 'plugins_loaded', 'relevanssi_export_log_check' );
57
+
58
  global $relevanssi_variables;
59
  register_activation_hook( $relevanssi_variables['file'], 'relevanssi_install' );
60
 
124
  require_once 'compatibility/wpml.php';
125
  }
126
 
127
+ if ( class_exists( 'Polylang', false ) ) {
128
  require_once 'compatibility/polylang.php';
129
  }
130
 
131
+ if ( class_exists( 'WooCommerce', false ) ) {
132
  require_once 'compatibility/woocommerce.php';
133
  }
134
 
135
+ if ( class_exists( 'acf', false ) ) {
136
  require_once 'compatibility/acf.php';
137
  }
138
+
139
+ if ( class_exists( 'Obenland_Wp_Search_Suggest', false ) ) {
140
+ require_once 'compatibility/wp-search-suggest.php';
141
+ }
142
  }
143
 
144
  /**
190
  $relevanssi_variables['file'],
191
  'relevanssi_search_stats'
192
  );
193
+ add_dashboard_page(
194
+ __( 'Admin search', 'relevanssi' ),
195
+ __( 'Admin search', 'relevanssi' ),
196
+ /**
197
+ * Filters the capability required to access Relevanssi admin search page.
198
+ *
199
+ * @param string The capability required. Default 'edit_posts'.
200
+ */
201
+ apply_filters( 'relevanssi_admin_search_capability', 'edit_posts' ),
202
+ 'relevanssi_admin_search',
203
+ 'relevanssi_admin_search_page'
204
+ );
205
  require_once 'contextual-help.php';
206
  add_action( 'load-' . $plugin_page, 'relevanssi_admin_help' );
207
  if ( function_exists( 'relevanssi_premium_plugin_page_actions' ) ) {
400
  remove_filter( 'posts_request', 'relevanssi_prevent_default_request' );
401
  remove_filter( 'the_posts', 'relevanssi_query', 99 );
402
  }
403
+
404
+ /**
405
+ * Checks if a log export is requested.
406
+ *
407
+ * If the 'relevanssi_export' query variable is set, a log export has been requested
408
+ * and one will be provided by relevanssi_export_log().
409
+ *
410
+ * @see relevanssi_export_log
411
+ */
412
+ function relevanssi_export_log_check() {
413
+ if ( isset( $_REQUEST['relevanssi_export'] ) ) { // WPCS: CSRF ok, just checking the parameter exists.
414
+ relevanssi_export_log();
415
+ }
416
+ }
lib/interface.php CHANGED
@@ -232,10 +232,19 @@ function update_relevanssi_options() {
232
  if ( empty( $value ) ) {
233
  $value = 0;
234
  }
 
235
  if ( 'relevanssi_weight_' === substr( $key, 0, strlen( 'relevanssi_weight_' ) ) ) {
236
  $type = substr( $key, strlen( 'relevanssi_weight_' ) );
237
  $post_type_weights[ $type ] = $value;
238
  }
 
 
 
 
 
 
 
 
239
  if ( 'relevanssi_index_type_' === substr( $key, 0, strlen( 'relevanssi_index_type_' ) ) ) {
240
  $type = substr( $key, strlen( 'relevanssi_index_type_' ) );
241
  if ( 'on' === $value ) {
@@ -456,6 +465,27 @@ function relevanssi_search_stats() {
456
  }
457
  }
458
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
459
  /**
460
  * Truncates the Relevanssi logs.
461
  *
@@ -731,6 +761,7 @@ function relevanssi_options_form() {
731
  <a href="<?php echo esc_attr( $this_page ); ?>&amp;tab=stopwords" class="nav-tab <?php echo 'stopwords' === $active_tab ? 'nav-tab-active' : ''; ?>"><?php esc_html_e( 'Stopwords', 'relevanssi' ); ?></a>
732
  <?php if ( RELEVANSSI_PREMIUM ) : ?>
733
  <a href="<?php echo esc_attr( $this_page ); ?>&amp;tab=importexport" class="nav-tab <?php echo 'importexport' === $active_tab ? 'nav-tab-active' : ''; ?>"><?php esc_html_e( 'Import / Export options', 'relevanssi' ); ?></a>
 
734
  <?php endif; ?>
735
  </h2>
736
 
@@ -816,6 +847,7 @@ function relevanssi_add_admin_scripts( $hook ) {
816
  'settings_page_relevanssi-premium/relevanssi',
817
  'toplevel_page_relevanssi/relevanssi',
818
  'settings_page_relevanssi/relevanssi',
 
819
  );
820
  if ( ! in_array( $hook, $acceptable_hooks, true ) ) {
821
  return;
232
  if ( empty( $value ) ) {
233
  $value = 0;
234
  }
235
+
236
  if ( 'relevanssi_weight_' === substr( $key, 0, strlen( 'relevanssi_weight_' ) ) ) {
237
  $type = substr( $key, strlen( 'relevanssi_weight_' ) );
238
  $post_type_weights[ $type ] = $value;
239
  }
240
+ if ( 'relevanssi_taxonomy_weight_' === substr( $key, 0, strlen( 'relevanssi_taxonomy_weight_' ) ) ) {
241
+ $type = 'post_tagged_with_' . substr( $key, strlen( 'relevanssi_taxonomy_weight_' ) );
242
+ $post_type_weights[ $type ] = $value;
243
+ }
244
+ if ( 'relevanssi_term_weight_' === substr( $key, 0, strlen( 'relevanssi_term_weight_' ) ) ) {
245
+ $type = 'taxonomy_term_' . substr( $key, strlen( 'relevanssi_term_weight_' ) );
246
+ $post_type_weights[ $type ] = $value;
247
+ }
248
  if ( 'relevanssi_index_type_' === substr( $key, 0, strlen( 'relevanssi_index_type_' ) ) ) {
249
  $type = substr( $key, strlen( 'relevanssi_index_type_' ) );
250
  if ( 'on' === $value ) {
465
  }
466
  }
467
 
468
+ /**
469
+ * Prints out the 'Admin search' page.
470
+ */
471
+ function relevanssi_admin_search_page() {
472
+ global $relevanssi_variables;
473
+
474
+ $relevanssi_hide_branding = get_option( 'relevanssi_hide_branding' );
475
+
476
+ $options_txt = __( 'Admin Search', 'relevanssi' );
477
+
478
+ wp_enqueue_style( 'dashboard' );
479
+ wp_print_styles( 'dashboard' );
480
+ wp_enqueue_script( 'dashboard' );
481
+ wp_print_scripts( 'dashboard' );
482
+
483
+ printf( "<div class='wrap'><h2>%s</h2>", esc_html( $options_txt ) );
484
+
485
+ require_once dirname( $relevanssi_variables['file'] ) . '/lib/tabs/search-page.php';
486
+ relevanssi_search_tab();
487
+ }
488
+
489
  /**
490
  * Truncates the Relevanssi logs.
491
  *
761
  <a href="<?php echo esc_attr( $this_page ); ?>&amp;tab=stopwords" class="nav-tab <?php echo 'stopwords' === $active_tab ? 'nav-tab-active' : ''; ?>"><?php esc_html_e( 'Stopwords', 'relevanssi' ); ?></a>
762
  <?php if ( RELEVANSSI_PREMIUM ) : ?>
763
  <a href="<?php echo esc_attr( $this_page ); ?>&amp;tab=importexport" class="nav-tab <?php echo 'importexport' === $active_tab ? 'nav-tab-active' : ''; ?>"><?php esc_html_e( 'Import / Export options', 'relevanssi' ); ?></a>
764
+ <a href="<?php echo esc_attr( $this_page ); ?>&amp;tab=search" class="nav-tab <?php echo 'search' === $active_tab ? 'nav-tab-active' : ''; ?>"><?php echo esc_html_x( 'Search', 'noun', 'relevanssi' ); ?></a>
765
  <?php endif; ?>
766
  </h2>
767
 
847
  'settings_page_relevanssi-premium/relevanssi',
848
  'toplevel_page_relevanssi/relevanssi',
849
  'settings_page_relevanssi/relevanssi',
850
+ 'dashboard_page_relevanssi_admin_search',
851
  );
852
  if ( ! in_array( $hook, $acceptable_hooks, true ) ) {
853
  return;
lib/log.php CHANGED
@@ -233,3 +233,37 @@ function relevanssi_erase_log_data( $user_id, $page ) {
233
  'done' => $done,
234
  );
235
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
233
  'done' => $done,
234
  );
235
  }
236
+
237
+ /**
238
+ * Prints out the Relevanssi log as a CSV file.
239
+ *
240
+ * Exports the whole Relevanssi search log as a CSV file.
241
+ *
242
+ * @since 2.2
243
+ */
244
+ function relevanssi_export_log() {
245
+ global $wpdb, $relevanssi_variables;
246
+
247
+ $now = gmdate( 'D, d M Y H:i:s' );
248
+ $filename = 'relevanssi_log.csv';
249
+
250
+ header( '"Expires: Tue, 03 Jul 2001 06:00:00 GMT' );
251
+ header( 'Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate' );
252
+ header( "Last-Modified: {$now} GMT" );
253
+ header( 'Content-Type: application/force-download' );
254
+ header( 'Content-Type: application/octet-stream' );
255
+ header( 'Content-Type: application/download' );
256
+ header( "Content-Disposition: attachment;filename={$filename}" );
257
+ header( 'Content-Transfer-Encoding: binary' );
258
+
259
+ $data = $wpdb->get_results( 'SELECT * FROM ' . $relevanssi_variables['log_table'], ARRAY_A ); // WPCS: unprepared SQL ok. Relevanssi table name.
260
+ ob_start();
261
+ $df = fopen( 'php://output', 'w' ); // phpcs:ignore WordPress.WP.AlternativeFunctions
262
+ fputcsv( $df, array_keys( reset( $data ) ) );
263
+ foreach ( $data as $row ) {
264
+ fputcsv( $df, $row );
265
+ }
266
+ fclose( $df ); // phpcs:ignore WordPress.WP.AlternativeFunctions
267
+ echo ob_get_clean(); // WPCS XSS ok.
268
+ die();
269
+ }
lib/search.php CHANGED
@@ -21,12 +21,6 @@
21
  * @param WP_Query $query The WP_Query object, default false.
22
  */
23
  function relevanssi_query( $posts, $query = false ) {
24
- $admin_search_option = get_option( 'relevanssi_admin_search' );
25
- $admin_search = false;
26
- if ( 'on' === $admin_search_option ) {
27
- $admin_search = true;
28
- }
29
-
30
  global $relevanssi_active;
31
 
32
  if ( ! $query ) {
@@ -45,8 +39,9 @@ function relevanssi_query( $posts, $query = false ) {
45
  // use 'admin_ajax' hook (which sets is_admin() to true whether it's an admin search
46
  // or not.
47
  if ( $query->is_search() && $query->is_admin ) {
48
- $search_ok = false; // But if this is an admin search, reconsider.
49
- if ( $admin_search ) {
 
50
  $search_ok = true; // Yes, we can search!
51
  }
52
  }
@@ -121,25 +116,27 @@ function relevanssi_search( $args ) {
121
  *
122
  * @param array The search parameters.
123
  */
124
- $filtered_args = apply_filters( 'relevanssi_search_filters', $args );
125
- $q = $filtered_args['q'];
126
- $tax_query = $filtered_args['tax_query'];
127
- $tax_query_relation = $filtered_args['tax_query_relation'];
128
- $post_query = $filtered_args['post_query'];
129
- $parent_query = $filtered_args['parent_query'];
130
- $meta_query = $filtered_args['meta_query'];
131
- $date_query = $filtered_args['date_query'];
132
- $expost = $filtered_args['expost'];
133
- $post_type = $filtered_args['post_type'];
134
- $post_status = $filtered_args['post_status'];
135
- $operator = $filtered_args['operator'];
136
- $search_blogs = $filtered_args['search_blogs'];
137
- $author = $filtered_args['author'];
138
- $orderby = $filtered_args['orderby'];
139
- $order = $filtered_args['order'];
140
- $fields = $filtered_args['fields'];
141
- $sentence = $filtered_args['sentence'];
142
- $by_date = $filtered_args['by_date'];
 
 
143
 
144
  $hits = array();
145
  $query_restrictions = '';
@@ -232,28 +229,28 @@ function relevanssi_search( $args ) {
232
  }
233
 
234
  if ( is_array( $parent_query ) ) {
235
- if ( ! empty( $parent_query['parent in'] ) ) {
236
  $valid_values = array();
237
  foreach ( $parent_query['parent in'] as $post_in_id ) {
238
- if ( is_numeric( $post_in_id ) ) {
239
  $valid_values[] = $post_in_id;
240
  }
241
  }
242
  $posts = implode( ',', $valid_values );
243
- if ( ! empty( $posts ) ) {
244
  $query_restrictions .= " AND relevanssi.doc IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
245
  // Clean: $posts is checked to be integers.
246
  }
247
  }
248
- if ( ! empty( $parent_query['parent not in'] ) ) {
249
  $valid_values = array();
250
  foreach ( $parent_query['parent not in'] as $post_not_in_id ) {
251
- if ( is_numeric( $post_not_in_id ) ) {
252
  $valid_values[] = $post_not_in_id;
253
  }
254
  }
255
  $posts = implode( ',', $valid_values );
256
- if ( ! empty( $posts ) ) {
257
  $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
258
  // Clean: $posts is checked to be integers.
259
  }
@@ -289,8 +286,8 @@ function relevanssi_search( $args ) {
289
  // If $post_type is not set, see if there are post types to exclude from the search.
290
  // If $post_type is set, there's no need to exclude, as we only include.
291
  $negative_post_type = null;
292
- if ( ! $post_type ) {
293
- $negative_post_type = relevanssi_get_negative_post_type();
294
  }
295
 
296
  $non_post_post_type = null;
@@ -612,13 +609,25 @@ function relevanssi_search( $args ) {
612
 
613
  $tag = $relevanssi_variables['post_type_weight_defaults']['post_tag'];
614
  $cat = $relevanssi_variables['post_type_weight_defaults']['category'];
 
 
 
 
 
 
 
 
 
615
  if ( ! empty( $post_type_weights['post_tag'] ) ) {
616
  $tag = $post_type_weights['post_tag'];
617
  }
618
  if ( ! empty( $post_type_weights['category'] ) ) {
619
  $cat = $post_type_weights['category'];
620
  }
 
 
621
  $include_these_posts = array();
 
622
  $df_counts = array();
623
 
624
  do {
@@ -700,6 +709,31 @@ function relevanssi_search( $args ) {
700
  relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
701
  FROM $relevanssi_table AS relevanssi WHERE relevanssi.doc IN ($post_ids_to_add)
702
  AND relevanssi.doc NOT IN ($existing_ids) AND $term_cond";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
  // Clean: no unescaped user inputs.
704
  $matches_to_add = $wpdb->get_results( $query ); // WPCS: unprepared SQL ok.
705
  $matches = array_merge( $matches, $matches_to_add );
@@ -719,6 +753,8 @@ function relevanssi_search( $args ) {
719
  foreach ( $matches as $match ) {
720
  if ( 'user' === $match->type ) {
721
  $match->doc = 'u_' . $match->item;
 
 
722
  } elseif ( ! in_array( $match->type, array( 'post', 'attachment' ), true ) ) {
723
  $match->doc = '**' . $match->type . '**' . $match->item;
724
  }
@@ -727,6 +763,16 @@ function relevanssi_search( $args ) {
727
  relevanssi_taxonomy_score( $match, $post_type_weights );
728
  } else {
729
  $tag_weight = 1;
 
 
 
 
 
 
 
 
 
 
730
  if ( isset( $post_type_weights['post_tag'] ) && is_numeric( $post_type_weights['post_tag'] ) ) {
731
  $tag_weight = $post_type_weights['post_tag'];
732
  }
@@ -735,6 +781,7 @@ function relevanssi_search( $args ) {
735
  if ( isset( $post_type_weights['category'] ) && is_numeric( $post_type_weights['category'] ) ) {
736
  $category_weight = $post_type_weights['category'];
737
  }
 
738
 
739
  $taxonomy_weight = 1;
740
 
@@ -817,6 +864,7 @@ function relevanssi_search( $args ) {
817
  $taxonomy_matches[ $match->doc ] += $match->taxonomy;
818
  $comment_matches[ $match->doc ] += $match->comment;
819
 
 
820
  $type = null;
821
  if ( isset( $relevanssi_post_types[ $match->doc ] ) ) {
822
  $type = $relevanssi_post_types[ $match->doc ];
@@ -825,6 +873,11 @@ function relevanssi_search( $args ) {
825
  $match->weight = $match->weight * $post_type_weights[ $type ];
826
  }
827
 
 
 
 
 
 
828
  /**
829
  * Filters the hit.
830
  *
@@ -868,6 +921,8 @@ function relevanssi_search( $args ) {
868
  if ( is_numeric( $match->doc ) ) {
869
  // This is to weed out taxonomies and users (t_XXX, u_XXX).
870
  $include_these_posts[ $match->doc ] = true;
 
 
871
  }
872
  }
873
  }
@@ -1214,6 +1269,11 @@ function relevanssi_do_query( &$query ) {
1214
  }
1215
 
1216
  $multi_args['meta_query'] = $meta_query;
 
 
 
 
 
1217
  if ( function_exists( 'relevanssi_search_multi' ) ) {
1218
  $return = relevanssi_search_multi( $multi_args );
1219
  }
@@ -1367,14 +1427,6 @@ function relevanssi_do_query( &$query ) {
1367
  'operator' => 'AND',
1368
  );
1369
  }
1370
- if ( ! empty( $query->query_vars['tag__not_in'] ) ) {
1371
- $tax_query[] = array(
1372
- 'taxonomy' => 'post_tag',
1373
- 'field' => 'id',
1374
- 'terms' => $query->query_vars['tag__not_in'],
1375
- 'operator' => 'NOT IN',
1376
- );
1377
- }
1378
  if ( ! empty( $query->query_vars['tag_slug__in'] ) ) {
1379
  $tax_query[] = array(
1380
  'taxonomy' => 'post_tag',
@@ -1423,6 +1475,7 @@ function relevanssi_do_query( &$query ) {
1423
  );
1424
  }
1425
  }
 
1426
  }
1427
 
1428
  $author = false;
@@ -1435,27 +1488,27 @@ function relevanssi_do_query( &$query ) {
1435
  }
1436
 
1437
  $post_query = array();
1438
- if ( ! empty( $query->query_vars['p'] ) ) {
1439
  $post_query = array( 'in' => array( $query->query_vars['p'] ) );
1440
  }
1441
- if ( ! empty( $query->query_vars['page_id'] ) ) {
1442
  $post_query = array( 'in' => array( $query->query_vars['page_id'] ) );
1443
  }
1444
- if ( ! empty( $query->query_vars['post__in'] ) ) {
1445
  $post_query = array( 'in' => $query->query_vars['post__in'] );
1446
  }
1447
- if ( ! empty( $query->query_vars['post__not_in'] ) ) {
1448
  $post_query = array( 'not in' => $query->query_vars['post__not_in'] );
1449
  }
1450
 
1451
  $parent_query = array();
1452
- if ( ! empty( $query->query_vars['post_parent'] ) ) {
1453
  $parent_query = array( 'parent in' => array( $query->query_vars['post_parent'] ) );
1454
  }
1455
- if ( ! empty( $query->query_vars['post_parent__in'] ) ) {
1456
  $parent_query = array( 'parent in' => $query->query_vars['post_parent__in'] );
1457
  }
1458
- if ( ! empty( $query->query_vars['post_parent__not_in'] ) ) {
1459
  $parent_query = array( 'parent not in' => $query->query_vars['post_parent__not_in'] );
1460
  }
1461
 
@@ -1596,6 +1649,17 @@ function relevanssi_do_query( &$query ) {
1596
  $by_date = $query->query_vars['by_date'];
1597
  }
1598
  }
 
 
 
 
 
 
 
 
 
 
 
1599
  // Add synonyms.
1600
  // This is done here so the new terms will get highlighting.
1601
  if ( 'OR' === $operator ) {
@@ -1604,24 +1668,26 @@ function relevanssi_do_query( &$query ) {
1604
  }
1605
 
1606
  $search_params = array(
1607
- 'q' => $q,
1608
- 'tax_query' => $tax_query,
1609
- 'tax_query_relation' => $tax_query_relation,
1610
- 'post_query' => $post_query,
1611
- 'parent_query' => $parent_query,
1612
- 'meta_query' => $meta_query,
1613
- 'date_query' => $date_query,
1614
- 'expost' => $expost,
1615
- 'post_type' => $post_type,
1616
- 'post_status' => $post_status,
1617
- 'operator' => $operator,
1618
- 'search_blogs' => $search_blogs,
1619
- 'author' => $author,
1620
- 'orderby' => $orderby,
1621
- 'order' => $order,
1622
- 'fields' => $fields,
1623
- 'sentence' => $sentence,
1624
- 'by_date' => $by_date,
 
 
1625
  );
1626
 
1627
  $return = relevanssi_search( $search_params );
@@ -1724,21 +1790,38 @@ function relevanssi_do_query( &$query ) {
1724
  }
1725
 
1726
  if ( 'on' === $make_excerpts && empty( $fields ) ) {
 
 
 
1727
  $post->original_excerpt = $post->post_excerpt;
1728
  $post->post_excerpt = relevanssi_do_excerpt( $post, $q );
 
 
 
1729
  }
1730
  if ( 'on' === get_option( 'relevanssi_show_matches' ) && empty( $fields ) ) {
1731
  $post_id = $post->ID;
1732
  if ( 'user' === $post->post_type ) {
1733
  $post_id = 'u_' . $post->user_id;
 
 
1734
  } elseif ( isset( $post->term_id ) ) {
1735
  $post_id = '**' . $post->post_type . '**' . $post->term_id;
1736
  }
 
 
 
1737
  $post->post_excerpt .= relevanssi_show_matches( $return, $post_id );
1738
  }
1739
 
1740
- if ( empty( $fields ) && isset( $return['scores'][ $post->ID ] ) ) {
1741
- $post->relevance_score = round( $return['scores'][ $post->ID ], 2 );
 
 
 
 
 
 
1742
  }
1743
 
1744
  $posts[] = $post;
@@ -1777,21 +1860,23 @@ function relevanssi_limit_filter( $query ) {
1777
  *
1778
  * Figures out the post types that are not included in the search.
1779
  *
1780
- * @global WP_Query $wp_query The global WP_Query object.
1781
  *
1782
  * @return string SQL escaped list of excluded post types.
1783
  */
1784
- function relevanssi_get_negative_post_type() {
1785
- global $wp_query;
1786
-
1787
  $negative_post_type = null;
1788
  $negative_post_type_list = array();
1789
 
1790
- if ( isset( $wp_query->query_vars['include_attachments'] ) && in_array( $wp_query->query_vars['include_attachments'], array( '0', 'off', 'false' ), true ) ) {
1791
  $negative_post_type_list[] = 'attachment';
1792
  }
1793
 
1794
- if ( 'on' === get_option( 'relevanssi_respect_exclude' ) ) {
 
 
 
 
1795
  // If Relevanssi is set to respect exclude_from_search, find out which
1796
  // post types should be excluded from search.
1797
  $pt_1 = get_post_types( array( 'exclude_from_search' => '1' ) );
@@ -2158,10 +2243,14 @@ function relevanssi_taxonomy_score( &$match, $post_type_weights ) {
2158
  $match->taxonomy_detail = json_decode( $match->taxonomy_detail );
2159
  if ( is_object( $match->taxonomy_detail ) ) {
2160
  foreach ( $match->taxonomy_detail as $tax => $count ) {
2161
- if ( empty( $post_type_weights[ $tax ] ) ) {
2162
- $match->taxonomy_score += $count * 1;
 
 
 
 
2163
  } else {
2164
- $match->taxonomy_score += $count * $post_type_weights[ $tax ];
2165
  }
2166
  }
2167
  }
21
  * @param WP_Query $query The WP_Query object, default false.
22
  */
23
  function relevanssi_query( $posts, $query = false ) {
 
 
 
 
 
 
24
  global $relevanssi_active;
25
 
26
  if ( ! $query ) {
39
  // use 'admin_ajax' hook (which sets is_admin() to true whether it's an admin search
40
  // or not.
41
  if ( $query->is_search() && $query->is_admin ) {
42
+ $search_ok = false; // But if this is an admin search, reconsider.
43
+ $admin_search_option = get_option( 'relevanssi_admin_search' );
44
+ if ( 'on' === $admin_search_option ) {
45
  $search_ok = true; // Yes, we can search!
46
  }
47
  }
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
 
141
  $hits = array();
142
  $query_restrictions = '';
229
  }
230
 
231
  if ( is_array( $parent_query ) ) {
232
+ if ( isset( $parent_query['parent in'] ) ) {
233
  $valid_values = array();
234
  foreach ( $parent_query['parent in'] as $post_in_id ) {
235
+ if ( is_int( $post_in_id ) ) {
236
  $valid_values[] = $post_in_id;
237
  }
238
  }
239
  $posts = implode( ',', $valid_values );
240
+ if ( strlen( $posts ) > 0 ) {
241
  $query_restrictions .= " AND relevanssi.doc IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
242
  // Clean: $posts is checked to be integers.
243
  }
244
  }
245
+ if ( isset( $parent_query['parent not in'] ) ) {
246
  $valid_values = array();
247
  foreach ( $parent_query['parent not in'] as $post_not_in_id ) {
248
+ if ( is_int( $post_not_in_id ) ) {
249
  $valid_values[] = $post_not_in_id;
250
  }
251
  }
252
  $posts = implode( ',', $valid_values );
253
+ if ( isset( $posts ) ) {
254
  $query_restrictions .= " AND relevanssi.doc NOT IN (SELECT ID FROM $wpdb->posts WHERE post_parent IN ($posts))";
255
  // Clean: $posts is checked to be integers.
256
  }
286
  // If $post_type is not set, see if there are post types to exclude from the search.
287
  // If $post_type is set, there's no need to exclude, as we only include.
288
  $negative_post_type = null;
289
+ if ( ! $post_type && ! $admin_search ) {
290
+ $negative_post_type = relevanssi_get_negative_post_type( $include_attachments );
291
  }
292
 
293
  $non_post_post_type = null;
609
 
610
  $tag = $relevanssi_variables['post_type_weight_defaults']['post_tag'];
611
  $cat = $relevanssi_variables['post_type_weight_defaults']['category'];
612
+
613
+ if ( ! empty( $post_type_weights['post_tagged_with_post_tag'] ) ) {
614
+ $tag = $post_type_weights['post_tagged_with_post_tag'];
615
+ }
616
+ if ( ! empty( $post_type_weights['post_tagged_with_category'] ) ) {
617
+ $cat = $post_type_weights['post_tagged_with_category'];
618
+ }
619
+
620
+ /* Legacy code, improvement introduced in 2.1.8, remove at some point. */
621
  if ( ! empty( $post_type_weights['post_tag'] ) ) {
622
  $tag = $post_type_weights['post_tag'];
623
  }
624
  if ( ! empty( $post_type_weights['category'] ) ) {
625
  $cat = $post_type_weights['category'];
626
  }
627
+ /* End legacy code. */
628
+
629
  $include_these_posts = array();
630
+ $include_these_items = array();
631
  $df_counts = array();
632
 
633
  do {
709
  relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
710
  FROM $relevanssi_table AS relevanssi WHERE relevanssi.doc IN ($post_ids_to_add)
711
  AND relevanssi.doc NOT IN ($existing_ids) AND $term_cond";
712
+
713
+ // Clean: no unescaped user inputs.
714
+ $matches_to_add = $wpdb->get_results( $query ); // WPCS: unprepared SQL ok.
715
+ $matches = array_merge( $matches, $matches_to_add );
716
+ }
717
+ if ( count( $include_these_items ) > 0 ) {
718
+ $items_to_add = implode( ',', array_keys( $include_these_items ) );
719
+ $existing_items = array();
720
+ foreach ( $matches as $match ) {
721
+ if ( 0 !== intval( $match->item ) ) {
722
+ $existing_items[] = $match->item;
723
+ }
724
+ }
725
+ $existing_items = implode( ',', $existing_items );
726
+ if ( ! empty( $existing_items ) ) {
727
+ $existing_items = "AND relevanssi.item NOT IN ($existing_items) ";
728
+ }
729
+ $query = "SELECT relevanssi.*, relevanssi.title * $title_boost +
730
+ relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
731
+ relevanssi.tag * $tag + relevanssi.link * $link_boost +
732
+ relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
733
+ relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
734
+ FROM $relevanssi_table AS relevanssi WHERE relevanssi.item IN ($items_to_add)
735
+ $existing_items AND $term_cond";
736
+
737
  // Clean: no unescaped user inputs.
738
  $matches_to_add = $wpdb->get_results( $query ); // WPCS: unprepared SQL ok.
739
  $matches = array_merge( $matches, $matches_to_add );
753
  foreach ( $matches as $match ) {
754
  if ( 'user' === $match->type ) {
755
  $match->doc = 'u_' . $match->item;
756
+ } elseif ( 'post_type' === $match->type ) {
757
+ $match->doc = 'p_' . $match->item;
758
  } elseif ( ! in_array( $match->type, array( 'post', 'attachment' ), true ) ) {
759
  $match->doc = '**' . $match->type . '**' . $match->item;
760
  }
763
  relevanssi_taxonomy_score( $match, $post_type_weights );
764
  } else {
765
  $tag_weight = 1;
766
+ if ( isset( $post_type_weights['post_tagged_with_post_tag'] ) && is_numeric( $post_type_weights['post_tagged_with_post_tag'] ) ) {
767
+ $tag_weight = $post_type_weights['post_tagged_with_post_tag'];
768
+ }
769
+
770
+ $category_weight = 1;
771
+ if ( isset( $post_type_weights['post_tagged_with_category'] ) && is_numeric( $post_type_weights['post_tagged_with_category'] ) ) {
772
+ $category_weight = $post_type_weights['post_tagged_with_category'];
773
+ }
774
+
775
+ /* Legacy code from 2.1.8. Remove at some point. */
776
  if ( isset( $post_type_weights['post_tag'] ) && is_numeric( $post_type_weights['post_tag'] ) ) {
777
  $tag_weight = $post_type_weights['post_tag'];
778
  }
781
  if ( isset( $post_type_weights['category'] ) && is_numeric( $post_type_weights['category'] ) ) {
782
  $category_weight = $post_type_weights['category'];
783
  }
784
+ /* End legacy code. */
785
 
786
  $taxonomy_weight = 1;
787
 
864
  $taxonomy_matches[ $match->doc ] += $match->taxonomy;
865
  $comment_matches[ $match->doc ] += $match->comment;
866
 
867
+ /* Post type weights. */
868
  $type = null;
869
  if ( isset( $relevanssi_post_types[ $match->doc ] ) ) {
870
  $type = $relevanssi_post_types[ $match->doc ];
873
  $match->weight = $match->weight * $post_type_weights[ $type ];
874
  }
875
 
876
+ /* Weight boost for taxonomy terms based on taxonomy. */
877
+ if ( ! empty( $post_type_weights[ 'taxonomy_term_' . $match->type ] ) ) {
878
+ $match->weight = $match->weight * $post_type_weights[ 'taxonomy_term_' . $match->type ];
879
+ }
880
+
881
  /**
882
  * Filters the hit.
883
  *
921
  if ( is_numeric( $match->doc ) ) {
922
  // This is to weed out taxonomies and users (t_XXX, u_XXX).
923
  $include_these_posts[ $match->doc ] = true;
924
+ } elseif ( 0 !== intval( $match->item ) ) {
925
+ $include_these_items[ $match->item ] = true;
926
  }
927
  }
928
  }
1269
  }
1270
 
1271
  $multi_args['meta_query'] = $meta_query;
1272
+
1273
+ if ( isset( $query->query_vars['include_attachments'] ) ) {
1274
+ $multi_args['include_attachments'] = $query->query_vars['include_attachments'];
1275
+ }
1276
+
1277
  if ( function_exists( 'relevanssi_search_multi' ) ) {
1278
  $return = relevanssi_search_multi( $multi_args );
1279
  }
1427
  'operator' => 'AND',
1428
  );
1429
  }
 
 
 
 
 
 
 
 
1430
  if ( ! empty( $query->query_vars['tag_slug__in'] ) ) {
1431
  $tax_query[] = array(
1432
  'taxonomy' => 'post_tag',
1475
  );
1476
  }
1477
  }
1478
+ $query->tax_query = $tax_query;
1479
  }
1480
 
1481
  $author = false;
1488
  }
1489
 
1490
  $post_query = array();
1491
+ if ( isset( $query->query_vars['p'] ) ) {
1492
  $post_query = array( 'in' => array( $query->query_vars['p'] ) );
1493
  }
1494
+ if ( isset( $query->query_vars['page_id'] ) ) {
1495
  $post_query = array( 'in' => array( $query->query_vars['page_id'] ) );
1496
  }
1497
+ if ( isset( $query->query_vars['post__in'] ) ) {
1498
  $post_query = array( 'in' => $query->query_vars['post__in'] );
1499
  }
1500
+ if ( isset( $query->query_vars['post__not_in'] ) ) {
1501
  $post_query = array( 'not in' => $query->query_vars['post__not_in'] );
1502
  }
1503
 
1504
  $parent_query = array();
1505
+ if ( isset( $query->query_vars['post_parent'] ) ) {
1506
  $parent_query = array( 'parent in' => array( $query->query_vars['post_parent'] ) );
1507
  }
1508
+ if ( is_array( $query->query_vars['post_parent__in'] ) && ! empty( $query->query_vars['post_parent__in'] ) ) {
1509
  $parent_query = array( 'parent in' => $query->query_vars['post_parent__in'] );
1510
  }
1511
+ if ( is_array( $query->query_vars['post_parent__not_in'] ) && ! empty( $query->query_vars['post_parent__not_in'] ) ) {
1512
  $parent_query = array( 'parent not in' => $query->query_vars['post_parent__not_in'] );
1513
  }
1514
 
1649
  $by_date = $query->query_vars['by_date'];
1650
  }
1651
  }
1652
+
1653
+ $admin_search = false;
1654
+ if ( isset( $query->query_vars['relevanssi_admin_search'] ) ) {
1655
+ $admin_search = true;
1656
+ }
1657
+
1658
+ $include_attachments = '';
1659
+ if ( isset( $query->query_vars['include_attachments'] ) ) {
1660
+ $include_attachments = $query->query_vars['include_attachments'];
1661
+ }
1662
+
1663
  // Add synonyms.
1664
  // This is done here so the new terms will get highlighting.
1665
  if ( 'OR' === $operator ) {
1668
  }
1669
 
1670
  $search_params = array(
1671
+ 'q' => $q,
1672
+ 'tax_query' => $tax_query,
1673
+ 'tax_query_relation' => $tax_query_relation,
1674
+ 'post_query' => $post_query,
1675
+ 'parent_query' => $parent_query,
1676
+ 'meta_query' => $meta_query,
1677
+ 'date_query' => $date_query,
1678
+ 'expost' => $expost,
1679
+ 'post_type' => $post_type,
1680
+ 'post_status' => $post_status,
1681
+ 'operator' => $operator,
1682
+ 'search_blogs' => $search_blogs,
1683
+ 'author' => $author,
1684
+ 'orderby' => $orderby,
1685
+ 'order' => $order,
1686
+ 'fields' => $fields,
1687
+ 'sentence' => $sentence,
1688
+ 'by_date' => $by_date,
1689
+ 'admin_search' => $admin_search,
1690
+ 'include_attachments' => $include_attachments,
1691
  );
1692
 
1693
  $return = relevanssi_search( $search_params );
1790
  }
1791
 
1792
  if ( 'on' === $make_excerpts && empty( $fields ) ) {
1793
+ if ( isset( $post->blog_id ) ) {
1794
+ switch_to_blog( $post->blog_id );
1795
+ }
1796
  $post->original_excerpt = $post->post_excerpt;
1797
  $post->post_excerpt = relevanssi_do_excerpt( $post, $q );
1798
+ if ( isset( $post->blog_id ) ) {
1799
+ restore_current_blog();
1800
+ }
1801
  }
1802
  if ( 'on' === get_option( 'relevanssi_show_matches' ) && empty( $fields ) ) {
1803
  $post_id = $post->ID;
1804
  if ( 'user' === $post->post_type ) {
1805
  $post_id = 'u_' . $post->user_id;
1806
+ } elseif ( 'post_type' === $post->post_type ) {
1807
+ $post_id = 'p_' . $post->ID;
1808
  } elseif ( isset( $post->term_id ) ) {
1809
  $post_id = '**' . $post->post_type . '**' . $post->term_id;
1810
  }
1811
+ if ( isset( $post->blog_id ) ) {
1812
+ $post_id = $post->blog_id . '|' . $post->ID;
1813
+ }
1814
  $post->post_excerpt .= relevanssi_show_matches( $return, $post_id );
1815
  }
1816
 
1817
+ if ( empty( $fields ) ) {
1818
+ $post_id = $post->ID;
1819
+ if ( isset( $post->blog_id ) ) {
1820
+ $post_id = $post->blog_id . '|' . $post->ID;
1821
+ }
1822
+ if ( isset( $return['scores'][ $post_id ] ) ) {
1823
+ $post->relevance_score = round( $return['scores'][ $post_id ], 2 );
1824
+ }
1825
  }
1826
 
1827
  $posts[] = $post;
1860
  *
1861
  * Figures out the post types that are not included in the search.
1862
  *
1863
+ * @param string $include_attachments Whether to include attachments or not.
1864
  *
1865
  * @return string SQL escaped list of excluded post types.
1866
  */
1867
+ function relevanssi_get_negative_post_type( $include_attachments ) {
 
 
1868
  $negative_post_type = null;
1869
  $negative_post_type_list = array();
1870
 
1871
+ if ( isset( $include_attachments ) && in_array( $include_attachments, array( '0', 'off', 'false' ), true ) ) {
1872
  $negative_post_type_list[] = 'attachment';
1873
  }
1874
 
1875
+ $front_end = true;
1876
+ if ( is_admin() && ( ! defined( 'DOING_AJAX' ) || ! DOING_AJAX ) ) {
1877
+ $front_end = false;
1878
+ }
1879
+ if ( 'on' === get_option( 'relevanssi_respect_exclude' ) && $front_end ) {
1880
  // If Relevanssi is set to respect exclude_from_search, find out which
1881
  // post types should be excluded from search.
1882
  $pt_1 = get_post_types( array( 'exclude_from_search' => '1' ) );
2243
  $match->taxonomy_detail = json_decode( $match->taxonomy_detail );
2244
  if ( is_object( $match->taxonomy_detail ) ) {
2245
  foreach ( $match->taxonomy_detail as $tax => $count ) {
2246
+ if ( empty( $post_type_weights[ 'post_tagged_with_' . $tax ] ) ) {
2247
+ if ( ! empty( $post_type_weights[ $tax ] ) ) { // Legacy code, needed for 2.1.8, remove later.
2248
+ $match->taxonomy_score += $count * $post_type_weights[ $tax ];
2249
+ } else {
2250
+ $match->taxonomy_score += $count * 1;
2251
+ }
2252
  } else {
2253
+ $match->taxonomy_score += $count * $post_type_weights[ 'post_tagged_with_' . $tax ];
2254
  }
2255
  }
2256
  }
lib/sorting.php CHANGED
@@ -70,7 +70,7 @@ function relevanssi_get_next_key( &$orderby ) {
70
  break;
71
  }
72
 
73
- $numeric_keys = array( 'menu_order', 'ID', 'post_parent', 'post_author', 'comment_count', 'relevance_score' );
74
  $date_keys = array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt' );
75
 
76
  $compare = 'string';
@@ -301,10 +301,6 @@ function relevanssi_object_sort( &$data, $orderby ) {
301
  } while ( ! empty( $values['key'] ) );
302
 
303
  $primary_key = $relevanssi_keys[0];
304
- if ( 'rand' !== $primary_key && ! isset( $data[0]->$primary_key ) ) {
305
- // Trying to sort by a non-existent key.
306
- return;
307
- }
308
 
309
  usort( $data, 'relevanssi_cmp_function' );
310
  }
70
  break;
71
  }
72
 
73
+ $numeric_keys = array( 'meta_value_num', 'menu_order', 'ID', 'post_parent', 'post_author', 'comment_count', 'relevance_score' );
74
  $date_keys = array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt' );
75
 
76
  $compare = 'string';
301
  } while ( ! empty( $values['key'] ) );
302
 
303
  $primary_key = $relevanssi_keys[0];
 
 
 
 
304
 
305
  usort( $data, 'relevanssi_cmp_function' );
306
  }
lib/tabs/excerpts-tab.php CHANGED
@@ -97,6 +97,14 @@ function relevanssi_excerpts_tab() {
97
  </label>
98
  </fieldset>
99
  <p class="description"><?php esc_html_e( 'Only enable this if you actually use the custom excerpts.', 'relevanssi' ); ?></p>
 
 
 
 
 
 
 
 
100
  </td>
101
  </tr>
102
  <tr id="tr_excerpt_length"
97
  </label>
98
  </fieldset>
99
  <p class="description"><?php esc_html_e( 'Only enable this if you actually use the custom excerpts.', 'relevanssi' ); ?></p>
100
+ <?php
101
+ $theme = wp_get_theme();
102
+ $template = $theme->get( 'Template' );
103
+ if ( 'divi' === strtolower( $template ) ) :
104
+ ?>
105
+ <?php // Translators: %1$s opens the link, %2$s closes it. ?>
106
+ <p class="important"><?php printf( esc_html__( 'Looks like you are using Divi. In order to use custom excerpts with Divi, you need to make some changes to your templates. %1$sSee instructions here%2$s.', 'relevanssi' ), '<a href="https://www.relevanssi.com/knowledge-base/divi-page-builder-and-cleaner-excerpts/">', '</a>' ); ?></p>
107
+ <?php endif; ?>
108
  </td>
109
  </tr>
110
  <tr id="tr_excerpt_length"
lib/tabs/indexing-tab.php CHANGED
@@ -143,7 +143,11 @@ function relevanssi_indexing_tab() {
143
 
144
  <?php
145
  if ( count( $index_post_types ) < 2 ) {
146
- printf( '<p><strong>%s</strong></p>', esc_html__( "WARNING: You've chosen no post types to index. Nothing will be indexed. Choose some post types to index.", 'relevanssi' ) );
 
 
 
 
147
  }
148
  ?>
149
 
@@ -280,6 +284,13 @@ function relevanssi_indexing_tab() {
280
  esc_html_e( "'Some' lets you choose individual custom fields to index.", 'relevanssi' );
281
  ?>
282
  </p>
 
 
 
 
 
 
 
283
  <div id="index_field_input"
284
  <?php
285
  if ( empty( $fields_select_some ) ) {
@@ -376,6 +387,9 @@ function relevanssi_indexing_tab() {
376
  if ( function_exists( 'relevanssi_form_index_taxonomies' ) ) {
377
  relevanssi_form_index_taxonomies();
378
  }
 
 
 
379
  if ( function_exists( 'relevanssi_form_index_pdf_parent' ) ) {
380
  relevanssi_form_index_pdf_parent();
381
  }
143
 
144
  <?php
145
  if ( count( $index_post_types ) < 2 ) {
146
+ $index_users = get_option( 'relevanssi_index_users', 'off' );
147
+ $index_taxonomies = get_option( 'relevanssi_index_taxonomies', 'off' );
148
+ if ( 'off' === $index_users && 'off' === $index_taxonomies ) {
149
+ printf( '<p><strong>%s</strong></p>', esc_html__( "WARNING: You've chosen no post types to index. Nothing will be indexed. Choose some post types to index.", 'relevanssi' ) );
150
+ }
151
  }
152
  ?>
153
 
284
  esc_html_e( "'Some' lets you choose individual custom fields to index.", 'relevanssi' );
285
  ?>
286
  </p>
287
+ <?php
288
+ if ( class_exists( 'acf' ) && $fields_select_all ) {
289
+ echo "<p class='description important'>";
290
+ esc_html_e( 'Advanced Custom Fields has lots of invisible custom fields with meta data. Selecting "all" will include lots of garbage in the index and excerpts. "Visible" is usually a better option with ACF.' );
291
+ echo '</p>';
292
+ }
293
+ ?>
294
  <div id="index_field_input"
295
  <?php
296
  if ( empty( $fields_select_some ) ) {
387
  if ( function_exists( 'relevanssi_form_index_taxonomies' ) ) {
388
  relevanssi_form_index_taxonomies();
389
  }
390
+ if ( function_exists( 'relevanssi_form_index_post_type_archives' ) ) {
391
+ relevanssi_form_index_post_type_archives();
392
+ }
393
  if ( function_exists( 'relevanssi_form_index_pdf_parent' ) ) {
394
  relevanssi_form_index_pdf_parent();
395
  }
lib/tabs/logging-tab.php CHANGED
@@ -90,6 +90,16 @@ function relevanssi_logging_tab() {
90
  </td>
91
  </tr>
92
 
 
 
 
 
 
 
 
 
 
 
93
  </table>
94
  <?php
95
  }
90
  </td>
91
  </tr>
92
 
93
+ <tr>
94
+ <th scope="row">
95
+ <label for='relevanssi_export_logs'><?php esc_html_e( 'Export logs', 'relevanssi' ); ?></label>
96
+ </th>
97
+ <td>
98
+ <?php submit_button( __( 'Export the log as a CSV file', 'relevanssi' ), 'secondary', 'relevanssi_export' ); ?>
99
+ <p class="description"><?php esc_html_e( 'Push the button to export the search log as a CSV file.', 'relevanssi' ); ?></p>
100
+ </td>
101
+ </tr>
102
+
103
  </table>
104
  <?php
105
  }
lib/tabs/overview-tab.php CHANGED
@@ -30,6 +30,9 @@ function relevanssi_overview_tab() {
30
  if ( function_exists( 'relevanssi_form_hide_post_controls' ) ) {
31
  relevanssi_form_hide_post_controls();
32
  }
 
 
 
33
  ?>
34
  <tr>
35
  <th scope="row"><?php esc_html_e( 'Getting started', 'relevanssi' ); ?></th>
30
  if ( function_exists( 'relevanssi_form_hide_post_controls' ) ) {
31
  relevanssi_form_hide_post_controls();
32
  }
33
+ if ( function_exists( 'relevanssi_form_do_not_call_home' ) ) {
34
+ relevanssi_form_do_not_call_home();
35
+ }
36
  ?>
37
  <tr>
38
  <th scope="row"><?php esc_html_e( 'Getting started', 'relevanssi' ); ?></th>
lib/tabs/search-page.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /premium/tabs/search-tab.php
4
+ *
5
+ * Prints out the Premium search tab in Relevanssi settings.
6
+ *
7
+ * @package Relevanssi_Premium
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
+ * Prints out the Premium search tab in Relevanssi settings.
15
+ */
16
+ function relevanssi_search_tab() {
17
+ ?>
18
+ <p><?php esc_html_e( 'You can use this search to perform Relevanssi searches without any restrictions from WordPress. You can search all post types here.', 'relevanssi' ); ?></p>
19
+
20
+ <table class="form-table">
21
+ <tr>
22
+ <th scope="row">
23
+ <label for='s'><?php esc_html_e( 'Search terms', 'relevanssi' ); ?></label>
24
+ </th>
25
+ <td>
26
+ <input type='text' name='s' id='s' size='60' />
27
+ </td>
28
+ </tr>
29
+ <tr>
30
+ <th scope="row">
31
+ <label for='posts_per_page'><?php esc_html_e( 'Posts per page', 'relevanssi' ); ?></label>
32
+ </th>
33
+ <td>
34
+ <select name='posts_per_page' id='posts_per_page'>
35
+ <option value='0'><?php esc_html_e( 'All', 'relevanssi' ); ?></option>
36
+ <option>10</option>
37
+ <option>50</option>
38
+ <option>100</option>
39
+ </select>
40
+ </td>
41
+ </tr>
42
+ <tr>
43
+ <th scope="row">
44
+ <label for='args'><?php esc_html_e( 'Search parameters', 'relevanssi' ); ?></label>
45
+ </th>
46
+ <td>
47
+ <input type='text' name='args' id='args' size='60' />
48
+ <?php // Translators: example query string. ?>
49
+ <p class='description'><?php printf( esc_html__( 'Use query parameter formatting here, the same that would appear on search page results URL. For example %s.', 'relevanssi' ), '<code>posts_per_page=10&post_types=page&from=2018-01-01</code>' ); ?></p>
50
+ </td>
51
+ </tr>
52
+ <tr>
53
+ <th scope="row">
54
+ </th>
55
+ <td>
56
+ <input type='submit' name='search' id='search' value='<?php echo esc_html_x( 'Search', 'button action', 'relevanssi' ); ?>' class='button' />
57
+ </td>
58
+ </tr>
59
+ </table>
60
+
61
+ <div id='results'></div>
62
+ <?php
63
+ }
lib/tabs/searching-tab.php CHANGED
@@ -332,7 +332,7 @@ function relevanssi_searching_tab() {
332
  <td>
333
  <div class="categorydiv" style="max-width: 400px">
334
  <div class="tabs-panel">
335
- <ul id="categorychecklist">
336
  <?php
337
  $selected_cats = explode( ',', $cat );
338
  $walker = get_relevanssi_taxonomy_walker();
@@ -357,7 +357,7 @@ function relevanssi_searching_tab() {
357
  <td>
358
  <div class="categorydiv" style="max-width: 400px">
359
  <div class="tabs-panel">
360
- <ul id="categorychecklist">
361
  <?php
362
  $selected_cats = explode( ',', $excat );
363
  $walker = get_relevanssi_taxonomy_walker();
332
  <td>
333
  <div class="categorydiv" style="max-width: 400px">
334
  <div class="tabs-panel">
335
+ <ul id="category_inclusion_checklist">
336
  <?php
337
  $selected_cats = explode( ',', $cat );
338
  $walker = get_relevanssi_taxonomy_walker();
357
  <td>
358
  <div class="categorydiv" style="max-width: 400px">
359
  <div class="tabs-panel">
360
+ <ul id="category_exclusion_checklist">
361
  <?php
362
  $selected_cats = explode( ',', $excat );
363
  $walker = get_relevanssi_taxonomy_walker();
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: search, relevance, better search
5
  Requires at least: 4.0
6
  Tested up to: 5.0
7
  Requires PHP: 5.6
8
- Stable tag: 4.0.11
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -130,6 +130,21 @@ Each document database is full of useless words. All the little words that appea
130
 
131
  == Changelog ==
132
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  = 4.0.11 =
134
  * Home page links were getting the highlight parameter even though they shouldn't. This has been fixed.
135
  * Added support for WP JV Post Reading Groups.
@@ -155,38 +170,11 @@ Each document database is full of useless words. All the little words that appea
155
  * Random order works again; using orderby `rand` didn't work properly. The `rand(seed)` format is also supported now.
156
  * Fixed quotes and apostrophes in Did you mean suggestions.
157
 
158
- = 4.0.9 =
159
- * Fixes broken tag and category indexing and searching. If you use tags and categories, rebuild the index after updating.
160
- * Phrases were not highlighted correctly on documents. This is now fixed.
161
- * Shortcode fix: 'wp_show_posts' shouldn't cause problems anymore.
162
- * New filter: `relevanssi_indexing_restriction` allows filtering posts before indexing.
163
- * New WooCommerce product visibility filtering tool makes WooCommerce product indexing faster.
164
- * MemberPress post controls were loose and showed drafts to searchers. That is now fixed.
165
- * Highlighting was too loose, even if matching was set to whole words.
166
- * Highlighting now works better in cases where there's a hyphen or an apostrophe inside a word.
167
-
168
- = 4.0.8 =
169
- * Fixed cases where Relevanssi added an ellipsis even if the excerpt was from the start of the post.
170
- * Highlighting now works with numeric search strings.
171
- * Improved highlighting for accented words. Thanks to Paul Ryan.
172
- * A surplus comma at the end of post exclusion setting won't break the search anymore.
173
- * Fixed instructions for adjusting the throttle limit.
174
-
175
- = 4.0.7 =
176
- * Recent post bonus is now applied to searches.
177
- * Exact term setting can now be disabled.
178
- * Users of Members plugin would have drafts appear in search results. This is now fixed.
179
-
180
- = 4.0.6 =
181
- * Indexing bugs squashed.
182
- * Missing tag and category weight settings returned.
183
- * Fusion builder shortcodes are removed from excerpts.
184
- * MemberPress post control was backwards.
185
- * User searches page reset buttons fixed.
186
- * WPML language filter fix.
187
-
188
  == Upgrade notice ==
189
 
 
 
 
190
  = 4.0.11 =
191
  * Several small improvements, new filters and highlighting fixes.
192
 
@@ -194,16 +182,4 @@ Each document database is full of useless words. All the little words that appea
194
  * Privacy feature bug fix.
195
 
196
  = 4.0.10 =
197
- * Privacy update, with some bug fixes.
198
-
199
- = 4.0.9 =
200
- * Fixes broken tag and category searching and indexing. Reindex after the update!
201
-
202
- = 4.0.8 =
203
- * Improvements to highlighting and excerpts.
204
-
205
- = 4.0.7 =
206
- * Small bug fixes.
207
-
208
- = 4.0.6 =
209
- * Indexing bugs fixed and WPML support corrected.
5
  Requires at least: 4.0
6
  Tested up to: 5.0
7
  Requires PHP: 5.6
8
+ Stable tag: 4.1
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
130
 
131
  == Changelog ==
132
 
133
+ = 4.1 =
134
+ * New feature: You can now export the search log as a CSV file.
135
+ * New feature: Admin Search page allows you to perform searches in WP admin using Relevanssi.
136
+ * New filter: `relevanssi_admin_search_capability` can be used to adjust who sees the admin search page.
137
+ * New filter: `relevanssi_entities_inside_pre` and `relevanssi_entities_inside_code` adjust how HTML entities are handled inside `pre` and `code` tags.
138
+ * Numeric meta values (`meta_value_num`) are now sorted as numbers and not strings.
139
+ * Pinned posts have `$post->relevanssi_pinned` set to 1 for debugging purposes, but you can also use this for styling the posts in the search results templates.
140
+ * The Did you mean feature has been toned down a bit, to make the suggestions slightly less weird in some cases.
141
+ * Post parent parameters now accept 0 as a value, making it easier to search for children of any post or posts without a parent.
142
+ * Polylang compatibility has been improved.
143
+ * Phrases with apostrophes inside work better.
144
+ * The `relevanssi_excerpt` filter hook got a second parameter that holds the post ID.
145
+ * Custom field sorting actually works now.
146
+ * WP Search Suggest compatibility added.
147
+
148
  = 4.0.11 =
149
  * Home page links were getting the highlight parameter even though they shouldn't. This has been fixed.
150
  * Added support for WP JV Post Reading Groups.
170
  * Random order works again; using orderby `rand` didn't work properly. The `rand(seed)` format is also supported now.
171
  * Fixed quotes and apostrophes in Did you mean suggestions.
172
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
173
  == Upgrade notice ==
174
 
175
+ = 4.1 =
176
+ * New features and plenty of small fixes.
177
+
178
  = 4.0.11 =
179
  * Several small improvements, new filters and highlighting fixes.
180
 
182
  * Privacy feature bug fix.
183
 
184
  = 4.0.10 =
185
+ * Privacy update, with some bug fixes.
 
 
 
 
 
 
 
 
 
 
 
 
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.0.11
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
@@ -39,6 +39,8 @@
39
 
40
  define( 'RELEVANSSI_PREMIUM', false );
41
 
 
 
42
  global $relevanssi_variables;
43
  global $wpdb;
44
 
@@ -56,16 +58,15 @@ $relevanssi_variables['database_version'] = 5;
56
  $relevanssi_variables['file'] = __FILE__;
57
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
58
 
59
- require_once 'lib/common.php';
60
- require_once 'lib/excerpts-highlights.php';
61
- require_once 'lib/indexing.php';
62
- require_once 'lib/init.php';
63
  require_once 'lib/install.php';
 
64
  require_once 'lib/interface.php';
 
65
  require_once 'lib/log.php';
66
- require_once 'lib/privacy.php';
 
67
  require_once 'lib/search.php';
 
68
  require_once 'lib/shortcodes.php';
69
- require_once 'lib/stopwords.php';
70
- require_once 'lib/sorting.php';
71
- require_once 'lib/uninstall.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.1
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
39
 
40
  define( 'RELEVANSSI_PREMIUM', false );
41
 
42
+ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'relevanssi_action_links' );
43
+
44
  global $relevanssi_variables;
45
  global $wpdb;
46
 
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';