Relevanssi – A Better Search - Version 4.6.0

Version Description

  • Changed behaviour: Phrases in OR search are now less restrictive. A search for 'foo "bar baz"' used to only return posts with the "bar baz" phrase, but now also posts with just the word 'foo' will be returned.
  • Minor fix: User Access Manager showed drafts in search results for all users. This is now fixed.
Download this release

Release Info

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

Code changes from version 4.5.0 to 4.6.0

lib/common.php CHANGED
@@ -413,6 +413,8 @@ function relevanssi_recognize_phrases( $search_query, $operator = 'AND' ) {
413
  return $all_queries;
414
  }
415
 
 
 
416
  foreach ( $phrases as $phrase ) {
417
  $queries = array();
418
  $phrase = $wpdb->esc_like( $phrase );
@@ -501,6 +503,8 @@ function relevanssi_recognize_phrases( $search_query, $operator = 'AND' ) {
501
  $queries = implode( ' OR relevanssi.doc IN ', $queries );
502
  $queries = "(relevanssi.doc IN $queries)";
503
  $all_queries[] = $queries;
 
 
504
  }
505
 
506
  $operator = strtoupper( $operator );
@@ -512,7 +516,10 @@ function relevanssi_recognize_phrases( $search_query, $operator = 'AND' ) {
512
  $all_queries = ' AND ( ' . implode( ' ' . $operator . ' ', $all_queries ) . ' ) ';
513
  }
514
 
515
- return $all_queries;
 
 
 
516
  }
517
 
518
  /**
@@ -1913,6 +1920,8 @@ function relevanssi_remove_page_builder_shortcodes( $content ) {
1913
  '/\[et_pb_sidebar.*?\].*\[\/et_pb_sidebar\]/im',
1914
  '/\[et_pb_fullwidth_slider.*?\].*\[\/et_pb_fullwidth_slider\]/im',
1915
  '/\[vc_raw_html.*?\].*\[\/vc_raw_html\]/im',
 
 
1916
  // Remove only the tags.
1917
  '/\[\/?et_pb.*?\]/im',
1918
  '/\[\/?vc.*?\]/im',
413
  return $all_queries;
414
  }
415
 
416
+ $phrase_queries = array();
417
+
418
  foreach ( $phrases as $phrase ) {
419
  $queries = array();
420
  $phrase = $wpdb->esc_like( $phrase );
503
  $queries = implode( ' OR relevanssi.doc IN ', $queries );
504
  $queries = "(relevanssi.doc IN $queries)";
505
  $all_queries[] = $queries;
506
+
507
+ $phrase_queries[ $phrase ] = $queries;
508
  }
509
 
510
  $operator = strtoupper( $operator );
516
  $all_queries = ' AND ( ' . implode( ' ' . $operator . ' ', $all_queries ) . ' ) ';
517
  }
518
 
519
+ return array(
520
+ 'and' => $all_queries,
521
+ 'or' => $phrase_queries,
522
+ );
523
  }
524
 
525
  /**
1920
  '/\[et_pb_sidebar.*?\].*\[\/et_pb_sidebar\]/im',
1921
  '/\[et_pb_fullwidth_slider.*?\].*\[\/et_pb_fullwidth_slider\]/im',
1922
  '/\[vc_raw_html.*?\].*\[\/vc_raw_html\]/im',
1923
+ '/\[fusion_imageframe.*?\].*\[\/fusion_imageframe\]/im',
1924
+ '/\[fusion_code.*?\].*\[\/fusion_code\]/im',
1925
  // Remove only the tags.
1926
  '/\[\/?et_pb.*?\]/im',
1927
  '/\[\/?vc.*?\]/im',
lib/compatibility/gutenberg.php CHANGED
@@ -10,8 +10,31 @@
10
  * @see https://www.relevanssi.com/
11
  */
12
 
13
- if ( RELEVANSSI_PREMIUM ) {
14
- add_action( 'rest_after_insert_post', 'relevanssi_save_gutenberg_postdata' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
 
17
  add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 10 );
10
  * @see https://www.relevanssi.com/
11
  */
12
 
13
+ /**
14
+ * Registers rest_after_insert_{post_type} actions for all indexed post types.
15
+ *
16
+ * Runs on `admin_init` action hook and registers the function
17
+ * `relevanssi_save_gutenberg_postdata` for all indexed post types.
18
+ *
19
+ * @see relevanssi_save_gutenberg_postdata
20
+ */
21
+ function relevanssi_register_gutenberg_actions() {
22
+ if ( ! RELEVANSSI_PREMIUM ) {
23
+ return;
24
+ }
25
+ $index_post_types = get_option( 'relevanssi_index_post_types', array() );
26
+ array_walk(
27
+ $index_post_types,
28
+ function ( $post_type ) {
29
+ if ( 'bogus' !== $post_type ) {
30
+ add_action(
31
+ 'rest_after_insert_' . $post_type,
32
+ 'relevanssi_save_gutenberg_postdata'
33
+ );
34
+ }
35
+
36
+ }
37
+ );
38
  }
39
 
40
  add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 10 );
lib/compatibility/useraccessmanager.php CHANGED
@@ -22,11 +22,17 @@ add_filter( 'relevanssi_post_ok', 'relevanssi_useraccessmanager_compatibility',
22
  * otherwise false.
23
  */
24
  function relevanssi_useraccessmanager_compatibility( $post_ok, $post_id ) {
25
- // phpcs:disable WordPress.NamingConventions.ValidVariableName
26
- global $userAccessManager;
27
- $type = relevanssi_get_post_type( $post_id );
28
- $post_ok = $userAccessManager->getAccessHandler()->checkObjectAccess( $type, $post_id );
29
- // phpcs:enable WordPress.NamingConventions.ValidVariableName
 
 
 
 
 
 
30
 
31
  return $post_ok;
32
  }
22
  * otherwise false.
23
  */
24
  function relevanssi_useraccessmanager_compatibility( $post_ok, $post_id ) {
25
+ $status = relevanssi_get_post_status( $post_id );
26
+
27
+ if ( 'publish' === $status ) {
28
+ // Only apply to published posts, don't apply to drafts.
29
+
30
+ // phpcs:disable WordPress.NamingConventions.ValidVariableName
31
+ global $userAccessManager;
32
+ $type = relevanssi_get_post_type( $post_id );
33
+ $post_ok = $userAccessManager->getAccessHandler()->checkObjectAccess( $type, $post_id );
34
+ // phpcs:enable WordPress.NamingConventions.ValidVariableName
35
+ }
36
 
37
  return $post_ok;
38
  }
lib/excerpts-highlights.php CHANGED
@@ -1194,7 +1194,14 @@ function relevanssi_get_custom_field_content( $post_id ) {
1194
 
1195
  // Flatten other array data.
1196
  if ( is_array( $value ) ) {
1197
- $value = implode( ' ', $value );
 
 
 
 
 
 
 
1198
  }
1199
  $custom_field_content .= ' ' . $value;
1200
  }
1194
 
1195
  // Flatten other array data.
1196
  if ( is_array( $value ) ) {
1197
+ $value_as_string = '';
1198
+ array_walk_recursive(
1199
+ $value,
1200
+ function( $val ) use ( &$value_as_string ) {
1201
+ $value_as_string .= ' ' . $val;
1202
+ }
1203
+ );
1204
+ $value = $value_as_string;
1205
  }
1206
  $custom_field_content .= ' ' . $value;
1207
  }
lib/indexing.php CHANGED
@@ -1199,7 +1199,6 @@ function relevanssi_index_custom_fields( &$insert_data, $post_id, $custom_fields
1199
  * @param int $post_id The post ID.
1200
  */
1201
  $values = apply_filters( 'relevanssi_custom_field_value', get_post_meta( $post_id, $field, false ), $field, $post_id );
1202
-
1203
  if ( empty( $values ) || ! is_array( $values ) ) {
1204
  continue;
1205
  }
@@ -1230,12 +1229,20 @@ function relevanssi_index_custom_fields( &$insert_data, $post_id, $custom_fields
1230
  relevanssi_debug_echo( "\tKey: " . $field . ' - value: ' . $value );
1231
  }
1232
 
 
 
 
 
 
 
 
1233
  /** This filter is documented in lib/indexing.php */
1234
  $value_tokens = apply_filters(
1235
  'relevanssi_indexing_tokens',
1236
- relevanssi_tokenize( $value, true, $min_word_length ),
1237
- 'custom_field'
1238
  );
 
1239
  foreach ( $value_tokens as $token => $count ) {
1240
  $n++;
1241
  if ( ! isset( $insert_data[ $token ]['customfield'] ) ) {
1199
  * @param int $post_id The post ID.
1200
  */
1201
  $values = apply_filters( 'relevanssi_custom_field_value', get_post_meta( $post_id, $field, false ), $field, $post_id );
 
1202
  if ( empty( $values ) || ! is_array( $values ) ) {
1203
  continue;
1204
  }
1229
  relevanssi_debug_echo( "\tKey: " . $field . ' - value: ' . $value );
1230
  }
1231
 
1232
+ $context = 'custom_field';
1233
+ $remove_stops = true;
1234
+ if ( '_relevanssi_pdf_content' === $field ) {
1235
+ $context = 'body';
1236
+ $remove_stops = 'body';
1237
+ }
1238
+
1239
  /** This filter is documented in lib/indexing.php */
1240
  $value_tokens = apply_filters(
1241
  'relevanssi_indexing_tokens',
1242
+ relevanssi_tokenize( $value, $remove_stops, $min_word_length ),
1243
+ $context
1244
  );
1245
+
1246
  foreach ( $value_tokens as $token => $count ) {
1247
  $n++;
1248
  if ( ! isset( $insert_data[ $token ]['customfield'] ) ) {
lib/search-query-restrictions.php CHANGED
@@ -25,6 +25,11 @@ function relevanssi_process_query_args( $args ) {
25
  $query = '';
26
  $query_no_synonyms = '';
27
 
 
 
 
 
 
28
  if ( function_exists( 'wp_encode_emoji' ) ) {
29
  $query = wp_encode_emoji( $args['q'] );
30
  $query_no_synonyms = wp_encode_emoji( $args['q_no_synonyms'] );
@@ -71,8 +76,7 @@ function relevanssi_process_query_args( $args ) {
71
 
72
  $phrases = relevanssi_recognize_phrases( $query, $args['operator'] );
73
  if ( $phrases ) {
74
- $query_restrictions .= " $phrases";
75
- // Clean: $phrases is escaped earlier.
76
  }
77
 
78
  if ( $args['post_type'] || $args['include_attachments'] ) {
@@ -92,6 +96,7 @@ function relevanssi_process_query_args( $args ) {
92
  'query_join' => $query_join,
93
  'query_query' => $query,
94
  'query_no_synonyms' => $query_no_synonyms,
 
95
  );
96
  }
97
 
@@ -536,3 +541,32 @@ function relevanssi_process_post_status( $post_status ) {
536
 
537
  return $query_restrictions;
538
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  $query = '';
26
  $query_no_synonyms = '';
27
 
28
+ $phrase_query_restrictions = array(
29
+ 'and' => '',
30
+ 'or' => array(),
31
+ );
32
+
33
  if ( function_exists( 'wp_encode_emoji' ) ) {
34
  $query = wp_encode_emoji( $args['q'] );
35
  $query_no_synonyms = wp_encode_emoji( $args['q_no_synonyms'] );
76
 
77
  $phrases = relevanssi_recognize_phrases( $query, $args['operator'] );
78
  if ( $phrases ) {
79
+ $phrase_query_restrictions = $phrases;
 
80
  }
81
 
82
  if ( $args['post_type'] || $args['include_attachments'] ) {
96
  'query_join' => $query_join,
97
  'query_query' => $query,
98
  'query_no_synonyms' => $query_no_synonyms,
99
+ 'phrase_queries' => $phrase_query_restrictions,
100
  );
101
  }
102
 
541
 
542
  return $query_restrictions;
543
  }
544
+
545
+ /**
546
+ * Adds phrase restrictions to the query.
547
+ *
548
+ * For OR searches, adds the phrases only for matching terms that are in the
549
+ * phrases, achieving the OR search effect for phrases: posts without the phrase
550
+ * but with another search term are not excluded from the search. In AND
551
+ * searches, all search terms must match to documents containing the phrase.
552
+ *
553
+ * @param string $query_restrictions The MySQL query restriction for the search.
554
+ * @param array $phrase_queries The phrase queries - 'and' contains the
555
+ * main query, while 'or' has the phrase-specific queries.
556
+ * @param string $term The current search term.
557
+ * @param string $operator AND or OR.
558
+ *
559
+ * @return string The query restrictions with the phrase restrictions added.
560
+ */
561
+ function relevanssi_add_phrase_restrictions( $query_restrictions, $phrase_queries, $term, $operator ) {
562
+ if ( 'OR' === $operator ) {
563
+ foreach ( $phrase_queries['or'] as $phrase_terms => $restriction ) {
564
+ if ( relevanssi_stripos( $phrase_terms, $term ) !== false ) {
565
+ $query_restrictions .= ' AND ' . $restriction;
566
+ }
567
+ }
568
+ } else {
569
+ $query_restrictions .= $phrase_queries['and'];
570
+ }
571
+ return $query_restrictions;
572
+ }
lib/search.php CHANGED
@@ -130,6 +130,7 @@ function relevanssi_search( $args ) {
130
  $query_join = $query_data['query_join'];
131
  $q = $query_data['query_query'];
132
  $q_no_synonyms = $query_data['query_no_synonyms'];
 
133
 
134
  /**
135
  * Filters whether stopwords are removed from titles.
@@ -269,9 +270,17 @@ function relevanssi_search( $args ) {
269
  if ( null === $term_cond ) {
270
  continue;
271
  }
 
 
 
 
 
 
 
 
272
  $query = "SELECT COUNT(DISTINCT(relevanssi.doc)) FROM $relevanssi_table AS relevanssi
273
- $query_join WHERE $term_cond $query_restrictions";
274
- // Clean: $query_restrictions is escaped, $term_cond is escaped.
275
  /**
276
  * Filters the DF query.
277
  *
@@ -306,13 +315,20 @@ function relevanssi_search( $args ) {
306
  foreach ( $df_counts as $term => $df ) {
307
  $term_cond = relevanssi_generate_term_where( $term, $search_again, $no_terms );
308
 
 
 
 
 
 
 
 
309
  $query = "SELECT DISTINCT(relevanssi.doc), relevanssi.*, relevanssi.title * $title_boost +
310
  relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
311
  relevanssi.tag * $tag + relevanssi.link * $link_boost +
312
  relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
313
  relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
314
- FROM $relevanssi_table AS relevanssi $query_join WHERE $term_cond $query_restrictions";
315
- /** Clean: $query_restrictions is escaped, $term_cond is escaped. */
316
 
317
  /**
318
  * Filters the Relevanssi MySQL query.
130
  $query_join = $query_data['query_join'];
131
  $q = $query_data['query_query'];
132
  $q_no_synonyms = $query_data['query_no_synonyms'];
133
+ $phrase_queries = $query_data['phrase_queries'];
134
 
135
  /**
136
  * Filters whether stopwords are removed from titles.
270
  if ( null === $term_cond ) {
271
  continue;
272
  }
273
+
274
+ $this_query_restrictions = relevanssi_add_phrase_restrictions(
275
+ $query_restrictions,
276
+ $phrase_queries,
277
+ $term,
278
+ $operator
279
+ );
280
+
281
  $query = "SELECT COUNT(DISTINCT(relevanssi.doc)) FROM $relevanssi_table AS relevanssi
282
+ $query_join WHERE $term_cond $this_query_restrictions";
283
+ // Clean: $this_query_restrictions is escaped, $term_cond is escaped.
284
  /**
285
  * Filters the DF query.
286
  *
315
  foreach ( $df_counts as $term => $df ) {
316
  $term_cond = relevanssi_generate_term_where( $term, $search_again, $no_terms );
317
 
318
+ $this_query_restrictions = relevanssi_add_phrase_restrictions(
319
+ $query_restrictions,
320
+ $phrase_queries,
321
+ $term,
322
+ $operator
323
+ );
324
+
325
  $query = "SELECT DISTINCT(relevanssi.doc), relevanssi.*, relevanssi.title * $title_boost +
326
  relevanssi.content * $content_boost + relevanssi.comment * $comment_boost +
327
  relevanssi.tag * $tag + relevanssi.link * $link_boost +
328
  relevanssi.author + relevanssi.category * $cat + relevanssi.excerpt +
329
  relevanssi.taxonomy + relevanssi.customfield + relevanssi.mysqlcolumn AS tf
330
+ FROM $relevanssi_table AS relevanssi $query_join WHERE $term_cond $this_query_restrictions";
331
+ /** Clean: $this_query_restrictions is escaped, $term_cond is escaped. */
332
 
333
  /**
334
  * Filters the Relevanssi MySQL query.
lib/tabs/indexing-tab.php CHANGED
@@ -275,14 +275,14 @@ function relevanssi_indexing_tab() {
275
  $checked = 'checked="checked"';
276
  }
277
 
278
- // Translators: %s is the post type name.
279
  $screen_reader_label = sprintf( __( 'Index taxonomy %s', 'relevanssi' ), $taxonomy->name );
280
  $public = __( 'no', 'relevanssi' );
281
- // Translators: %s is the post type name.
282
  $screen_reader_public = sprintf( __( 'Taxonomy %s is not public', 'relevanssi' ), $taxonomy->name );
283
  if ( $taxonomy->public ) {
284
  $public = __( 'yes', 'relevanssi' );
285
- // Translators: %s is the post type name.
286
  $screen_reader_public = sprintf( __( 'Taxonomy %s is public', 'relevanssi' ), $taxonomy->name );
287
  }
288
 
275
  $checked = 'checked="checked"';
276
  }
277
 
278
+ // Translators: %s is the taxonomy name.
279
  $screen_reader_label = sprintf( __( 'Index taxonomy %s', 'relevanssi' ), $taxonomy->name );
280
  $public = __( 'no', 'relevanssi' );
281
+ // Translators: %s is the taxonomy name.
282
  $screen_reader_public = sprintf( __( 'Taxonomy %s is not public', 'relevanssi' ), $taxonomy->name );
283
  if ( $taxonomy->public ) {
284
  $public = __( 'yes', 'relevanssi' );
285
+ // Translators: %s is the taxonomy name.
286
  $screen_reader_public = sprintf( __( 'Taxonomy %s is public', 'relevanssi' ), $taxonomy->name );
287
  }
288
 
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: search, relevance, better search
5
  Requires at least: 4.9
6
  Tested up to: 5.3.2
7
  Requires PHP: 5.6
8
- Stable tag: 4.5.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -133,6 +133,10 @@ Each document database is full of useless words. All the little words that appea
133
  * John Calahan for extensive 4.0 beta testing.
134
 
135
  == Changelog ==
 
 
 
 
136
  = 4.5.0 =
137
  * New feature: New filter hook `relevanssi_disable_stopwords` can be used to disable stopwords completely. Just add a filter function that returns `true`.
138
  * Changed behaviour: Stopwords are no longer automatically restored if emptied. It's now possible to empty the stopword list. If you want to restore the stopwords from the file (or from the database, if you're upgrading from an earlier version of Relevanssi and find your stopwords missing), just click the button on the stopwords settings page that restores the stopwords.
@@ -158,6 +162,9 @@ Each document database is full of useless words. All the little words that appea
158
  * Minor fix: Indexing did not adjust the number of posts indexed at one go like it should.
159
 
160
  == Upgrade notice ==
 
 
 
161
  = 4.5.0 =
162
  * Bug fixes and improvements to stopword management.
163
 
5
  Requires at least: 4.9
6
  Tested up to: 5.3.2
7
  Requires PHP: 5.6
8
+ Stable tag: 4.6.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
133
  * John Calahan for extensive 4.0 beta testing.
134
 
135
  == Changelog ==
136
+ = 4.6.0 =
137
+ * Changed behaviour: Phrases in OR search are now less restrictive. A search for 'foo "bar baz"' used to only return posts with the "bar baz" phrase, but now also posts with just the word 'foo' will be returned.
138
+ * Minor fix: User Access Manager showed drafts in search results for all users. This is now fixed.
139
+
140
  = 4.5.0 =
141
  * New feature: New filter hook `relevanssi_disable_stopwords` can be used to disable stopwords completely. Just add a filter function that returns `true`.
142
  * Changed behaviour: Stopwords are no longer automatically restored if emptied. It's now possible to empty the stopword list. If you want to restore the stopwords from the file (or from the database, if you're upgrading from an earlier version of Relevanssi and find your stopwords missing), just click the button on the stopwords settings page that restores the stopwords.
162
  * Minor fix: Indexing did not adjust the number of posts indexed at one go like it should.
163
 
164
  == Upgrade notice ==
165
+ = 4.6.0 =
166
+ * Changes how phrases work in OR search and fixes a User Access Manager issue.
167
+
168
  = 4.5.0 =
169
  * Bug fixes and improvements to stopword management.
170
 
relevanssi.php CHANGED
@@ -13,14 +13,14 @@
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.5.0
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
20
  */
21
 
22
  /**
23
- * Copyright 2019 Mikko Saari (email: mikko@mikkosaari.fi)
24
  * This file is part of Relevanssi, a search plugin for WordPress.
25
  *
26
  * Relevanssi is free software: you can redistribute it and/or modify
@@ -65,7 +65,7 @@ $relevanssi_variables['database_version'] = 5;
65
  $relevanssi_variables['file'] = __FILE__;
66
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
67
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
68
- $relevanssi_variables['plugin_version'] = '4.5.0';
69
 
70
  require_once 'lib/admin-ajax.php';
71
  require_once 'lib/common.php';
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
+ * Version: 4.6.0
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
20
  */
21
 
22
  /**
23
+ * Copyright 2020 Mikko Saari (email: mikko@mikkosaari.fi)
24
  * This file is part of Relevanssi, a search plugin for WordPress.
25
  *
26
  * Relevanssi is free software: you can redistribute it and/or modify
65
  $relevanssi_variables['file'] = __FILE__;
66
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
67
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
68
+ $relevanssi_variables['plugin_version'] = '4.6.0';
69
 
70
  require_once 'lib/admin-ajax.php';
71
  require_once 'lib/common.php';