Relevanssi – A Better Search - Version 4.14.0

Version Description

  • 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.
  • New feature: The user searches page has been improved a lot.
  • 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.
  • 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.
  • New feature: New filter hook relevanssi_search_params lets you filter search parameters after they've been collected from the WP_Query.
  • New feature: New filter hook relevanssi_excerpt_post lets you make Relevanssi skip creating excerpts for specific posts.
  • 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.
  • Minor fix: In some languages, iOS uses for quotes. Relevanssi now understands those for the phrase operator.
  • Minor fix: Stops Relevanssi from blocking the admin search for WooCommerce coupons and other WooCommerce custom post types.
  • Minor fix: Fixes problems with the WP-Members compatibility.
  • Minor fix: New parameter for relevanssi_tokenize() introduces the context (indexing or search query). The relevanssi_extract_phrases() is only used on search queries.
  • Minor fix: Relevanssi won't let you adjust synonyms and stopwords anymore if you use Polylang and are in 'Show all languages' mode.
  • Minor fix: Highlighting is improved by a more precise HTML entity filter, thanks to Jacob Bearce.
Download this release

Release Info

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

Code changes from version 4.13.3.1 to 4.14.0

changelog.txt CHANGED
@@ -1,3 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  = 4.9.1 =
2
  * Changed behaviour: The `relevanssi_excerpt_part` filter hook now gets the post ID as a second parameter. The documentation for the filter has been fixed to match actual use: this filter is applied to the excerpt part after the highlighting and the ellipsis have been added.
3
  * Changed behaviour: The `relevanssi_index_custom_fields` filter hook is no longer used when determining which custom fields are used for phrase searching. If you have a use case where this change matters, please contact us.
1
+ = 4.10.2 =
2
+ * New feature: You can force Relevanssi to be active by setting the query variable `relevanssi` to `true`. Thanks to Jan Willem Oostendorp.
3
+ * Changed behaviour: Relevanssi has been moved from `the_posts` filter to `posts_pre_query`. This change doesn't do much, but increases performance slightly as WordPress needs to do less useless work, as now the default query is no longer run. Thanks to Jan Willem Oostendorp.
4
+ * Minor fix: Highlighting didn't work properly when highlighting something immediately following a HTML tag.
5
+ * Minor fix: You can no longer set the value of minimum word length to less than 1 or higher than 9 from the settings page.
6
+ * Minor fix: Importing options broke synonym and stopword settings.
7
+ * Minor fix: Improves the Rank Math SEO compatibility to avoid errors in plugin activation.
8
+ * Minor fix: WPML search results that included non-post results caused fatal errors and crashes. This fixes the crashing and makes non-post results work better in both WPML and Polylang.
9
+
10
+ = 4.10.1 =
11
+ * Major fix: The multilingual stopwords and synonyms were used based on the global language. Now when indexing posts, the post language is used instead of the global language.
12
+
13
+ = 4.10.0 =
14
+ * New feature: Relevanssi now supports multilingual synonyms and stopwords. Relevanssi now has a different set of synonyms and stopwords for each language. This feature is compatible with WPML and Polylang.
15
+ * New feature: SEO by Rank Math compatibility is added: posts marked as 'noindex' with Rank Math are not indexed by Relevanssi.
16
+ * Minor fix: With keyword matching set to 'whole words' and the 'expand highlights' disabled, words that ended with an 's' weren't highlighted correctly.
17
+ * Minor fix: The 'Post exclusion' setting didn't work correctly. It has been fixed.
18
+ * Minor fix: It's now impossible to set negative weights in searching settings. They did not work as expected anyway.
19
+ * Minor fix: Relevanssi had an unnecessary index on the `doc` column in the `wp_relevanssi` database table. It is now removed to save space. Thanks to Matthew Wang.
20
+ * Minor fix: Improved Oxygen Builder support makes sure `ct_builder_shortcodes` custom field is always indexed.
21
+
22
  = 4.9.1 =
23
  * Changed behaviour: The `relevanssi_excerpt_part` filter hook now gets the post ID as a second parameter. The documentation for the filter has been fixed to match actual use: this filter is applied to the excerpt part after the highlighting and the ellipsis have been added.
24
  * Changed behaviour: The `relevanssi_index_custom_fields` filter hook is no longer used when determining which custom fields are used for phrase searching. If you have a use case where this change matters, please contact us.
lib/admin_scripts.js CHANGED
@@ -8,7 +8,12 @@ jQuery(document).ready(function ($) {
8
 
9
  $("#removeallstopwords").click(function () {
10
  var c = confirm(relevanssi.confirm_stopwords)
11
- return c //you can just return c because it will be true or false
 
 
 
 
 
12
  })
13
  })
14
 
8
 
9
  $("#removeallstopwords").click(function () {
10
  var c = confirm(relevanssi.confirm_stopwords)
11
+ return c
12
+ })
13
+
14
+ $("#delete_query").click(function () {
15
+ var c = confirm(relevanssi.confirm_delete_query)
16
+ return c
17
  })
18
  })
19
 
lib/admin_styles.css CHANGED
@@ -10,7 +10,7 @@ table.form-table table.widefat th {
10
  width: 3em;
11
  }
12
 
13
- #relevanssi_trim_logs {
14
  width: 4em;
15
  }
16
 
10
  width: 3em;
11
  }
12
 
13
+ #relevanssi_trim_logs, #relevanssi_trim_click_logs {
14
  width: 4em;
15
  }
16
 
lib/common.php CHANGED
@@ -551,19 +551,25 @@ function relevanssi_prevent_default_request( $request, $query ) {
551
  * 'body', also removes the body stopwords. Default true.
552
  * @param int $min_word_length The minimum word length to include.
553
  * Default -1.
 
 
554
  *
555
  * @return int[] An array of tokens as the keys and their frequency as the
556
  * value.
557
  */
558
- function relevanssi_tokenize( $string, $remove_stops = true, int $min_word_length = -1 ) : array {
559
  if ( ! $string || ( ! is_string( $string ) && ! is_array( $string ) ) ) {
560
  return array();
561
  }
562
- $string_for_phrases = is_array( $string ) ? implode( ' ', $string ) : $string;
563
- $phrases = relevanssi_extract_phrases( $string_for_phrases );
564
- $phrase_words = array();
565
- foreach ( $phrases as $phrase ) {
566
- $phrase_words = array_merge( $phrase_words, explode( ' ', $phrase ) );
 
 
 
 
567
  }
568
 
569
  $tokens = array();
@@ -637,9 +643,11 @@ function relevanssi_tokenize( $string, $remove_stops = true, int $min_word_lengt
637
  * Filters the token through the Relevanssi Premium tokenizer to add
638
  * some Premium features to the tokenizing (mostly stemming).
639
  *
640
- * @param string $token Search query token.
 
 
641
  */
642
- $token = apply_filters( 'relevanssi_premium_tokenizer', $token );
643
  }
644
 
645
  if ( $accept ) {
@@ -1034,6 +1042,10 @@ function relevanssi_permalink( $link, $link_post = null ) {
1034
  $link = relevanssi_add_highlight( $link, $link_post );
1035
  }
1036
 
 
 
 
 
1037
  return $link;
1038
  }
1039
 
@@ -1193,6 +1205,11 @@ function relevanssi_get_forbidden_post_types() {
1193
  'wffn_optin', // WooFunnel.
1194
  'wffn_oty', // WooFunnel.
1195
  'wp_template', // Block templates.
 
 
 
 
 
1196
  );
1197
  }
1198
 
@@ -1203,18 +1220,20 @@ function relevanssi_get_forbidden_post_types() {
1203
  */
1204
  function relevanssi_get_forbidden_taxonomies() {
1205
  return array(
1206
- 'nav_menu', // Navigation menus.
1207
- 'link_category', // Link categories.
1208
- 'amp_validation_error', // AMP.
1209
- 'product_visibility', // WooCommerce.
1210
- 'wpforms_log_type', // WP Forms.
1211
- 'amp_template', // AMP.
1212
- 'edd_commission_status', // Easy Digital Downloads.
1213
- 'edd_log_type', // Easy Digital Downloads.
1214
- 'elementor_library_type', // Elementor.
1215
- 'elementor_library_category', // Elementor.
1216
- 'elementor_font_type', // Elementor.
1217
- 'wp_theme', // WordPress themes.
 
 
1218
  );
1219
  }
1220
 
@@ -1415,10 +1434,11 @@ EOH;
1415
  *
1416
  * @param int $post_id The post ID.
1417
  * @param boolean $display If false, add "display: none" style to the element.
 
1418
  *
1419
  * @return string The HTML code for the "How Relevanssi sees this post".
1420
  */
1421
- function relevanssi_generate_how_relevanssi_sees( $post_id, $display = true ) {
1422
  $style = '';
1423
  if ( ! $display ) {
1424
  $style = 'style="display: none"';
@@ -1426,12 +1446,12 @@ function relevanssi_generate_how_relevanssi_sees( $post_id, $display = true ) {
1426
 
1427
  $element = '<div id="relevanssi_sees_container" ' . $style . '>';
1428
 
1429
- $data = relevanssi_fetch_sees_data( $post_id );
1430
 
1431
  if ( empty( $data['terms_list'] ) && empty( $data['reason'] ) ) {
1432
  $element .= '<p>'
1433
  // Translators: %d is the post ID.
1434
- . sprintf( __( 'Nothing found for post ID %d.', 'relevanssi' ), $post_id )
1435
  . '</p>';
1436
  $element .= '</div>';
1437
  return $element;
@@ -1442,11 +1462,11 @@ function relevanssi_generate_how_relevanssi_sees( $post_id, $display = true ) {
1442
  $element .= '<p>' . esc_html( $data['reason'] ) . '</p>';
1443
  }
1444
  if ( ! empty( $data['title'] ) ) {
1445
- $element .= '<h3>' . esc_html__( 'Post title', 'relevanssi' ) . '</h3>';
1446
  $element .= '<p>' . esc_html( $data['title'] ) . '</p>';
1447
  }
1448
  if ( ! empty( $data['content'] ) ) {
1449
- $element .= '<h3>' . esc_html__( 'Post content', 'relevanssi' ) . '</h3>';
1450
  $element .= '<p>' . esc_html( $data['content'] ) . '</p>';
1451
  }
1452
  if ( ! empty( $data['comment'] ) ) {
@@ -1492,7 +1512,8 @@ function relevanssi_generate_how_relevanssi_sees( $post_id, $display = true ) {
1492
  /**
1493
  * Fetches the Relevanssi indexing data for a post.
1494
  *
1495
- * @param int $post_id The post ID.
 
1496
  *
1497
  * @global array $relevanssi_variables The Relevanssi global variables array,
1498
  * used for the database table name.
@@ -1501,16 +1522,29 @@ function relevanssi_generate_how_relevanssi_sees( $post_id, $display = true ) {
1501
  * @return array The indexed terms for various parts of the post in an
1502
  * associative array.
1503
  */
1504
- function relevanssi_fetch_sees_data( $post_id ) {
1505
  global $wpdb, $relevanssi_variables;
1506
 
1507
- $terms_list = $wpdb->get_results(
1508
- $wpdb->prepare(
1509
  'SELECT * FROM ' . $relevanssi_variables['relevanssi_table'] . ' WHERE doc = %d', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
1510
  $post_id
1511
- ),
1512
- OBJECT
1513
- );
 
 
 
 
 
 
 
 
 
 
 
 
 
1514
 
1515
  $terms['content'] = array();
1516
  $terms['title'] = array();
551
  * 'body', also removes the body stopwords. Default true.
552
  * @param int $min_word_length The minimum word length to include.
553
  * Default -1.
554
+ * @param string $context The context for tokenization, can be
555
+ * 'indexing' or 'search_query'.
556
  *
557
  * @return int[] An array of tokens as the keys and their frequency as the
558
  * value.
559
  */
560
+ function relevanssi_tokenize( $string, $remove_stops = true, int $min_word_length = -1, $context = 'indexing' ) : array {
561
  if ( ! $string || ( ! is_string( $string ) && ! is_array( $string ) ) ) {
562
  return array();
563
  }
564
+
565
+ $phrase_words = array();
566
+ if ( RELEVANSSI_PREMIUM && 'search_query' === $context ) {
567
+ $string_for_phrases = is_array( $string ) ? implode( ' ', $string ) : $string;
568
+ $phrases = relevanssi_extract_phrases( $string_for_phrases );
569
+ $phrase_words = array();
570
+ foreach ( $phrases as $phrase ) {
571
+ $phrase_words = array_merge( $phrase_words, explode( ' ', $phrase ) );
572
+ }
573
  }
574
 
575
  $tokens = array();
643
  * Filters the token through the Relevanssi Premium tokenizer to add
644
  * some Premium features to the tokenizing (mostly stemming).
645
  *
646
+ * @param string $token Search query token.
647
+ * @param string $context The context for tokenization, can be
648
+ * 'indexing' or 'search_query'.
649
  */
650
+ $token = apply_filters( 'relevanssi_premium_tokenizer', $token, $context );
651
  }
652
 
653
  if ( $accept ) {
1042
  $link = relevanssi_add_highlight( $link, $link_post );
1043
  }
1044
 
1045
+ if ( function_exists( 'relevanssi_add_tracking' ) ) {
1046
+ $link = relevanssi_add_tracking( $link, $link_post );
1047
+ }
1048
+
1049
  return $link;
1050
  }
1051
 
1205
  'wffn_optin', // WooFunnel.
1206
  'wffn_oty', // WooFunnel.
1207
  'wp_template', // Block templates.
1208
+ 'memberpressrule', // Memberpress.
1209
+ 'memberpresscoupon', // Memberpress.
1210
+ 'fl-builder-template', // Beaver Builder.
1211
+ 'itsec-dashboard', // iThemes Security.
1212
+ 'itsec-dash-card', // iThemes Security.
1213
  );
1214
  }
1215
 
1220
  */
1221
  function relevanssi_get_forbidden_taxonomies() {
1222
  return array(
1223
+ 'nav_menu', // Navigation menus.
1224
+ 'link_category', // Link categories.
1225
+ 'amp_validation_error', // AMP.
1226
+ 'product_visibility', // WooCommerce.
1227
+ 'wpforms_log_type', // WP Forms.
1228
+ 'amp_template', // AMP.
1229
+ 'edd_commission_status', // Easy Digital Downloads.
1230
+ 'edd_log_type', // Easy Digital Downloads.
1231
+ 'elementor_library_type', // Elementor.
1232
+ 'elementor_library_category', // Elementor.
1233
+ 'elementor_font_type', // Elementor.
1234
+ 'wp_theme', // WordPress themes.
1235
+ 'fl-builder-template-category', // Beaver Builder.
1236
+ 'fl-builder-template-type', // Beaver Builder.
1237
  );
1238
  }
1239
 
1434
  *
1435
  * @param int $post_id The post ID.
1436
  * @param boolean $display If false, add "display: none" style to the element.
1437
+ * @param string $type One of 'post', 'term' or 'user'. Default 'post'.
1438
  *
1439
  * @return string The HTML code for the "How Relevanssi sees this post".
1440
  */
1441
+ function relevanssi_generate_how_relevanssi_sees( $post_id, $display = true, $type = 'post' ) {
1442
  $style = '';
1443
  if ( ! $display ) {
1444
  $style = 'style="display: none"';
1446
 
1447
  $element = '<div id="relevanssi_sees_container" ' . $style . '>';
1448
 
1449
+ $data = relevanssi_fetch_sees_data( $post_id, $type );
1450
 
1451
  if ( empty( $data['terms_list'] ) && empty( $data['reason'] ) ) {
1452
  $element .= '<p>'
1453
  // Translators: %d is the post ID.
1454
+ . sprintf( __( 'Nothing found for ID %d.', 'relevanssi' ), $post_id )
1455
  . '</p>';
1456
  $element .= '</div>';
1457
  return $element;
1462
  $element .= '<p>' . esc_html( $data['reason'] ) . '</p>';
1463
  }
1464
  if ( ! empty( $data['title'] ) ) {
1465
+ $element .= '<h3>' . esc_html__( 'The title', 'relevanssi' ) . '</h3>';
1466
  $element .= '<p>' . esc_html( $data['title'] ) . '</p>';
1467
  }
1468
  if ( ! empty( $data['content'] ) ) {
1469
+ $element .= '<h3>' . esc_html__( 'The content', 'relevanssi' ) . '</h3>';
1470
  $element .= '<p>' . esc_html( $data['content'] ) . '</p>';
1471
  }
1472
  if ( ! empty( $data['comment'] ) ) {
1512
  /**
1513
  * Fetches the Relevanssi indexing data for a post.
1514
  *
1515
+ * @param int $post_id The post ID.
1516
+ * @param string $type One of 'post', 'term', or 'user'. Default 'post'.
1517
  *
1518
  * @global array $relevanssi_variables The Relevanssi global variables array,
1519
  * used for the database table name.
1522
  * @return array The indexed terms for various parts of the post in an
1523
  * associative array.
1524
  */
1525
+ function relevanssi_fetch_sees_data( $post_id, $type = 'post' ) {
1526
  global $wpdb, $relevanssi_variables;
1527
 
1528
+ if ( 'post' === $type ) {
1529
+ $query = $wpdb->prepare(
1530
  'SELECT * FROM ' . $relevanssi_variables['relevanssi_table'] . ' WHERE doc = %d', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
1531
  $post_id
1532
+ );
1533
+ }
1534
+ if ( 'term' === $type ) {
1535
+ $query = $wpdb->prepare(
1536
+ 'SELECT * FROM ' . $relevanssi_variables['relevanssi_table'] . ' WHERE type NOT IN ("post", "user") AND item = %d', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
1537
+ $post_id
1538
+ );
1539
+ }
1540
+ if ( 'user' === $type ) {
1541
+ $query = $wpdb->prepare(
1542
+ 'SELECT * FROM ' . $relevanssi_variables['relevanssi_table'] . ' WHERE type = "user" AND item = %d', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
1543
+ $post_id
1544
+ );
1545
+ }
1546
+
1547
+ $terms_list = $wpdb->get_results( $query, OBJECT ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
1548
 
1549
  $terms['content'] = array();
1550
  $terms['title'] = array();
lib/compatibility/acf.php CHANGED
@@ -93,7 +93,7 @@ function relevanssi_index_acf( &$insert_data, $post_id, $field_name, $field_valu
93
  $min_word_length = get_option( 'relevanssi_min_word_length', 3 );
94
 
95
  /** This filter is documented in lib/indexing.php */
96
- $value_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $value, true, $min_word_length ), 'custom_field' );
97
  foreach ( $value_tokens as $token => $count ) {
98
  $n++;
99
  if ( ! isset( $insert_data[ $token ]['customfield'] ) ) {
93
  $min_word_length = get_option( 'relevanssi_min_word_length', 3 );
94
 
95
  /** This filter is documented in lib/indexing.php */
96
+ $value_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $value, true, $min_word_length, 'indexing' ), 'custom_field' );
97
  foreach ( $value_tokens as $token => $count ) {
98
  $n++;
99
  if ( ! isset( $insert_data[ $token ]['customfield'] ) ) {
lib/compatibility/bricks.php CHANGED
@@ -13,7 +13,7 @@
13
  add_filter( 'bricks/posts/query_vars', 'relevanssi_bricks_enable', 10 );
14
 
15
  /**
16
- * Undocumented function
17
  *
18
  * @param array $query_vars The query variables.
19
  *
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.
17
  *
18
  * @param array $query_vars The query variables.
19
  *
lib/compatibility/gutenberg.php CHANGED
@@ -37,7 +37,7 @@ function relevanssi_register_gutenberg_actions() {
37
  );
38
  }
39
 
40
- add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 10 );
41
 
42
  /**
43
  * Renders Gutenberg blocks.
@@ -49,11 +49,24 @@ add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 1
49
  *
50
  * @see do_blocks()
51
  *
52
- * @param string $content The post content.
 
53
  *
54
  * @return string The post content with the rendered content added.
55
  */
56
- function relevanssi_gutenberg_block_rendering( $content ) {
 
 
 
 
 
 
 
 
 
 
 
 
57
  $blocks = parse_blocks( $content );
58
  $output = '';
59
 
37
  );
38
  }
39
 
40
+ add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 10, 2 );
41
 
42
  /**
43
  * Renders Gutenberg blocks.
49
  *
50
  * @see do_blocks()
51
  *
52
+ * @param string $content The post content.
53
+ * @param object $post_object The post object.
54
  *
55
  * @return string The post content with the rendered content added.
56
  */
57
+ function relevanssi_gutenberg_block_rendering( $content, $post_object ) {
58
+ /**
59
+ * Filters whether the blocks are rendered or not.
60
+ *
61
+ * If this filter returns false, the blocks in this post are not rendered,
62
+ * and the post content is returned as such.
63
+ *
64
+ * @param boolean If true, render the blocks. Default true.
65
+ * @param object The post object.
66
+ */
67
+ if ( ! apply_filters( 'relevanssi_render_blocks', true, $post_object ) ) {
68
+ return $content;
69
+ }
70
  $blocks = parse_blocks( $content );
71
  $output = '';
72
 
lib/compatibility/polylang.php CHANGED
@@ -148,13 +148,13 @@ function relevanssi_polylang_term_filter( $hits ) {
148
  $original_hit = $hit;
149
  $hit = get_post( $hit );
150
  }
151
- if ( ! isset( $hit->post_content ) ) {
152
  // The "fields" is set to "id=>parent".
153
  $original_hit = $hit;
154
  $hit = get_post( $hit->ID );
155
  }
156
 
157
- if ( -1 === $hit->ID && isset( $hit->term_id ) ) {
158
  $term_id = intval( $hit->term_id );
159
  $translations = pll_get_term_translations( $term_id );
160
  if (
148
  $original_hit = $hit;
149
  $hit = get_post( $hit );
150
  }
151
+ if ( ! isset( $hit->post_content ) && isset( $hit->ID ) ) {
152
  // The "fields" is set to "id=>parent".
153
  $original_hit = $hit;
154
  $hit = get_post( $hit->ID );
155
  }
156
 
157
+ if ( isset( $hit->ID ) && -1 === $hit->ID && isset( $hit->term_id ) ) {
158
  $term_id = intval( $hit->term_id );
159
  $translations = pll_get_term_translations( $term_id );
160
  if (
lib/compatibility/woocommerce.php CHANGED
@@ -11,6 +11,7 @@
11
  */
12
 
13
  add_filter( 'relevanssi_indexing_restriction', 'relevanssi_woocommerce_restriction' );
 
14
 
15
  /**
16
  * This action solves the problems introduced by adjust_posts_count() in
@@ -129,3 +130,25 @@ function relevanssi_sku_boost( $match ) {
129
  }
130
  return $match;
131
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  */
12
 
13
  add_filter( 'relevanssi_indexing_restriction', 'relevanssi_woocommerce_restriction' );
14
+ add_filter( 'relevanssi_admin_search_blocked_post_types', 'relevanssi_woocommerce_admin_search_blocked_post_types' );
15
 
16
  /**
17
  * This action solves the problems introduced by adjust_posts_count() in
130
  }
131
  return $match;
132
  }
133
+
134
+ /**
135
+ * Adds blocked WooCommerce post types to the list of blocked post types.
136
+ *
137
+ * Stops Relevanssi from taking over the admin search for the WooCommerce
138
+ * blocked post types using the relevanssi_admin_search_blocked_post_types
139
+ * filter hook.
140
+ *
141
+ * @param array $post_types The list of blocked post types.
142
+ * @return array
143
+ */
144
+ function relevanssi_woocommerce_admin_search_blocked_post_types( array $post_types ) : array {
145
+ $woo_post_types = array(
146
+ 'shop_coupon',
147
+ 'shop_order',
148
+ 'shop_order_refund',
149
+ 'wc_order_status',
150
+ 'wc_order_email',
151
+ 'shop_webhook',
152
+ );
153
+ return array_merge( $post_types, $woo_post_types );
154
+ }
lib/compatibility/wp-members.php CHANGED
@@ -19,12 +19,12 @@ add_filter( 'relevanssi_post_ok', 'relevanssi_wpmembers_compatibility', 10, 2 );
19
  * the post is blocked by the _wpmem_block custom field, or if the post type is
20
  * blocked in the $wpmem global.
21
  *
22
- * @param bool $post_ok Whether the user is allowed to see the post.
23
- * @param int $post_id The post ID.
24
  *
25
  * @return bool
26
  */
27
- function relevanssi_wpmembers_compatibility( bool $post_ok, int $post_id ) : bool {
28
  global $wpmem;
29
 
30
  if ( is_user_logged_in() ) {
@@ -32,7 +32,9 @@ function relevanssi_wpmembers_compatibility( bool $post_ok, int $post_id ) : boo
32
  }
33
 
34
  $post_meta = get_post_meta( $post_id, '_wpmem_block', true );
35
- $post_type = $wpmem->block[ relevanssi_get_post_type( $post_id ) ];
 
 
36
 
37
  if ( '1' === $post_meta ) {
38
  $post_ok = false;
19
  * the post is blocked by the _wpmem_block custom field, or if the post type is
20
  * blocked in the $wpmem global.
21
  *
22
+ * @param bool $post_ok Whether the user is allowed to see the post.
23
+ * @param int|string $post_id The post ID.
24
  *
25
  * @return bool
26
  */
27
+ function relevanssi_wpmembers_compatibility( bool $post_ok, $post_id ) : bool {
28
  global $wpmem;
29
 
30
  if ( is_user_logged_in() ) {
32
  }
33
 
34
  $post_meta = get_post_meta( $post_id, '_wpmem_block', true );
35
+ $post_type = isset( $wpmem->block[ relevanssi_get_post_type( $post_id ) ] )
36
+ ? $wpmem->block[ relevanssi_get_post_type( $post_id ) ]
37
+ : 0;
38
 
39
  if ( '1' === $post_meta ) {
40
  $post_ok = false;
lib/didyoumean.php CHANGED
@@ -156,7 +156,7 @@ function relevanssi_simple_generate_suggestion( $query ) {
156
  }
157
 
158
  $query = htmlspecialchars_decode( $query, ENT_QUOTES );
159
- $tokens = relevanssi_tokenize( $query );
160
  $suggestions_made = false;
161
  $suggestion = '';
162
 
156
  }
157
 
158
  $query = htmlspecialchars_decode( $query, ENT_QUOTES );
159
+ $tokens = relevanssi_tokenize( $query, true, -1, 'search_query' );
160
  $suggestions_made = false;
161
  $suggestion = '';
162
 
lib/excerpts-highlights.php CHANGED
@@ -69,7 +69,7 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
69
  $min_word_length = 1;
70
  }
71
 
72
- $terms = relevanssi_tokenize( $query, $remove_stopwords, $min_word_length );
73
 
74
  if ( is_array( $query ) ) {
75
  $untokenized_terms = array_filter( $query );
@@ -379,7 +379,7 @@ function relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length =
379
  $remove_stopwords = false;
380
  $non_phrase_terms = array();
381
  foreach ( $phrases as $phrase ) {
382
- $phrase_terms = array_keys( relevanssi_tokenize( $phrase, $remove_stopwords ) );
383
  foreach ( array_keys( $terms ) as $term ) { // array_keys(), because tokenized terms have the term as key.
384
  if ( ! in_array( $term, $phrase_terms, true ) ) {
385
  $non_phrase_terms[ $term ] = true;
@@ -567,7 +567,8 @@ function relevanssi_highlight_terms( $content, $query, $convert_entities = false
567
  relevanssi_tokenize(
568
  $query,
569
  $remove_stopwords,
570
- $min_word_length
 
571
  )
572
  );
573
 
@@ -603,7 +604,7 @@ function relevanssi_highlight_terms( $content, $query, $convert_entities = false
603
  $remove_stopwords = false;
604
  $non_phrase_terms = array();
605
  foreach ( $phrases as $phrase ) {
606
- $phrase_terms = array_keys( relevanssi_tokenize( $phrase, $remove_stopwords ) );
607
  foreach ( $terms as $term ) {
608
  if ( ! in_array( $term, $phrase_terms, true ) ) {
609
  $non_phrase_terms[] = $term;
@@ -617,7 +618,9 @@ function relevanssi_highlight_terms( $content, $query, $convert_entities = false
617
 
618
  $content = strtr( $content, array( "\xC2\xAD" => '' ) );
619
  $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
620
- $content = str_replace( "\n", ' ', $content );
 
 
621
 
622
  foreach ( $terms as $term ) {
623
  $pr_term = preg_quote( $term, '/' );
@@ -693,7 +696,14 @@ function relevanssi_highlight_terms( $content, $query, $convert_entities = false
693
  }
694
  }
695
 
696
- if ( preg_match_all( '/&.*;/U', $content, $matches ) > 0 ) {
 
 
 
 
 
 
 
697
  // Remove highlights from inside HTML entities.
698
  foreach ( $matches as $match ) {
699
  $new_match = str_replace( $start_emp_token, '', $match );
@@ -1493,16 +1503,28 @@ function relevanssi_add_excerpt( &$post, $query ) {
1493
  if ( isset( $post->blog_id ) ) {
1494
  switch_to_blog( $post->blog_id );
1495
  }
1496
- $excerpt_length = get_option( 'relevanssi_excerpt_length' );
1497
- $excerpt_type = get_option( 'relevanssi_excerpt_type' );
1498
  $post->original_excerpt = $post->post_excerpt;
1499
- $post->post_excerpt = relevanssi_do_excerpt(
1500
- $post,
1501
- $query,
1502
- $excerpt_length,
1503
- $excerpt_type
1504
- );
1505
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1506
  if ( isset( $post->blog_id ) ) {
1507
  restore_current_blog();
1508
  }
69
  $min_word_length = 1;
70
  }
71
 
72
+ $terms = relevanssi_tokenize( $query, $remove_stopwords, $min_word_length, 'search_query' );
73
 
74
  if ( is_array( $query ) ) {
75
  $untokenized_terms = array_filter( $query );
379
  $remove_stopwords = false;
380
  $non_phrase_terms = array();
381
  foreach ( $phrases as $phrase ) {
382
+ $phrase_terms = array_keys( relevanssi_tokenize( $phrase, $remove_stopwords, -1, 'search_query' ) );
383
  foreach ( array_keys( $terms ) as $term ) { // array_keys(), because tokenized terms have the term as key.
384
  if ( ! in_array( $term, $phrase_terms, true ) ) {
385
  $non_phrase_terms[ $term ] = true;
567
  relevanssi_tokenize(
568
  $query,
569
  $remove_stopwords,
570
+ $min_word_length,
571
+ 'search_query'
572
  )
573
  );
574
 
604
  $remove_stopwords = false;
605
  $non_phrase_terms = array();
606
  foreach ( $phrases as $phrase ) {
607
+ $phrase_terms = array_keys( relevanssi_tokenize( $phrase, $remove_stopwords, -1, 'search_query' ) );
608
  foreach ( $terms as $term ) {
609
  if ( ! in_array( $term, $phrase_terms, true ) ) {
610
  $non_phrase_terms[] = $term;
618
 
619
  $content = strtr( $content, array( "\xC2\xAD" => '' ) );
620
  $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
621
+ if ( ! $convert_entities ) {
622
+ $content = str_replace( "\n", ' ', $content );
623
+ }
624
 
625
  foreach ( $terms as $term ) {
626
  $pr_term = preg_quote( $term, '/' );
696
  }
697
  }
698
 
699
+ $start_quoted = preg_quote( $start_emp_token, '/' );
700
+ $end_quoted = preg_quote( $end_emp_token, '/' );
701
+ if (
702
+ preg_match_all(
703
+ '/&' . $start_quoted . '([a-z0-9]+|#[0-9]{1,6}|#x[0-9a-fA-F]{1,6})' . $end_quoted . ';/U',
704
+ $content,
705
+ $matches
706
+ ) > 0 ) {
707
  // Remove highlights from inside HTML entities.
708
  foreach ( $matches as $match ) {
709
  $new_match = str_replace( $start_emp_token, '', $match );
1503
  if ( isset( $post->blog_id ) ) {
1504
  switch_to_blog( $post->blog_id );
1505
  }
 
 
1506
  $post->original_excerpt = $post->post_excerpt;
1507
+ /**
1508
+ * Filters whether an excerpt should be added to a post or not.
1509
+ *
1510
+ * If this filter hook returns false, Relevanssi does not create an excerpt
1511
+ * for the post. The original excerpt is still copied to
1512
+ * $post->original_excerpt.
1513
+ *
1514
+ * @param boolean If true, create an excerpt. Default true.
1515
+ * @param WP_Post $post The post object.
1516
+ * @param string $query The search quer.
1517
+ */
1518
+ if ( apply_filters( 'relevanssi_excerpt_post', true, $post, $query ) ) {
1519
+ $excerpt_length = get_option( 'relevanssi_excerpt_length' );
1520
+ $excerpt_type = get_option( 'relevanssi_excerpt_type' );
1521
+ $post->post_excerpt = relevanssi_do_excerpt(
1522
+ $post,
1523
+ $query,
1524
+ $excerpt_length,
1525
+ $excerpt_type
1526
+ );
1527
+ }
1528
  if ( isset( $post->blog_id ) ) {
1529
  restore_current_blog();
1530
  }
lib/indexing.php CHANGED
@@ -654,7 +654,7 @@ function relevanssi_index_taxonomy_terms( &$insert_data, $post_id, $taxonomy, $d
654
  /** This filter is documented in lib/indexing.php */
655
  $term_tokens = apply_filters(
656
  'relevanssi_indexing_tokens',
657
- relevanssi_tokenize( $term_string, true, $min_word_length ),
658
  'taxonomy-' . $taxonomy
659
  );
660
 
@@ -1160,7 +1160,7 @@ function relevanssi_index_comments( &$insert_data, $post_id, $min_word_length, $
1160
  */
1161
  $post_comments_tokens = apply_filters(
1162
  'relevanssi_indexing_tokens',
1163
- relevanssi_tokenize( $post_comments, true, $min_word_length ),
1164
  'comments'
1165
  );
1166
  if ( count( $post_comments_tokens ) > 0 ) {
@@ -1199,7 +1199,7 @@ function relevanssi_index_author( &$insert_data, $post_author, $min_word_length,
1199
  /** This filter is documented in lib/indexing.php */
1200
  $name_tokens = apply_filters(
1201
  'relevanssi_indexing_tokens',
1202
- relevanssi_tokenize( $display_name, false, $min_word_length ),
1203
  'author'
1204
  );
1205
  if ( $debug ) {
@@ -1294,7 +1294,7 @@ function relevanssi_index_custom_fields( &$insert_data, $post_id, $custom_fields
1294
  /** This filter is documented in lib/indexing.php */
1295
  $value_tokens = apply_filters(
1296
  'relevanssi_indexing_tokens',
1297
- relevanssi_tokenize( $value, $remove_stops, $min_word_length ),
1298
  $context
1299
  );
1300
 
@@ -1341,7 +1341,7 @@ function relevanssi_index_excerpt( &$insert_data, $excerpt, $min_word_length, $d
1341
  /** This filter is documented in common/indexing.php */
1342
  $excerpt_tokens = apply_filters(
1343
  'relevanssi_indexing_tokens',
1344
- relevanssi_tokenize( $excerpt, true, $min_word_length ),
1345
  'excerpt'
1346
  );
1347
  foreach ( $excerpt_tokens as $token => $count ) {
@@ -1400,7 +1400,8 @@ function relevanssi_index_title( &$insert_data, $post, $min_word_length, $debug
1400
  * @param boolean If true, remove stopwords. Default true.
1401
  */
1402
  apply_filters( 'relevanssi_remove_stopwords_in_titles', true ),
1403
- $min_word_length
 
1404
  );
1405
  /** This filter is documented in lib/indexing.php */
1406
  $title_tokens = apply_filters( 'relevanssi_indexing_tokens', $title_tokens, 'title' );
@@ -1510,12 +1511,14 @@ function relevanssi_index_content( &$insert_data, $post_object, $min_word_length
1510
  * @param object $post_object The full post object.
1511
  */
1512
  $contents = apply_filters( 'relevanssi_post_content_before_tokenize', $contents, $post_object );
 
1513
  /** This filter is documented in lib/indexing.php */
1514
  $content_tokens = apply_filters(
1515
  'relevanssi_indexing_tokens',
1516
- relevanssi_tokenize( $contents, 'body', $min_word_length ),
1517
  'content'
1518
  );
 
1519
  if ( $debug ) {
1520
  relevanssi_debug_echo( "\tContent, tokenized:\n" . implode( ' ', array_keys( $content_tokens ) ) );
1521
  }
654
  /** This filter is documented in lib/indexing.php */
655
  $term_tokens = apply_filters(
656
  'relevanssi_indexing_tokens',
657
+ relevanssi_tokenize( $term_string, true, $min_word_length, 'indexing' ),
658
  'taxonomy-' . $taxonomy
659
  );
660
 
1160
  */
1161
  $post_comments_tokens = apply_filters(
1162
  'relevanssi_indexing_tokens',
1163
+ relevanssi_tokenize( $post_comments, true, $min_word_length, 'indexing' ),
1164
  'comments'
1165
  );
1166
  if ( count( $post_comments_tokens ) > 0 ) {
1199
  /** This filter is documented in lib/indexing.php */
1200
  $name_tokens = apply_filters(
1201
  'relevanssi_indexing_tokens',
1202
+ relevanssi_tokenize( $display_name, false, $min_word_length, 'indexing' ),
1203
  'author'
1204
  );
1205
  if ( $debug ) {
1294
  /** This filter is documented in lib/indexing.php */
1295
  $value_tokens = apply_filters(
1296
  'relevanssi_indexing_tokens',
1297
+ relevanssi_tokenize( $value, $remove_stops, $min_word_length, 'indexing' ),
1298
  $context
1299
  );
1300
 
1341
  /** This filter is documented in common/indexing.php */
1342
  $excerpt_tokens = apply_filters(
1343
  'relevanssi_indexing_tokens',
1344
+ relevanssi_tokenize( $excerpt, true, $min_word_length, 'indexing' ),
1345
  'excerpt'
1346
  );
1347
  foreach ( $excerpt_tokens as $token => $count ) {
1400
  * @param boolean If true, remove stopwords. Default true.
1401
  */
1402
  apply_filters( 'relevanssi_remove_stopwords_in_titles', true ),
1403
+ $min_word_length,
1404
+ 'indexing'
1405
  );
1406
  /** This filter is documented in lib/indexing.php */
1407
  $title_tokens = apply_filters( 'relevanssi_indexing_tokens', $title_tokens, 'title' );
1511
  * @param object $post_object The full post object.
1512
  */
1513
  $contents = apply_filters( 'relevanssi_post_content_before_tokenize', $contents, $post_object );
1514
+
1515
  /** This filter is documented in lib/indexing.php */
1516
  $content_tokens = apply_filters(
1517
  'relevanssi_indexing_tokens',
1518
+ relevanssi_tokenize( $contents, 'body', $min_word_length, 'indexing' ),
1519
  'content'
1520
  );
1521
+
1522
  if ( $debug ) {
1523
  relevanssi_debug_echo( "\tContent, tokenized:\n" . implode( ' ', array_keys( $content_tokens ) ) );
1524
  }
lib/init.php CHANGED
@@ -179,6 +179,8 @@ function relevanssi_admin_init() {
179
 
180
  add_action( 'admin_enqueue_scripts', 'relevanssi_add_admin_scripts' );
181
  add_filter( 'plugin_action_links_' . $relevanssi_variables['plugin_basename'], 'relevanssi_action_links' );
 
 
182
  }
183
 
184
  /**
@@ -269,7 +271,11 @@ function relevanssi_query_vars( $qv ) {
269
  function relevanssi_create_database_tables( $relevanssi_db_version ) {
270
  global $wpdb;
271
 
272
- require_once ABSPATH . 'wp-admin/includes/upgrade.php';
 
 
 
 
273
 
274
  $charset_collate_bin_column = '';
275
  $charset_collate = '';
@@ -290,125 +296,135 @@ function relevanssi_create_database_tables( $relevanssi_db_version ) {
290
  $relevanssi_table = $wpdb->prefix . 'relevanssi';
291
  $relevanssi_stopword_table = $wpdb->prefix . 'relevanssi_stopwords';
292
  $relevanssi_log_table = $wpdb->prefix . 'relevanssi_log';
293
- $current_db_version = get_option( 'relevanssi_db_version' );
294
-
295
- if ( $current_db_version !== $relevanssi_db_version ) {
296
- $sql = 'CREATE TABLE ' . $relevanssi_table . " (doc bigint(20) NOT NULL DEFAULT '0',
297
- term varchar(50) NOT NULL DEFAULT '0',
298
- term_reverse varchar(50) NOT NULL DEFAULT '0',
299
- content mediumint(9) NOT NULL DEFAULT '0',
300
- title mediumint(9) NOT NULL DEFAULT '0',
301
- comment mediumint(9) NOT NULL DEFAULT '0',
302
- tag mediumint(9) NOT NULL DEFAULT '0',
303
- link mediumint(9) NOT NULL DEFAULT '0',
304
- author mediumint(9) NOT NULL DEFAULT '0',
305
- category mediumint(9) NOT NULL DEFAULT '0',
306
- excerpt mediumint(9) NOT NULL DEFAULT '0',
307
- taxonomy mediumint(9) NOT NULL DEFAULT '0',
308
- customfield mediumint(9) NOT NULL DEFAULT '0',
309
- mysqlcolumn mediumint(9) NOT NULL DEFAULT '0',
310
- taxonomy_detail longtext NOT NULL,
311
- customfield_detail longtext NOT NULL DEFAULT '',
312
- mysqlcolumn_detail longtext NOT NULL DEFAULT '',
313
- type varchar(210) NOT NULL DEFAULT 'post',
314
- item bigint(20) NOT NULL DEFAULT '0',
315
- PRIMARY KEY doctermitem (doc, term, item)) $charset_collate";
316
-
317
- dbDelta( $sql );
318
-
319
- $sql = "SHOW INDEX FROM $relevanssi_table";
320
- $indices = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
321
-
322
- $terms_exists = false;
323
- $relevanssi_term_reverse_idx_exists = false;
324
- $docs_exists = false;
325
- $typeitem_exists = false;
326
- $doctermitem_exists = false;
327
- foreach ( $indices as $index ) {
328
- if ( 'terms' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
329
- $terms_exists = true;
330
- }
331
- if ( 'relevanssi_term_reverse_idx' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
332
- $relevanssi_term_reverse_idx_exists = true;
333
- }
334
- if ( 'docs' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
335
- $docs_exists = true;
336
- }
337
- if ( 'typeitem' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
338
- $typeitem_exists = true;
339
- }
340
- if ( 'doctermitem' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
341
- $doctermitem_exists = true;
342
- }
343
- }
344
 
345
- if ( ! $terms_exists ) {
346
- $sql = "CREATE INDEX terms ON $relevanssi_table (term(20))";
347
- $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
348
- }
349
 
350
- if ( ! $relevanssi_term_reverse_idx_exists ) {
351
- $sql = "CREATE INDEX relevanssi_term_reverse_idx ON $relevanssi_table (term_reverse(10))";
352
- $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  }
354
-
355
- if ( ! $typeitem_exists ) {
356
- $sql = "CREATE INDEX typeitem ON $relevanssi_table (type(190), item)";
357
- $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
358
  }
359
-
360
- if ( $doctermitem_exists ) {
361
- $sql = "DROP INDEX doctermitem ON $relevanssi_table";
362
- $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
363
  }
364
-
365
- if ( $docs_exists ) { // This index was removed in 4.9.2 / 2.11.2.
366
- $sql = "DROP INDEX docs ON $relevanssi_table";
367
- $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
368
  }
 
 
 
 
369
 
370
- $sql = 'CREATE TABLE ' . $relevanssi_stopword_table . " (stopword varchar(50) $charset_collate_bin_column NOT NULL,
371
- PRIMARY KEY stopword (stopword)) $charset_collate;";
 
 
372
 
373
- dbDelta( $sql );
 
 
 
374
 
375
- $sql = 'CREATE TABLE ' . $relevanssi_log_table . " (id bigint(9) NOT NULL AUTO_INCREMENT,
376
- query varchar(200) NOT NULL,
377
- hits mediumint(9) NOT NULL DEFAULT '0',
378
- time timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
379
- user_id bigint(20) NOT NULL DEFAULT '0',
380
- ip varchar(40) NOT NULL DEFAULT '',
381
- PRIMARY KEY id (id)) $charset_collate;";
382
 
383
- dbDelta( $sql );
 
 
 
384
 
385
- $sql = "SHOW INDEX FROM $relevanssi_log_table";
386
- $indices = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
 
 
387
 
388
- $query_exists = false;
389
- $id_exists = false;
390
- foreach ( $indices as $index ) {
391
- if ( 'query' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
392
- $query_exists = true;
393
- }
394
- if ( 'id' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
395
- $id_exists = true;
396
- }
397
- }
398
 
399
- if ( ! $query_exists ) {
400
- $sql = "CREATE INDEX query ON $relevanssi_log_table (query(190))";
401
- $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
402
- }
403
 
404
- if ( $id_exists ) {
405
- $sql = "DROP INDEX id ON $relevanssi_log_table";
406
- $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  }
 
 
 
 
 
 
408
 
409
- update_option( 'relevanssi_db_version', $relevanssi_db_version );
 
 
410
  }
411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  $stopwords = relevanssi_fetch_stopwords();
413
  if ( empty( $stopwords ) ) {
414
  relevanssi_populate_stopwords();
179
 
180
  add_action( 'admin_enqueue_scripts', 'relevanssi_add_admin_scripts' );
181
  add_filter( 'plugin_action_links_' . $relevanssi_variables['plugin_basename'], 'relevanssi_action_links' );
182
+
183
+ relevanssi_create_database_tables( $relevanssi_variables['database_version'] );
184
  }
185
 
186
  /**
271
  function relevanssi_create_database_tables( $relevanssi_db_version ) {
272
  global $wpdb;
273
 
274
+ $current_db_version = get_option( 'relevanssi_db_version' );
275
+
276
+ if ( $current_db_version === $relevanssi_db_version ) {
277
+ return;
278
+ }
279
 
280
  $charset_collate_bin_column = '';
281
  $charset_collate = '';
296
  $relevanssi_table = $wpdb->prefix . 'relevanssi';
297
  $relevanssi_stopword_table = $wpdb->prefix . 'relevanssi_stopwords';
298
  $relevanssi_log_table = $wpdb->prefix . 'relevanssi_log';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
299
 
300
+ require_once ABSPATH . 'wp-admin/includes/upgrade.php';
 
 
 
301
 
302
+ $sql = 'CREATE TABLE ' . $relevanssi_table . " (doc bigint(20) NOT NULL DEFAULT '0',
303
+ term varchar(50) NOT NULL DEFAULT '0',
304
+ term_reverse varchar(50) NOT NULL DEFAULT '0',
305
+ content mediumint(9) NOT NULL DEFAULT '0',
306
+ title mediumint(9) NOT NULL DEFAULT '0',
307
+ comment mediumint(9) NOT NULL DEFAULT '0',
308
+ tag mediumint(9) NOT NULL DEFAULT '0',
309
+ link mediumint(9) NOT NULL DEFAULT '0',
310
+ author mediumint(9) NOT NULL DEFAULT '0',
311
+ category mediumint(9) NOT NULL DEFAULT '0',
312
+ excerpt mediumint(9) NOT NULL DEFAULT '0',
313
+ taxonomy mediumint(9) NOT NULL DEFAULT '0',
314
+ customfield mediumint(9) NOT NULL DEFAULT '0',
315
+ mysqlcolumn mediumint(9) NOT NULL DEFAULT '0',
316
+ taxonomy_detail longtext NOT NULL,
317
+ customfield_detail longtext NOT NULL DEFAULT '',
318
+ mysqlcolumn_detail longtext NOT NULL DEFAULT '',
319
+ type varchar(210) NOT NULL DEFAULT 'post',
320
+ item bigint(20) NOT NULL DEFAULT '0',
321
+ PRIMARY KEY doctermitem (doc, term, item)) $charset_collate";
322
+
323
+ dbDelta( $sql );
324
+
325
+ $sql = "SHOW INDEX FROM $relevanssi_table";
326
+ $indices = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery
327
+
328
+ $terms_exists = false;
329
+ $relevanssi_term_reverse_idx_exists = false;
330
+ $docs_exists = false;
331
+ $typeitem_exists = false;
332
+ $doctermitem_exists = false;
333
+ foreach ( $indices as $index ) {
334
+ if ( 'terms' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
335
+ $terms_exists = true;
336
  }
337
+ if ( 'relevanssi_term_reverse_idx' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
338
+ $relevanssi_term_reverse_idx_exists = true;
 
 
339
  }
340
+ if ( 'docs' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
341
+ $docs_exists = true;
 
 
342
  }
343
+ if ( 'typeitem' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
344
+ $typeitem_exists = true;
 
 
345
  }
346
+ if ( 'doctermitem' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
347
+ $doctermitem_exists = true;
348
+ }
349
+ }
350
 
351
+ if ( ! $terms_exists ) {
352
+ $sql = "CREATE INDEX terms ON $relevanssi_table (term(20))";
353
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
354
+ }
355
 
356
+ if ( ! $relevanssi_term_reverse_idx_exists ) {
357
+ $sql = "CREATE INDEX relevanssi_term_reverse_idx ON $relevanssi_table (term_reverse(10))";
358
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
359
+ }
360
 
361
+ if ( ! $typeitem_exists ) {
362
+ $sql = "CREATE INDEX typeitem ON $relevanssi_table (type(190), item)";
363
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
364
+ }
 
 
 
365
 
366
+ if ( $doctermitem_exists ) {
367
+ $sql = "DROP INDEX doctermitem ON $relevanssi_table";
368
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
369
+ }
370
 
371
+ if ( $docs_exists ) { // This index was removed in 4.9.2 / 2.11.2.
372
+ $sql = "DROP INDEX docs ON $relevanssi_table";
373
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery
374
+ }
375
 
376
+ $sql = 'CREATE TABLE ' . $relevanssi_stopword_table . " (stopword varchar(50) $charset_collate_bin_column NOT NULL,
377
+ PRIMARY KEY stopword (stopword)) $charset_collate;";
 
 
 
 
 
 
 
 
378
 
379
+ dbDelta( $sql );
 
 
 
380
 
381
+ $sql = 'CREATE TABLE ' . $relevanssi_log_table . " (id bigint(9) NOT NULL AUTO_INCREMENT,
382
+ query varchar(200) NOT NULL,
383
+ hits mediumint(9) NOT NULL DEFAULT '0',
384
+ time timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
385
+ user_id bigint(20) NOT NULL DEFAULT '0',
386
+ ip varchar(40) NOT NULL DEFAULT '',
387
+ PRIMARY KEY id (id)) $charset_collate;";
388
+
389
+ dbDelta( $sql );
390
+
391
+ $sql = "SHOW INDEX FROM $relevanssi_log_table";
392
+ $indices = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
393
+
394
+ $query_exists = false;
395
+ $id_exists = false;
396
+ foreach ( $indices as $index ) {
397
+ if ( 'query' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
398
+ $query_exists = true;
399
+ }
400
+ if ( 'id' === $index->Key_name ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
401
+ $id_exists = true;
402
  }
403
+ }
404
+
405
+ if ( ! $query_exists ) {
406
+ $sql = "CREATE INDEX query ON $relevanssi_log_table (query(190))";
407
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
408
+ }
409
 
410
+ if ( $id_exists ) {
411
+ $sql = "DROP INDEX id ON $relevanssi_log_table";
412
+ $wpdb->query( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.NoCaching
413
  }
414
 
415
+ /**
416
+ * Allows adding database tables.
417
+ *
418
+ * An action hook that runs in the create tables process if the database
419
+ * version in the options doesn't match the database version in the
420
+ * code.
421
+ *
422
+ * @param string $charset_collate The collation.
423
+ */
424
+ do_action( 'relevanssi_create_tables', $charset_collate );
425
+
426
+ update_option( 'relevanssi_db_version', $relevanssi_db_version );
427
+
428
  $stopwords = relevanssi_fetch_stopwords();
429
  if ( empty( $stopwords ) ) {
430
  relevanssi_populate_stopwords();
lib/install.php CHANGED
@@ -91,7 +91,7 @@ function _relevanssi_install() {
91
  add_option( 'relevanssi_expand_highlights', 'off' );
92
  add_option( 'relevanssi_expand_shortcodes', 'on' );
93
  add_option( 'relevanssi_extag', '0' );
94
- add_option( 'relevanssi_fuzzy', 'sometimes' );
95
  add_option( 'relevanssi_highlight', 'strong' );
96
  add_option( 'relevanssi_highlight_comments', 'off' );
97
  add_option( 'relevanssi_highlight_docs', 'off' );
91
  add_option( 'relevanssi_expand_highlights', 'off' );
92
  add_option( 'relevanssi_expand_shortcodes', 'on' );
93
  add_option( 'relevanssi_extag', '0' );
94
+ add_option( 'relevanssi_fuzzy', 'always' );
95
  add_option( 'relevanssi_highlight', 'strong' );
96
  add_option( 'relevanssi_highlight_comments', 'off' );
97
  add_option( 'relevanssi_highlight_docs', 'off' );
lib/interface.php CHANGED
@@ -99,45 +99,12 @@ function relevanssi_options() {
99
  echo "<div style='clear:both'></div></div>";
100
  }
101
 
102
- /**
103
- * Prints out the 'User searches' page.
104
- */
105
- function relevanssi_search_stats() {
106
- $relevanssi_hide_branding = get_option( 'relevanssi_hide_branding' );
107
-
108
- if ( 'on' === $relevanssi_hide_branding ) {
109
- $options_txt = __( 'User searches', 'relevanssi' );
110
- } else {
111
- $options_txt = __( 'Relevanssi User Searches', 'relevanssi' );
112
- }
113
-
114
- if ( isset( $_REQUEST['relevanssi_reset'] ) && current_user_can( 'manage_options' ) ) {
115
- check_admin_referer( 'relevanssi_reset_logs', '_relresnonce' );
116
- if ( isset( $_REQUEST['relevanssi_reset_code'] ) ) {
117
- if ( 'reset' === $_REQUEST['relevanssi_reset_code'] ) {
118
- $verbose = true;
119
- relevanssi_truncate_logs( $verbose );
120
- }
121
- }
122
- }
123
-
124
- printf( "<div class='wrap'><h2>%s</h2>", esc_html( $options_txt ) );
125
-
126
- if ( 'on' === get_option( 'relevanssi_log_queries' ) ) {
127
- relevanssi_query_log();
128
- } else {
129
- printf( '<p>%s</p>', esc_html__( 'Enable query logging to see stats here.', 'relevanssi' ) );
130
- }
131
- }
132
-
133
  /**
134
  * Prints out the 'Admin search' page.
135
  */
136
  function relevanssi_admin_search_page() {
137
  global $relevanssi_variables;
138
 
139
- $relevanssi_hide_branding = get_option( 'relevanssi_hide_branding' );
140
-
141
  $options_txt = __( 'Admin Search', 'relevanssi' );
142
 
143
  printf( "<div class='wrap'><h2>%s</h2>", esc_html( $options_txt ) );
@@ -172,232 +139,13 @@ function relevanssi_truncate_logs( $verbose = true ) {
172
  return $result;
173
  }
174
 
175
- /**
176
- * Shows the query log with the most common queries
177
- *
178
- * Uses relevanssi_total_queries() and relevanssi_date_queries() to fetch the data.
179
- */
180
- function relevanssi_query_log() {
181
- /**
182
- * Adjusts the number of days to show the logs in User searches page.
183
- *
184
- * @param int Number of days, default 1.
185
- */
186
- $days1 = apply_filters( 'relevanssi_1day', 1 );
187
-
188
- /**
189
- * Adjusts the number of days to show the logs in User searches page.
190
- *
191
- * @param int Number of days, default 7.
192
- */
193
- $days7 = apply_filters( 'relevanssi_7days', 7 );
194
-
195
- /**
196
- * Adjusts the number of days to show the logs in User searches page.
197
- *
198
- * @param int Number of days, default 30.
199
- */
200
- $days30 = apply_filters( 'relevanssi_30days', 30 );
201
-
202
- printf( '<h3>%s</h3>', esc_html__( 'Total Searches', 'relevanssi' ) );
203
-
204
- printf( "<div style='width: 50%%; overflow: auto'>%s</div>", relevanssi_total_queries( __( 'Totals', 'relevanssi' ) ) ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
205
-
206
- echo '<div style="clear: both"></div>';
207
-
208
- printf( '<h3>%s</h3>', esc_html__( 'Common Queries', 'relevanssi' ) );
209
-
210
- /**
211
- * Filters the number of rows to show.
212
- *
213
- * @param int Number of top results to show, default 20.
214
- */
215
- $limit = apply_filters( 'relevanssi_user_searches_limit', 20 );
216
-
217
- // Translators: %d is the number of queries to show.
218
- printf( '<p>%s</p>', esc_html( sprintf( __( 'Here you can see the %d most common user search queries, how many times those queries were made and how many results were found for those queries.', 'relevanssi' ), $limit ) ) );
219
-
220
- echo "<div style='width: 30%; float: left; margin-right: 2%; overflow: auto'>";
221
- if ( 1 === $days1 ) {
222
- relevanssi_date_queries( $days1, __( 'Today and yesterday', 'relevanssi' ) );
223
- } else {
224
- // Translators: number of days to show.
225
- relevanssi_date_queries( $days1, sprintf( __( 'Last %d days', 'relevanssi' ), $days1 ) );
226
- }
227
- echo '</div>';
228
-
229
- echo "<div style='width: 30%; float: left; margin-right: 2%; overflow: auto'>";
230
- // Translators: number of days to show.
231
- relevanssi_date_queries( $days7, sprintf( __( 'Last %d days', 'relevanssi' ), $days7 ) );
232
- echo '</div>';
233
-
234
- echo "<div style='width: 30%; float: left; margin-right: 2%; overflow: auto'>";
235
- // Translators: number of days to show.
236
- relevanssi_date_queries( $days30, sprintf( __( 'Last %d days', 'relevanssi' ), $days30 ) );
237
- echo '</div>';
238
-
239
- echo '<div style="clear: both"></div>';
240
-
241
- printf( '<h3>%s</h3>', esc_html__( 'Unsuccessful Queries', 'relevanssi' ) );
242
-
243
- echo "<div style='width: 30%; float: left; margin-right: 2%; overflow: auto'>";
244
- relevanssi_date_queries( 1, __( 'Today and yesterday', 'relevanssi' ), 'bad' );
245
- echo '</div>';
246
-
247
- echo "<div style='width: 30%; float: left; margin-right: 2%; overflow: auto'>";
248
- relevanssi_date_queries( 7, __( 'Last 7 days', 'relevanssi' ), 'bad' );
249
- echo '</div>';
250
-
251
- echo "<div style='width: 30%; float: left; margin-right: 2%; overflow: auto'>";
252
- // Translators: number of days to show.
253
- relevanssi_date_queries( $days30, sprintf( __( 'Last %d days', 'relevanssi' ), $days30 ), 'bad' );
254
- echo '</div>';
255
-
256
- if ( current_user_can( 'manage_options' ) ) {
257
-
258
- echo '<div style="clear: both"></div>';
259
- printf( '<h3>%s</h3>', esc_html__( 'Reset Logs', 'relevanssi' ) );
260
- print( "<form method='post'>" );
261
- wp_nonce_field( 'relevanssi_reset_logs', '_relresnonce', true, true );
262
- printf(
263
- '<p><label for="relevanssi_reset_code">%s</label>
264
- <input type="text" id="relevanssi_reset_code" name="relevanssi_reset_code" />
265
- <input type="submit" name="relevanssi_reset" value="%s" class="button" /></p></form>',
266
- // Translators: do not translate "reset".
267
- esc_html__(
268
- 'To reset the logs, type "reset" into the box here and click the Reset button',
269
- 'relevanssi'
270
- ),
271
- esc_html__( 'Reset', 'relevanssi' )
272
- );
273
- }
274
-
275
- echo '</div>';
276
- }
277
-
278
- /**
279
- * Shows the total number of searches on 'User searches' page.
280
- *
281
- * @global object $wpdb The WP database interface.
282
- * @global array $relevanssi_variables The global Relevanssi variables array.
283
- *
284
- * @param string $title The title that is printed out on top of the results.
285
- */
286
- function relevanssi_total_queries( $title ) {
287
- global $wpdb, $relevanssi_variables;
288
- $log_table = $relevanssi_variables['log_table'];
289
-
290
- $count = array();
291
- $titles = array();
292
-
293
- $titles[0] = __( 'Today and yesterday', 'relevanssi' );
294
- $titles[1] = __( 'Last 7 days', 'relevanssi' );
295
- $titles[2] = __( 'Last 30 days', 'relevanssi' );
296
- $titles[3] = __( 'Forever', 'relevanssi' );
297
-
298
- $count[0] = $wpdb->get_var( "SELECT COUNT(id) FROM $log_table WHERE TIMESTAMPDIFF(DAY, time, NOW()) <= 1;" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
299
- $count[1] = $wpdb->get_var( "SELECT COUNT(id) FROM $log_table WHERE TIMESTAMPDIFF(DAY, time, NOW()) <= 7;" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
300
- $count[2] = $wpdb->get_var( "SELECT COUNT(id) FROM $log_table WHERE TIMESTAMPDIFF(DAY, time, NOW()) <= 30;" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
301
- $count[3] = $wpdb->get_var( "SELECT COUNT(id) FROM $log_table;" ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
302
-
303
- printf(
304
- '<table class="widefat"><thead><tr><th colspan="2">%1$s</th></tr></thead><tbody><tr><th>%2$s</th><th style="text-align: center">%3$s</th></tr>',
305
- esc_html( $title ),
306
- esc_html__( 'When', 'relevanssi' ),
307
- esc_html__( 'Searches', 'relevanssi' )
308
- );
309
-
310
- foreach ( $count as $key => $searches ) {
311
- $when = $titles[ $key ];
312
- printf( "<tr><td>%s</td><td style='text-align: center'>%d</td></tr>", esc_html( $when ), intval( $searches ) );
313
- }
314
- echo '</tbody></table>';
315
- }
316
-
317
- /**
318
- * Shows the most common search queries on different time periods.
319
- *
320
- * @global object $wpdb The WP database interface.
321
- * @global array $relevanssi_variables The global Relevanssi variables array.
322
- *
323
- * @param int $days The number of days to show.
324
- * @param string $title The title that is printed out on top of the results.
325
- * @param string $version If 'good', show the searches that found something; if
326
- * 'bad', show the searches that didn't find anything. Default 'good'.
327
- */
328
- function relevanssi_date_queries( $days, $title, $version = 'good' ) {
329
- global $wpdb, $relevanssi_variables;
330
- $log_table = $relevanssi_variables['log_table'];
331
-
332
- /** Documented in lib/interface.php. */
333
- $limit = apply_filters( 'relevanssi_user_searches_limit', 20 );
334
-
335
- if ( 'good' === $version ) {
336
- $queries = $wpdb->get_results(
337
- $wpdb->prepare(
338
- 'SELECT COUNT(DISTINCT(id)) as cnt, query, hits ' .
339
- "FROM $log_table " . // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
340
- 'WHERE TIMESTAMPDIFF(DAY, time, NOW()) <= %d
341
- GROUP BY query
342
- ORDER BY cnt DESC
343
- LIMIT %d',
344
- $days,
345
- $limit
346
- )
347
- );
348
- }
349
-
350
- if ( 'bad' === $version ) {
351
- $queries = $wpdb->get_results(
352
- $wpdb->prepare(
353
- 'SELECT COUNT(DISTINCT(id)) as cnt, query, hits ' .
354
- "FROM $log_table " . // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
355
- 'WHERE TIMESTAMPDIFF(DAY, time, NOW()) <= %d AND hits = 0
356
- GROUP BY query
357
- ORDER BY cnt DESC
358
- LIMIT %d',
359
- $days,
360
- $limit
361
- )
362
- ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
363
- }
364
-
365
- if ( count( $queries ) > 0 ) {
366
- printf(
367
- "<table class='widefat'><thead><tr><th colspan='3'>%s</th></tr></thead><tbody><tr><th>%s</th><th style='text-align: center'>#</th><th style='text-align: center'>%s</th></tr>",
368
- esc_html( $title ),
369
- esc_html__( 'Query', 'relevanssi' ),
370
- esc_html__( 'Hits', 'relevanssi' )
371
- );
372
- $url = get_bloginfo( 'url' );
373
- foreach ( $queries as $query ) {
374
- $search_parameter = rawurlencode( $query->query );
375
- /**
376
- * Filters the query URL for the user searches page.
377
- *
378
- * @param string Query URL.
379
- */
380
- $query_url = apply_filters( 'relevanssi_user_searches_query_url', $url . '/?s=' . $search_parameter );
381
- printf(
382
- "<tr><td><a href='%s'>%s</a></td><td style='padding: 3px 5px; text-align: center'>%d</td><td style='padding: 3px 5px; text-align: center'>%d</td></tr>",
383
- esc_attr( $query_url ),
384
- esc_attr( $query->query ),
385
- intval( $query->cnt ),
386
- intval( $query->hits )
387
- );
388
- }
389
- echo '</tbody></table>';
390
- }
391
- }
392
-
393
  /**
394
  * Prints out the Relevanssi options form.
395
  *
396
- * @global object $wpdb The WP database interface.
397
- * @global array $relevanssi_variables The global Relevanssi variables array.
398
  */
399
  function relevanssi_options_form() {
400
- global $relevanssi_variables, $wpdb;
401
 
402
  echo "<div class='postbox-container'>";
403
  echo "<form method='post'>";
@@ -557,8 +305,10 @@ function relevanssi_add_admin_scripts( $hook ) {
557
  $acceptable_hooks = array(
558
  'toplevel_page_relevanssi-premium/relevanssi',
559
  'settings_page_relevanssi-premium/relevanssi',
 
560
  'toplevel_page_relevanssi/relevanssi',
561
  'settings_page_relevanssi/relevanssi',
 
562
  'dashboard_page_relevanssi_admin_search',
563
  );
564
  /**
@@ -586,9 +336,14 @@ function relevanssi_add_admin_scripts( $hook ) {
586
  }
587
  wp_enqueue_style( 'relevanssi_admin_css', $plugin_dir_url . 'lib/admin_styles.css', array(), $relevanssi_variables['plugin_version'] );
588
 
 
 
 
 
589
  $localizations = array(
590
  'confirm' => __( 'Click OK to copy Relevanssi options to all subsites', 'relevanssi' ),
591
  'confirm_stopwords' => __( 'Are you sure you want to remove all stopwords?', 'relevanssi' ),
 
592
  'truncating_index' => __( 'Wiping out the index...', 'relevanssi' ),
593
  'done' => __( 'Done.', 'relevanssi' ),
594
  'indexing_users' => __( 'Indexing users...', 'relevanssi' ),
@@ -702,3 +457,63 @@ function relevanssi_form_tag_weight() {
702
  </tr>
703
  <?php
704
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  echo "<div style='clear:both'></div></div>";
100
  }
101
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  /**
103
  * Prints out the 'Admin search' page.
104
  */
105
  function relevanssi_admin_search_page() {
106
  global $relevanssi_variables;
107
 
 
 
108
  $options_txt = __( 'Admin Search', 'relevanssi' );
109
 
110
  printf( "<div class='wrap'><h2>%s</h2>", esc_html( $options_txt ) );
139
  return $result;
140
  }
141
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
142
  /**
143
  * Prints out the Relevanssi options form.
144
  *
145
+ * @global array $relevanssi_variables The global Relevanssi variables array.
 
146
  */
147
  function relevanssi_options_form() {
148
+ global $relevanssi_variables;
149
 
150
  echo "<div class='postbox-container'>";
151
  echo "<form method='post'>";
305
  $acceptable_hooks = array(
306
  'toplevel_page_relevanssi-premium/relevanssi',
307
  'settings_page_relevanssi-premium/relevanssi',
308
+ 'dashboard_page_relevanssi-premium/relevanssi',
309
  'toplevel_page_relevanssi/relevanssi',
310
  'settings_page_relevanssi/relevanssi',
311
+ 'dashboard_page_relevanssi/relevanssi',
312
  'dashboard_page_relevanssi_admin_search',
313
  );
314
  /**
336
  }
337
  wp_enqueue_style( 'relevanssi_admin_css', $plugin_dir_url . 'lib/admin_styles.css', array(), $relevanssi_variables['plugin_version'] );
338
 
339
+ if ( 'dashboard_page_relevanssi' === substr( $hook, 0, strlen( 'dashboard_page_relevanssi' ) ) ) {
340
+ wp_enqueue_script( 'chartjs', 'https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.3.2/chart.min.js', array(), '3.3.2', false );
341
+ }
342
+
343
  $localizations = array(
344
  'confirm' => __( 'Click OK to copy Relevanssi options to all subsites', 'relevanssi' ),
345
  'confirm_stopwords' => __( 'Are you sure you want to remove all stopwords?', 'relevanssi' ),
346
+ 'confirm_delete_query' => __( 'Are you sure you want to delete the query?', 'relevanssi' ),
347
  'truncating_index' => __( 'Wiping out the index...', 'relevanssi' ),
348
  'done' => __( 'Done.', 'relevanssi' ),
349
  'indexing_users' => __( 'Indexing users...', 'relevanssi' ),
457
  </tr>
458
  <?php
459
  }
460
+
461
+ /**
462
+ * Creates a line chart.
463
+ *
464
+ * @param array $labels An array of labels for the line chart. These will be
465
+ * wrapped in apostrophes.
466
+ * @param array $datasets An array of (label, dataset) pairs.
467
+ */
468
+ function relevanssi_create_line_chart( array $labels, array $datasets ) {
469
+ $labels = implode( ', ', array_map( 'relevanssi_add_apostrophes', $labels ) );
470
+ $datasets_array = array();
471
+ $bg_colors = array(
472
+ "'rgba(255, 99, 132, 0.2)'",
473
+ "'rgba(0, 175, 255, 0.2)'",
474
+ );
475
+ $border_colors = array(
476
+ "'rgba(255, 99, 132, 1)'",
477
+ "'rgba(0, 175, 255, 1)'",
478
+ );
479
+ foreach ( $datasets as $label => $values ) {
480
+ $values = implode( ', ', $values );
481
+ $bg_color = array_shift( $bg_colors );
482
+ $border_color = array_shift( $border_colors );
483
+ $datasets_array[] = <<< EOJSON
484
+ {
485
+ label: "$label",
486
+ data: [ $values ],
487
+ backgroundColor: [ $bg_color ],
488
+ borderColor: [ $border_color ],
489
+ borderWidth: 2,
490
+ fill: {
491
+ target: 'origin',
492
+ below: $border_color,
493
+ },
494
+ pointRadius: 1,
495
+ cubicInterpolationMode: 'monotone'
496
+ }
497
+ EOJSON;
498
+ }
499
+ ?>
500
+ <canvas id="search_chart" height="100"></canvas>
501
+ <script>
502
+ var ctx = document.getElementById('search_chart').getContext('2d');
503
+ var myChart = new Chart(ctx, {
504
+ type: 'line',
505
+ data: {
506
+ labels: [<?php echo $labels; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>],
507
+ datasets: [<?php echo implode( ",\n", $datasets_array ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>],
508
+ },
509
+ options: {
510
+ scales: {
511
+ y: {
512
+ beginAtZero: true
513
+ }
514
+ }
515
+ }
516
+ });
517
+ </script>
518
+ <?php
519
+ }
lib/phrases.php CHANGED
@@ -19,9 +19,9 @@
19
  * @return array An array of phrases (strings).
20
  */
21
  function relevanssi_extract_phrases( string $query ) {
22
- // iOS uses “” as the default quotes, so Relevanssi needs to understand
23
- // that as well.
24
- $normalized_query = str_replace( array( '”', '“' ), '"', $query );
25
  $pos = relevanssi_stripos( $normalized_query, '"' );
26
 
27
  $phrases = array();
19
  * @return array An array of phrases (strings).
20
  */
21
  function relevanssi_extract_phrases( string $query ) {
22
+ // iOS uses “” or „“ as the default quotes, so Relevanssi needs to
23
+ // understand those as well.
24
+ $normalized_query = str_replace( array( '”', '“', '„' ), '"', $query );
25
  $pos = relevanssi_stripos( $normalized_query, '"' );
26
 
27
  $phrases = array();
lib/search.php CHANGED
@@ -136,15 +136,15 @@ function relevanssi_search( $args ) {
136
  */
137
  $remove_stopwords = apply_filters( 'relevanssi_remove_stopwords_in_titles', true );
138
 
139
- $terms['terms'] = array_keys( relevanssi_tokenize( $q, $remove_stopwords, $min_length ) );
140
 
141
  $terms['original_terms'] = $q_no_synonyms !== $q
142
- ? array_keys( relevanssi_tokenize( $q_no_synonyms, $remove_stopwords, $min_length ) )
143
  : $terms['terms'];
144
 
145
  if ( has_filter( 'relevanssi_stemmer' ) ) {
146
  do_action( 'relevanssi_disable_stemmer' );
147
- $terms['original_terms'] = array_keys( relevanssi_tokenize( $q_no_synonyms, $remove_stopwords, $min_length ) );
148
  do_action( 'relevanssi_enable_stemmer' );
149
  }
150
 
@@ -226,7 +226,7 @@ function relevanssi_search( $args ) {
226
  );
227
 
228
  $query = relevanssi_generate_search_query( $term, $search_again, $no_terms, $query_join, $this_query_restrictions );
229
- $matches = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared.
230
 
231
  if ( count( $matches ) < 1 ) {
232
  continue;
@@ -357,17 +357,17 @@ function relevanssi_search( $args ) {
357
 
358
  if ( ! $remove_stopwords ) {
359
  $strip_stops = true;
360
- $terms['no_stops'] = array_keys( relevanssi_tokenize( implode( ' ', $terms['terms'] ), $strip_stops, $min_length ) );
361
 
362
  if ( $q !== $q_no_synonyms ) {
363
- $terms['original_terms_no_stops'] = array_keys( relevanssi_tokenize( implode( ' ', $terms['original_terms'] ), $strip_stops, $min_length ) );
364
  } else {
365
  $terms['original_terms_no_stops'] = $terms['no_stops'];
366
  }
367
 
368
  if ( has_filter( 'relevanssi_stemmer' ) ) {
369
  do_action( 'relevanssi_disable_stemmer' );
370
- $terms['original_terms_no_stops'] = array_keys( relevanssi_tokenize( implode( ' ', $terms['original_terms'] ), $strip_stops, $min_length ) );
371
  do_action( 'relevanssi_enable_stemmer' );
372
  } else {
373
  $terms['original_terms_no_stops'] = $terms['no_stops'];
@@ -376,7 +376,7 @@ function relevanssi_search( $args ) {
376
  $terms['no_stops'] = $terms['terms'];
377
  $terms['original_terms_no_stops'] = $terms['original_terms'];
378
  }
379
- $total_terms = count( $terms['no_stops'] );
380
 
381
  if ( isset( $doc_weight ) ) {
382
  /**
@@ -1087,6 +1087,23 @@ function relevanssi_compile_search_args( $query, $q ) {
1087
  )
1088
  );
1089
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1090
  return $search_params;
1091
  }
1092
 
136
  */
137
  $remove_stopwords = apply_filters( 'relevanssi_remove_stopwords_in_titles', true );
138
 
139
+ $terms['terms'] = array_keys( relevanssi_tokenize( $q, $remove_stopwords, $min_length, 'search_query' ) );
140
 
141
  $terms['original_terms'] = $q_no_synonyms !== $q
142
+ ? array_keys( relevanssi_tokenize( $q_no_synonyms, $remove_stopwords, $min_length, 'search_query' ) )
143
  : $terms['terms'];
144
 
145
  if ( has_filter( 'relevanssi_stemmer' ) ) {
146
  do_action( 'relevanssi_disable_stemmer' );
147
+ $terms['original_terms'] = array_keys( relevanssi_tokenize( $q_no_synonyms, $remove_stopwords, $min_length, 'search_query' ) );
148
  do_action( 'relevanssi_enable_stemmer' );
149
  }
150
 
226
  );
227
 
228
  $query = relevanssi_generate_search_query( $term, $search_again, $no_terms, $query_join, $this_query_restrictions );
229
+ $matches = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
230
 
231
  if ( count( $matches ) < 1 ) {
232
  continue;
357
 
358
  if ( ! $remove_stopwords ) {
359
  $strip_stops = true;
360
+ $terms['no_stops'] = array_keys( relevanssi_tokenize( implode( ' ', $terms['terms'] ), $strip_stops, $min_length, 'search_query' ) );
361
 
362
  if ( $q !== $q_no_synonyms ) {
363
+ $terms['original_terms_no_stops'] = array_keys( relevanssi_tokenize( implode( ' ', $terms['original_terms'] ), $strip_stops, $min_length, 'search_query' ) );
364
  } else {
365
  $terms['original_terms_no_stops'] = $terms['no_stops'];
366
  }
367
 
368
  if ( has_filter( 'relevanssi_stemmer' ) ) {
369
  do_action( 'relevanssi_disable_stemmer' );
370
+ $terms['original_terms_no_stops'] = array_keys( relevanssi_tokenize( implode( ' ', $terms['original_terms'] ), $strip_stops, $min_length, 'search_query' ) );
371
  do_action( 'relevanssi_enable_stemmer' );
372
  } else {
373
  $terms['original_terms_no_stops'] = $terms['no_stops'];
376
  $terms['no_stops'] = $terms['terms'];
377
  $terms['original_terms_no_stops'] = $terms['original_terms'];
378
  }
379
+ $total_terms = count( $terms['original_terms_no_stops'] );
380
 
381
  if ( isset( $doc_weight ) ) {
382
  /**
1087
  )
1088
  );
1089
 
1090
+ /**
1091
+ * Filters the Relevanssi search parameters after compiling.
1092
+ *
1093
+ * Relevanssi picks up the search parameters from the WP_Query query
1094
+ * variables and collects them in an array you can filter here.
1095
+ *
1096
+ * @param array $search_params The search parameters.
1097
+ * @param WP_Query $query The full WP_Query object.
1098
+ *
1099
+ * @return array The filtered parameters.
1100
+ */
1101
+ $search_params = apply_filters(
1102
+ 'relevanssi_search_params',
1103
+ $search_params,
1104
+ $query
1105
+ );
1106
+
1107
  return $search_params;
1108
  }
1109
 
lib/shortcodes.php CHANGED
@@ -111,7 +111,29 @@ function relevanssi_search_form( $atts ) {
111
  if ( is_array( $atts ) ) {
112
  $additional_fields = array();
113
  foreach ( $atts as $key => $value ) {
114
- if ( 'dropdown' === $key && 'post_type' === $value ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  $field = '<select name="post_type">';
116
  $types = get_option( 'relevanssi_index_post_types' );
117
  if ( ! is_array( $types ) ) {
111
  if ( is_array( $atts ) ) {
112
  $additional_fields = array();
113
  foreach ( $atts as $key => $value ) {
114
+ if ( 'dropdown' === substr( $key, 0, 8 ) ) {
115
+ $key = 'dropdown';
116
+ }
117
+ if ( 'post_type_boxes' === $key ) {
118
+ $post_types = explode( ',', $value );
119
+ if ( is_array( $post_types ) ) {
120
+ $post_type_objects = get_post_types( array(), 'objects' );
121
+ $additional_fields[] = '<div class="post_types"><strong>Post types</strong>: ';
122
+ foreach ( $post_types as $post_type ) {
123
+ $checked = '';
124
+ if ( '*' === substr( $post_type, 0, 1 ) ) {
125
+ $post_type = substr( $post_type, 1 );
126
+ $checked = ' checked="checked" ';
127
+ }
128
+ if ( isset( $post_type_objects[ $post_type ] ) ) {
129
+ $additional_fields[] = '<span class="post_type post_type_' . $post_type . '">'
130
+ . '<input type="checkbox" name="post_types[]" value="' . $post_type . '"' . $checked . '/> '
131
+ . $post_type_objects[ $post_type ]->name . '</span>';
132
+ }
133
+ }
134
+ $additional_fields[] = '</div>';
135
+ }
136
+ } elseif ( 'dropdown' === $key && 'post_type' === $value ) {
137
  $field = '<select name="post_type">';
138
  $types = get_option( 'relevanssi_index_post_types' );
139
  if ( ! is_array( $types ) ) {
lib/sorting.php CHANGED
@@ -68,10 +68,27 @@ function relevanssi_get_next_key( &$orderby ) {
68
  case 'relevance':
69
  $key = 'relevance_score';
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
  $filter_keys = array( 'post_type' );
76
 
77
  $compare = 'string';
@@ -201,6 +218,8 @@ function relevanssi_get_compare_values( $key, $item_1, $item_2 ) {
201
  */
202
  $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
203
  }
 
 
204
  } else {
205
  global $relevanssi_meta_query;
206
  if ( isset( $item_1->$key ) ) {
68
  case 'relevance':
69
  $key = 'relevance_score';
70
  break;
71
+ case 'distance':
72
+ $key = 'proximity';
73
+ break;
74
  }
75
 
76
+ $numeric_keys = array(
77
+ 'meta_value_num',
78
+ 'menu_order',
79
+ 'ID',
80
+ 'post_parent',
81
+ 'post_author',
82
+ 'comment_count',
83
+ 'relevance_score',
84
+ 'proximity',
85
+ );
86
+ $date_keys = array(
87
+ 'post_date',
88
+ 'post_date_gmt',
89
+ 'post_modified',
90
+ 'post_modified_gmt',
91
+ );
92
  $filter_keys = array( 'post_type' );
93
 
94
  $compare = 'string';
218
  */
219
  $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
220
  }
221
+ } elseif ( 'proximity' === $key && function_exists( 'relevanssi_get_proximity_values' ) ) {
222
+ list( $key1, $key2 ) = relevanssi_get_proximity_values( $item_1, $item_2 );
223
  } else {
224
  global $relevanssi_meta_query;
225
  if ( isset( $item_1->$key ) ) {
lib/tabs/debugging-tab.php CHANGED
@@ -16,11 +16,27 @@
16
  function relevanssi_debugging_tab() {
17
  $how_relevanssi_sees = '';
18
  $current_post_id = 0;
 
19
  if ( isset( $_REQUEST['post_id'] ) ) {
20
  wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
 
 
 
 
 
 
 
 
 
 
 
21
  if ( intval( $_REQUEST['post_id'] ) > 0 ) {
22
  $current_post_id = intval( $_REQUEST['post_id'] );
23
- $how_relevanssi_sees = relevanssi_generate_how_relevanssi_sees( intval( $current_post_id ) );
 
 
 
 
24
  }
25
  }
26
  wp_nonce_field( 'relevanssi_how_relevanssi_sees', '_relevanssi_nonce', true, true );
@@ -29,19 +45,45 @@ function relevanssi_debugging_tab() {
29
 
30
  <p><?php esc_html_e( 'In order to figure out problems with indexing posts, you can test how Relevanssi sees the post by entering the post ID number in the field below.', 'relevanssi' ); ?></p>
31
  <?php
 
 
 
 
 
32
  if ( ! RELEVANSSI_PREMIUM ) {
33
  // Translators: %1$s starts the link, %2$s closes it.
34
  printf( '<p>' . esc_html__( 'In Relevanssi Premium, you can find this feature for each post on the post edit page. %1$sBuy Relevanssi Premium here%2$s.', 'relevanssi' ) . '</p>', '<a href="https://www.relevanssi.com/buy-premium/">', '</a>' );
35
  }
36
  ?>
37
- <p><label for="post_id"><?php esc_html_e( 'The post ID', 'relevanssi' ); ?></label>:
38
  <input type="text" name="post_id" id="post_id"
39
  <?php
40
  if ( $current_post_id > 0 ) {
41
  echo 'value="' . esc_attr( $current_post_id ) . '"';
42
  }
43
  ?>
44
- /></p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  <p>
46
  <input
47
  type='submit' name='submit'
16
  function relevanssi_debugging_tab() {
17
  $how_relevanssi_sees = '';
18
  $current_post_id = 0;
19
+ $selected = 'post';
20
  if ( isset( $_REQUEST['post_id'] ) ) {
21
  wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
22
+ $type = 'post';
23
+ if ( isset( $_REQUEST['type'] ) ) {
24
+ if ( 'term' === $_REQUEST['type'] ) {
25
+ $type = 'term';
26
+ $selected = 'term';
27
+ }
28
+ if ( 'user' === $_REQUEST['type'] ) {
29
+ $type = 'user';
30
+ $selected = 'user';
31
+ }
32
+ }
33
  if ( intval( $_REQUEST['post_id'] ) > 0 ) {
34
  $current_post_id = intval( $_REQUEST['post_id'] );
35
+ $how_relevanssi_sees = relevanssi_generate_how_relevanssi_sees(
36
+ intval( $current_post_id ),
37
+ true,
38
+ $type
39
+ );
40
  }
41
  }
42
  wp_nonce_field( 'relevanssi_how_relevanssi_sees', '_relevanssi_nonce', true, true );
45
 
46
  <p><?php esc_html_e( 'In order to figure out problems with indexing posts, you can test how Relevanssi sees the post by entering the post ID number in the field below.', 'relevanssi' ); ?></p>
47
  <?php
48
+ if ( RELEVANSSI_PREMIUM ) {
49
+ ?>
50
+ <p><?php esc_html_e( 'You can also check user profiles and taxonomy terms by choosing the type from the dropdown.', 'relevanssi' ); ?></p>
51
+ <?php
52
+ }
53
  if ( ! RELEVANSSI_PREMIUM ) {
54
  // Translators: %1$s starts the link, %2$s closes it.
55
  printf( '<p>' . esc_html__( 'In Relevanssi Premium, you can find this feature for each post on the post edit page. %1$sBuy Relevanssi Premium here%2$s.', 'relevanssi' ) . '</p>', '<a href="https://www.relevanssi.com/buy-premium/">', '</a>' );
56
  }
57
  ?>
58
+ <p><label for="post_id"><?php esc_html_e( 'The ID', 'relevanssi' ); ?></label>:
59
  <input type="text" name="post_id" id="post_id"
60
  <?php
61
  if ( $current_post_id > 0 ) {
62
  echo 'value="' . esc_attr( $current_post_id ) . '"';
63
  }
64
  ?>
65
+ />
66
+ <?php
67
+ if ( RELEVANSSI_PREMIUM ) {
68
+ ?>
69
+ <select name="type">
70
+ <option value="post"
71
+ <?php if ( 'post' === $selected ) { ?>
72
+ selected="selected"
73
+ <?php } ?>><?php esc_html_e( 'Post', 'relevanssi' ); ?></option>
74
+ <option value="term"
75
+ <?php if ( 'term' === $selected ) { ?>
76
+ selected="selected"
77
+ <?php } ?>><?php esc_html_e( 'Taxonomy term', 'relevanssi' ); ?></option>
78
+ <option value="user"
79
+ <?php if ( 'user' === $selected ) { ?>
80
+ selected="selected"
81
+ <?php } ?>><?php esc_html_e( 'User', 'relevanssi' ); ?></option>
82
+ </select>
83
+ <?php
84
+ }
85
+ ?>
86
+ </p>
87
  <p>
88
  <input
89
  type='submit' name='submit'
lib/tabs/logging-tab.php CHANGED
@@ -14,10 +14,9 @@
14
  * Prints out the logging tab in Relevanssi settings.
15
  *
16
  * @global $wpdb The WordPress database interface.
17
- * @global $relevanssi_variables The global Relevanssi variables array.
18
  */
19
  function relevanssi_logging_tab() {
20
- global $wpdb, $relevanssi_variables;
21
 
22
  $log_queries = get_option( 'relevanssi_log_queries' );
23
  $log_queries = relevanssi_check( $log_queries );
@@ -96,7 +95,7 @@ function relevanssi_logging_tab() {
96
  } else {
97
  echo '<p class="description">';
98
  // Translators: %d is the setting for no trim (probably 0).
99
- printf( esc_html__( ' Set to %d for no trimming.', 'relevanssi' ), 0 );
100
  echo '</p>';
101
  }
102
  ?>
@@ -115,4 +114,13 @@ function relevanssi_logging_tab() {
115
 
116
  </table>
117
  <?php
 
 
 
 
 
 
 
 
 
118
  }
14
  * Prints out the logging tab in Relevanssi settings.
15
  *
16
  * @global $wpdb The WordPress database interface.
 
17
  */
18
  function relevanssi_logging_tab() {
19
+ global $wpdb;
20
 
21
  $log_queries = get_option( 'relevanssi_log_queries' );
22
  $log_queries = relevanssi_check( $log_queries );
95
  } else {
96
  echo '<p class="description">';
97
  // Translators: %d is the setting for no trim (probably 0).
98
+ printf( esc_html__( 'Set to %d for no trimming.', 'relevanssi' ), 0 );
99
  echo '</p>';
100
  }
101
  ?>
114
 
115
  </table>
116
  <?php
117
+
118
+ if ( function_exists( 'relevanssi_click_tracking_interface' ) ) {
119
+ relevanssi_click_tracking_interface();
120
+ } else {
121
+ ?>
122
+ <h3><?php esc_html_e( 'Click tracking', 'relevanssi' ); ?></h3>
123
+ <p><?php esc_html_e( 'Relevanssi Premium has a click tracking feature where you can track which posts are clicked from the search results. That way you can tell what is your most interesting content and how the search is actually used to access posts.', 'relevanssi' ); ?></p>
124
+ <?php
125
+ }
126
  }
lib/tabs/search-page.php CHANGED
@@ -35,21 +35,26 @@ function relevanssi_search_tab() {
35
  <select name='post_types' id='post_types'>
36
  <option value="any"><?php esc_html_e( 'Any', 'relevanssi' ); ?></option>
37
  <?php
38
- echo implode(
39
- ' ',
40
- array_map( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
41
- function ( $post_type ) {
42
- $pt = get_post_type_object( $post_type );
43
- if ( $pt ) {
44
- $post_type_value = esc_attr( $post_type );
45
- $post_type_name = esc_html( $pt->labels->singular_name );
46
- return "<option value='{$post_type_value}'>{$post_type_name}</option>";
47
- }
48
- return null;
49
- },
50
- get_option( 'relevanssi_index_post_types' )
51
- )
52
- );
 
 
 
 
 
53
  ?>
54
  </select>
55
  </td>
35
  <select name='post_types' id='post_types'>
36
  <option value="any"><?php esc_html_e( 'Any', 'relevanssi' ); ?></option>
37
  <?php
38
+ echo implode(
39
+ ' ',
40
+ array_map( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
41
+ function ( $post_type ) {
42
+ $pt = get_post_type_object( $post_type );
43
+ if ( $pt ) {
44
+ $post_type_value = esc_attr( $post_type );
45
+ $post_type_name = esc_html( $pt->labels->singular_name );
46
+ return "<option value='{$post_type_value}'>{$post_type_name}</option>";
47
+ }
48
+ return null;
49
+ },
50
+ get_option( 'relevanssi_index_post_types' )
51
+ )
52
+ );
53
+ if ( 'on' === get_option( 'relevanssi_index_users' ) ) {
54
+ ?>
55
+ <option value='user'><?php esc_html_e( 'Users', 'relevanssi' ); ?></option>
56
+ <?php
57
+ }
58
  ?>
59
  </select>
60
  </td>
lib/tabs/stopwords-tab.php CHANGED
@@ -14,6 +14,10 @@
14
  * Prints out the stopwords tab in Relevanssi settings.
15
  */
16
  function relevanssi_stopwords_tab() {
 
 
 
 
17
  ?>
18
  <h3 id="stopwords"><?php esc_html_e( 'Stopwords', 'relevanssi' ); ?></h3>
19
  <?php
@@ -131,3 +135,15 @@ function relevanssi_show_stopwords() {
131
 
132
  <?php
133
  }
 
 
 
 
 
 
 
 
 
 
 
 
14
  * Prints out the stopwords tab in Relevanssi settings.
15
  */
16
  function relevanssi_stopwords_tab() {
17
+ if ( class_exists( 'Polylang', false ) && ! relevanssi_get_current_language() ) {
18
+ relevanssi_polylang_all_languages_stopwords();
19
+ return;
20
+ }
21
  ?>
22
  <h3 id="stopwords"><?php esc_html_e( 'Stopwords', 'relevanssi' ); ?></h3>
23
  <?php
135
 
136
  <?php
137
  }
138
+
139
+ /**
140
+ * Displays an error message when Polylang is in all languages mode.
141
+ */
142
+ function relevanssi_polylang_all_languages_stopwords() {
143
+ ?>
144
+ <h3 id="stopwords"><?php esc_html_e( 'Stopwords', 'relevanssi' ); ?></h3>
145
+
146
+ <p class="description"><?php esc_html_e( 'You are using Polylang and are in "Show all languages" mode. Please select a language before adjusting the stopword settings.', 'relevanssi' ); ?></p>
147
+ <?php
148
+ }
149
+
lib/tabs/synonyms-tab.php CHANGED
@@ -15,8 +15,12 @@
15
  */
16
  function relevanssi_synonyms_tab() {
17
  $current_language = relevanssi_get_current_language();
18
- $synonyms_array = get_option( 'relevanssi_synonyms', array() );
19
- $synonyms = isset( $synonyms_array[ $current_language ] ) ? $synonyms_array[ $current_language ] : '';
 
 
 
 
20
 
21
  if ( isset( $synonyms ) ) {
22
  $synonyms = str_replace( ';', "\n", $synonyms );
@@ -45,10 +49,28 @@ function relevanssi_synonyms_tab() {
45
  <p class="description"><?php _e( "It's possible to use phrases for the value, but not for the key. <code>dog = \"great dane\"</code> works, but <code>\"great dane\" = dog</code> doesn't.", 'relevanssi' ); // phpcs:ignore WordPress.Security.EscapeOutput.UnsafePrintingFunction ?></p>
46
 
47
  <?php if ( RELEVANSSI_PREMIUM ) : ?>
48
- <p class="description"><?php esc_html_e( 'If you want to use synonyms in AND searches, enable synonym indexing on the Indexing tab.', 'relevanssi' ); ?></p>
 
 
 
 
 
 
 
49
  <?php endif; ?>
50
  </td>
51
  </tr>
52
  </table>
53
  <?php
54
  }
 
 
 
 
 
 
 
 
 
 
 
15
  */
16
  function relevanssi_synonyms_tab() {
17
  $current_language = relevanssi_get_current_language();
18
+ if ( class_exists( 'Polylang', false ) && ! $current_language ) {
19
+ relevanssi_polylang_all_languages_synonyms();
20
+ return;
21
+ }
22
+ $synonyms_array = get_option( 'relevanssi_synonyms', array() );
23
+ $synonyms = isset( $synonyms_array[ $current_language ] ) ? $synonyms_array[ $current_language ] : '';
24
 
25
  if ( isset( $synonyms ) ) {
26
  $synonyms = str_replace( ';', "\n", $synonyms );
49
  <p class="description"><?php _e( "It's possible to use phrases for the value, but not for the key. <code>dog = \"great dane\"</code> works, but <code>\"great dane\" = dog</code> doesn't.", 'relevanssi' ); // phpcs:ignore WordPress.Security.EscapeOutput.UnsafePrintingFunction ?></p>
50
 
51
  <?php if ( RELEVANSSI_PREMIUM ) : ?>
52
+ </td>
53
+ </tr>
54
+ <tr>
55
+ <th scope="row">
56
+ <?php echo esc_html_e( 'Synonyms in AND searches', 'relevanssi' ); ?>
57
+ </th>
58
+ <td>
59
+ <p class="description"><?php esc_html_e( "If you want to use synonyms in AND searches, enable synonym indexing on the Indexing tab. Also, any changes to the synonyms won't take effect until you rebuild the index.", 'relevanssi' ); ?></p>
60
  <?php endif; ?>
61
  </td>
62
  </tr>
63
  </table>
64
  <?php
65
  }
66
+
67
+ /**
68
+ * Displays an error message when Polylang is in all languages mode.
69
+ */
70
+ function relevanssi_polylang_all_languages_synonyms() {
71
+ ?>
72
+ <h3 id="synonyms"><?php esc_html_e( 'Synonyms', 'relevanssi' ); ?></h3>
73
+
74
+ <p class="description"><?php esc_html_e( 'You are using Polylang and are in "Show all languages" mode. Please select a language before adjusting the synonym settings.', 'relevanssi' ); ?></p>
75
+ <?php
76
+ }
lib/uninstall.php CHANGED
@@ -26,6 +26,7 @@ function relevanssi_drop_database_tables() {
26
  $relevanssi_table = $wpdb->prefix . 'relevanssi';
27
  $stopword_table = $wpdb->prefix . 'relevanssi_stopwords';
28
  $log_table = $wpdb->prefix . 'relevanssi_log';
 
29
 
30
  // phpcs:disable WordPress.DB.PreparedSQL
31
  if ( $wpdb->get_var( "SHOW TABLES LIKE '$stopword_table'" ) === $stopword_table ) {
@@ -39,6 +40,10 @@ function relevanssi_drop_database_tables() {
39
  if ( $wpdb->get_var( "SHOW TABLES LIKE '$log_table'" ) === $log_table ) {
40
  $wpdb->query( "DROP TABLE $log_table" );
41
  }
 
 
 
 
42
  // phpcs:enable WordPress.DB.PreparedSQL
43
  }
44
 
26
  $relevanssi_table = $wpdb->prefix . 'relevanssi';
27
  $stopword_table = $wpdb->prefix . 'relevanssi_stopwords';
28
  $log_table = $wpdb->prefix . 'relevanssi_log';
29
+ $tracking_table = $wpdb->prefix . 'relevanssi_tracking';
30
 
31
  // phpcs:disable WordPress.DB.PreparedSQL
32
  if ( $wpdb->get_var( "SHOW TABLES LIKE '$stopword_table'" ) === $stopword_table ) {
40
  if ( $wpdb->get_var( "SHOW TABLES LIKE '$log_table'" ) === $log_table ) {
41
  $wpdb->query( "DROP TABLE $log_table" );
42
  }
43
+
44
+ if ( $wpdb->get_var( "SHOW TABLES LIKE '$tracking_table'" ) === $tracking_table ) {
45
+ $wpdb->query( "DROP TABLE $tracking_table" );
46
+ }
47
  // phpcs:enable WordPress.DB.PreparedSQL
48
  }
49
 
lib/user-searches.php ADDED
@@ -0,0 +1,370 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/user-searches.php
4
+ *
5
+ * @package Relevanssi
6
+ * @author Mikko Saari
7
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
8
+ * @see https://www.relevanssi.com/
9
+ */
10
+
11
+ /**
12
+ * Prints out the 'User searches' page.
13
+ */
14
+ function relevanssi_search_stats() {
15
+ $relevanssi_hide_branding = get_option( 'relevanssi_hide_branding' );
16
+
17
+ if ( 'on' === $relevanssi_hide_branding ) {
18
+ $options_txt = __( 'User searches', 'relevanssi' );
19
+ } else {
20
+ $options_txt = __( 'Relevanssi User Searches', 'relevanssi' );
21
+ }
22
+
23
+ if ( isset( $_REQUEST['relevanssi_reset'] ) && current_user_can( 'manage_options' ) ) {
24
+ check_admin_referer( 'relevanssi_reset_logs', '_relresnonce' );
25
+ if ( isset( $_REQUEST['relevanssi_reset_code'] ) ) {
26
+ if ( 'reset' === $_REQUEST['relevanssi_reset_code'] ) {
27
+ $verbose = true;
28
+ relevanssi_truncate_logs( $verbose );
29
+ }
30
+ }
31
+ }
32
+
33
+ printf( "<div class='wrap'><h2>%s</h2>", esc_html( $options_txt ) );
34
+
35
+ $premium_screens_displayed =
36
+ function_exists( 'relevanssi_handle_insights_screens' )
37
+ ? relevanssi_handle_insights_screens( $_REQUEST )
38
+ : false;
39
+
40
+ if ( ! $premium_screens_displayed ) {
41
+ if ( 'on' === get_option( 'relevanssi_log_queries' ) ) {
42
+ relevanssi_query_log();
43
+ } else {
44
+ printf( '<p>%s</p>', esc_html__( 'Enable query logging to see stats here.', 'relevanssi' ) );
45
+ }
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Shows the query log with the most common queries
51
+ *
52
+ * Uses relevanssi_total_queries() and relevanssi_date_queries() to fetch the data.
53
+ */
54
+ function relevanssi_query_log() {
55
+ global $wpdb, $relevanssi_variables;
56
+ $data = $wpdb->get_results(
57
+ 'SELECT LEFT( `time`, 10 ) as `day`, count(*) as `count` ' .
58
+ "FROM {$relevanssi_variables['log_table']} " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
59
+ 'GROUP BY LEFT( `time`, 10 )'
60
+ );
61
+
62
+ $labels = array();
63
+ $values = array();
64
+ $from = gmdate( 'Y-m-d' );
65
+ foreach ( $data as $point ) {
66
+ if ( $point->day < $from ) {
67
+ $from = $point->day;
68
+ }
69
+ }
70
+
71
+ wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_user_searches' );
72
+
73
+ $from_and_to = relevanssi_from_and_to( $_REQUEST, $from );
74
+ $to = $from_and_to['to'];
75
+ $from = $from_and_to['from'];
76
+
77
+ foreach ( $data as $point ) {
78
+ if ( $point->day >= $from && $point->day <= $to ) {
79
+ $labels[] = gmdate( 'M j', strtotime( $point->day ) );
80
+ $values[] = $point->count;
81
+ }
82
+ }
83
+
84
+ ?>
85
+ <form method="post" style="background: white; padding: 10px; margin-top: 20px;">
86
+ <?php
87
+ wp_nonce_field( 'relevanssi_user_searches', '_relevanssi_nonce', true, true );
88
+ ?>
89
+ <div style="display: grid; grid-template-columns: 1fr 1fr; grid-gap: 20px">
90
+ <div>
91
+ <?php echo esc_html__( 'From:', 'relevanssi' ); ?> <input type="date" name="from" value="<?php echo esc_attr( $from ); ?>" />
92
+ <?php echo esc_html__( 'To:', 'relevanssi' ); ?> <input type="date" name="to" value="<?php echo esc_attr( $to ); ?>" />
93
+ <input type="submit" value="<?php echo esc_attr( __( 'Filter', 'relevanssi' ) ); ?>" /></p>
94
+ </div>
95
+ <div>
96
+ <input type="submit" value="<?php echo esc_attr( __( 'Year so far', 'relevanssi' ) ); ?>" name="this_year" style="margin-bottom: 10px" />
97
+ <input type="submit" value="<?php echo esc_attr( __( 'This month', 'relevanssi' ) ); ?>" name="this_month" />
98
+ <input type="submit" value="<?php echo esc_attr( __( 'Last month', 'relevanssi' ) ); ?>" name="last_month" />
99
+ <input type="submit" value="<?php echo esc_attr( __( '30 days', 'relevanssi' ) ); ?>" name="last_30" />
100
+ <input type="submit" value="<?php echo esc_attr( __( 'This week', 'relevanssi' ) ); ?>" name="this_week" />
101
+ <input type="submit" value="<?php echo esc_attr( __( 'Last week', 'relevanssi' ) ); ?>" name="last_week" />
102
+ <input type="submit" value="<?php echo esc_attr( __( '7 days', 'relevanssi' ) ); ?>" name="last_7" />
103
+ <input type="submit" value="<?php echo esc_attr( __( 'All history', 'relevanssi' ) ); ?>" name="everything" />
104
+ </div>
105
+ </div>
106
+ </form>
107
+ <?php
108
+
109
+ relevanssi_create_line_chart(
110
+ $labels,
111
+ array(
112
+ __( '# of Searches', 'relevanssi' ) => $values,
113
+ )
114
+ );
115
+
116
+ $total_queries = relevanssi_total_queries( $from, $to );
117
+ ?>
118
+ <div style="background: white; padding: 10px; display: grid; grid-template-columns: 1fr 2fr 2fr; grid-gap: 20px; margin-top: 20px">
119
+ <div>
120
+ <div style="margin-bottom: 20px"><?php esc_html_e( 'Total searches', 'relevanssi' ); ?>
121
+ <span style="display: block; font-size: 42px; font-weight: bolder; line-height: 50px">
122
+ <?php echo intval( $total_queries ); ?>
123
+ </span>
124
+ </div>
125
+ <div style="margin-bottom: 20px"><?php esc_html_e( 'Searches that found nothing', 'relevanssi' ); ?>
126
+ <span style="display: block; font-size: 42px; font-weight: bolder; line-height: 50px">
127
+ <?php echo intval( relevanssi_nothing_found_queries( $from, $to ) ); ?>
128
+ </span>
129
+ </div>
130
+ <?php
131
+ if ( function_exists( 'relevanssi_user_searches_clicks' ) ) {
132
+ relevanssi_user_searches_clicks( $from, $to, $total_queries );
133
+ }
134
+ ?>
135
+ </div>
136
+ <div>
137
+ <h3><?php esc_html_e( 'Successful searches', 'relevanssi' ); ?></h3>
138
+ <p><?php esc_html_e( '"Hits" is the average hits this search query has found.', 'relevanssi' ); ?></p>
139
+ <?php
140
+ if ( ! function_exists( 'relevanssi_get_query_clicks' ) ) {
141
+ ?>
142
+ <p><?php esc_html_e( 'In order to see the clicks, you need Relevanssi Premium.', 'relevanssi' ); ?></p>
143
+ <?php
144
+ } elseif ( 'on' !== get_option( 'relevanssi_click_tracking' ) ) {
145
+ ?>
146
+ <p><?php esc_html_e( 'In order to see the clicks, you need to enable click tracking. Click tracking is not currently enabled, and you\'re not collecting new clicks.', 'relevanssi' ); ?></p>
147
+ <?php
148
+ }
149
+ relevanssi_date_queries( $from, $to, 'good' );
150
+ ?>
151
+ </div>
152
+ <div>
153
+ <h3><?php esc_html_e( 'Unsuccessful searches', 'relevanssi' ); ?></h3>
154
+ <p><?php esc_html_e( 'These queries have found no results.', 'relevanssi' ); ?></p>
155
+ <?php relevanssi_date_queries( $from, $to, 'bad' ); ?>
156
+ </div>
157
+ </div>
158
+ <?php
159
+
160
+ if ( current_user_can( 'manage_options' ) ) {
161
+
162
+ echo '<div style="clear: both"></div>';
163
+ printf( '<h3>%s</h3>', esc_html__( 'Reset Logs', 'relevanssi' ) );
164
+ print( "<form method='post'>" );
165
+ wp_nonce_field( 'relevanssi_reset_logs', '_relresnonce', true, true );
166
+ printf(
167
+ '<p><label for="relevanssi_reset_code">%s</label>
168
+ <input type="text" id="relevanssi_reset_code" name="relevanssi_reset_code" />
169
+ <input type="submit" name="relevanssi_reset" value="%s" class="button" /></p></form>',
170
+ // Translators: do not translate "reset".
171
+ esc_html__(
172
+ 'To reset the logs, type "reset" into the box here and click the Reset button',
173
+ 'relevanssi'
174
+ ),
175
+ esc_html__( 'Reset', 'relevanssi' )
176
+ );
177
+ }
178
+
179
+ echo '</div>';
180
+ }
181
+
182
+ /**
183
+ * Shows the total number of searches on 'User searches' page.
184
+ *
185
+ * @global object $wpdb The WP database interface.
186
+ * @global array $relevanssi_variables The global Relevanssi variables array.
187
+ *
188
+ * @param string $from The start date.
189
+ * @param string $to The end date.
190
+ *
191
+ * @return int The number of searches.
192
+ */
193
+ function relevanssi_total_queries( string $from, string $to ) {
194
+ global $wpdb, $relevanssi_variables;
195
+ $log_table = $relevanssi_variables['log_table'];
196
+
197
+ $count = $wpdb->get_var(
198
+ $wpdb->prepare(
199
+ "SELECT COUNT(id) FROM $log_table " // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
200
+ . 'WHERE time >= %s
201
+ AND time <= %s',
202
+ $from . ' 00:00:00',
203
+ $to . ' 23:59:59'
204
+ )
205
+ );
206
+
207
+ return $count;
208
+ }
209
+
210
+ /**
211
+ * Shows the total number of searches on 'User searches' page.
212
+ *
213
+ * @global object $wpdb The WP database interface.
214
+ * @global array $relevanssi_variables The global Relevanssi variables array.
215
+ *
216
+ * @param string $from The start date.
217
+ * @param string $to The end date.
218
+ */
219
+ function relevanssi_nothing_found_queries( string $from, string $to ) {
220
+ global $wpdb, $relevanssi_variables;
221
+ $log_table = $relevanssi_variables['log_table'];
222
+
223
+ $count = $wpdb->get_var(
224
+ $wpdb->prepare(
225
+ "SELECT COUNT(id) FROM $log_table " // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
226
+ . 'WHERE time >= %s
227
+ AND time <= %s
228
+ AND hits = 0',
229
+ $from . ' 00:00:00',
230
+ $to . ' 23:59:59'
231
+ )
232
+ );
233
+
234
+ return $count;
235
+ }
236
+
237
+ /**
238
+ * Shows the most common search queries on different time periods.
239
+ *
240
+ * @global object $wpdb The WP database interface.
241
+ * @global array $relevanssi_variables The global Relevanssi variables array.
242
+ *
243
+ * @param string $from The beginning date.
244
+ * @param string $to The ending date.
245
+ * @param string $version If 'good', show the searches that found something; if
246
+ * 'bad', show the searches that didn't find anything. Default 'good'.
247
+ */
248
+ function relevanssi_date_queries( string $from, string $to, string $version = 'good' ) {
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 ) {
256
+ $queries = $wpdb->get_results(
257
+ $wpdb->prepare(
258
+ 'SELECT COUNT(DISTINCT(id)) as cnt, query, AVG(hits) AS hits ' .
259
+ "FROM $log_table " . // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
260
+ 'WHERE time >= %s
261
+ AND time <= %s
262
+ AND hits > 0
263
+ GROUP BY query
264
+ ORDER BY cnt DESC
265
+ LIMIT %d',
266
+ $from . ' 00:00:00',
267
+ $to . ' 23:59:59',
268
+ $limit
269
+ )
270
+ );
271
+ }
272
+
273
+ if ( 'bad' === $version ) {
274
+ $queries = $wpdb->get_results(
275
+ $wpdb->prepare(
276
+ 'SELECT COUNT(DISTINCT(id)) as cnt, query, hits ' .
277
+ "FROM $log_table " . // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
278
+ 'WHERE time >= %s
279
+ AND time <= %s
280
+ AND hits = 0
281
+ GROUP BY query
282
+ ORDER BY cnt DESC
283
+ LIMIT %d',
284
+ $from . ' 00:00:00',
285
+ $to . ' 23:59:59',
286
+ $limit
287
+ )
288
+ );
289
+ }
290
+
291
+ if ( count( $queries ) > 0 ) {
292
+ if ( 'good' === $version ) {
293
+ printf(
294
+ "<table class='widefat' style='border: none'>
295
+ <thead>
296
+ <tr>
297
+ <th>%s</th>
298
+ <th style='text-align: center'>#</th>
299
+ <th style='text-align: center'>%s</th>
300
+ <th style='text-align: center'>%s</th>
301
+ </tr>
302
+ </thead>
303
+ <tbody>",
304
+ esc_html__( 'Query', 'relevanssi' ),
305
+ esc_html__( 'Hits', 'relevanssi' ),
306
+ esc_html__( 'Clicks', 'relevanssi' )
307
+ );
308
+ } else {
309
+ printf(
310
+ "<table class='widefat' style='border: none'>
311
+ <thead>
312
+ <tr>
313
+ <th>%s</th>
314
+ <th style='text-align: center'>#</th>
315
+ </tr>
316
+ </thead>
317
+ <tbody>",
318
+ esc_html__( 'Query', 'relevanssi' )
319
+ );
320
+ }
321
+ $url = get_bloginfo( 'url' );
322
+ foreach ( $queries as $query ) {
323
+ if ( 'good' === $version && function_exists( 'relevanssi_get_query_clicks' ) ) {
324
+ $clicks = intval( relevanssi_get_query_clicks( $query->query ) );
325
+ } else {
326
+ $clicks = '-';
327
+ }
328
+ $search_parameter = rawurlencode( $query->query );
329
+ /**
330
+ * Filters the query URL for the user searches page.
331
+ *
332
+ * @param string Query URL.
333
+ */
334
+ $query_url = apply_filters( 'relevanssi_user_searches_query_url', $url . '/?s=' . $search_parameter );
335
+
336
+ if ( function_exists( 'relevanssi_insights_link' ) ) {
337
+ $query_link = relevanssi_insights_link( $query );
338
+ } else {
339
+ $query_link = $query->query;
340
+ }
341
+
342
+ if ( 'good' === $version ) {
343
+ printf(
344
+ "<tr>
345
+ <td>%s <a href='%s'><span class='dashicons dashicons-external'></span></a></td>
346
+ <td style='padding: 3px 5px; text-align: center'>%d</td>
347
+ <td style='padding: 3px 5px; text-align: center'>%d</td>
348
+ <td style='padding: 3px 5px; text-align: center'>%s</td>
349
+ </tr>",
350
+ $query_link, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
351
+ esc_attr( $query_url ),
352
+ intval( $query->cnt ),
353
+ intval( $query->hits ),
354
+ $clicks // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
355
+ );
356
+ } else {
357
+ printf(
358
+ "<tr>
359
+ <td>%s <a href='%s'><span class='dashicons dashicons-external'></span></a></td>
360
+ <td style='padding: 3px 5px; text-align: center'>%d</td>
361
+ </tr>",
362
+ $query_link, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
363
+ esc_attr( $query_url ),
364
+ intval( $query->cnt )
365
+ );
366
+ }
367
+ }
368
+ echo '</tbody></table>';
369
+ }
370
+ }
lib/utils.php CHANGED
@@ -20,6 +20,17 @@ function get_relevanssi_taxonomy_walker() {
20
  return new Relevanssi_Taxonomy_Walker();
21
  }
22
 
 
 
 
 
 
 
 
 
 
 
 
23
  /**
24
  * Adds quotes around a string.
25
  *
@@ -45,6 +56,25 @@ function relevanssi_array_walk_trim( string &$string ) {
45
  $string = relevanssi_mb_trim( $string );
46
  }
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  /**
49
  * Returns 'checked' if the option is enabled.
50
  *
@@ -175,6 +205,79 @@ function relevanssi_flatten_array( array $array ) {
175
  return trim( $return_value );
176
  }
177
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  /**
179
  * Generates closing tags for an array of tags.
180
  *
20
  return new Relevanssi_Taxonomy_Walker();
21
  }
22
 
23
+ /**
24
+ * Adds apostrophes around a string.
25
+ *
26
+ * @param string $string The string.
27
+ *
28
+ * @return string The string with apostrophes around it.
29
+ */
30
+ function relevanssi_add_apostrophes( $string ) {
31
+ return "'" . $string . "'";
32
+ }
33
+
34
  /**
35
  * Adds quotes around a string.
36
  *
56
  $string = relevanssi_mb_trim( $string );
57
  }
58
 
59
+ /**
60
+ * Converts sums in an array to averages, based on an array containing counts.
61
+ *
62
+ * Both arrays need to have (key, value) pairs with the same keys. The values
63
+ * in $array are then divided by the matching values in $counts, so when we have
64
+ * sums in $array and counts in $counts, we end up with averages.
65
+ *
66
+ * @param array $array The array with sums, passed as reference.
67
+ * @param array $counts The array with counts.
68
+ */
69
+ function relevanssi_average_array( array &$array, array $counts ) {
70
+ array_walk(
71
+ $array,
72
+ function ( &$value, $key ) use ( $counts ) {
73
+ $value = $value / $counts[ $key ];
74
+ }
75
+ );
76
+ }
77
+
78
  /**
79
  * Returns 'checked' if the option is enabled.
80
  *
205
  return trim( $return_value );
206
  }
207
 
208
+ /**
209
+ * Generates from and to date values from ranges.
210
+ *
211
+ * Possible values in the $request array: 'from' and 'to' for direct dates,
212
+ * 'this_year' for Jan 1st to today, 'this_month' for 1st of month to today,
213
+ * 'last_month' for 1st of previous month to last of previous month,
214
+ * 'this_week' for Monday of this week to today (or Sunday, if the
215
+ * relevanssi_week_starts_on_sunday returns `true`), 'last_week' for the
216
+ * previous week, 'last_30' for from 30 days ago to today, 'last_7' for from
217
+ * 7 days ago to today.
218
+ *
219
+ * @param array $request The request array where the settings are.
220
+ * @param string $from The default 'from' date in "Y-m-d" format.
221
+ * @return array The from date in 'from' and the to date in 'to' in "Y-m-d"
222
+ * format.
223
+ */
224
+ function relevanssi_from_and_to( array $request, string $from ) : array {
225
+ $today = gmdate( 'Y-m-d' );
226
+ $week_start = 'monday';
227
+ $to = $today;
228
+
229
+ /**
230
+ * Controls whether the week starts on Sunday or Monday.
231
+ *
232
+ * @param boolean If `true`, week starts on Sunday. Default `false`, week
233
+ * starts on Monday.
234
+ */
235
+ if ( apply_filters( 'relevanssi_week_starts_on_sunday', false ) ) {
236
+ $week_start = 'sunday';
237
+ }
238
+ if ( ! isset( $request['everything'] ) && isset( $request['from'] ) && $request['from'] > $from ) {
239
+ $from = $request['from'];
240
+ }
241
+ if ( ! isset( $request['everything'] ) && isset( $request['to'] ) && $request['to'] < $today ) {
242
+ $to = $request['to'];
243
+ }
244
+ if ( isset( $request['this_year'] ) ) {
245
+ $from = gmdate( 'Y-m-d', strtotime( 'first day of january this year' ) );
246
+ $to = gmdate( 'Y-m-d' );
247
+ }
248
+ if ( isset( $request['this_month'] ) ) {
249
+ $from = gmdate( 'Y-m-d', strtotime( 'first day of this month' ) );
250
+ $to = gmdate( 'Y-m-d' );
251
+ }
252
+ if ( isset( $request['last_month'] ) ) {
253
+ $from = gmdate( 'Y-m-d', strtotime( 'first day of previous month' ) );
254
+ $to = gmdate( 'Y-m-d', strtotime( 'last day of previous month' ) );
255
+ }
256
+ if ( isset( $request['this_week'] ) ) {
257
+ $from = gmdate( 'Y-m-d', strtotime( 'previous ' . $week_start ) );
258
+ $to = gmdate( 'Y-m-d' );
259
+ }
260
+ if ( isset( $request['last_week'] ) ) {
261
+ $start = 'sunday' === $week_start ? gmdate( 'w' ) + 7 : gmdate( 'w' ) + 6;
262
+ $end = 'sunday' === $week_start ? gmdate( 'w' ) + 1 : gmdate( 'w' );
263
+ $from = gmdate( 'Y-m-d', strtotime( '-' . $start . ' days' ) );
264
+ $to = gmdate( 'Y-m-d', strtotime( '-' . $end . ' days' ) );
265
+ }
266
+ if ( isset( $request['last_30'] ) ) {
267
+ $from = gmdate( 'Y-m-d', strtotime( '-30 days' ) );
268
+ $to = gmdate( 'Y-m-d' );
269
+ }
270
+ if ( isset( $request['last_7'] ) ) {
271
+ $from = gmdate( 'Y-m-d', strtotime( '-7 days' ) );
272
+ $to = gmdate( 'Y-m-d' );
273
+ }
274
+
275
+ return array(
276
+ 'from' => $from,
277
+ 'to' => $to,
278
+ );
279
+ }
280
+
281
  /**
282
  * Generates closing tags for an array of tags.
283
  *
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.8
7
  Requires PHP: 7.0
8
- Stable tag: 4.13.3.1
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -131,6 +131,21 @@ Each document database is full of useless words. All the little words that appea
131
  * John Calahan for extensive 4.0 beta testing.
132
 
133
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  = 4.13.3.1 =
135
  * Minor fix: The Bricks compatibility was broken. This version fixes it.
136
 
@@ -226,28 +241,10 @@ Each document database is full of useless words. All the little words that appea
226
  * Minor fix: Old legacy scripts that caused Javascript warnings on admin pages have been removed.
227
  * Minor fix: In some cases, having less than or greater than symbols in PDF content would block that PDF content from being indexed.
228
 
229
- = 4.10.2 =
230
- * New feature: You can force Relevanssi to be active by setting the query variable `relevanssi` to `true`. Thanks to Jan Willem Oostendorp.
231
- * Changed behaviour: Relevanssi has been moved from `the_posts` filter to `posts_pre_query`. This change doesn't do much, but increases performance slightly as WordPress needs to do less useless work, as now the default query is no longer run. Thanks to Jan Willem Oostendorp.
232
- * Minor fix: Highlighting didn't work properly when highlighting something immediately following a HTML tag.
233
- * Minor fix: You can no longer set the value of minimum word length to less than 1 or higher than 9 from the settings page.
234
- * Minor fix: Importing options broke synonym and stopword settings.
235
- * Minor fix: Improves the Rank Math SEO compatibility to avoid errors in plugin activation.
236
- * Minor fix: WPML search results that included non-post results caused fatal errors and crashes. This fixes the crashing and makes non-post results work better in both WPML and Polylang.
237
-
238
- = 4.10.1 =
239
- * Major fix: The multilingual stopwords and synonyms were used based on the global language. Now when indexing posts, the post language is used instead of the global language.
240
-
241
- = 4.10.0 =
242
- * New feature: Relevanssi now supports multilingual synonyms and stopwords. Relevanssi now has a different set of synonyms and stopwords for each language. This feature is compatible with WPML and Polylang.
243
- * New feature: SEO by Rank Math compatibility is added: posts marked as 'noindex' with Rank Math are not indexed by Relevanssi.
244
- * Minor fix: With keyword matching set to 'whole words' and the 'expand highlights' disabled, words that ended with an 's' weren't highlighted correctly.
245
- * Minor fix: The 'Post exclusion' setting didn't work correctly. It has been fixed.
246
- * Minor fix: It's now impossible to set negative weights in searching settings. They did not work as expected anyway.
247
- * Minor fix: Relevanssi had an unnecessary index on the `doc` column in the `wp_relevanssi` database table. It is now removed to save space. Thanks to Matthew Wang.
248
- * Minor fix: Improved Oxygen Builder support makes sure `ct_builder_shortcodes` custom field is always indexed.
249
-
250
  == Upgrade notice ==
 
 
 
251
  = 4.13.3.1 =
252
  * Fixes the Bricks compatibility.
253
 
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.8.1
7
  Requires PHP: 7.0
8
+ Stable tag: 4.14.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
131
  * John Calahan for extensive 4.0 beta testing.
132
 
133
  == Changelog ==
134
+ = 4.14.0 =
135
+ * 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.
136
+ * New feature: The user searches page has been improved a lot.
137
+ * 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.
138
+ * 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.
139
+ * New feature: New filter hook `relevanssi_search_params` lets you filter search parameters after they've been collected from the WP_Query.
140
+ * New feature: New filter hook `relevanssi_excerpt_post` lets you make Relevanssi skip creating excerpts for specific posts.
141
+ * 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.
142
+ * Minor fix: In some languages, iOS uses „“ for quotes. Relevanssi now understands those for the phrase operator.
143
+ * Minor fix: Stops Relevanssi from blocking the admin search for WooCommerce coupons and other WooCommerce custom post types.
144
+ * Minor fix: Fixes problems with the WP-Members compatibility.
145
+ * Minor fix: New parameter for `relevanssi_tokenize()` introduces the context (indexing or search query). The `relevanssi_extract_phrases()` is only used on search queries.
146
+ * Minor fix: Relevanssi won't let you adjust synonyms and stopwords anymore if you use Polylang and are in 'Show all languages' mode.
147
+ * Minor fix: Highlighting is improved by a more precise HTML entity filter, thanks to Jacob Bearce.
148
+
149
  = 4.13.3.1 =
150
  * Minor fix: The Bricks compatibility was broken. This version fixes it.
151
 
241
  * Minor fix: Old legacy scripts that caused Javascript warnings on admin pages have been removed.
242
  * Minor fix: In some cases, having less than or greater than symbols in PDF content would block that PDF content from being indexed.
243
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  == Upgrade notice ==
245
+ = 4.14.0 =
246
+ * User searches page update, bug fixes and improvements.
247
+
248
  = 4.13.3.1 =
249
  * Fixes the Bricks compatibility.
250
 
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.13.3.1
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.13.3.1';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';
@@ -87,4 +87,5 @@ require_once 'lib/search-query-restrictions.php';
87
  require_once 'lib/shortcodes.php';
88
  require_once 'lib/sorting.php';
89
  require_once 'lib/stopwords.php';
 
90
  require_once 'lib/utils.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.14.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.14.0';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';
87
  require_once 'lib/shortcodes.php';
88
  require_once 'lib/sorting.php';
89
  require_once 'lib/stopwords.php';
90
+ require_once 'lib/user-searches.php';
91
  require_once 'lib/utils.php';