Relevanssi – A Better Search - Version 4.16.0

Version Description

  • New feature: Oxygen compatibility has been upgraded to support JSON data from Oxygen 4. This is still in early stages, so feedback from Oxygen users is welcome.
  • New feature: New filter hook relevanssi_oxygen_element is used to filter Oxygen JSON elements. The earlier relevanssi_oxygen_section_filters and relevanssi_oxygen_section_content filters are no longer used with Oxygen 4; this hook is the only way to filter Oxygen elements.
  • Changed behaviour: Relevanssi now applies remove_accents() to all strings. This is because default database collations do not care for accents and having accents may cause missing information in indexing. If you use a database collation that doesn't ignore accents, make sure you disable this filter.
  • Minor fix: Relevanssi used the_category filter with too few parameters. The missing parameters have been added.
  • Minor fix: Stops drafts and pending posts from showing up in Relevanssi Live Ajax Searches.
  • Minor fix: Phrases weren't used in some cases where a multiple-word phrase looked like a single-word phrase.
  • Minor fix: Prevents fatal errors from relevanssi_strip_all_tags().
Download this release

Release Info

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

Code changes from version 4.14.7 to 4.16.0

changelog.txt CHANGED
@@ -1,3 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  = 4.13.3.1 =
2
  * Minor fix: The Bricks compatibility was broken. This version fixes it.
3
 
1
+ = 4.14.7 =
2
+ * User interface: The synonym settings page now alerts if the synonyms aren't active because of the AND search.
3
+
4
+ = 4.14.6 =
5
+ * Security fix: Extra hardening for AJAX requests. Some AJAX actions in Relevanssi could leak information to site subscribers who knew what to look for.
6
+
7
+ = 4.14.5 =
8
+ * Security fix: Any registered user could empty the Relevanssi index by triggering the index truncate AJAX action. That is no longer possible.
9
+ * New feature: The [searchform] shortcode has a new parameter, 'checklist', which you can use to create taxonomy checklists.
10
+ * Changed behaviour: The `relevanssi_search_again` parameter array has more parameters the filter can modify.
11
+ * Changed behaviour: The `relevanssi_show_matches` filter hook gets the post object as the second parameter.
12
+ * Minor fix: The `cats` and `tags` parameters work better and support array values.
13
+
14
+ = 4.14.4 =
15
+ * Minor fix: `relevanssi_orderby` did not always accept an array-format orderby parameter.
16
+ * Minor fix: Removes a highlighting problem stemming from uppercase search terms.
17
+ * Minor fix: Relevanssi removes highlights better from inside multiline HTML tags.
18
+ * Minor fix: When image attachment indexing was disabled, saving image attachments would still index the images. Image attachment blocking is now a `relevanssi_indexing_restriction` filter function, which means it's always active.
19
+
20
+ = 4.14.3 =
21
+ * Security fix: User searches page had a XSS vulnerability.
22
+
23
+ = 4.14.2 =
24
+ * Minor fix: Remove unnecessary database calls from admin pages.
25
+ * Minor fix: Improved Oxygen compatibility.
26
+
27
+ = 4.14.1 =
28
+ * Adds a missing file.
29
+
30
+ = 4.14.0 =
31
+ * New feature: New filter hook `relevanssi_render_blocks` controls whether Relevanssi renders blocks in a post or not. If you are having problems updating long posts with lots of blocks, having this filter hook return `false` for the post in question will likely help, as rendering the blocks in a long post can take huge amounts of memory.
32
+ * New feature: The user searches page has been improved a lot.
33
+ * New feature: The [searchform] shortcode has a new parameter, 'post_type_boxes', which creates a checkbox for each post type you list in the value. For example [searchform post_type_boxes='*post,page'] would create a search with a checkbox for 'post' and 'page' post types, with 'post' pre-checked.
34
+ * New feature: You can now have multiple dropdowns in one [searchform] shortcode. Anything that begins with 'dropdown' is considered a dropdown parameter, so you can do [searchform dropdown_1='category' dropdown_2='post_tag'] for example.
35
+ * New feature: New filter hook `relevanssi_search_params` lets you filter search parameters after they've been collected from the WP_Query.
36
+ * New feature: New filter hook `relevanssi_excerpt_post` lets you make Relevanssi skip creating excerpts for specific posts.
37
+ * Changed behaviour: Filter hooks `relevanssi_1day`, `relevanssi_7days` and `relevanssi_30days` are removed, as the user searches page is now different. The default value for `relevanssi_user_searches_limit` is now 100 instead of 20.
38
+ * Minor fix: In some languages, iOS uses „“ for quotes. Relevanssi now understands those for the phrase operator.
39
+ * Minor fix: Stops Relevanssi from blocking the admin search for WooCommerce coupons and other WooCommerce custom post types.
40
+ * Minor fix: Fixes problems with the WP-Members compatibility.
41
+ * Minor fix: New parameter for `relevanssi_tokenize()` introduces the context (indexing or search query). The `relevanssi_extract_phrases()` is only used on search queries.
42
+ * Minor fix: Relevanssi won't let you adjust synonyms and stopwords anymore if you use Polylang and are in 'Show all languages' mode.
43
+ * Minor fix: Highlighting is improved by a more precise HTML entity filter, thanks to Jacob Bearce.
44
+
45
  = 4.13.3.1 =
46
  * Minor fix: The Bricks compatibility was broken. This version fixes it.
47
 
lib/class-relevanssi-taxonomy-walker.php CHANGED
@@ -57,18 +57,18 @@ class Relevanssi_Taxonomy_Walker extends Walker_Category_Checklist {
57
  $aria_checked = 'false';
58
  $inner_class = 'category';
59
 
60
- /** This filter is documented in wp-includes/category-template.php */
61
  $output .= "\n" . '<li' . $class . '>' .
62
  '<div class="' . $inner_class . '" data-term-id=' . $category->term_id .
63
  ' tabindex="0" role="checkbox" aria-checked="' . $aria_checked . '">' .
64
- esc_html( apply_filters( 'the_category', $category->name ) ) . '</div>';
65
- } else {
66
  /** This filter is documented in wp-includes/category-template.php */
 
 
67
  $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
68
  '<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="' . $name . '[]" id="in-' . $taxonomy . '-' . $category->term_id . '"' .
69
  checked( in_array( intval( $category->term_id ), $args['selected_cats'], true ), true, false ) .
70
  disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
71
- esc_html( apply_filters( 'the_category', $category->name ) ) . '</label>';
 
72
  }
73
  }
74
  }
57
  $aria_checked = 'false';
58
  $inner_class = 'category';
59
 
 
60
  $output .= "\n" . '<li' . $class . '>' .
61
  '<div class="' . $inner_class . '" data-term-id=' . $category->term_id .
62
  ' tabindex="0" role="checkbox" aria-checked="' . $aria_checked . '">' .
 
 
63
  /** This filter is documented in wp-includes/category-template.php */
64
+ esc_html( apply_filters( 'the_category', $category->name, '', '' ) ) . '</div>';
65
+ } else {
66
  $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
67
  '<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="' . $name . '[]" id="in-' . $taxonomy . '-' . $category->term_id . '"' .
68
  checked( in_array( intval( $category->term_id ), $args['selected_cats'], true ), true, false ) .
69
  disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
70
+ /** This filter is documented in wp-includes/category-template.php */
71
+ esc_html( apply_filters( 'the_category', $category->name, '', '' ) ) . '</label>';
72
  }
73
  }
74
  }
lib/common.php CHANGED
@@ -11,7 +11,7 @@
11
  /**
12
  * Adds the search result match breakdown to the post object.
13
  *
14
- * Reads in the number of matches and stores it in the relevanssi_hits filed
15
  * of the post object. The post object is passed as a reference and modified
16
  * on the fly.
17
  *
@@ -28,11 +28,15 @@ function relevanssi_add_matches( &$post, $data ) {
28
  $hits['author'] = $data['author_matches'][ $post->ID ] ?? 0;
29
  $hits['excerpt'] = $data['excerpt_matches'][ $post->ID ] ?? 0;
30
  $hits['customfield'] = $data['customfield_matches'][ $post->ID ] ?? 0;
31
- $hits['mysqlcolumn'] = $data['mysqlcolumn_matches'][ $post->ID ] ?? 0;
32
  $hits['score'] = isset( $data['doc_weights'][ $post->ID ] ) ? round( $data['doc_weights'][ $post->ID ], 2 ) : 0;
33
  $hits['terms'] = $data['term_hits'][ $post->ID ] ?? array();
34
  $hits['missing_terms'] = $data['missing_terms'][ $post->ID ] ?? array();
35
 
 
 
 
 
36
  arsort( $hits['terms'] );
37
 
38
  $post->relevanssi_hits = $hits;
@@ -220,7 +224,7 @@ function relevanssi_default_post_ok( $post_ok, $post_id ) {
220
  apply_filters( 'relevanssi_valid_admin_status', array( 'draft', 'pending', 'future' ) ),
221
  true
222
  )
223
- && is_admin() ) {
224
  // Only show drafts, pending and future posts in admin search.
225
  $post_ok = true;
226
  }
@@ -1134,6 +1138,9 @@ function relevanssi_common_words( $limit = 25, $wp_cli = false ) {
1134
  */
1135
  function relevanssi_get_forbidden_post_types() {
1136
  return array(
 
 
 
1137
  'nav_menu_item', // Navigation menu items.
1138
  'revision', // Never index revisions.
1139
  'acf', // Advanced Custom Fields.
@@ -1214,6 +1221,12 @@ function relevanssi_get_forbidden_post_types() {
1214
  'fl-builder-template', // Beaver Builder.
1215
  'itsec-dashboard', // iThemes Security.
1216
  'itsec-dash-card', // iThemes Security.
 
 
 
 
 
 
1217
  );
1218
  }
1219
 
@@ -1224,6 +1237,7 @@ function relevanssi_get_forbidden_post_types() {
1224
  */
1225
  function relevanssi_get_forbidden_taxonomies() {
1226
  return array(
 
1227
  'nav_menu', // Navigation menus.
1228
  'link_category', // Link categories.
1229
  'amp_validation_error', // AMP.
@@ -1717,6 +1731,9 @@ function relevanssi_replace_synonyms_in_terms( array $terms ) : array {
1717
  function ( $term ) use ( $synonyms ) {
1718
  $new_term = array();
1719
  foreach ( $synonyms as $pair ) {
 
 
 
1720
  list( $key, $value ) = explode( '=', $pair );
1721
  if ( $value === $term ) {
1722
  $new_term[] = $key;
@@ -1794,3 +1811,40 @@ function relevanssi_bot_block_list() : array {
1794
  );
1795
  return $bots;
1796
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  /**
12
  * Adds the search result match breakdown to the post object.
13
  *
14
+ * Reads in the number of matches and stores it in the relevanssi_hits field
15
  * of the post object. The post object is passed as a reference and modified
16
  * on the fly.
17
  *
28
  $hits['author'] = $data['author_matches'][ $post->ID ] ?? 0;
29
  $hits['excerpt'] = $data['excerpt_matches'][ $post->ID ] ?? 0;
30
  $hits['customfield'] = $data['customfield_matches'][ $post->ID ] ?? 0;
31
+ $hits['mysqlcolumn'] = 0;
32
  $hits['score'] = isset( $data['doc_weights'][ $post->ID ] ) ? round( $data['doc_weights'][ $post->ID ], 2 ) : 0;
33
  $hits['terms'] = $data['term_hits'][ $post->ID ] ?? array();
34
  $hits['missing_terms'] = $data['missing_terms'][ $post->ID ] ?? array();
35
 
36
+ if ( function_exists( 'relevanssi_premium_add_matches' ) ) {
37
+ relevanssi_premium_add_matches( $hits, $data, $post->ID );
38
+ }
39
+
40
  arsort( $hits['terms'] );
41
 
42
  $post->relevanssi_hits = $hits;
224
  apply_filters( 'relevanssi_valid_admin_status', array( 'draft', 'pending', 'future' ) ),
225
  true
226
  )
227
+ && is_admin() && ! relevanssi_is_live_search() ) {
228
  // Only show drafts, pending and future posts in admin search.
229
  $post_ok = true;
230
  }
1138
  */
1139
  function relevanssi_get_forbidden_post_types() {
1140
  return array(
1141
+ 'wp_template_part', // WP template parts.
1142
+ 'wp_global_styles', // WP global styles.
1143
+ 'wp_navigation', // Navigation menus.
1144
  'nav_menu_item', // Navigation menu items.
1145
  'revision', // Never index revisions.
1146
  'acf', // Advanced Custom Fields.
1221
  'fl-builder-template', // Beaver Builder.
1222
  'itsec-dashboard', // iThemes Security.
1223
  'itsec-dash-card', // iThemes Security.
1224
+ 'astra-advanced-hook', // Astra.
1225
+ 'astra_adv_header', // Astra.
1226
+ 'astra_adv_header', // Astra.
1227
+ 'udb_widgets', // Ultimate Dashboard.
1228
+ 'udb_admin_page', // Ultimate Dashboard.
1229
+ 'oxy_user_library', // Oxygen.
1230
  );
1231
  }
1232
 
1237
  */
1238
  function relevanssi_get_forbidden_taxonomies() {
1239
  return array(
1240
+ 'wp_template_part_area', // WP templates.
1241
  'nav_menu', // Navigation menus.
1242
  'link_category', // Link categories.
1243
  'amp_validation_error', // AMP.
1731
  function ( $term ) use ( $synonyms ) {
1732
  $new_term = array();
1733
  foreach ( $synonyms as $pair ) {
1734
+ if ( empty( $pair ) ) {
1735
+ continue;
1736
+ }
1737
  list( $key, $value ) = explode( '=', $pair );
1738
  if ( $value === $term ) {
1739
  $new_term[] = $key;
1811
  );
1812
  return $bots;
1813
  }
1814
+
1815
+ /**
1816
+ * Removes unwanted metadata fields from custom field indexing.
1817
+ *
1818
+ * This function hooks on to relevanssi_index_custom_fields and stops Relevanssi
1819
+ * from indexing a bunch of custom fields than only contain metadata that is
1820
+ * not useful to index.
1821
+ *
1822
+ * @param array $custom_fields A list of custom field names.
1823
+ *
1824
+ * @return @array The custom fields with the excluded fields removed.
1825
+ */
1826
+ function relevanssi_remove_metadata_fields( array $custom_fields ) : array {
1827
+ $excluded_fields = array(
1828
+ '_edit_last',
1829
+ '_edit_lock',
1830
+ '_encloseme',
1831
+ '_pingme',
1832
+ '_relevanssi_hide_content',
1833
+ '_relevanssi_hide_content',
1834
+ '_relevanssi_hide_post',
1835
+ '_relevanssi_pin_for_all',
1836
+ '_relevanssi_pin_keywords',
1837
+ '_relevanssi_related_exclude_ids',
1838
+ '_relevanssi_related_include_ids',
1839
+ '_relevanssi_related_keywords',
1840
+ '_relevanssi_related_no_append',
1841
+ '_relevanssi_related_not_related',
1842
+ '_relevanssi_related_posts',
1843
+ '_relevanssi_unpin_keywords',
1844
+ '_thumbnail_id',
1845
+ '_wp_attachment_metadata',
1846
+ '_wp_page_template',
1847
+ 'classic-editor-remember',
1848
+ );
1849
+ return array_diff( $custom_fields, $excluded_fields );
1850
+ }
lib/compatibility/bricks.php CHANGED
@@ -11,6 +11,10 @@
11
  */
12
 
13
  add_filter( 'bricks/posts/query_vars', 'relevanssi_bricks_enable', 10 );
 
 
 
 
14
 
15
  /**
16
  * Enables Relevanssi in the query when the 's' query var is set.
@@ -25,3 +29,70 @@ function relevanssi_bricks_enable( $query_vars ) {
25
  }
26
  return $query_vars;
27
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  */
12
 
13
  add_filter( 'bricks/posts/query_vars', 'relevanssi_bricks_enable', 10 );
14
+ add_filter( 'relevanssi_custom_field_value', 'relevanssi_bricks_values', 10, 3 );
15
+ add_filter( 'relevanssi_index_custom_fields', 'relevanssi_add_bricks' );
16
+ add_filter( 'option_relevanssi_index_fields', 'relevanssi_bricks_fix_none_setting' );
17
+ add_action( 'save_post', 'relevanssi_insert_edit', 99, 1 );
18
 
19
  /**
20
  * Enables Relevanssi in the query when the 's' query var is set.
29
  }
30
  return $query_vars;
31
  }
32
+
33
+ /**
34
+ * Adds the `_bricks_page_content_2` to the list of indexed custom fields.
35
+ *
36
+ * @param array|boolean $fields An array of custom fields to index, or false.
37
+ *
38
+ * @return array An array of custom fields, including `_bricks_page_content_2`.
39
+ */
40
+ function relevanssi_add_bricks( $fields ) {
41
+ if ( ! is_array( $fields ) ) {
42
+ $fields = array();
43
+ }
44
+ if ( ! in_array( '_bricks_page_content_2', $fields, true ) ) {
45
+ $fields[] = '_bricks_page_content_2';
46
+ }
47
+
48
+ return $fields;
49
+ }
50
+
51
+ /**
52
+ * Includes only text from _bricks_page_content_2 custom field.
53
+ *
54
+ * This function goes through the multilevel array of _bricks_page_content_2
55
+ * and only picks up the "text" elements inside it, discarding everything else.
56
+ *
57
+ * @param array $value An array of custom field values.
58
+ * @param string $field The name of the custom field.
59
+ * @param int $post_id The post ID.
60
+ *
61
+ * @return array An array containing a string with all the values concatenated
62
+ * together.
63
+ */
64
+ function relevanssi_bricks_values( $value, $field, $post_id ) {
65
+ if ( '_bricks_page_content_2' !== $field ) {
66
+ return $value;
67
+ }
68
+
69
+ $content = '';
70
+ array_walk_recursive(
71
+ $value,
72
+ function( $text, $key ) use ( &$content ) {
73
+ if ( 'text' === $key ) {
74
+ $content .= ' ' . $text;
75
+ }
76
+ }
77
+ );
78
+
79
+ return array( $content );
80
+ }
81
+
82
+ /**
83
+ * Makes sure the Bricks builder shortcode is included in the index, even when
84
+ * the custom field setting is set to 'none'.
85
+ *
86
+ * @param string $value The custom field indexing setting value. The parameter
87
+ * is ignored, Relevanssi disables this filter and then checks the option to
88
+ * see what the value is.
89
+ *
90
+ * @return string If value is undefined, it's set to '_bricks_page_content_2'.
91
+ */
92
+ function relevanssi_bricks_fix_none_setting( $value ) {
93
+ if ( ! $value ) {
94
+ $value = '_bricks_page_content_2';
95
+ }
96
+
97
+ return $value;
98
+ }
lib/compatibility/oxygen.php CHANGED
@@ -35,6 +35,23 @@ add_action( 'save_post', 'relevanssi_insert_edit', 99, 1 );
35
  * @return array|null An array of custom field values, null if no value exists.
36
  */
37
  function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  if ( 'ct_builder_shortcodes_revisions_dates' === $field ) {
39
  return '';
40
  }
@@ -42,6 +59,9 @@ function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
42
  return '';
43
  }
44
  if ( 'ct_builder_shortcodes' === $field ) {
 
 
 
45
  if ( empty( $value ) ) {
46
  return null;
47
  }
@@ -118,18 +138,66 @@ function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
118
  }
119
 
120
  /**
121
- * Adds the `ct_builder_shortcodes` to the list of indexed custom fields.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  *
123
  * @param array|boolean $fields An array of custom fields to index, or false.
124
  *
125
- * @return array An array of custom fields, including `ct_builder_shortcodes`.
 
126
  */
127
  function relevanssi_add_oxygen( $fields ) {
 
 
 
128
  if ( ! is_array( $fields ) ) {
129
  $fields = array();
130
  }
131
- if ( ! in_array( 'ct_builder_shortcodes', $fields, true ) ) {
132
- $fields[] = 'ct_builder_shortcodes';
133
  }
134
 
135
  return $fields;
@@ -143,11 +211,14 @@ function relevanssi_add_oxygen( $fields ) {
143
  * is ignored, Relevanssi disables this filter and then checks the option to
144
  * see what the value is.
145
  *
146
- * @return string If value is undefined, it's set to 'ct_builder_shortcodes'.
 
147
  */
148
  function relevanssi_oxygen_fix_none_setting( $value ) {
149
  if ( ! $value ) {
150
- $value = 'ct_builder_shortcodes';
 
 
151
  }
152
 
153
  return $value;
35
  * @return array|null An array of custom field values, null if no value exists.
36
  */
37
  function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
38
+ if ( 'ct_builder_json' === $field ) {
39
+ $json = array();
40
+ foreach ( $value as $row ) {
41
+ $json[] = json_decode( $row );
42
+ }
43
+
44
+ $content = '';
45
+ if ( isset( $json[0]->children ) ) {
46
+ foreach ( $json[0]->children as $child ) {
47
+ $content .= relevanssi_process_oxygen_child( $child );
48
+ }
49
+ }
50
+
51
+ $value[0] = $content;
52
+ return $value;
53
+ }
54
+
55
  if ( 'ct_builder_shortcodes_revisions_dates' === $field ) {
56
  return '';
57
  }
59
  return '';
60
  }
61
  if ( 'ct_builder_shortcodes' === $field ) {
62
+ if ( version_compare( CT_VERSION, '4.0', '>=' ) ) {
63
+ return null;
64
+ }
65
  if ( empty( $value ) ) {
66
  return null;
67
  }
138
  }
139
 
140
  /**
141
+ * Recursively processes the Oxygen JSON data.
142
+ *
143
+ * This function extracts all the ct_content data from the JSON. All elements
144
+ * are run through the relevanssi_oxygen_element filter hook. You can use that
145
+ * filter hook to modify or to eliminate elements from the JSON.
146
+ *
147
+ * @param array $child The child element array.
148
+ *
149
+ * @return string The content from the child and the grandchildren.
150
+ */
151
+ function relevanssi_process_oxygen_child( $child ) : string {
152
+ /**
153
+ * Filters the Oxygen JSON child element.
154
+ *
155
+ * If the filter returns an empty value, the child element and all its
156
+ * children will be ignored.
157
+ *
158
+ * @param array $child The JSON child element.
159
+ */
160
+ $child = apply_filters( 'relevanssi_oxygen_element', $child );
161
+ if ( empty( $child ) ) {
162
+ return '';
163
+ }
164
+
165
+ $child_content = ' ';
166
+ if ( isset( $child->options->ct_content ) ) {
167
+ $child_content .= $child->options->ct_content;
168
+ }
169
+
170
+ if ( isset( $child->options->original->{'code-php'} ) ) {
171
+ // For code and HTML blocks, strip all tags.
172
+ $child_content .= wp_strip_all_tags( $child->options->original->{'code-php'} );
173
+ }
174
+
175
+ if ( isset( $child->children ) ) {
176
+ foreach ( $child->children as $grandchild ) {
177
+ $child_content .= relevanssi_process_oxygen_child( $grandchild );
178
+ }
179
+ }
180
+
181
+ return $child_content;
182
+ }
183
+
184
+ /**
185
+ * Adds the Oxygen custom field to the list of indexed custom fields.
186
  *
187
  * @param array|boolean $fields An array of custom fields to index, or false.
188
  *
189
+ * @return array An array of custom fields, including `ct_builder_json` or
190
+ * `ct_builder_shortcodes`.
191
  */
192
  function relevanssi_add_oxygen( $fields ) {
193
+ $oxygen_field = version_compare( CT_VERSION, '4.0', '>=' )
194
+ ? 'ct_builder_json'
195
+ : 'ct_builder_shortcodes';
196
  if ( ! is_array( $fields ) ) {
197
  $fields = array();
198
  }
199
+ if ( ! in_array( $oxygen_field, $fields, true ) ) {
200
+ $fields[] = $oxygen_field;
201
  }
202
 
203
  return $fields;
211
  * is ignored, Relevanssi disables this filter and then checks the option to
212
  * see what the value is.
213
  *
214
+ * @return string If value is undefined, it's set to 'ct_builder_json' or
215
+ * 'ct_builder_shortcodes'.
216
  */
217
  function relevanssi_oxygen_fix_none_setting( $value ) {
218
  if ( ! $value ) {
219
+ $value = version_compare( CT_VERSION, '4.0', '>=' )
220
+ ? 'ct_builder_json'
221
+ : 'ct_builder_shortcodes';
222
  }
223
 
224
  return $value;
lib/didyoumean.php CHANGED
@@ -161,6 +161,22 @@ function relevanssi_simple_generate_suggestion( $query ) {
161
  $suggestion = '';
162
 
163
  foreach ( $tokens as $token => $count ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  $closest = '';
165
  $distance = -1;
166
  foreach ( $data as $row ) {
161
  $suggestion = '';
162
 
163
  foreach ( $tokens as $token => $count ) {
164
+ /**
165
+ * Filters the tokens for Did you mean suggestions.
166
+ *
167
+ * You can use this filter hook to modify the tokens before Relevanssi
168
+ * tries to come up with Did you mean suggestions for them. If you
169
+ * return an empty string, the token will be skipped and no suggestion
170
+ * will be made for the token.
171
+ *
172
+ * @param string $token An individual word from the search query.
173
+ *
174
+ * @return string The token.
175
+ */
176
+ $token = apply_filters( 'relevanssi_didyoumean_token', trim( $token ) );
177
+ if ( ! $token ) {
178
+ continue;
179
+ }
180
  $closest = '';
181
  $distance = -1;
182
  foreach ( $data as $row ) {
lib/excerpts-highlights.php CHANGED
@@ -122,7 +122,11 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
122
 
123
  // Add the custom field content.
124
  if ( 'on' === get_option( 'relevanssi_excerpt_custom_fields' ) ) {
125
- $content .= relevanssi_get_custom_field_content( $post->ID );
 
 
 
 
126
  }
127
 
128
  /**
@@ -992,9 +996,11 @@ function relevanssi_extract_locations( $words, $fulltext ) {
992
  * @return int Number of times the words appear in the text.
993
  */
994
  function relevanssi_count_matches( $words, $complete_text ) {
995
- $count = 0;
996
- $lowercase_text = relevanssi_strtolower( $complete_text, 'UTF-8' );
997
- $text = '';
 
 
998
 
999
  $count_words = count( $words );
1000
  for ( $t = 0; $t < $count_words; $t++ ) {
122
 
123
  // Add the custom field content.
124
  if ( 'on' === get_option( 'relevanssi_excerpt_custom_fields' ) ) {
125
+ if ( 'user' === $post->post_type && function_exists( 'relevanssi_get_user_custom_field_content' ) ) {
126
+ $content .= relevanssi_get_user_custom_field_content( $post->ID );
127
+ } else {
128
+ $content .= relevanssi_get_custom_field_content( $post->ID );
129
+ }
130
  }
131
 
132
  /**
996
  * @return int Number of times the words appear in the text.
997
  */
998
  function relevanssi_count_matches( $words, $complete_text ) {
999
+ $count = 0;
1000
+ $text = '';
1001
+
1002
+ // Add the space in case the match is the last word in the text.
1003
+ $lowercase_text = relevanssi_strtolower( $complete_text, 'UTF-8' ) . ' ';
1004
 
1005
  $count_words = count( $words );
1006
  for ( $t = 0; $t < $count_words; $t++ ) {
lib/indexing.php CHANGED
@@ -1460,6 +1460,10 @@ function relevanssi_index_content( &$insert_data, $post_object, $min_word_length
1460
  return $n;
1461
  }
1462
 
 
 
 
 
1463
  if ( $debug ) {
1464
  relevanssi_debug_echo( 'Indexing post content.' );
1465
  }
1460
  return $n;
1461
  }
1462
 
1463
+ if ( 'on' === get_post_meta( $post_object->ID, '_relevanssi_hide_content', true ) ) {
1464
+ return $n;
1465
+ }
1466
+
1467
  if ( $debug ) {
1468
  relevanssi_debug_echo( 'Indexing post content.' );
1469
  }
lib/init.php CHANGED
@@ -43,12 +43,14 @@ add_action( 'edit_attachment', 'relevanssi_insert_edit' );
43
  add_action( 'transition_post_status', 'relevanssi_update_child_posts', 99, 3 );
44
 
45
  // Relevanssi features.
 
46
  add_filter( 'relevanssi_remove_punctuation', 'relevanssi_remove_punct' );
47
  add_filter( 'relevanssi_post_ok', 'relevanssi_default_post_ok', 9, 2 );
48
  add_filter( 'relevanssi_query_filter', 'relevanssi_limit_filter' );
49
  add_action( 'relevanssi_trim_logs', 'relevanssi_trim_logs' );
50
  add_action( 'relevanssi_update_counts', 'relevanssi_update_counts' );
51
  add_action( 'relevanssi_custom_field_value', 'relevanssi_filter_custom_fields', 10, 2 );
 
52
 
53
  // Excerpts and highlights.
54
  add_action( 'relevanssi_pre_the_content', 'relevanssi_kill_autoembed' );
@@ -167,6 +169,8 @@ function relevanssi_init() {
167
  // style to new style. Remove eventually.
168
  relevanssi_update_synonyms_setting();
169
  }
 
 
170
  }
171
 
172
  /**
43
  add_action( 'transition_post_status', 'relevanssi_update_child_posts', 99, 3 );
44
 
45
  // Relevanssi features.
46
+ add_filter( 'relevanssi_remove_punctuation', 'remove_accents', 9 );
47
  add_filter( 'relevanssi_remove_punctuation', 'relevanssi_remove_punct' );
48
  add_filter( 'relevanssi_post_ok', 'relevanssi_default_post_ok', 9, 2 );
49
  add_filter( 'relevanssi_query_filter', 'relevanssi_limit_filter' );
50
  add_action( 'relevanssi_trim_logs', 'relevanssi_trim_logs' );
51
  add_action( 'relevanssi_update_counts', 'relevanssi_update_counts' );
52
  add_action( 'relevanssi_custom_field_value', 'relevanssi_filter_custom_fields', 10, 2 );
53
+ add_filter( 'relevanssi_index_custom_fields', 'relevanssi_remove_metadata_fields' );
54
 
55
  // Excerpts and highlights.
56
  add_action( 'relevanssi_pre_the_content', 'relevanssi_kill_autoembed' );
169
  // style to new style. Remove eventually.
170
  relevanssi_update_synonyms_setting();
171
  }
172
+
173
+ do_action( 'relevanssi_init' );
174
  }
175
 
176
  /**
lib/options.php CHANGED
@@ -80,6 +80,13 @@ function update_relevanssi_options( array $request ) {
80
  update_option( 'relevanssi_show_matches_text', $value );
81
  }
82
  relevanssi_update_intval( $request, 'relevanssi_excerpt_length', true, 10 );
 
 
 
 
 
 
 
83
  }
84
 
85
  relevanssi_process_weights_and_indexing( $request );
@@ -99,7 +106,6 @@ function update_relevanssi_options( array $request ) {
99
  'relevanssi_default_orderby' => true,
100
  'relevanssi_disable_or_fallback' => true,
101
  'relevanssi_exact_match_bonus' => true,
102
- 'relevanssi_excerpt_allowable_tags' => true,
103
  'relevanssi_excerpt_custom_fields' => true,
104
  'relevanssi_excerpt_type' => true,
105
  'relevanssi_excerpts' => true,
80
  update_option( 'relevanssi_show_matches_text', $value );
81
  }
82
  relevanssi_update_intval( $request, 'relevanssi_excerpt_length', true, 10 );
83
+
84
+ if ( isset( $request['relevanssi_excerpt_allowable_tags'] ) ) {
85
+ $value = $request['relevanssi_excerpt_allowable_tags'];
86
+ $value = str_replace( array( ' ', '/' ), '', $value );
87
+ $value = implode( '>', array_unique( explode( '>', $value ) ) );
88
+ update_option( 'relevanssi_excerpt_allowable_tags', $value );
89
+ }
90
  }
91
 
92
  relevanssi_process_weights_and_indexing( $request );
106
  'relevanssi_default_orderby' => true,
107
  'relevanssi_disable_or_fallback' => true,
108
  'relevanssi_exact_match_bonus' => true,
 
109
  'relevanssi_excerpt_custom_fields' => true,
110
  'relevanssi_excerpt_type' => true,
111
  'relevanssi_excerpts' => true,
lib/phrases.php CHANGED
@@ -48,7 +48,7 @@ function relevanssi_extract_phrases( string $query ) {
48
  $phrase = trim( $phrase );
49
 
50
  // Do not count single-word phrases as phrases.
51
- if ( ! empty( $phrase ) && count( explode( ' ', $phrase ) ) > 1 ) {
52
  $phrases[] = $phrase;
53
  }
54
  $pos = $end + 1;
@@ -175,10 +175,11 @@ $custom_fields, string $excerpts ) : array {
175
  $phrase_queries = array();
176
 
177
  foreach ( $phrases as $phrase ) {
178
- $queries = array();
179
- $phrase = $wpdb->esc_like( $phrase );
180
- $phrase = str_replace( array( '‘', '’', "'", '"', '”', '“', '“', '„', '´' ), '_', $phrase );
181
- $phrase = htmlspecialchars( $phrase );
 
182
 
183
  /**
184
  * Filters each phrase before it's passed through esc_sql() and used in
@@ -198,7 +199,7 @@ $custom_fields, string $excerpts ) : array {
198
 
199
  $query = "(SELECT ID FROM $wpdb->posts
200
  WHERE (post_content LIKE '%$phrase%'
201
- OR post_title LIKE '%$phrase%' $excerpt)
202
  AND post_status IN ($status))";
203
 
204
  $queries[] = array(
48
  $phrase = trim( $phrase );
49
 
50
  // Do not count single-word phrases as phrases.
51
+ if ( relevanssi_is_multiple_words( $phrase ) ) {
52
  $phrases[] = $phrase;
53
  }
54
  $pos = $end + 1;
175
  $phrase_queries = array();
176
 
177
  foreach ( $phrases as $phrase ) {
178
+ $queries = array();
179
+ $phrase = $wpdb->esc_like( $phrase );
180
+ $phrase = str_replace( array( '‘', '’', "'", '"', '”', '“', '“', '„', '´' ), '_', $phrase );
181
+ $title_phrase = $phrase;
182
+ $phrase = htmlspecialchars( $phrase );
183
 
184
  /**
185
  * Filters each phrase before it's passed through esc_sql() and used in
199
 
200
  $query = "(SELECT ID FROM $wpdb->posts
201
  WHERE (post_content LIKE '%$phrase%'
202
+ OR post_title LIKE '%$title_phrase%' $excerpt)
203
  AND post_status IN ($status))";
204
 
205
  $queries[] = array(
lib/search-query-restrictions.php CHANGED
@@ -101,14 +101,14 @@ function relevanssi_process_query_args( $args ) {
101
  /**
102
  * Processes the 'in' and 'not in' parameters to MySQL query restrictions.
103
  *
104
- * Checks that the parameters are integers and formulates a MySQL query restriction
105
- * from them. If the same posts are both included and excluded, exclusion will take
106
- * precedence.
107
  *
108
  * Tested.
109
  *
110
- * @param array $post_query An array where included posts are in $post_query['in']
111
- * and excluded posts are in $post_query['not in'].
112
  *
113
  * @return string MySQL query restrictions matching the array.
114
  */
@@ -142,7 +142,16 @@ function relevanssi_process_post_query( $post_query ) {
142
  // Clean: $posts is checked to be integers.
143
  }
144
  }
145
- return $query_restrictions;
 
 
 
 
 
 
 
 
 
146
  }
147
 
148
  /**
@@ -192,7 +201,15 @@ function relevanssi_process_parent_query( $parent_query ) {
192
  }
193
  }
194
 
195
- return $query_restrictions;
 
 
 
 
 
 
 
 
196
  }
197
 
198
  /**
@@ -269,7 +286,16 @@ function relevanssi_process_date_query( $date_query ) {
269
  // Clean: $sql generated by $date_query->get_sql() query.
270
  }
271
  }
272
- return $query_restrictions;
 
 
 
 
 
 
 
 
 
273
  }
274
 
275
  /**
@@ -341,7 +367,15 @@ function relevanssi_process_author( $author ) {
341
  // Clean: $authors is always just numbers.
342
  }
343
 
344
- return $query_restrictions;
 
 
 
 
 
 
 
 
345
  }
346
 
347
  /**
@@ -354,15 +388,15 @@ function relevanssi_process_author( $author ) {
354
  *
355
  * @global object $wpdb The WP database interface.
356
  *
357
- * @param string $n The date parameter.
358
  *
359
  * @return string The MySQL query restriction.
360
  */
361
- function relevanssi_process_by_date( $n ) {
362
  global $wpdb;
363
  $query_restrictions = '';
364
 
365
- $u = substr( $n, -1, 1 );
366
  switch ( $u ) {
367
  case 'h':
368
  $unit = 'HOUR';
@@ -383,7 +417,7 @@ function relevanssi_process_by_date( $n ) {
383
  $unit = 'DAY';
384
  }
385
 
386
- $n = preg_replace( '/[hdmyw]/', '', $n );
387
 
388
  if ( is_numeric( $n ) ) {
389
  $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
@@ -391,7 +425,15 @@ function relevanssi_process_by_date( $n ) {
391
  // Clean: $n is always numeric, $unit is Relevanssi-generated.
392
  }
393
 
394
- return $query_restrictions;
 
 
 
 
 
 
 
 
395
  }
396
 
397
  /**
@@ -493,7 +535,17 @@ function relevanssi_process_post_type( $post_type, $admin_search, $include_attac
493
  // Clean: $negative_post_type is escaped.
494
  }
495
 
496
- return $query_restrictions;
 
 
 
 
 
 
 
 
 
 
497
  }
498
 
499
  /**
@@ -527,7 +579,14 @@ function relevanssi_process_post_status( $post_status ) {
527
  }
528
 
529
  if ( $escaped_post_status ) {
 
530
  if ( $wp_query->is_admin || $relevanssi_admin_test ) {
 
 
 
 
 
 
531
  $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
532
  WHERE posts.post_status IN ($escaped_post_status))))";
533
  } else {
@@ -537,7 +596,15 @@ function relevanssi_process_post_status( $post_status ) {
537
  }
538
  }
539
 
540
- return $query_restrictions;
 
 
 
 
 
 
 
 
541
  }
542
 
543
  /**
101
  /**
102
  * Processes the 'in' and 'not in' parameters to MySQL query restrictions.
103
  *
104
+ * Checks that the parameters are integers and formulates a MySQL query
105
+ * restriction from them. If the same posts are both included and excluded,
106
+ * exclusion will take precedence.
107
  *
108
  * Tested.
109
  *
110
+ * @param array $post_query An array where included posts are in
111
+ * $post_query['in'] and excluded posts are in $post_query['not in'].
112
  *
113
  * @return string MySQL query restrictions matching the array.
114
  */
142
  // Clean: $posts is checked to be integers.
143
  }
144
  }
145
+
146
+ /**
147
+ * Filters the MySQL query for restricting the search by post parameters.
148
+ *
149
+ * @param string $query_restrictions The MySQL query.
150
+ * @param array $post_query The post query parameters.
151
+ *
152
+ * @return string The MySQL query.
153
+ */
154
+ return apply_filters( 'relevanssi_post_query_filter', $query_restrictions, $post_query );
155
  }
156
 
157
  /**
201
  }
202
  }
203
 
204
+ /**
205
+ * Filters the MySQL query for restricting the search by the post parent.
206
+ *
207
+ * @param string $query_restrictions The MySQL query.
208
+ * @param array $parent_query The parent query parameters.
209
+ *
210
+ * @return string The MySQL query.
211
+ */
212
+ return apply_filters( 'relevanssi_parent_query_filter', $query_restrictions, $parent_query );
213
  }
214
 
215
  /**
286
  // Clean: $sql generated by $date_query->get_sql() query.
287
  }
288
  }
289
+
290
+ /**
291
+ * Filters the MySQL query for restricting the search by the date_query.
292
+ *
293
+ * @param string $query_restrictions The MySQL query.
294
+ * @param WP_Date_Query $date_query The date_query object.
295
+ *
296
+ * @return string The MySQL query.
297
+ */
298
+ return apply_filters( 'relevanssi_date_query_filter', $query_restrictions, $date_query );
299
  }
300
 
301
  /**
367
  // Clean: $authors is always just numbers.
368
  }
369
 
370
+ /**
371
+ * Filters the MySQL query for restricting the search by the post author.
372
+ *
373
+ * @param string $query_restrictions The MySQL query.
374
+ * @param array $author An array of author IDs.
375
+ *
376
+ * @return string The MySQL query.
377
+ */
378
+ return apply_filters( 'relevanssi_author_query_filter', $query_restrictions, $author );
379
  }
380
 
381
  /**
388
  *
389
  * @global object $wpdb The WP database interface.
390
  *
391
+ * @param string $by_date The date parameter.
392
  *
393
  * @return string The MySQL query restriction.
394
  */
395
+ function relevanssi_process_by_date( $by_date ) {
396
  global $wpdb;
397
  $query_restrictions = '';
398
 
399
+ $u = substr( $by_date, -1, 1 );
400
  switch ( $u ) {
401
  case 'h':
402
  $unit = 'HOUR';
417
  $unit = 'DAY';
418
  }
419
 
420
+ $n = preg_replace( '/[hdmyw]/', '', $by_date );
421
 
422
  if ( is_numeric( $n ) ) {
423
  $query_restrictions .= " AND relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
425
  // Clean: $n is always numeric, $unit is Relevanssi-generated.
426
  }
427
 
428
+ /**
429
+ * Filters the MySQL query for restricting the search by the by_date.
430
+ *
431
+ * @param string $query_restrictions The MySQL query.
432
+ * @param string $by_date The by_date parameter.
433
+ *
434
+ * @return string The MySQL query.
435
+ */
436
+ return apply_filters( 'relevanssi_by_date_query_filter', $query_restrictions, $by_date );
437
  }
438
 
439
  /**
535
  // Clean: $negative_post_type is escaped.
536
  }
537
 
538
+ /**
539
+ * Filters the MySQL query for restricting the search by the post type.
540
+ *
541
+ * @param string $query_restrictions The MySQL query.
542
+ * @param string|array $post_type The post type(s).
543
+ * @param boolean $include_attachments True if attachments are allowed.
544
+ * @param boolean $admin_search True if this is an admin search.
545
+ *
546
+ * @return string The MySQL query.
547
+ */
548
+ return apply_filters( 'relevanssi_post_type_query_filter', $query_restrictions, $post_type, $include_attachments, $admin_search );
549
  }
550
 
551
  /**
579
  }
580
 
581
  if ( $escaped_post_status ) {
582
+ $block_non_post_results = false;
583
  if ( $wp_query->is_admin || $relevanssi_admin_test ) {
584
+ $block_non_post_results = true;
585
+ }
586
+ if ( $wp_query->is_admin && isset( $wp_query->query_vars['action'] ) && 'relevanssi_live_search' === $wp_query->query_vars['action'] ) {
587
+ $block_non_post_results = false;
588
+ }
589
+ if ( $block_non_post_results ) {
590
  $query_restrictions .= " AND ((relevanssi.doc IN (SELECT DISTINCT(posts.ID) FROM $wpdb->posts AS posts
591
  WHERE posts.post_status IN ($escaped_post_status))))";
592
  } else {
596
  }
597
  }
598
 
599
+ /**
600
+ * Filters the MySQL query for restricting the search by the post status.
601
+ *
602
+ * @param string $query_restrictions The MySQL query.
603
+ * @param string $post_status The post status(es).
604
+ *
605
+ * @return string The MySQL query.
606
+ */
607
+ return apply_filters( 'relevanssi_post_status_query_filter', $query_restrictions, $post_status );
608
  }
609
 
610
  /**
lib/search.php CHANGED
@@ -528,6 +528,9 @@ function relevanssi_search( $args ) {
528
  'missing_terms' => $missing_terms,
529
  );
530
 
 
 
 
531
  return $return;
532
  }
533
 
@@ -589,14 +592,15 @@ function relevanssi_do_query( &$query ) {
589
  * One of the key filters for Relevanssi. If you want to modify the results
590
  * Relevanssi finds, use this filter.
591
  *
592
- * @param array $filter_data The index 0 has an array of post objects (or
593
  * post IDs, or parent=>ID pairs, depending on the `fields` parameter) found
594
  * in the search, index 1 has the search query string.
 
595
  *
596
  * @return array The return array composition is the same as the parameter
597
  * array, but Relevanssi only uses the index 0.
598
  */
599
- $hits_filters_applied = apply_filters( 'relevanssi_hits_filter', $filter_data );
600
  // array_values() to make sure the $hits array is indexed in numerical order
601
  // Manipulating the array with array_unique() for example may mess with that.
602
  $hits = array_values( $hits_filters_applied[0] );
@@ -1439,21 +1443,10 @@ function relevanssi_update_term_hits( &$term_hits, &$match_arrays, $match, $term
1439
  relevanssi_increase_value( $match_arrays['customfield'][ $match->doc ], $match->customfield );
1440
  relevanssi_increase_value( $match_arrays['author'][ $match->doc ], $match->author );
1441
  relevanssi_increase_value( $match_arrays['excerpt'][ $match->doc ], $match->excerpt );
1442
- relevanssi_increase_value( $match_arrays['mysqlcolumn'][ $match->doc ], $match->mysqlcolumn );
1443
- }
1444
 
1445
- /**
1446
- * Increases a value. If it's not set, sets it first to the default value.
1447
- *
1448
- * @param int $value The value to increase (passed by reference).
1449
- * @param int $increase The amount to increase the value, default 1.
1450
- * @param int $default The default value, default 0.
1451
- */
1452
- function relevanssi_increase_value( &$value, $increase = 1, $default = 0 ) {
1453
- if ( ! isset( $value ) ) {
1454
- $value = $default;
1455
  }
1456
- $value += $increase;
1457
  }
1458
 
1459
  /**
528
  'missing_terms' => $missing_terms,
529
  );
530
 
531
+ if ( function_exists( 'relevanssi_premium_update_return_array' ) ) {
532
+ relevanssi_premium_update_return_array( $return, $match_arrays );
533
+ }
534
  return $return;
535
  }
536
 
592
  * One of the key filters for Relevanssi. If you want to modify the results
593
  * Relevanssi finds, use this filter.
594
  *
595
+ * @param array $filter_data The index 0 has an array of post objects (or
596
  * post IDs, or parent=>ID pairs, depending on the `fields` parameter) found
597
  * in the search, index 1 has the search query string.
598
+ * @param WP_Query $query The WP_Query object.
599
  *
600
  * @return array The return array composition is the same as the parameter
601
  * array, but Relevanssi only uses the index 0.
602
  */
603
+ $hits_filters_applied = apply_filters( 'relevanssi_hits_filter', $filter_data, $query );
604
  // array_values() to make sure the $hits array is indexed in numerical order
605
  // Manipulating the array with array_unique() for example may mess with that.
606
  $hits = array_values( $hits_filters_applied[0] );
1443
  relevanssi_increase_value( $match_arrays['customfield'][ $match->doc ], $match->customfield );
1444
  relevanssi_increase_value( $match_arrays['author'][ $match->doc ], $match->author );
1445
  relevanssi_increase_value( $match_arrays['excerpt'][ $match->doc ], $match->excerpt );
 
 
1446
 
1447
+ if ( function_exists( 'relevanssi_premium_update_term_hits' ) ) {
1448
+ relevanssi_premium_update_term_hits( $term_hits, $match_arrays, $match, $term );
 
 
 
 
 
 
 
 
1449
  }
 
1450
  }
1451
 
1452
  /**
lib/user-searches.php CHANGED
@@ -249,7 +249,11 @@ function relevanssi_date_queries( string $from, string $to, string $version = 'g
249
  global $wpdb, $relevanssi_variables;
250
  $log_table = $relevanssi_variables['log_table'];
251
 
252
- /** Documented in lib/interface.php. */
 
 
 
 
253
  $limit = apply_filters( 'relevanssi_user_searches_limit', 100 );
254
 
255
  if ( 'good' === $version ) {
249
  global $wpdb, $relevanssi_variables;
250
  $log_table = $relevanssi_variables['log_table'];
251
 
252
+ /**
253
+ * Filters the number of most common queries to show.
254
+ *
255
+ * @param int The number of most common queries to show, default 100.
256
+ */
257
  $limit = apply_filters( 'relevanssi_user_searches_limit', 100 );
258
 
259
  if ( 'good' === $version ) {
lib/utils.php CHANGED
@@ -440,17 +440,19 @@ function relevanssi_get_current_language( bool $locale = true ) {
440
  * Uses get_permalink() to get the permalink, then adds the 'highlight'
441
  * parameter if necessary using relevanssi_add_highlight().
442
  *
 
 
443
  * @see get_permalink()
444
  *
445
  * @return string The permalink.
446
  */
447
- function relevanssi_get_permalink() {
448
  /**
449
  * Filters the permalink.
450
  *
451
  * @param string The permalink, generated by get_permalink().
452
  */
453
- $permalink = apply_filters( 'relevanssi_permalink', get_permalink() );
454
 
455
  return $permalink;
456
  }
@@ -665,6 +667,20 @@ function relevanssi_implode( array $request, string $option, string $glue = ','
665
  return '';
666
  }
667
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  /**
669
  * Returns the intval of the option if it exists, null otherwise.
670
  *
@@ -682,6 +698,85 @@ function relevanssi_intval( array $request, string $option ) {
682
  return null;
683
  }
684
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
685
  /**
686
  * Launches an asynchronous Ajax action.
687
  *
@@ -991,9 +1086,10 @@ function relevanssi_strip_all_tags( $content ) : string {
991
  if ( ! is_string( $content ) ) {
992
  $content = '';
993
  }
994
- $content = preg_replace( '/<!--.*?-->/ms', '', $content );
995
- $content = preg_replace( '/<[!a-zA-Z\/][^>].*?>/ms', ' ', $content );
996
- return $content;
 
997
  }
998
 
999
  /**
@@ -1059,6 +1155,7 @@ function relevanssi_strip_tags( $content ) {
1059
  '/(<\/?hr.*?>)/',
1060
  '/(<\/?li.*?>)/',
1061
  '/(<img.*?>)/',
 
1062
  );
1063
 
1064
  $content = preg_replace( $space_tags, '$1 ', $content );
@@ -1238,10 +1335,12 @@ function relevanssi_the_excerpt() {
1238
  * Uses get_permalink() to get the permalink, then adds the 'highlight'
1239
  * parameter if necessary using relevanssi_add_highlight(), then echoes it out.
1240
  *
 
 
1241
  * @uses relevanssi_get_permalink() Fetches the current post permalink.
1242
  */
1243
- function relevanssi_the_permalink() {
1244
- echo esc_url( relevanssi_get_permalink() );
1245
  }
1246
 
1247
  /**
440
  * Uses get_permalink() to get the permalink, then adds the 'highlight'
441
  * parameter if necessary using relevanssi_add_highlight().
442
  *
443
+ * @param int|WP_Post $post Post ID or post object. Default is the global $post.
444
+ *
445
  * @see get_permalink()
446
  *
447
  * @return string The permalink.
448
  */
449
+ function relevanssi_get_permalink( $post = 0 ) {
450
  /**
451
  * Filters the permalink.
452
  *
453
  * @param string The permalink, generated by get_permalink().
454
  */
455
+ $permalink = apply_filters( 'relevanssi_permalink', get_permalink( $post ) );
456
 
457
  return $permalink;
458
  }
667
  return '';
668
  }
669
 
670
+ /**
671
+ * Increases a value. If it's not set, sets it first to the default value.
672
+ *
673
+ * @param int $value The value to increase (passed by reference).
674
+ * @param int $increase The amount to increase the value, default 1.
675
+ * @param int $default The default value, default 0.
676
+ */
677
+ function relevanssi_increase_value( &$value, $increase = 1, $default = 0 ) {
678
+ if ( ! isset( $value ) ) {
679
+ $value = $default;
680
+ }
681
+ $value += $increase;
682
+ }
683
+
684
  /**
685
  * Returns the intval of the option if it exists, null otherwise.
686
  *
698
  return null;
699
  }
700
 
701
+ /**
702
+ * Returns true if the search is from Relevanssi Live Ajax Search.
703
+ *
704
+ * Checks if $wp_query->query_vars['action'] is set to "relevanssi_live_search".
705
+ *
706
+ * @return bool True if the search is from Relevanssi Live Ajax Search, false
707
+ * otherwise.
708
+ */
709
+ function relevanssi_is_live_search() {
710
+ global $wp_query;
711
+ $relevanssi_live_search = false;
712
+ if ( isset( $wp_query->query_vars['action'] ) && 'relevanssi_live_search' === $wp_query->query_vars['action'] ) {
713
+ $relevanssi_live_search = true;
714
+ }
715
+ return $relevanssi_live_search;
716
+ }
717
+
718
+ /**
719
+ * Checks if a string is a multiple-word phrase.
720
+ *
721
+ * Replaces hyphens, quotes and ampersands with spaces if necessary based on
722
+ * the Relevanssi advanced indexing settings.
723
+ *
724
+ * @param string $string The string to check.
725
+ *
726
+ * @return boolean True if the string is a multiple-word phrase, false otherwise.
727
+ */
728
+ function relevanssi_is_multiple_words( string $string ) : bool {
729
+ if ( empty( $string ) ) {
730
+ return false;
731
+ }
732
+ $punctuation = get_option( 'relevanssi_punctuation' );
733
+ if ( 'replace' === $punctuation['hyphens'] ) {
734
+ $string = str_replace(
735
+ array(
736
+ '-',
737
+ '–',
738
+ '—',
739
+ ),
740
+ ' ',
741
+ $string
742
+ );
743
+ }
744
+ if ( 'replace' === $punctuation['quotes'] ) {
745
+ $string = str_replace(
746
+ array(
747
+ '&#8217;',
748
+ "'",
749
+ '’',
750
+ '‘',
751
+ '”',
752
+ '“',
753
+ '„',
754
+ '´',
755
+ '″',
756
+ ),
757
+ ' ',
758
+ $string
759
+ );
760
+ }
761
+ if ( 'replace' === $punctuation['ampersands'] ) {
762
+ $string = str_replace(
763
+ array(
764
+ '&#038;',
765
+ '&amp;',
766
+ '&',
767
+ ),
768
+ ' ',
769
+ $string
770
+ );
771
+ }
772
+
773
+ if ( count( explode( ' ', $string ) ) > 1 ) {
774
+ return true;
775
+ }
776
+
777
+ return false;
778
+ }
779
+
780
  /**
781
  * Launches an asynchronous Ajax action.
782
  *
1086
  if ( ! is_string( $content ) ) {
1087
  $content = '';
1088
  }
1089
+ $content = preg_replace( '/<!--.*?-->/ums', '', $content );
1090
+ $content = preg_replace( '/<[!a-zA-Z\/][^>].*?>/ums', ' ', $content );
1091
+
1092
+ return $content ?? '';
1093
  }
1094
 
1095
  /**
1155
  '/(<\/?hr.*?>)/',
1156
  '/(<\/?li.*?>)/',
1157
  '/(<img.*?>)/',
1158
+ '/(<\/td>)/',
1159
  );
1160
 
1161
  $content = preg_replace( $space_tags, '$1 ', $content );
1335
  * Uses get_permalink() to get the permalink, then adds the 'highlight'
1336
  * parameter if necessary using relevanssi_add_highlight(), then echoes it out.
1337
  *
1338
+ * @param int|WP_Post $post Post ID or post object. Default is the global $post.
1339
+ *
1340
  * @uses relevanssi_get_permalink() Fetches the current post permalink.
1341
  */
1342
+ function relevanssi_the_permalink( $post = 0 ) {
1343
+ echo esc_url( relevanssi_get_permalink( $post ) );
1344
  }
1345
 
1346
  /**
readme.txt CHANGED
@@ -3,9 +3,9 @@ Contributors: msaari
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search, product search, woocommerce search
5
  Requires at least: 4.9
6
- Tested up to: 5.9
7
  Requires PHP: 7.0
8
- Stable tag: 4.14.7
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -128,71 +128,38 @@ Each document database is full of useless words. All the little words that appea
128
  * John Calahan for extensive 4.0 beta testing.
129
 
130
  == Changelog ==
131
- = 4.14.7 =
132
- * User interface: The synonym settings page now alerts if the synonyms aren't active because of the AND search.
133
-
134
- = 4.14.6 =
135
- * Security fix: Extra hardening for AJAX requests. Some AJAX actions in Relevanssi could leak information to site subscribers who knew what to look for.
136
-
137
- = 4.14.5 =
138
- * Security fix: Any registered user could empty the Relevanssi index by triggering the index truncate AJAX action. That is no longer possible.
139
- * New feature: The [searchform] shortcode has a new parameter, 'checklist', which you can use to create taxonomy checklists.
140
- * Changed behaviour: The `relevanssi_search_again` parameter array has more parameters the filter can modify.
141
- * Changed behaviour: The `relevanssi_show_matches` filter hook gets the post object as the second parameter.
142
- * Minor fix: The `cats` and `tags` parameters work better and support array values.
143
-
144
- = 4.14.4 =
145
- * Minor fix: `relevanssi_orderby` did not always accept an array-format orderby parameter.
146
- * Minor fix: Removes a highlighting problem stemming from uppercase search terms.
147
- * Minor fix: Relevanssi removes highlights better from inside multiline HTML tags.
148
- * Minor fix: When image attachment indexing was disabled, saving image attachments would still index the images. Image attachment blocking is now a `relevanssi_indexing_restriction` filter function, which means it's always active.
149
-
150
- = 4.14.3 =
151
- * Security fix: User searches page had a XSS vulnerability.
152
-
153
- = 4.14.2 =
154
- * Minor fix: Remove unnecessary database calls from admin pages.
155
- * Minor fix: Improved Oxygen compatibility.
156
-
157
- = 4.14.1 =
158
- * Adds a missing file.
159
-
160
- = 4.14.0 =
161
- * New feature: New filter hook `relevanssi_render_blocks` controls whether Relevanssi renders blocks in a post or not. If you are having problems updating long posts with lots of blocks, having this filter hook return `false` for the post in question will likely help, as rendering the blocks in a long post can take huge amounts of memory.
162
- * New feature: The user searches page has been improved a lot.
163
- * New feature: The [searchform] shortcode has a new parameter, 'post_type_boxes', which creates a checkbox for each post type you list in the value. For example [searchform post_type_boxes='*post,page'] would create a search with a checkbox for 'post' and 'page' post types, with 'post' pre-checked.
164
- * New feature: You can now have multiple dropdowns in one [searchform] shortcode. Anything that begins with 'dropdown' is considered a dropdown parameter, so you can do [searchform dropdown_1='category' dropdown_2='post_tag'] for example.
165
- * New feature: New filter hook `relevanssi_search_params` lets you filter search parameters after they've been collected from the WP_Query.
166
- * New feature: New filter hook `relevanssi_excerpt_post` lets you make Relevanssi skip creating excerpts for specific posts.
167
- * Changed behaviour: Filter hooks `relevanssi_1day`, `relevanssi_7days` and `relevanssi_30days` are removed, as the user searches page is now different. The default value for `relevanssi_user_searches_limit` is now 100 instead of 20.
168
- * Minor fix: In some languages, iOS uses „“ for quotes. Relevanssi now understands those for the phrase operator.
169
- * Minor fix: Stops Relevanssi from blocking the admin search for WooCommerce coupons and other WooCommerce custom post types.
170
- * Minor fix: Fixes problems with the WP-Members compatibility.
171
- * Minor fix: New parameter for `relevanssi_tokenize()` introduces the context (indexing or search query). The `relevanssi_extract_phrases()` is only used on search queries.
172
- * Minor fix: Relevanssi won't let you adjust synonyms and stopwords anymore if you use Polylang and are in 'Show all languages' mode.
173
- * Minor fix: Highlighting is improved by a more precise HTML entity filter, thanks to Jacob Bearce.
174
 
175
  == Upgrade notice ==
176
- = 4.14.7 =
177
- * Small user interface fixes.
178
-
179
- = 4.14.6 =
180
- * Security fix: Extra security checks for AJAX actions.
181
-
182
- = 4.14.5 =
183
- * Security fix: registered users could delete the Relevanssi index.
184
-
185
- = 4.14.4 =
186
- * Small bug fixes.
187
-
188
- = 4.14.3 =
189
- * Security fix: User searches page had a XSS vulnerability.
190
-
191
- = 4.14.2 =
192
- * Removes database calls on admin pages.
193
-
194
- = 4.14.1 =
195
- * Adds a missing file.
196
-
197
- = 4.14.0 =
198
- * User searches page update, bug fixes and improvements.
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search, product search, woocommerce search
5
  Requires at least: 4.9
6
+ Tested up to: 6.0
7
  Requires PHP: 7.0
8
+ Stable tag: 4.16.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
128
  * John Calahan for extensive 4.0 beta testing.
129
 
130
  == Changelog ==
131
+ = 4.16.0 =
132
+ * New feature: Oxygen compatibility has been upgraded to support JSON data from Oxygen 4. This is still in early stages, so feedback from Oxygen users is welcome.
133
+ * New feature: New filter hook `relevanssi_oxygen_element` is used to filter Oxygen JSON elements. The earlier `relevanssi_oxygen_section_filters` and `relevanssi_oxygen_section_content` filters are no longer used with Oxygen 4; this hook is the only way to filter Oxygen elements.
134
+ * Changed behaviour: Relevanssi now applies `remove_accents()` to all strings. This is because default database collations do not care for accents and having accents may cause missing information in indexing. If you use a database collation that doesn't ignore accents, make sure you disable this filter.
135
+ * Minor fix: Relevanssi used `the_category` filter with too few parameters. The missing parameters have been added.
136
+ * Minor fix: Stops drafts and pending posts from showing up in Relevanssi Live Ajax Searches.
137
+ * Minor fix: Phrases weren't used in some cases where a multiple-word phrase looked like a single-word phrase.
138
+ * Minor fix: Prevents fatal errors from `relevanssi_strip_all_tags()`.
139
+
140
+ = 4.15.2 =
141
+ * New feature: New filter hook `relevanssi_didyoumean_token` lets you filter Did you mean words before correction. You can use this filter hook to exclude words from being corrected.
142
+ * Minor fix: Phrase search couldn't find phrases that include an ampersand if they matched the post title. This works now.
143
+ * Minor fix: Relevanssi now adds spaces after table cell tags to avoid table cell content sticking together in excerpts.
144
+ * Minor fix: The 'Allowable tags in excerpts' function now automatically corrects the entered value to match what Relevanssi expects the value to be.
145
+
146
+ = 4.15.1 =
147
+ * Changed behaviour: Relevanssi now ignores WordPress metadata custom fields that aren't interesting for Relevanssi indexing.
148
+ * Changed behaviour: Both `relevanssi_get_permalink()` and `relevanssi_the_permalink()` now can take post ID or a post object as a parameter and can thus be used outside the Loop.
149
+ * Changed behaviour: The `relevanssi_hits_filter` hook now gets the WP_Query object as the second parameter.
150
+ * Minor fix: Avoid error messages for missing `mysqlcolumn_matches` array key.
151
+
152
+ = 4.15.0 =
153
+ * New feature: The action hook `relevanssi_init` runs at the end of the `relevanssi_init()` function.
154
+ * New feature: New filter hook `relevanssi_author_query_filter` filters the post author MySQL query.
155
+ * New feature: New filter hook `relevanssi_by_date_query_filter` filters the by_date MySQL query.
156
+ * New feature: New filter hook `relevanssi_date_query_filter` filters the date query MySQL query.
157
+ * New feature: New filter hook `relevanssi_parent_query_filter` filters the post parent MySQL query.
158
+ * New feature: New filter hook `relevanssi_post_query_filter` filters the post__in and post__not_in MySQL query.
159
+ * New feature: New filter hook `relevanssi_post_status_query_filter` filters the post_status MySQL query.
160
+ * New feature: New filter hook `relevanssi_post_type_query_filter` filters the post_type MySQL query.
161
+ * Minor fix: The Bricks compatibility was improved, Relevanssi now notices changes to Bricks posts more often. Relevanssi also only reads the text from the `_bricks_page_content_2` custom field.
 
 
 
 
 
 
 
 
 
 
 
 
162
 
163
  == Upgrade notice ==
164
+ = 4.16.0 =
165
+ * Indexing update; please reindex after the upgrade! Oxygen 4 compatibility.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
relevanssi.php CHANGED
@@ -13,7 +13,7 @@
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
- * Version: 4.14.7
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
@@ -67,7 +67,7 @@ $relevanssi_variables['database_version'] = 6;
67
  $relevanssi_variables['file'] = __FILE__;
68
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
69
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
70
- $relevanssi_variables['plugin_version'] = '4.14.7';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
+ * Version: 4.16.0
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
67
  $relevanssi_variables['file'] = __FILE__;
68
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
69
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
70
+ $relevanssi_variables['plugin_version'] = '4.16.0';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';