Relevanssi – A Better Search - Version 4.0.10

Version Description

  • Privacy: If you log search queries, Relevanssi will suggest some additional content to your privacy policy page.
  • Privacy: Relevanssi now supports the new Privacy Policy and Personal Data tools in WordPress 4.9.6.
  • Saving synonyms with quotes worked, but the synonyms showed up wrong.
  • Relevanssi could in some situations override navigation menu links with links to the user profiles or taxonomy terms found in the search. This update fixes that behaviour.
  • Random order works again; using orderby rand didn't work properly. The rand(seed) format is also supported now.
  • Fixed quotes and apostrophes in Did you mean suggestions.
Download this release

Release Info

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

Code changes from version 4.0.9 to 4.0.10

lib/common.php CHANGED
@@ -8,218 +8,6 @@
8
  * @see https://www.relevanssi.com/
9
  */
10
 
11
- /**
12
- * Gets the next key-direction pair from the orderby array.
13
- *
14
- * Fetches a key-direction pair from the orderby array. Converts key names to match
15
- * the post object parameters when necessary and seeds the random generator, if
16
- * required.
17
- *
18
- * @param array $orderby An array of key-direction pairs.
19
- *
20
- * @return array A set of 'key', 'dir' for direction and 'compare' for proper
21
- * comparison method.
22
- */
23
- function relevanssi_get_next_key( &$orderby ) {
24
- if ( ! is_array( $orderby ) || count( $orderby ) < 1 ) {
25
- // Nothing to see here.
26
- return array(
27
- 'key' => null,
28
- 'dir' => null,
29
- 'compare' => null,
30
- );
31
- }
32
-
33
- list( $key ) = array_keys( $orderby );
34
- $dir = $orderby[ $key ];
35
- unset( $orderby[ $key ] );
36
-
37
- if ( 'rand' === strtolower( $dir ) ) {
38
- $key = 'rand';
39
- }
40
-
41
- // Correcting the key for couple of shorthand cases.
42
- switch ( $key ) {
43
- case 'title':
44
- $key = 'post_title';
45
- break;
46
- case 'date':
47
- $key = 'post_date';
48
- break;
49
- case 'modified':
50
- $key = 'post_modified';
51
- break;
52
- case 'parent':
53
- $key = 'post_parent';
54
- break;
55
- case 'type':
56
- $key = 'post_type';
57
- break;
58
- case 'name':
59
- $key = 'post_name';
60
- break;
61
- case 'author':
62
- $key = 'post_author';
63
- break;
64
- case 'relevance':
65
- $key = 'relevance_score';
66
- break;
67
- }
68
-
69
- $numeric_keys = array( 'menu_order', 'ID', 'post_parent', 'post_author', 'comment_count', 'relevance_score' );
70
- $date_keys = array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt' );
71
-
72
- $compare = 'string';
73
- if ( in_array( $key, $numeric_keys, true ) ) {
74
- $compare = 'number';
75
- } elseif ( in_array( $key, $date_keys, true ) ) {
76
- $compare = 'date';
77
- }
78
-
79
- if ( 'rand' === $key ) {
80
- if ( is_numeric( $dir ) ) {
81
- // A specific random seed is requested.
82
- mt_srand( $dir );
83
- }
84
- } else {
85
- $dir = strtolower( $dir );
86
- if ( 'asc' !== $dir ) {
87
- $dir = 'desc';
88
- }
89
- }
90
-
91
- $values = array(
92
- 'key' => $key,
93
- 'dir' => $dir,
94
- 'compare' => $compare,
95
- );
96
- return $values;
97
- }
98
-
99
- /**
100
- * Gets the values for comparing items for given key.
101
- *
102
- * Fetches the key values for the item pair. If random order is required, this
103
- * function will randomize the order.
104
- *
105
- * @global object $wp_query The global WP_Query object.
106
- *
107
- * @param string $key The key used.
108
- * @param object $item_1 The first post object to compare.
109
- * @param object $item_2 The second post object to compare.
110
- *
111
- * @return array Array with the key values: 'key1' and 'key2', respectively.
112
- */
113
- function relevanssi_get_compare_values( $key, $item_1, $item_2 ) {
114
- if ( 'rand' === $key ) {
115
- do {
116
- $key1 = rand();
117
- $key2 = rand();
118
- } while ( $key1 === $key2 );
119
- $keys = array(
120
- 'key1' => $key1,
121
- 'key2' => $key2,
122
- );
123
- return $keys;
124
- }
125
-
126
- $key1 = '';
127
- $key2 = '';
128
-
129
- if ( 'meta_value' === $key || 'meta_value_num' === $key ) {
130
- global $wp_query;
131
- // Get the name of the field from the global WP_Query.
132
- $key = $wp_query->query_vars['meta_key'];
133
- if ( ! isset( $key ) ) {
134
- // The key is not set.
135
- return array( '', '' );
136
- }
137
-
138
- $key1 = get_post_meta( $item_1->ID, $key, true );
139
- if ( empty( $key1 ) ) {
140
- /**
141
- * Adds in a missing sorting value.
142
- *
143
- * In some cases the sorting method may not have values for all posts
144
- * (for example when sorting by 'menu_order'). If you still want to use
145
- * a sorting method like this, you can use this function to fill in a
146
- * value (in the case of 'menu_order', for example, one could use
147
- * PHP_INT_MAX.)
148
- *
149
- * @param string $key1 The value to filter.
150
- * @param string $key The name of the key.
151
- */
152
- $key1 = apply_filters( 'relevanssi_missing_sort_key', $key1, $key );
153
- }
154
-
155
- $key2 = get_post_meta( $item_2->ID, $key, true );
156
- if ( empty( $key2 ) ) {
157
- /**
158
- * Documented in lib/common.php.
159
- */
160
- $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
161
- }
162
- } else {
163
- if ( isset( $item_1->$key ) ) {
164
- $key1 = relevanssi_strtolower( $item_1->$key );
165
- } else {
166
- /**
167
- * Documented in lib/common.php.
168
- */
169
- $key1 = apply_filters( 'relevanssi_missing_sort_key', $key1, $key );
170
- }
171
- if ( isset( $item_2->$key ) ) {
172
- $key2 = relevanssi_strtolower( $item_2->$key );
173
- } else {
174
- /**
175
- * Documented in lib/common.php.
176
- */
177
- $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
178
- }
179
- }
180
-
181
- $keys = array(
182
- 'key1' => $key1,
183
- 'key2' => $key2,
184
- );
185
- return $keys;
186
- }
187
-
188
- /**
189
- * Compares to values.
190
- *
191
- * Compares two sorting keys using date based comparison, string comparison or
192
- * numeric comparison.
193
- *
194
- * @param string $key1 The first key.
195
- * @param string $key2 The second key.
196
- * @param string $compare The comparison method; possible values are 'date' for
197
- * date comparisons and 'string' for string comparison, everything else is
198
- * considered a numeric comparison.
199
- *
200
- * @return int $val Returns < 0 if key1 is less than key2; > 0 if key1 is greater
201
- * than key2, and 0 if they are equal.
202
- */
203
- function relevanssi_compare_values( $key1, $key2, $compare ) {
204
- $val = 0;
205
- if ( 'date' === $compare ) {
206
- if ( strtotime( $key1 ) > strtotime( $key2 ) ) {
207
- $val = 1;
208
- } elseif ( strtotime( $key1 ) < strtotime( $key2 ) ) {
209
- $val = -1;
210
- }
211
- } elseif ( 'string' === $compare ) {
212
- $val = relevanssi_mb_strcasecmp( $key1, $key2 );
213
- } else {
214
- if ( $key1 > $key2 ) {
215
- $val = 1;
216
- } elseif ( $key1 < $key2 ) {
217
- $val = -1;
218
- }
219
- }
220
- return $val;
221
- }
222
-
223
  /**
224
  * Multibyte friendly case-insensitive string comparison.
225
  *
@@ -262,86 +50,6 @@ function relevanssi_strtolower( $string ) {
262
  }
263
  }
264
 
265
- /**
266
- * Compares values using multiple levels of sorting keys.
267
- *
268
- * Comparison function for usort() using multiple levels of sorting methods. If one
269
- * level produces a tie, the sort will get a next level of sorting methods.
270
- *
271
- * @global array $relevanssi_keys An array of sorting keys by level.
272
- * @global array $relevanssi_dirs An array of sorting directions by level.
273
- * @global array $relevanssi_compares An array of comparison methods by level.
274
- *
275
- * @param object $a A post object.
276
- * @param object $b A post object.
277
- *
278
- * @return int $val Returns < 0 if a is less than b; > 0 if a is greater
279
- * than b, and 0 if they are equal.
280
- */
281
- function relevanssi_cmp_function( $a, $b ) {
282
- global $relevanssi_keys, $relevanssi_dirs, $relevanssi_compares;
283
-
284
- $level = -1;
285
- $val = 0;
286
-
287
- while ( 0 === $val ) {
288
- $level++;
289
- if ( ! isset( $relevanssi_keys[ $level ] ) ) {
290
- // No more levels; we've hit the bedrock.
291
- $level--;
292
- break;
293
- }
294
- $compare = $relevanssi_compares[ $level ];
295
- $compare_values = relevanssi_get_compare_values( $relevanssi_keys[ $level ], $a, $b );
296
- $val = relevanssi_compare_values( $compare_values['key1'], $compare_values['key2'], $compare );
297
- }
298
-
299
- if ( 'desc' === $relevanssi_dirs[ $level ] ) {
300
- $val = $val * -1;
301
- }
302
-
303
- return $val;
304
- }
305
-
306
- /**
307
- * Sorts post objects.
308
- *
309
- * Sorts post objects using multiple levels of sorting methods. This function was
310
- * originally written by Matthew Hood and published in the PHP manual comments.
311
- * The actual sorting is handled by relevanssi_cmp_function().
312
- *
313
- * @global array $relevanssi_keys An array of sorting keys by level.
314
- * @global array $relevanssi_dirs An array of sorting directions by level.
315
- * @global array $relevanssi_compares An array of comparison methods by level.
316
- *
317
- * @param array $data The posts to sort are in $data[0], used as a reference.
318
- * @param array $orderby The array of orderby rules with directions.
319
- */
320
- function relevanssi_object_sort( &$data, $orderby ) {
321
- global $relevanssi_keys, $relevanssi_dirs, $relevanssi_compares;
322
-
323
- $relevanssi_keys = array();
324
- $relevanssi_dirs = array();
325
- $relevanssi_compares = array();
326
-
327
- do {
328
- $values = relevanssi_get_next_key( $orderby );
329
- if ( ! empty( $values['key'] ) ) {
330
- $relevanssi_keys[] = $values['key'];
331
- $relevanssi_dirs[] = $values['dir'];
332
- $relevanssi_compares[] = $values['compare'];
333
- }
334
- } while ( ! empty( $values['key'] ) );
335
-
336
- $primary_key = $relevanssi_keys[0];
337
- if ( ! isset( $data[0]->$primary_key ) ) {
338
- // Trying to sort by a non-existent key.
339
- return;
340
- }
341
-
342
- usort( $data, 'relevanssi_cmp_function' );
343
- }
344
-
345
  /**
346
  * Generates the search result breakdown added to the search results.
347
  *
@@ -419,115 +127,6 @@ function relevanssi_show_matches( $data, $hit ) {
419
  return apply_filters( 'relevanssi_show_matches', $result );
420
  }
421
 
422
- /**
423
- * Adds the search query to the log.
424
- *
425
- * Logs the search query, trying to avoid bots.
426
- *
427
- * @global object $wpdb The WordPress database interface.
428
- * @global array $relevanssi_variables The global Relevanssi variables, used for database table names.
429
- *
430
- * @param string $query The search query.
431
- * @param int $hits The number of hits found.
432
- */
433
- function relevanssi_update_log( $query, $hits ) {
434
- // Bot filter, by Justin_K.
435
- // See: http://wordpress.org/support/topic/bot-logging-problem-w-tested-solution.
436
- $user_agent = '';
437
- if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
438
- $user_agent = $_SERVER['HTTP_USER_AGENT'];
439
- $bots = array( 'Google' => 'Mediapartners-Google' );
440
-
441
- /**
442
- * Filters the bots Relevanssi should block from logs.
443
- *
444
- * Lets you filter the bots that are blocked from Relevanssi logs.
445
- *
446
- * @param array $bots An array of bot user agents.
447
- */
448
- $bots = apply_filters( 'relevanssi_bots_to_not_log', $bots );
449
- foreach ( $bots as $name => $lookfor ) {
450
- if ( false !== stristr( $user_agent, $lookfor ) ) {
451
- return;
452
- }
453
- }
454
- }
455
-
456
- /**
457
- * Filters the current user for logs.
458
- *
459
- * The current user is checked before logging a query to omit particular users.
460
- * You can use this filter to filter out the user.
461
- *
462
- * @param object The current user object.
463
- */
464
- $user = apply_filters( 'relevanssi_log_get_user', wp_get_current_user() );
465
- if ( 0 !== $user->ID && get_option( 'relevanssi_omit_from_logs' ) ) {
466
- $omit = explode( ',', get_option( 'relevanssi_omit_from_logs' ) );
467
- if ( in_array( strval( $user->ID ), $omit, true ) ) {
468
- return;
469
- }
470
- if ( in_array( $user->user_login, $omit, true ) ) {
471
- return;
472
- }
473
- }
474
-
475
- $ip = '';
476
- if ( 'on' === get_option( 'relevanssi_log_queries_with_ip' ) ) {
477
- /**
478
- * Filters the IP address of the searcher.
479
- *
480
- * Relevanssi may store the IP address of the searches in the logs. If the
481
- * setting is enabled, this filter can be used to filter out the IP address
482
- * before the log entry is made.
483
- *
484
- * Do note that storing the IP address may be illegal or get you in GDPR
485
- * trouble.
486
- *
487
- * @param string $ip The IP address, from $_SERVER['REMOTE_ADDR'].
488
- */
489
- $ip = apply_filters( 'relevanssi_remote_addr', $_SERVER['REMOTE_ADDR'] );
490
- }
491
-
492
- /**
493
- * Filters whether a query should be logged or not.
494
- *
495
- * This filter can used to determine whether a query should be logged or not.
496
- *
497
- * @param boolean $ok_to_log Can the query be logged.
498
- * @param string $query The actual query string.
499
- * @param int $hits The number of hits found.
500
- * @param string $user_agent The user agent that made the search.
501
- * @param string $ip The IP address the search came from (or empty).
502
- */
503
- $ok_to_log = apply_filters( 'relevanssi_ok_to_log', true, $query, $hits, $user_agent, $ip );
504
- if ( $ok_to_log ) {
505
- global $wpdb, $relevanssi_variables;
506
-
507
- $wpdb->query(
508
- $wpdb->prepare( 'INSERT INTO ' . $relevanssi_variables['log_table'] . ' (query, hits, user_id, ip, time) VALUES (%s, %d, %d, %s, NOW())',
509
- $query, intval( $hits ), $user->ID, $ip )
510
- ); // WPCS: unprepared SQL ok, Relevanssi database table name.
511
- }
512
- }
513
-
514
- /**
515
- * Trims Relevanssi log table.
516
- *
517
- * Trims Relevanssi log table, using the day interval setting from 'relevanssi_trim_logs'.
518
- *
519
- * @global object $wpdb The WordPress database interface.
520
- * @global array $relevanssi_variables The global Relevanssi variables, used for database table names.
521
- */
522
- function relevanssi_trim_logs() {
523
- global $wpdb, $relevanssi_variables;
524
- $interval = intval( get_option( 'relevanssi_trim_logs' ) );
525
- $wpdb->query(
526
- $wpdb->prepare( 'DELETE FROM ' . $relevanssi_variables['log_table'] . ' WHERE time < TIMESTAMP(DATE_SUB(NOW(), INTERVAL %d DAY))',
527
- $interval )
528
- ); // WPCS: unprepared SQL ok, Relevanssi database table name.
529
- }
530
-
531
  /**
532
  * Checks whether the user is allowed to see the post.
533
  *
@@ -590,13 +189,9 @@ function relevanssi_default_post_ok( $post_ok, $post_id ) {
590
  }
591
  if ( defined( 'SIMPLE_WP_MEMBERSHIP_VER' ) ) {
592
  // Simple Membership.
593
- $logged_in = SwpmMemberUtils::is_member_logged_in();
594
- if ( ! $logged_in ) {
595
- $post_ok = false;
596
- } else {
597
- $access_ctrl = SwpmAccessControl::get_instance();
598
- $post_ok = $access_ctrl->can_i_read_post( $post_id );
599
- }
600
  }
601
 
602
  /**
@@ -813,22 +408,6 @@ function relevanssi_strip_invisibles( $text ) {
813
  return $text;
814
  }
815
 
816
- /**
817
- * Sorts strings by length.
818
- *
819
- * A sorting function that sorts strings by length. Uses relevanssi_strlen() to
820
- * count the string length.
821
- *
822
- * @param string $a String A.
823
- * @param string $b String B.
824
- *
825
- * @return int Negative value, if string A is longer; zero, if strings are equally
826
- * long; positive, if string B is longer.
827
- */
828
- function relevanssi_strlen_sort( $a, $b ) {
829
- return relevanssi_strlen( $b ) - relevanssi_strlen( $a );
830
- }
831
-
832
  /**
833
  * Returns the custom fields to index.
834
  *
@@ -1106,24 +685,6 @@ function relevanssi_prevent_default_request( $request, $query ) {
1106
  return $request;
1107
  }
1108
 
1109
- /**
1110
- * Disables Relevanssi in the ACF Relationship field post search.
1111
- *
1112
- * We don't want to use Relevanssi on the ACF Relationship field post searches, so
1113
- * this function disables it (on the 'relevanssi_search_ok' hook).
1114
- *
1115
- * @param boolean $search_ok Block the search or not.
1116
- *
1117
- * @return boolean False, if this is an ACF Relationship field search, pass the
1118
- * parameter unchanged otherwise.
1119
- */
1120
- function relevanssi_acf_relationship_fields( $search_ok ) {
1121
- if ( isset( $_REQUEST['action'] ) && 'acf' === substr( $_REQUEST['action'], 0, 3 ) ) { // WPCS: CSRF ok.
1122
- $search_ok = false;
1123
- }
1124
- return $search_ok;
1125
- }
1126
-
1127
  /**
1128
  * Tokenizes strings.
1129
  *
@@ -1671,20 +1232,24 @@ function relevanssi_the_permalink() {
1671
  *
1672
  * @global object $post The global post object.
1673
  *
1674
- * @param string $link The link to adjust.
1675
- * @param object $link_post The post to modify. If null, use global $post.
1676
- * Defaults null.
1677
  *
1678
  * @return string The modified link.
1679
  */
1680
  function relevanssi_permalink( $link, $link_post = null ) {
1681
  if ( null === $link_post ) {
1682
  global $post;
 
 
 
1683
  }
1684
  // Using property_exists() to avoid troubles from magic variables.
1685
- if ( is_object( $post ) && property_exists( $post, 'relevanssi_link' ) ) {
1686
- $link = $post->relevanssi_link;
1687
  }
 
1688
  if ( is_search() ) {
1689
  $link = relevanssi_add_highlight( $link );
1690
  }
@@ -1804,7 +1369,7 @@ function relevanssi_simple_generate_suggestion( $query ) {
1804
  wp_cache_set( 'relevanssi_didyoumean_query', $data );
1805
  }
1806
 
1807
- $query = htmlspecialchars_decode( $query );
1808
  $tokens = relevanssi_tokenize( $query );
1809
  $suggestions_made = false;
1810
  $suggestion = '';
8
  * @see https://www.relevanssi.com/
9
  */
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  /**
12
  * Multibyte friendly case-insensitive string comparison.
13
  *
50
  }
51
  }
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  /**
54
  * Generates the search result breakdown added to the search results.
55
  *
127
  return apply_filters( 'relevanssi_show_matches', $result );
128
  }
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  /**
131
  * Checks whether the user is allowed to see the post.
132
  *
189
  }
190
  if ( defined( 'SIMPLE_WP_MEMBERSHIP_VER' ) ) {
191
  // Simple Membership.
192
+ $access_ctrl = SwpmAccessControl::get_instance();
193
+ $post = get_post( $post_id );
194
+ $post_ok = $access_ctrl->can_i_read_post( $post );
 
 
 
 
195
  }
196
 
197
  /**
408
  return $text;
409
  }
410
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  /**
412
  * Returns the custom fields to index.
413
  *
685
  return $request;
686
  }
687
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
  /**
689
  * Tokenizes strings.
690
  *
1232
  *
1233
  * @global object $post The global post object.
1234
  *
1235
+ * @param string $link The link to adjust.
1236
+ * @param object|int $link_post The post to modify, either WP post object or the post
1237
+ * ID. If null, use global $post. Defaults null.
1238
  *
1239
  * @return string The modified link.
1240
  */
1241
  function relevanssi_permalink( $link, $link_post = null ) {
1242
  if ( null === $link_post ) {
1243
  global $post;
1244
+ $link_post = $post;
1245
+ } elseif ( is_int( $link_post ) ) {
1246
+ $link_post = get_post( $link_post );
1247
  }
1248
  // Using property_exists() to avoid troubles from magic variables.
1249
+ if ( is_object( $link_post ) && property_exists( $link_post, 'relevanssi_link' ) ) {
1250
+ $link = $link_post->relevanssi_link;
1251
  }
1252
+
1253
  if ( is_search() ) {
1254
  $link = relevanssi_add_highlight( $link );
1255
  }
1369
  wp_cache_set( 'relevanssi_didyoumean_query', $data );
1370
  }
1371
 
1372
+ $query = htmlspecialchars_decode( $query, ENT_QUOTES );
1373
  $tokens = relevanssi_tokenize( $query );
1374
  $suggestions_made = false;
1375
  $suggestion = '';
lib/compatibility/acf.php ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/compatibility/acf.php
4
+ *
5
+ * Advanced Custom Fields compatibility features.
6
+ *
7
+ * @package Relevanssi
8
+ * @author Mikko Saari
9
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
10
+ * @see https://www.relevanssi.com/
11
+ */
12
+
13
+ add_filter( 'relevanssi_search_ok', 'relevanssi_acf_relationship_fields' );
14
+
15
+ /**
16
+ * Disables Relevanssi in the ACF Relationship field post search.
17
+ *
18
+ * We don't want to use Relevanssi on the ACF Relationship field post searches, so
19
+ * this function disables it (on the 'relevanssi_search_ok' hook).
20
+ *
21
+ * @param boolean $search_ok Block the search or not.
22
+ *
23
+ * @return boolean False, if this is an ACF Relationship field search, pass the
24
+ * parameter unchanged otherwise.
25
+ */
26
+ function relevanssi_acf_relationship_fields( $search_ok ) {
27
+ if ( isset( $_REQUEST['action'] ) && 'acf' === substr( $_REQUEST['action'], 0, 3 ) ) { // WPCS: CSRF ok.
28
+ $search_ok = false;
29
+ }
30
+ return $search_ok;
31
+ }
32
+
33
+ /**
34
+ * Indexes the human-readable value of "choice" options list from ACF.
35
+ *
36
+ * @author Droz Raphaël
37
+ *
38
+ * @param array $insert_data The insert data array.
39
+ * @param int $post_id The post ID.
40
+ * @param string $field_name Name of the field.
41
+ * @param string $field_value The field value.
42
+ */
43
+ function relevanssi_index_acf( &$insert_data, $post_id, $field_name, $field_value ) {
44
+ if ( ! is_admin() ) {
45
+ include_once ABSPATH . 'wp-admin/includes/plugin.php'; // Otherwise is_plugin_active() will cause a fatal error.
46
+ }
47
+ if ( ! function_exists( 'is_plugin_active' ) ) {
48
+ return;
49
+ }
50
+ if ( ! is_plugin_active( 'advanced-custom-fields/acf.php' ) && ! is_plugin_active( 'advanced-custom-fields-pro/acf.php' ) ) {
51
+ return;
52
+ }
53
+ if ( ! function_exists( 'get_field_object' ) ) {
54
+ return; // ACF is active, but not loaded.
55
+ }
56
+
57
+ $field_object = get_field_object( $field_name, $post_id );
58
+ if ( ! isset( $field_object['choices'] ) ) {
59
+ return; // Not a "select" field.
60
+ }
61
+ if ( is_array( $field_value ) ) {
62
+ return; // Not handled (currently).
63
+ }
64
+ if ( ! isset( $field_object['choices'][ $field_value ] ) ) {
65
+ return; // Value does not exist.
66
+ }
67
+
68
+ $value = $field_object['choices'][ $field_value ];
69
+ if ( $value ) {
70
+ if ( ! isset( $insert_data[ $value ]['customfield'] ) ) {
71
+ $insert_data[ $value ]['customfield'] = 0;
72
+ }
73
+ $insert_data[ $value ]['customfield']++;
74
+ }
75
+ }
lib/compatibility/polylang.php CHANGED
@@ -11,6 +11,8 @@
11
  */
12
 
13
  add_filter( 'relevanssi_modify_wp_query', 'relevanssi_polylang_filter' );
 
 
14
 
15
  /**
16
  * Removes the Polylang language filters.
@@ -64,3 +66,74 @@ function relevanssi_polylang_filter( $query ) {
64
 
65
  return $query;
66
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  */
12
 
13
  add_filter( 'relevanssi_modify_wp_query', 'relevanssi_polylang_filter' );
14
+ add_filter( 'relevanssi_where', 'relevanssi_polylang_where_include_terms' );
15
+ add_filter( 'relevanssi_hits_filter', 'relevanssi_polylang_term_filter' );
16
 
17
  /**
18
  * Removes the Polylang language filters.
66
 
67
  return $query;
68
  }
69
+
70
+ /**
71
+ * Allows taxonomy terms in language-restricted searches.
72
+ *
73
+ * This is a bit of a hack, where the language taxonomy WHERE clause is modified on
74
+ * the go to allow all posts with the post ID -1 (which means taxonomy terms and
75
+ * users). This may break suddenly in updates, but I haven't come up with a better
76
+ * way so far.
77
+ *
78
+ * @param string $where The WHERE clause to modify.
79
+ *
80
+ * @return string The WHERE clause with additional filtering included.
81
+ *
82
+ * @since 2.1.6
83
+ */
84
+ function relevanssi_polylang_where_include_terms( $where ) {
85
+ $current_language = pll_current_language();
86
+ $languages = get_terms( array( 'taxonomy' => 'language' ) );
87
+ $language_id = 0;
88
+ foreach ( $languages as $language ) {
89
+ if ( $language->slug === $current_language ) {
90
+ $language_id = intval( $language->term_id );
91
+ break;
92
+ }
93
+ }
94
+ // Language ID should now have current language ID.
95
+ if ( 0 !== $language_id ) {
96
+ // Do a simple search-and-replace to modify the query.
97
+ $where = preg_replace( '/\s+/', ' ', $where );
98
+ $where = str_replace(
99
+ "AND relevanssi.doc IN (SELECT DISTINCT(tr.object_id) FROM wp_term_relationships AS tr WHERE tr.term_taxonomy_id IN ($language_id))",
100
+ "AND (relevanssi.doc IN (SELECT DISTINCT(tr.object_id) FROM wp_term_relationships AS tr WHERE tr.term_taxonomy_id IN ($language_id)) OR (relevanssi.doc = -1))",
101
+ $where
102
+ );
103
+ }
104
+ return $where;
105
+ }
106
+
107
+ /**
108
+ * Filters out taxonomy terms in the wrong language.
109
+ *
110
+ * If all languages are not allowed, this filter goes through the results and removes
111
+ * the taxonomy terms in the wrong language. This can't be done in the original query
112
+ * because the term language information is slightly hard to find.
113
+ *
114
+ * @param array $hits The found posts are in $hits[0].
115
+ *
116
+ * @return array The $hits array with the unwanted posts removed.
117
+ *
118
+ * @since 2.1.6
119
+ */
120
+ function relevanssi_polylang_term_filter( $hits ) {
121
+ $polylang_allow_all = get_option( 'relevanssi_polylang_all_languages' );
122
+ if ( 'on' !== $polylang_allow_all ) {
123
+ $current_language = pll_current_language();
124
+ $accepted_hits = array();
125
+ foreach ( $hits[0] as $hit ) {
126
+ if ( -1 === $hit->ID && isset( $hit->term_id ) ) {
127
+ $term_id = intval( $hit->term_id );
128
+ $translations = pll_get_term_translations( $term_id );
129
+ if ( isset( $translations[ $current_language ] ) && $translations[ $current_language ] === $term_id ) {
130
+ $accepted_hits[] = $hit;
131
+ }
132
+ } else {
133
+ $accepted_hits[] = $hit;
134
+ }
135
+ }
136
+ $hits[0] = $accepted_hits;
137
+ }
138
+ return $hits;
139
+ }
lib/compatibility/wpml.php CHANGED
@@ -78,3 +78,21 @@ function relevanssi_wpml_filter( $data ) {
78
 
79
  return $data;
80
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  return $data;
80
  }
81
+
82
+ // add_action( 'wp_ajax_relevanssi_index_posts', 'relevanssi_wpml_remove_filters', 5 );
83
+ // add_action( 'wp_ajax_relevanssi_index_taxonomies', 'relevanssi_wpml_remove_filters', 5 );
84
+ /**
85
+ * Removes WPML filters from get_term().
86
+ *
87
+ * Relevanssi indexes WooCommerce terms in current language. Indexed term should
88
+ * have translated entry. Term title is filtered by WPML and value in current
89
+ * language is always returned. Removing this filter fixes the problem.
90
+ *
91
+ * @author Srdjan Jocić
92
+ */
93
+ function relevanssi_wpml_remove_filters() {
94
+ if ( did_action( 'wpml_loaded' ) ) {
95
+ global $sitepress;
96
+ remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
97
+ }
98
+ }
lib/contextual-help.php ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/contextual-help.php
4
+ *
5
+ * Adds the contextual help menus.
6
+ *
7
+ * @package Relevanssi
8
+ * @author Mikko Saari
9
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
10
+ * @see https://www.relevanssi.com/
11
+ */
12
+
13
+ /**
14
+ * Displays the contextual help menu.
15
+ *
16
+ * @global object $wpdb The WP database interface.
17
+ */
18
+ function relevanssi_admin_help() {
19
+ global $wpdb;
20
+
21
+ $screen = get_current_screen();
22
+ $screen->add_help_tab( array(
23
+ 'id' => 'relevanssi-searching',
24
+ 'title' => __( 'Searching', 'relevanssi' ),
25
+ 'content' => '<ul>' .
26
+ // Translators: %1$s is 'orderby', %2$s is the Codex page URL.
27
+ '<li>' . sprintf( __( "To adjust the post order, you can use the %1\$s query parameter. With %1\$s, you can use multiple layers of different sorting methods. See <a href='%2\$s'>WordPress Codex</a> for more details on using arrays for orderby.", 'relevanssi' ), '<code>orderby</code>', 'https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters' ) . '</li>' .
28
+ '<li>' . __( "Inside-word matching is disabled by default, because it increases garbage results that don't really match the search term. If you want to enable it, add the following function to your theme functions.php:", 'relevanssi' ) .
29
+ '<pre>add_filter( \'relevanssi_fuzzy_query\', \'rlv_partial_inside_words\' );
30
+ function rlv_partial_inside_words( $query ) {
31
+ return "(term LIKE \'%#term#%\')";
32
+ }</pre></li>' .
33
+ // Translators: %s is 'Uncheck this if you use non-ASCII characters' option name.
34
+ '<li>' . sprintf( __( 'To get inside-word highlights, uncheck the "%s" option. That has a side-effect of enabling the inside-word highlights.', 'relevanssi' ), __( 'Uncheck this if you use non-ASCII characters', 'relevanssi' ) ) . '</li>' .
35
+ // Translators: %s is 'relevanssi_throttle_limit'.
36
+ '<li>' . sprintf( __( 'In order to adjust the throttle limit, you can use the %s filter hook.', 'relevanssi' ), '<code>pre_option_relevanssi_throttle_limit</code>' ) .
37
+ '<pre>add_filter( \'pre_option_relevanssi_throttle_limit\', function( $limit ) { return 200; } );</pre></li>' .
38
+ '<li>' . __( "It's not usually necessary to adjust the limit from 500, but in some cases performance gains can be achieved by setting a lower limit. We don't suggest going under 200, as low values will make the results worse.", 'relevanssi' ) . '</li>' .
39
+ '</ul>',
40
+ ));
41
+ $screen->add_help_tab( array(
42
+ 'id' => 'relevanssi-search-restrictions',
43
+ 'title' => __( 'Restrictions', 'relevanssi' ),
44
+ 'content' => '<ul>' .
45
+ '<li>' . __( 'If you want the general search to target all posts, but have a single search form target only certain posts, you can add a hidden input variable to the search form. ', 'relevanssi' ) . '</li>' .
46
+ '<li>' . __( 'For example in order to restrict the search to categories 10, 14 and 17, you could add this to the search form:', 'relevanssi' ) .
47
+ '<pre>&lt;input type="hidden" name="cats" value="10,14,17" /&gt;</pre></li>' .
48
+ '<li>' . __( 'To restrict the search to posts tagged with alfa AND beta, you could add this to the search form:', 'relevanssi' ) .
49
+ '<pre>&lt;input type="hidden" name="tag" value="alfa+beta" /&gt;</pre></li>' .
50
+ // Translators: %s is the link to the Codex page.
51
+ '<li>' . sprintf( __( 'For all the possible options, see the Codex documentation for %s.', 'relevanssi' ), '<a href="https://codex.wordpress.org/Class_Reference/WP_Query">WP_Query</a>' ) . '</li>' .
52
+ '</ul>',
53
+ ));
54
+ $screen->add_help_tab( array(
55
+ 'id' => 'relevanssi-search-exclusions',
56
+ 'title' => __( 'Exclusions', 'relevanssi' ),
57
+ 'content' => '<ul>' .
58
+ // Translators: %s is the link to the Codex page.
59
+ '<li>' . sprintf( __( 'For more exclusion options, see the Codex documentation for %s. For example, to exclude tag ID 10, use', 'relevanssi' ), '<a href="https://codex.wordpress.org/Class_Reference/WP_Query">WP_Query</a>' ) .
60
+ '<pre>&lt;input type="hidden" name="tag__not_in" value="10" /&gt;</pre></li>' .
61
+ // Translators: %s is 'relevanssi_do_not_index'.
62
+ '<li>' . sprintf( __( 'To exclude posts from the index and not just from the search, you can use the %s filter hook. This would not index posts that have a certain taxonomy term:', 'relevanssi' ), '<code>relevanssi_do_not_index</code>' ) .
63
+ '<pre>add_filter( \'relevanssi_do_not_index\', \'rlv_index_filter\', 10, 2 );
64
+ function rlv_index_filter( $block, $post_id ) {
65
+ if ( has_term( \'jazz\', \'genre\', $post_id ) ) {
66
+ $block = true;
67
+ }
68
+ return $block;
69
+ }
70
+ </pre></li>' .
71
+ // Translators: %s is a link to the Relevanssi knowledge base.
72
+ '<li>' . sprintf( __( "For more examples, see <a href='%s'>the related knowledge base posts</a>.", 'relevanssi' ), 'https://www.relevanssi.com/tag/relevanssi_do_not_index/' ) . '</li>' .
73
+ '</ul>',
74
+ ));
75
+ $screen->add_help_tab( array(
76
+ 'id' => 'relevanssi-logging',
77
+ 'title' => __( 'Logs', 'relevanssi' ),
78
+ 'content' => '<ul>' .
79
+ // Translators: %s is 'relevanssi_user_searches_limit'.
80
+ '<li>' . sprintf( __( 'By default, the User searches page shows 20 most common keywords. In order to see more, you can adjust the value with the %s filter hook, like this:', 'relevanssi' ), '<code>relevanssi_user_searches_limit</code>' ) .
81
+ "<pre>add_filter( 'relevanssi_user_searches_limit', function() { return 50; } );</pre></li>" .
82
+ // Translators: %s is the name of the database table.
83
+ '<li>' . sprintf( __( 'The complete logs are stored in the %s database table, where you can access them if you need more information than what the User searches page provides.', 'relevanssi' ), '<code>' . $wpdb->prefix . 'relevanssi_log</code>' ) . '</li>' .
84
+ '</ul>',
85
+ ));
86
+ $screen->add_help_tab( array(
87
+ 'id' => 'relevanssi-excerpts',
88
+ 'title' => __( 'Excerpts', 'relevanssi' ),
89
+ 'content' => '<ul>' .
90
+ '<li>' . __( 'Building custom excerpts can be slow. If you are not actually using the excerpts, make sure you disable the option.', 'relevanssi' ) . '</li>' .
91
+ // Translators: %s is 'the_excerpt()'.
92
+ '<li>' . sprintf( __( 'Custom snippets require that the search results template uses %s to print out the excerpts.', 'relevanssi' ), '<code>the_excerpt()</code>' ) . '</li>' .
93
+ '<li>' . __( 'Generally, Relevanssi generates the excerpts from post content. If you want to include custom field content in the excerpt-building, this can be done with a simple setting from the excerpt settings.', 'relevanssi' ) . '</li>' .
94
+ // Translators: %1$s is 'relevanssi_pre_excerpt_content', %2$s is 'relevanssi_excerpt_content'.
95
+ '<li>' . sprintf( __( 'If you want more control over what content Relevanssi uses to create the excerpts, you can use the %1$s and %2$s filter hooks to adjust the content.', 'relevanssi' ), '<code>relevanssi_pre_excerpt_content</code>', '<code>relevanssi_excerpt_content</code>' ) . '</li>' .
96
+ // Translators: %s is 'relevanssi_disable_shortcodes_excerpt'.
97
+ '<li>' . sprintf( __( 'Some shortcode do not work well with Relevanssi excerpt-generation. Relevanssi disables some shortcodes automatically to prevent problems. This can be adjusted with the %s filter hook.', 'relevanssi' ), '<code>relevanssi_disable_shortcodes_excerpt</code>' ) . '</li>' .
98
+ // Translators: %s is 'relevanssi_optimize_excerpts'.
99
+ '<li>' . sprintf( __( "If you want Relevanssi to build excerpts faster and don't mind that they may be less than perfect in quality, add a filter that returns true on hook %s.", 'relevanssi' ), '<code>relevanssi_optimize_excerpts</code>' ) .
100
+ "<pre>add_filter( 'relevanssi_optimize_excerpts', '__return_true' );</pre></li>" .
101
+ '</ul>',
102
+ ));
103
+ $screen->add_help_tab( array(
104
+ 'id' => 'relevanssi-highlights',
105
+ 'title' => __( 'Highlights', 'relevanssi' ),
106
+ 'content' => '<ul>' .
107
+ '<li>' . __( "Title highlights don't appear automatically, because that led to problems with highlights appearing in wrong places and messing up navigation menus, for example.", 'relevanssi' ) . '</li>' .
108
+ // Translators: %1$s is 'the_title()', %2$s is 'relevanssi_the_title()'.
109
+ '<li>' . sprintf( __( 'In order to see title highlights from Relevanssi, replace %1$s in the search results template with %2$s. It does the same thing, but supports Relevanssi title highlights.', 'relevanssi' ), '<code>the_title()</code>', '<code>relevanssi_the_title()</code>' ) . '</li>' .
110
+ '</ul>',
111
+ ));
112
+ $screen->add_help_tab( array(
113
+ 'id' => 'relevanssi-punctuation',
114
+ 'title' => __( 'Punctuation', 'relevanssi' ),
115
+ 'content' => '<ul>' .
116
+ '<li>' . __( 'Relevanssi removes punctuation. Some punctuation is removed, some replaced with spaces. Advanced indexing settings include some of the more common settings people want to change.', 'relevanssi' ) . '</li>' .
117
+ // Translators: %1$s is 'relevanssi_punctuation_filter', %2$s is 'relevanssi_remove_punctuation'.
118
+ '<li>' . sprintf( __( 'For more fine-tuned changes, you can use %1$s filter hook to adjust what is replaced with what, and %2$s filter hook to completely override the default punctuation control.', 'relevanssi' ), '<code>relevanssi_punctuation_filter</code>', '<code>relevanssi_remove_punctuation</code>' ) . '</li>' .
119
+ // Translators: %s is the URL to the Knowledge Base entry.
120
+ '<li>' . sprintf( __( "For more examples, see <a href='%s'>the related knowledge base posts</a>.", 'relevanssi' ), 'https://www.relevanssi.com/tag/relevanssi_remove_punct/' ) . '</li>' .
121
+ '</ul>',
122
+ ));
123
+ $screen->add_help_tab( array(
124
+ 'id' => 'relevanssi-helpful-shortcodes',
125
+ 'title' => __( 'Helpful shortcodes', 'relevanssi' ),
126
+ 'content' => '<ul>' .
127
+ // Translators: %s is '[noindex]'.
128
+ '<li>' . sprintf( __( "If you have content that you don't want indexed, you can wrap that content in a %s shortcode.", 'relevanssi' ), '<code>[noindex]</code>' ) . '</li>' .
129
+ // Translators: %s is '[searchform]'.
130
+ '<li>' . sprintf( __( 'If you need a search form on some page on your site, you can use the %s shortcode to print out a basic search form.', 'relevanssi' ), '<code>[searchform]</code>' ) . '</li>' .
131
+ // Translators: %1$s is '[searchform post_types="page"]', %2$s is '[searchform cats="10,14,17"]'.
132
+ '<li>' . sprintf( __( 'If you need to add query variables to the search form, the shortcode takes parameters, which are then printed out as hidden input fields. To get a search form with a post type restriction, you can use %1$s. To restrict the search to categories 10, 14 and 17, you can use %2$s and so on.', 'relevanssi' ), '<code>[searchform post_types="page"]</code>', '<code>[searchform cats="10,14,17"]</code>' ) . '</li>' .
133
+ '</ul>',
134
+ ));
135
+ $screen->add_help_tab( array(
136
+ 'id' => 'relevanssi-title-woocommerce',
137
+ 'title' => __( 'WooCommerce', 'relevanssi' ),
138
+ 'content' => '<ul>' .
139
+ '<li>' . __( "If your SKUs include hyphens or other punctuation, do note that Relevanssi replaces most punctuation with spaces. That's going to cause issues with SKU searches.", 'relevanssi' ) . '</li>' .
140
+ // Translators: %s is the Knowledge Base URL.
141
+ '<li>' . sprintf( __( "For more details how to fix that issue, see <a href='%s'>WooCommerce tips in Relevanssi user manual</a>.", 'relevanssi' ), 'https://www.relevanssi.com/user-manual/woocommerce/' ) . '</li>' .
142
+ '<li>' . __( "If you don't want to index products that are out of stock, excluded from the catalog or excluded from the search, there's a product visibility filtering method that is described in the user manual (see link above).", 'relevanssi' ) . '</li>' .
143
+ '</ul>',
144
+ ));
145
+ $screen->add_help_tab( array(
146
+ 'id' => 'relevanssi-exact-match',
147
+ 'title' => __( 'Exact match bonus', 'relevanssi' ),
148
+ 'content' => '<ul>' .
149
+ // Translators: %s is the name of the filter hook.
150
+ '<li>' . sprintf( __( 'To adjust the amount of the exact match bonus, you can use the %s filter hook. It works like this:', 'relevanssi' ), '<code>relevanssi_exact_match_bonus</code>' ) .
151
+ "<pre>add_filter( 'relevanssi_exact_match_bonus', 'rlv_adjust_bonus' );
152
+ function rlv_adjust_bonus( \$bonus ) {
153
+ return array( 'title' => 10, 'content' => 5 );
154
+ }</li>" .
155
+ // Translators: %1$s is the title weight and %2$s is the content weight.
156
+ '<li>' . sprintf( esc_html__( 'The default values are %1$s for titles and %2$s for content.', 'relevanssi' ), '<code>5</code>', '<code>2</code>' ) . '</ul>',
157
+ ));
158
+ $screen->set_help_sidebar(
159
+ '<p><strong>' . __( 'For more information:', 'relevanssi' ) . '</strong></p>' .
160
+ '<p><a href="https://www.relevanssi.com/knowledge-base/" target="_blank">' . __( 'Plugin knowledge base', 'relevanssi' ) . '</a></p>' .
161
+ '<p><a href="https://wordpress.org/tags/relevanssi?forum_id=10" target="_blank">' . __( 'WordPress.org forum', 'relevanssi' ) . '</a></p>'
162
+ );
163
+ }
lib/indexing.php CHANGED
@@ -100,6 +100,18 @@ function relevanssi_generate_indexing_query( $valid_status, $extend = false, $re
100
  global $wpdb, $relevanssi_variables;
101
  $relevanssi_table = $relevanssi_variables['relevanssi_table'];
102
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  $restriction = apply_filters( 'relevanssi_indexing_restriction', $restriction );
104
 
105
  if ( ! $extend ) {
@@ -197,12 +209,8 @@ function relevanssi_valid_status_array() {
197
  $valid_status = array();
198
 
199
  if ( is_array( $valid_status_array ) && count( $valid_status_array ) > 0 ) {
200
- $post_stati = get_post_stati( array(), 'names' );
201
  foreach ( $valid_status_array as $status ) {
202
- if ( in_array( $status, $post_stati, true ) ) {
203
- // Only include post statuses that actually exist.
204
- $valid_status[] = "'$status'";
205
- }
206
  }
207
  $valid_status = implode( ',', $valid_status );
208
  } else {
@@ -210,7 +218,6 @@ function relevanssi_valid_status_array() {
210
  // default values.
211
  $valid_status = "'publish', 'draft', 'private', 'pending', 'future'";
212
  }
213
-
214
  return $valid_status;
215
  }
216
 
@@ -650,8 +657,10 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
650
  $value = $value['post_title'];
651
  }
652
 
653
- // Handle ACF fields.
654
- relevanssi_index_acf( $insert_data, $post->ID, $field, $value );
 
 
655
 
656
  // Flatten other arrays.
657
  if ( is_array( $value ) ) {
@@ -900,7 +909,7 @@ function relevanssi_index_doc( $index_post, $remove_first = false, $custom_field
900
  $content_tokens = relevanssi_tokenize( $contents, true, $min_word_length );
901
 
902
  if ( $debug ) {
903
- relevanssi_debug_echo( "\tContent, tokenized:\n" . implode( ' ', array_keys( $contents ) ) );
904
  }
905
 
906
  if ( count( $content_tokens ) > 0 ) {
@@ -1153,13 +1162,30 @@ function relevanssi_insert_edit( $post_id ) {
1153
  $post_status = get_post_status( $parent_id );
1154
  }
1155
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1156
  $index_statuses = apply_filters( 'relevanssi_valid_status', array( 'publish', 'private', 'draft', 'future', 'pending' ) );
1157
  if ( ! in_array( $post_status, $index_statuses, true ) ) {
1158
- // The post isn't supposed to be indexed anymore, remove it from index.
1159
- relevanssi_remove_doc( $post_id );
1160
- } else {
 
1161
  $bypass_global_post = true;
1162
  relevanssi_publish( $post_id, $bypass_global_post );
 
 
 
1163
  }
1164
 
1165
  relevanssi_update_doc_count();
@@ -1319,50 +1345,6 @@ function relevanssi_get_comments( $post_id ) {
1319
  return $comment_string;
1320
  }
1321
 
1322
- /**
1323
- * Indexes the human-readable value of "choice" options list from ACF.
1324
- *
1325
- * @author Droz Raphaël
1326
- *
1327
- * @param array $insert_data The insert data array.
1328
- * @param int $post_id The post ID.
1329
- * @param string $field_name Name of the field.
1330
- * @param string $field_value The field value.
1331
- */
1332
- function relevanssi_index_acf( &$insert_data, $post_id, $field_name, $field_value ) {
1333
- if ( ! is_admin() ) {
1334
- include_once ABSPATH . 'wp-admin/includes/plugin.php'; // Otherwise is_plugin_active() will cause a fatal error.
1335
- }
1336
- if ( ! function_exists( 'is_plugin_active' ) ) {
1337
- return;
1338
- }
1339
- if ( ! is_plugin_active( 'advanced-custom-fields/acf.php' ) && ! is_plugin_active( 'advanced-custom-fields-pro/acf.php' ) ) {
1340
- return;
1341
- }
1342
- if ( ! function_exists( 'get_field_object' ) ) {
1343
- return; // ACF is active, but not loaded.
1344
- }
1345
-
1346
- $field_object = get_field_object( $field_name, $post_id );
1347
- if ( ! isset( $field_object['choices'] ) ) {
1348
- return; // Not a "select" field.
1349
- }
1350
- if ( is_array( $field_value ) ) {
1351
- return; // Not handled (currently).
1352
- }
1353
- if ( ! isset( $field_object['choices'][ $field_value ] ) ) {
1354
- return; // Value does not exist.
1355
- }
1356
-
1357
- $value = $field_object['choices'][ $field_value ];
1358
- if ( $value ) {
1359
- if ( ! isset( $insert_data[ $value ]['customfield'] ) ) {
1360
- $insert_data[ $value ]['customfield'] = 0;
1361
- }
1362
- $insert_data[ $value ]['customfield']++;
1363
- }
1364
- }
1365
-
1366
  /**
1367
  * Truncates the Relevanssi index.
1368
  *
100
  global $wpdb, $relevanssi_variables;
101
  $relevanssi_table = $relevanssi_variables['relevanssi_table'];
102
 
103
+ /**
104
+ * Filters the WHERE restriction for indexing queries.
105
+ *
106
+ * This filter hook can be used to exclude posts from indexing as early as is
107
+ * possible.
108
+ *
109
+ * @since 4.0.9 / 2.1.5
110
+ *
111
+ * @param string The WHERE restriction.
112
+ *
113
+ * @return string The modified WHERE restriction.
114
+ */
115
  $restriction = apply_filters( 'relevanssi_indexing_restriction', $restriction );
116
 
117
  if ( ! $extend ) {
209
  $valid_status = array();
210
 
211
  if ( is_array( $valid_status_array ) && count( $valid_status_array ) > 0 ) {
 
212
  foreach ( $valid_status_array as $status ) {
213
+ $valid_status[] = "'" . esc_sql( $status ) . "'";
 
 
 
214
  }
215
  $valid_status = implode( ',', $valid_status );
216
  } else {
218
  // default values.
219
  $valid_status = "'publish', 'draft', 'private', 'pending', 'future'";
220
  }
 
221
  return $valid_status;
222
  }
223
 
657
  $value = $value['post_title'];
658
  }
659
 
660
+ if ( function_exists( 'relevanssi_index_acf' ) ) {
661
+ // Handle ACF fields. Only defined when ACF is active.
662
+ relevanssi_index_acf( $insert_data, $post->ID, $field, $value );
663
+ }
664
 
665
  // Flatten other arrays.
666
  if ( is_array( $value ) ) {
909
  $content_tokens = relevanssi_tokenize( $contents, true, $min_word_length );
910
 
911
  if ( $debug ) {
912
+ relevanssi_debug_echo( "\tContent, tokenized:\n" . implode( ' ', array_keys( $content_tokens ) ) );
913
  }
914
 
915
  if ( count( $content_tokens ) > 0 ) {
1162
  $post_status = get_post_status( $parent_id );
1163
  }
1164
 
1165
+ $index_this_post = true;
1166
+
1167
+ /* Documented in lib/indexing.php. */
1168
+ $restriction = apply_filters( 'relevanssi_indexing_restriction', '' );
1169
+ if ( ! empty( $restriction ) ) {
1170
+ // Check the indexing restriction filter: if the post passes the filter, this
1171
+ // should return the post ID.
1172
+ $is_unrestricted = $wpdb->get_var( "SELECT ID FROM $wpdb->posts AS post WHERE ID = $post_id $restriction" ); // WPCS: unprepared SQL ok.
1173
+ if ( ! $is_unrestricted ) {
1174
+ $index_this_post = false;
1175
+ }
1176
+ }
1177
+
1178
  $index_statuses = apply_filters( 'relevanssi_valid_status', array( 'publish', 'private', 'draft', 'future', 'pending' ) );
1179
  if ( ! in_array( $post_status, $index_statuses, true ) ) {
1180
+ $index_this_post = false;
1181
+ }
1182
+
1183
+ if ( $index_this_post ) {
1184
  $bypass_global_post = true;
1185
  relevanssi_publish( $post_id, $bypass_global_post );
1186
+ } else {
1187
+ // The post isn't supposed to be indexed anymore, remove it from index.
1188
+ relevanssi_remove_doc( $post_id );
1189
  }
1190
 
1191
  relevanssi_update_doc_count();
1345
  return $comment_string;
1346
  }
1347
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1348
  /**
1349
  * Truncates the Relevanssi index.
1350
  *
lib/init.php CHANGED
@@ -11,10 +11,10 @@
11
  // Setup.
12
  add_action( 'init', 'relevanssi_init' );
13
  add_filter( 'query_vars', 'relevanssi_query_vars' );
14
- add_action( 'admin_menu', 'relevanssi_menu' );
15
  add_filter( 'rest_api_init', 'relevanssi_rest_api_disable' );
16
  add_action( 'switch_blog', 'relevanssi_switch_blog', 1, 2 );
17
- add_action( 'admin_enqueue_scripts', 'relevanssi_add_admin_scripts' );
 
18
 
19
  // Taking over the search.
20
  add_filter( 'the_posts', 'relevanssi_query', 99, 2 );
@@ -45,12 +45,11 @@ add_action( 'relevanssi_trim_logs', 'relevanssi_trim_logs' );
45
 
46
  // Plugin and theme compatibility.
47
  add_filter( 'relevanssi_pre_excerpt_content', 'relevanssi_remove_page_builder_shortcodes', 9 );
48
- add_filter( 'relevanssi_search_ok', 'relevanssi_acf_relationship_fields' );
49
 
50
  // Permalink handling.
51
- add_filter( 'the_permalink', 'relevanssi_permalink' );
52
- add_filter( 'post_link', 'relevanssi_permalink' );
53
- add_filter( 'page_link', 'relevanssi_permalink' );
54
  add_filter( 'relevanssi_permalink', 'relevanssi_permalink' );
55
 
56
  global $relevanssi_variables;
@@ -129,6 +128,24 @@ function relevanssi_init() {
129
  if ( class_exists( 'WooCommerce' ) ) {
130
  require_once 'compatibility/woocommerce.php';
131
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
  /**
@@ -166,6 +183,7 @@ function relevanssi_menu() {
166
  $relevanssi_variables['file'],
167
  'relevanssi_search_stats'
168
  );
 
169
  add_action( 'load-' . $plugin_page, 'relevanssi_admin_help' );
170
  if ( function_exists( 'relevanssi_premium_plugin_page_actions' ) ) {
171
  // Loads contextual help and JS for Premium version.
11
  // Setup.
12
  add_action( 'init', 'relevanssi_init' );
13
  add_filter( 'query_vars', 'relevanssi_query_vars' );
 
14
  add_filter( 'rest_api_init', 'relevanssi_rest_api_disable' );
15
  add_action( 'switch_blog', 'relevanssi_switch_blog', 1, 2 );
16
+ add_action( 'admin_init', 'relevanssi_admin_init' );
17
+ add_action( 'admin_menu', 'relevanssi_menu' );
18
 
19
  // Taking over the search.
20
  add_filter( 'the_posts', 'relevanssi_query', 99, 2 );
45
 
46
  // Plugin and theme compatibility.
47
  add_filter( 'relevanssi_pre_excerpt_content', 'relevanssi_remove_page_builder_shortcodes', 9 );
 
48
 
49
  // Permalink handling.
50
+ add_filter( 'the_permalink', 'relevanssi_permalink', 10, 2 );
51
+ add_filter( 'post_link', 'relevanssi_permalink', 10, 2 );
52
+ add_filter( 'page_link', 'relevanssi_permalink', 10, 2 );
53
  add_filter( 'relevanssi_permalink', 'relevanssi_permalink' );
54
 
55
  global $relevanssi_variables;
128
  if ( class_exists( 'WooCommerce' ) ) {
129
  require_once 'compatibility/woocommerce.php';
130
  }
131
+
132
+ if ( class_exists( 'acf' ) ) {
133
+ require_once 'compatibility/acf.php';
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Iniatiates Relevanssi for admin.
139
+ *
140
+ * @global array $relevanssi_variables Global Relevanssi variables array.
141
+ */
142
+ function relevanssi_admin_init() {
143
+ global $relevanssi_variables;
144
+
145
+ require $relevanssi_variables['plugin_dir'] . 'lib/admin-ajax.php';
146
+
147
+ add_action( 'admin_enqueue_scripts', 'relevanssi_add_admin_scripts' );
148
+ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'relevanssi_action_links' );
149
  }
150
 
151
  /**
183
  $relevanssi_variables['file'],
184
  'relevanssi_search_stats'
185
  );
186
+ require_once 'contextual-help.php';
187
  add_action( 'load-' . $plugin_page, 'relevanssi_admin_help' );
188
  if ( function_exists( 'relevanssi_premium_plugin_page_actions' ) ) {
189
  // Loads contextual help and JS for Premium version.
lib/interface.php CHANGED
@@ -793,156 +793,6 @@ function relevanssi_options_form() {
793
  <?php
794
  }
795
 
796
- /**
797
- * Displays the contextual help menu.
798
- *
799
- * @global object $wpdb The WP database interface.
800
- */
801
- function relevanssi_admin_help() {
802
- global $wpdb;
803
-
804
- $screen = get_current_screen();
805
- $screen->add_help_tab( array(
806
- 'id' => 'relevanssi-searching',
807
- 'title' => __( 'Searching', 'relevanssi' ),
808
- 'content' => '<ul>' .
809
- // Translators: %1$s is 'orderby', %2$s is the Codex page URL.
810
- '<li>' . sprintf( __( "To adjust the post order, you can use the %1\$s query parameter. With %1\$s, you can use multiple layers of different sorting methods. See <a href='%2\$s'>WordPress Codex</a> for more details on using arrays for orderby.", 'relevanssi' ), '<code>orderby</code>', 'https://codex.wordpress.org/Class_Reference/WP_Query#Order_.26_Orderby_Parameters' ) . '</li>' .
811
- '<li>' . __( "Inside-word matching is disabled by default, because it increases garbage results that don't really match the search term. If you want to enable it, add the following function to your theme functions.php:", 'relevanssi' ) .
812
- '<pre>add_filter( \'relevanssi_fuzzy_query\', \'rlv_partial_inside_words\' );
813
- function rlv_partial_inside_words( $query ) {
814
- return "(term LIKE \'%#term#%\')";
815
- }</pre></li>' .
816
- // Translators: %s is 'relevanssi_throttle_limit'.
817
- '<li>' . sprintf( __( 'In order to adjust the throttle limit, you can use the %s filter hook.', 'relevanssi' ), '<code>pre_option_relevanssi_throttle_limit</code>' ) .
818
- '<pre>add_filter( \'pre_option_relevanssi_throttle_limit\', function( $limit ) { return 200; } );</pre>' .
819
- '<li>' . __( "It's not usually necessary to adjust the limit from 500, but in some cases performance gains can be achieved by setting a lower limit. We don't suggest going under 200, as low values will make the results worse.", 'relevanssi' ) . '</li>' .
820
- '</ul>',
821
- ));
822
- $screen->add_help_tab( array(
823
- 'id' => 'relevanssi-search-restrictions',
824
- 'title' => __( 'Restrictions', 'relevanssi' ),
825
- 'content' => '<ul>' .
826
- '<li>' . __( 'If you want the general search to target all posts, but have a single search form target only certain posts, you can add a hidden input variable to the search form. ', 'relevanssi' ) . '</li>' .
827
- '<li>' . __( 'For example in order to restrict the search to categories 10, 14 and 17, you could add this to the search form:', 'relevanssi' ) .
828
- '<pre>&lt;input type="hidden" name="cats" value="10,14,17" /&gt;</pre></li>' .
829
- '<li>' . __( 'To restrict the search to posts tagged with alfa AND beta, you could add this to the search form:', 'relevanssi' ) .
830
- '<pre>&lt;input type="hidden" name="tag" value="alfa+beta" /&gt;</pre></li>' .
831
- // Translators: %s is the link to the Codex page.
832
- '<li>' . sprintf( __( 'For all the possible options, see the Codex documentation for %s.', 'relevanssi' ), '<a href="https://codex.wordpress.org/Class_Reference/WP_Query">WP_Query</a>' ) . '</li>' .
833
- '</ul>',
834
- ));
835
- $screen->add_help_tab( array(
836
- 'id' => 'relevanssi-search-exclusions',
837
- 'title' => __( 'Exclusions', 'relevanssi' ),
838
- 'content' => '<ul>' .
839
- // Translators: %s is the link to the Codex page.
840
- '<li>' . sprintf( __( 'For more exclusion options, see the Codex documentation for %s. For example, to exclude tag ID 10, use', 'relevanssi' ), '<a href="https://codex.wordpress.org/Class_Reference/WP_Query">WP_Query</a>' ) .
841
- '<pre>&lt;input type="hidden" name="tag__not_in" value="10" /&gt;</pre></li>' .
842
- // Translators: %s is 'relevanssi_do_not_index'.
843
- '<li>' . sprintf( __( 'To exclude posts from the index and not just from the search, you can use the %s filter hook. This would not index posts that have a certain taxonomy term:', 'relevanssi' ), '<code>relevanssi_do_not_index</code>' ) .
844
- '<pre>add_filter( \'relevanssi_do_not_index\', \'rlv_index_filter\', 10, 2 );
845
- function rlv_index_filter( $block, $post_id ) {
846
- if ( has_term( \'jazz\', \'genre\', $post_id ) ) {
847
- $block = true;
848
- }
849
- return $block;
850
- }
851
- </pre></li>' .
852
- // Translators: %s is a link to the Relevanssi knowledge base.
853
- '<li>' . sprintf( __( "For more examples, see <a href='%s'>the related knowledge base posts</a>.", 'relevanssi' ), 'https://www.relevanssi.com/tag/relevanssi_do_not_index/' ) . '</li>' .
854
- '</ul>',
855
- ));
856
- $screen->add_help_tab( array(
857
- 'id' => 'relevanssi-logging',
858
- 'title' => __( 'Logs', 'relevanssi' ),
859
- 'content' => '<ul>' .
860
- // Translators: %s is 'relevanssi_user_searches_limit'.
861
- '<li>' . sprintf( __( 'By default, the User searches page shows 20 most common keywords. In order to see more, you can adjust the value with the %s filter hook, like this:', 'relevanssi' ), '<code>relevanssi_user_searches_limit</code>' ) .
862
- "<pre>add_filter( 'relevanssi_user_searches_limit', function() { return 50; } );</pre></li>" .
863
- // Translators: %s is the name of the database table.
864
- '<li>' . sprintf( __( 'The complete logs are stored in the %s database table, where you can access them if you need more information than what the User searches page provides.', 'relevanssi' ), '<code>' . $wpdb->prefix . 'relevanssi_log</code>' ) . '</li>' .
865
- '</ul>',
866
- ));
867
- $screen->add_help_tab( array(
868
- 'id' => 'relevanssi-excerpts',
869
- 'title' => __( 'Excerpts', 'relevanssi' ),
870
- 'content' => '<ul>' .
871
- '<li>' . __( 'Building custom excerpts can be slow. If you are not actually using the excerpts, make sure you disable the option.', 'relevanssi' ) . '</li>' .
872
- // Translators: %s is 'the_excerpt()'.
873
- '<li>' . sprintf( __( 'Custom snippets require that the search results template uses %s to print out the excerpts.', 'relevanssi' ), '<code>the_excerpt()</code>' ) . '</li>' .
874
- '<li>' . __( 'Generally, Relevanssi generates the excerpts from post content. If you want to include custom field content in the excerpt-building, this can be done with a simple setting from the excerpt settings.', 'relevanssi' ) . '</li>' .
875
- // Translators: %1$s is 'relevanssi_pre_excerpt_content', %2$s is 'relevanssi_excerpt_content'.
876
- '<li>' . sprintf( __( 'If you want more control over what content Relevanssi uses to create the excerpts, you can use the %1$s and %2$s filter hooks to adjust the content.', 'relevanssi' ), '<code>relevanssi_pre_excerpt_content</code>', '<code>relevanssi_excerpt_content</code>' ) . '</li>' .
877
- // Translators: %s is 'relevanssi_disable_shortcodes_excerpt'.
878
- '<li>' . sprintf( __( 'Some shortcode do not work well with Relevanssi excerpt-generation. Relevanssi disables some shortcodes automatically to prevent problems. This can be adjusted with the %s filter hook.', 'relevanssi' ), '<code>relevanssi_disable_shortcodes_excerpt</code>' ) . '</li>' .
879
- // Translators: %s is 'relevanssi_optimize_excerpts'.
880
- '<li>' . sprintf( __( "If you want Relevanssi to build excerpts faster and don't mind that they may be less than perfect in quality, add a filter that returns true on hook %s.", 'relevanssi' ), '<code>relevanssi_optimize_excerpts</code>' ) .
881
- "<pre>add_filter( 'relevanssi_optimize_excerpts', '__return_true' );</pre></li>" .
882
- '</ul>',
883
- ));
884
- $screen->add_help_tab( array(
885
- 'id' => 'relevanssi-highlights',
886
- 'title' => __( 'Highlights', 'relevanssi' ),
887
- 'content' => '<ul>' .
888
- '<li>' . __( "Title highlights don't appear automatically, because that led to problems with highlights appearing in wrong places and messing up navigation menus, for example.", 'relevanssi' ) . '</li>' .
889
- // Translators: %1$s is 'the_title()', %2$s is 'relevanssi_the_title()'.
890
- '<li>' . sprintf( __( 'In order to see title highlights from Relevanssi, replace %1$s in the search results template with %2$s. It does the same thing, but supports Relevanssi title highlights.', 'relevanssi' ), '<code>the_title()</code>', '<code>relevanssi_the_title()</code>' ) . '</li>' .
891
- '</ul>',
892
- ));
893
- $screen->add_help_tab( array(
894
- 'id' => 'relevanssi-punctuation',
895
- 'title' => __( 'Punctuation', 'relevanssi' ),
896
- 'content' => '<ul>' .
897
- '<li>' . __( 'Relevanssi removes punctuation. Some punctuation is removed, some replaced with spaces. Advanced indexing settings include some of the more common settings people want to change.', 'relevanssi' ) . '</li>' .
898
- // Translators: %1$s is 'relevanssi_punctuation_filter', %2$s is 'relevanssi_remove_punctuation'.
899
- '<li>' . sprintf( __( 'For more fine-tuned changes, you can use %1$s filter hook to adjust what is replaced with what, and %2$s filter hook to completely override the default punctuation control.', 'relevanssi' ), '<code>relevanssi_punctuation_filter</code>', '<code>relevanssi_remove_punctuation</code>' ) . '</li>' .
900
- // Translators: %s is the URL to the Knowledge Base entry.
901
- '<li>' . sprintf( __( "For more examples, see <a href='%s'>the related knowledge base posts</a>.", 'relevanssi' ), 'https://www.relevanssi.com/tag/relevanssi_remove_punct/' ) . '</li>' .
902
- '</ul>',
903
- ));
904
- $screen->add_help_tab( array(
905
- 'id' => 'relevanssi-helpful-shortcodes',
906
- 'title' => __( 'Helpful shortcodes', 'relevanssi' ),
907
- 'content' => '<ul>' .
908
- // Translators: %s is '[noindex]'.
909
- '<li>' . sprintf( __( "If you have content that you don't want indexed, you can wrap that content in a %s shortcode.", 'relevanssi' ), '<code>[noindex]</code>' ) . '</li>' .
910
- // Translators: %s is '[searchform]'.
911
- '<li>' . sprintf( __( 'If you need a search form on some page on your site, you can use the %s shortcode to print out a basic search form.', 'relevanssi' ), '<code>[searchform]</code>' ) . '</li>' .
912
- // Translators: %1$s is '[searchform post_types="page"]', %2$s is '[searchform cats="10,14,17"]'.
913
- '<li>' . sprintf( __( 'If you need to add query variables to the search form, the shortcode takes parameters, which are then printed out as hidden input fields. To get a search form with a post type restriction, you can use %1$s. To restrict the search to categories 10, 14 and 17, you can use %2$s and so on.', 'relevanssi' ), '<code>[searchform post_types="page"]</code>', '<code>[searchform cats="10,14,17"]</code>' ) . '</li>' .
914
- '</ul>',
915
- ));
916
- $screen->add_help_tab( array(
917
- 'id' => 'relevanssi-title-woocommerce',
918
- 'title' => __( 'WooCommerce', 'relevanssi' ),
919
- 'content' => '<ul>' .
920
- '<li>' . __( "If your SKUs include hyphens or other punctuation, do note that Relevanssi replaces most punctuation with spaces. That's going to cause issues with SKU searches.", 'relevanssi' ) . '</li>' .
921
- // Translators: %s is the Knowledge Base URL.
922
- '<li>' . sprintf( __( "For more details how to fix that issue, see <a href='%s'>WooCommerce tips in Relevanssi user manual</a>.", 'relevanssi' ), 'https://www.relevanssi.com/user-manual/woocommerce/' ) . '</li>' .
923
- '<li>' . __( "If you don't want to index products that are out of stock, excluded from the catalog or excluded from the search, there's a product visibility filtering method that is described in the user manual (see link above).", 'relevanssi' ) . '</li>' .
924
- '</ul>',
925
- ));
926
- $screen->add_help_tab( array(
927
- 'id' => 'relevanssi-exact-match',
928
- 'title' => __( 'Exact match bonus', 'relevanssi' ),
929
- 'content' => '<ul>' .
930
- // Translators: %s is the name of the filter hook.
931
- '<li>' . sprintf( __( 'To adjust the amount of the exact match bonus, you can use the %s filter hook. It works like this:', 'relevanssi' ), '<code>relevanssi_exact_match_bonus</code>' ) .
932
- "<pre>add_filter( 'relevanssi_exact_match_bonus', 'rlv_adjust_bonus' );
933
- function rlv_adjust_bonus( \$bonus ) {
934
- return array( 'title' => 10, 'content' => 5 );
935
- }</li>" .
936
- // Translators: %1$s is the title weight and %2$s is the content weight.
937
- '<li>' . sprintf( esc_html__( 'The default values are %1$s for titles and %2$s for content.', 'relevanssi' ), '<code>5</code>', '<code>2</code>' ) . '</ul>',
938
- ));
939
- $screen->set_help_sidebar(
940
- '<p><strong>' . __( 'For more information:', 'relevanssi' ) . '</strong></p>' .
941
- '<p><a href="https://www.relevanssi.com/knowledge-base/" target="_blank">' . __( 'Plugin knowledge base', 'relevanssi' ) . '</a></p>' .
942
- '<p><a href="https://wordpress.org/tags/relevanssi?forum_id=10" target="_blank">' . __( 'WordPress.org forum', 'relevanssi' ) . '</a></p>'
943
- );
944
- }
945
-
946
  /**
947
  * Adds admin scripts to Relevanssi pages.
948
  *
793
  <?php
794
  }
795
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
796
  /**
797
  * Adds admin scripts to Relevanssi pages.
798
  *
lib/log.php ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/log.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
+ * Adds the search query to the log.
13
+ *
14
+ * Logs the search query, trying to avoid bots.
15
+ *
16
+ * @global object $wpdb The WordPress database interface.
17
+ * @global array $relevanssi_variables The global Relevanssi variables, used for database table names.
18
+ *
19
+ * @param string $query The search query.
20
+ * @param int $hits The number of hits found.
21
+ */
22
+ function relevanssi_update_log( $query, $hits ) {
23
+ // Bot filter, by Justin_K.
24
+ // See: http://wordpress.org/support/topic/bot-logging-problem-w-tested-solution.
25
+ $user_agent = '';
26
+ if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
27
+ $user_agent = $_SERVER['HTTP_USER_AGENT'];
28
+ $bots = array( 'Google' => 'Mediapartners-Google' );
29
+
30
+ /**
31
+ * Filters the bots Relevanssi should block from logs.
32
+ *
33
+ * Lets you filter the bots that are blocked from Relevanssi logs.
34
+ *
35
+ * @param array $bots An array of bot user agents.
36
+ */
37
+ $bots = apply_filters( 'relevanssi_bots_to_not_log', $bots );
38
+ foreach ( $bots as $name => $lookfor ) {
39
+ if ( false !== stristr( $user_agent, $lookfor ) ) {
40
+ return;
41
+ }
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Filters the current user for logs.
47
+ *
48
+ * The current user is checked before logging a query to omit particular users.
49
+ * You can use this filter to filter out the user.
50
+ *
51
+ * @param object The current user object.
52
+ */
53
+ $user = apply_filters( 'relevanssi_log_get_user', wp_get_current_user() );
54
+ if ( 0 !== $user->ID && get_option( 'relevanssi_omit_from_logs' ) ) {
55
+ $omit = explode( ',', get_option( 'relevanssi_omit_from_logs' ) );
56
+ if ( in_array( strval( $user->ID ), $omit, true ) ) {
57
+ return;
58
+ }
59
+ if ( in_array( $user->user_login, $omit, true ) ) {
60
+ return;
61
+ }
62
+ }
63
+
64
+ $ip = '';
65
+ if ( 'on' === get_option( 'relevanssi_log_queries_with_ip' ) ) {
66
+ /**
67
+ * Filters the IP address of the searcher.
68
+ *
69
+ * Relevanssi may store the IP address of the searches in the logs. If the
70
+ * setting is enabled, this filter can be used to filter out the IP address
71
+ * before the log entry is made.
72
+ *
73
+ * Do note that storing the IP address may be illegal or get you in GDPR
74
+ * trouble.
75
+ *
76
+ * @param string $ip The IP address, from $_SERVER['REMOTE_ADDR'].
77
+ */
78
+ $ip = apply_filters( 'relevanssi_remote_addr', $_SERVER['REMOTE_ADDR'] );
79
+ }
80
+
81
+ /**
82
+ * Filters whether a query should be logged or not.
83
+ *
84
+ * This filter can used to determine whether a query should be logged or not.
85
+ *
86
+ * @param boolean $ok_to_log Can the query be logged.
87
+ * @param string $query The actual query string.
88
+ * @param int $hits The number of hits found.
89
+ * @param string $user_agent The user agent that made the search.
90
+ * @param string $ip The IP address the search came from (or empty).
91
+ */
92
+ $ok_to_log = apply_filters( 'relevanssi_ok_to_log', true, $query, $hits, $user_agent, $ip );
93
+ if ( $ok_to_log ) {
94
+ global $wpdb, $relevanssi_variables;
95
+
96
+ $wpdb->query(
97
+ $wpdb->prepare( 'INSERT INTO ' . $relevanssi_variables['log_table'] . ' (query, hits, user_id, ip, time) VALUES (%s, %d, %d, %s, NOW())',
98
+ $query, intval( $hits ), $user->ID, $ip )
99
+ ); // WPCS: unprepared SQL ok, Relevanssi database table name.
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Trims Relevanssi log table.
105
+ *
106
+ * Trims Relevanssi log table, using the day interval setting from 'relevanssi_trim_logs'.
107
+ *
108
+ * @global object $wpdb The WordPress database interface.
109
+ * @global array $relevanssi_variables The global Relevanssi variables, used for database table names.
110
+ */
111
+ function relevanssi_trim_logs() {
112
+ global $wpdb, $relevanssi_variables;
113
+ $interval = intval( get_option( 'relevanssi_trim_logs' ) );
114
+ $wpdb->query(
115
+ $wpdb->prepare( 'DELETE FROM ' . $relevanssi_variables['log_table'] . ' WHERE time < TIMESTAMP(DATE_SUB(NOW(), INTERVAL %d DAY))',
116
+ $interval )
117
+ ); // WPCS: unprepared SQL ok, Relevanssi database table name.
118
+ }
119
+
120
+ /**
121
+ * Generates the user export data.
122
+ *
123
+ * @since 4.0.10
124
+ *
125
+ * @param int $user_id The user ID to export.
126
+ * @param int $page Paging to avoid time outs.
127
+ *
128
+ * @return array Two-item array: 'done' is a Boolean that tells if the exporter is
129
+ * done, 'data' contains the actual data.
130
+ */
131
+ function relevanssi_export_log_data( $user_id, $page ) {
132
+ global $wpdb, $relevanssi_variables;
133
+
134
+ $page = (int) $page;
135
+ if ( $page < 1 ) {
136
+ $page = 1;
137
+ }
138
+ $limit = 500;
139
+ $offset = $limit * ( $page - 1 );
140
+ $log_data = $wpdb->get_results(
141
+ $wpdb->prepare( 'SELECT * FROM ' . $relevanssi_variables['log_table'] . ' WHERE user_id = %d LIMIT %d OFFSET %d',
142
+ $user_id, $limit, $offset )
143
+ ); // WPCS: unprepared SQL ok, Relevanssi database table name.
144
+
145
+ $export_items = array();
146
+
147
+ foreach ( $log_data as $row ) {
148
+ $time = $row->time;
149
+ $query = $row->query;
150
+ $id = $row->id;
151
+ $ip = $row->ip;
152
+ $hits = $row->hits;
153
+
154
+ $item_id = "relevanssi_logged_search-{$id}";
155
+ $group_id = 'relevanssi_logged_searches';
156
+ $group_label = __( 'Logged seaches', 'relevanssi' );
157
+ $data = array(
158
+ array(
159
+ 'name' => __( 'Time', 'relevanssi' ),
160
+ 'value' => $time,
161
+ ),
162
+ array(
163
+ 'name' => __( 'Query', 'relevanssi' ),
164
+ 'value' => $query,
165
+ ),
166
+ array(
167
+ 'name' => __( 'Hits found', 'relevanssi' ),
168
+ 'value' => $hits,
169
+ ),
170
+ array(
171
+ 'name' => __( 'IP address', 'relevanssi' ),
172
+ 'value' => $ip,
173
+ ),
174
+ );
175
+
176
+ $export_items[] = array(
177
+ 'group_id' => $group_id,
178
+ 'group_label' => $group_label,
179
+ 'item_id' => $item_id,
180
+ 'data' => $data,
181
+ );
182
+ }
183
+
184
+ $done = false;
185
+ if ( count( $log_data ) < $limit ) {
186
+ $done = true;
187
+ }
188
+
189
+ return array(
190
+ 'done' => $done,
191
+ 'data' => $export_items,
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Erases the user log data.
197
+ *
198
+ * @since 4.0.10
199
+ *
200
+ * @param int $user_id The user ID to erase.
201
+ * @param int $page Paging to avoid time outs.
202
+ *
203
+ * @return array Four-item array: 'items_removed' is a Boolean that tells if
204
+ * something was removed, 'done' is a Boolean that tells if the eraser is done,
205
+ * 'items_retained' is always false, 'messages' is always an empty array.
206
+ */
207
+ function relevanssi_erase_log_data( $user_id, $page ) {
208
+ global $wpdb, $relevanssi_variables;
209
+
210
+ $page = (int) $page;
211
+ if ( $page < 1 ) {
212
+ $page = 1;
213
+ }
214
+ $limit = 500;
215
+ $rows_removed = $wpdb->query(
216
+ $wpdb->prepare( 'DELETE FROM ' . $relevanssi_variables['log_table'] . ' WHERE user_id = %d LIMIT %d',
217
+ $user_id, $limit )
218
+ ); // WPCS: unprepared SQL ok, Relevanssi database table name.
219
+
220
+ $done = false;
221
+ if ( $rows_removed < $limit ) {
222
+ $done = true;
223
+ }
224
+ $items_removed = false;
225
+ if ( $rows_removed > 0 ) {
226
+ $items_removed = true;
227
+ }
228
+
229
+ return array(
230
+ 'items_removed' => $items_removed,
231
+ 'items_retained' => false,
232
+ 'messages' => array(),
233
+ 'done' => $done,
234
+ );
235
+ }
lib/privacy.php ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/privacy.php
4
+ *
5
+ * Privacy policy features.
6
+ *
7
+ * @since 4.0.10
8
+ *
9
+ * @package Relevanssi
10
+ * @author Mikko Saari
11
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
12
+ * @see https://www.relevanssi.com/
13
+ */
14
+
15
+ add_action( 'admin_init', 'relevanssi_register_privacy_policy' );
16
+ add_filter( 'wp_privacy_personal_data_exporters', 'relevanssi_register_exporter', 10 );
17
+ add_filter( 'wp_privacy_personal_data_erasers', 'relevanssi_register_eraser', 10 );
18
+
19
+ /**
20
+ * Registers the Relevanssi privacy policy information.
21
+ *
22
+ * @since 4.0.10
23
+ */
24
+ function relevanssi_register_privacy_policy() {
25
+ $name = 'Relevanssi';
26
+ if ( RELEVANSSI_PREMIUM ) {
27
+ $name .= ' Premium';
28
+ }
29
+ if ( 'on' === get_option( 'relevanssi_log_queries' ) ) {
30
+ $content = '<h2>' . __( 'What personal data we collect and why we collect it' ) . '</h2>';
31
+ if ( 'on' === get_option( 'relevanssi_log_queries_with_ip' ) ) {
32
+ $content .= '<h3>' . __( 'IP address for searches', 'relevanssi' ) . '</h3>';
33
+ $content .= '<p>' . __( 'All searches performed using the internal site search are logged in the database, including the following information: the search query, the number of hits found, user ID for users who are logged in, date and time and the IP address. The IP address is stored for security and auditing purposes.', 'relevanssi' ) . '</p>';
34
+ } else {
35
+ $content .= '<p>' . __( 'All searches performed using the internal site search are logged in the database, including the following information: the search query, the number of hits found, user ID for users who are logged in and date and time.', 'relevanssi' ) . '</p>';
36
+ }
37
+ $interval = intval( get_option( 'relevanssi_trim_logs' ) );
38
+ $content .= '<h2>' . __( 'How long we retain your data' ) . '</h2>';
39
+ if ( $interval > 0 ) {
40
+ // Translators: %d is the number of days.
41
+ $content .= '<p>' . sprintf( __( 'The search logs are stored for %d days before they are automatically removed.', 'relevanssi' ), $interval ) . '</p>';
42
+ } else {
43
+ $content .= '<p>' . sprintf( __( 'The search logs are stored indefinitely.', 'relevanssi' ), $interval ) . '</p>';
44
+ }
45
+ }
46
+ wp_add_privacy_policy_content( $name, $content );
47
+ }
48
+
49
+ /**
50
+ * Registers the Relevanssi data exporter.
51
+ *
52
+ * @since 4.0.10
53
+ *
54
+ * @param array $exporters The exporters array.
55
+ *
56
+ * @return array The exporters array, with Relevanssi added.
57
+ */
58
+ function relevanssi_register_exporter( $exporters ) {
59
+ $exporters['relevanssi'] = array(
60
+ 'exporter_friendly_name' => __( 'Relevanssi Search Logs' ),
61
+ 'callback' => 'relevanssi_privacy_exporter',
62
+ );
63
+ return $exporters;
64
+ }
65
+
66
+ /**
67
+ * Registers the Relevanssi data eraser.
68
+ *
69
+ * @since 4.0.10
70
+ *
71
+ * @param array $erasers The erasers array.
72
+ *
73
+ * @return array The erasers array, with Relevanssi added.
74
+ */
75
+ function relevanssi_register_eraser( $erasers ) {
76
+ $erasers['relevanssi'] = array(
77
+ 'eraser_friendly_name' => __( 'Relevanssi Search Logs' ),
78
+ 'callback' => 'relevanssi_privacy_eraser',
79
+ );
80
+ return $erasers;
81
+ }
82
+
83
+ /**
84
+ * Exports the log entries based on user email.
85
+ *
86
+ * @since 4.0.10
87
+ *
88
+ * @param string $email_address The user email address.
89
+ * @param int $page The page number, default 1.
90
+ *
91
+ * @return array Two-item array: 'done' is a Boolean that tells if the exporter is
92
+ * done, 'data' contains the actual data.
93
+ */
94
+ function relevanssi_privacy_exporter( $email_address, $page = 1 ) {
95
+ $user = get_user_by( 'email', $email_address );
96
+ if ( ! $user ) {
97
+ // No user found.
98
+ return array(
99
+ 'done' => true,
100
+ 'data' => array(),
101
+ );
102
+ } else {
103
+ $result = relevanssi_export_log_data( $user->ID, $page );
104
+ return array(
105
+ 'done' => $result['done'],
106
+ 'data' => $result['data'],
107
+ );
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Erases the log entries based on user email.
113
+ *
114
+ * @since 4.0.10
115
+ *
116
+ * @param string $email_address The user email address.
117
+ * @param int $page The page number, default 1.
118
+ *
119
+ * @return array Four-item array: 'items_removed' is a Boolean that tells if
120
+ * something was removed, 'done' is a Boolean that tells if the eraser is done,
121
+ * 'items_retained' is always false, 'messages' is always an empty array.
122
+ */
123
+ function relevanssi_privacy_eraser( $email_address, $page = 1 ) {
124
+ $user = get_user_by( 'email', $email_address );
125
+ if ( ! $user ) {
126
+ // No user found.
127
+ return array(
128
+ 'items_removed' => false,
129
+ 'done' => true,
130
+ 'items_retained' => false,
131
+ 'messages' => array(),
132
+ );
133
+ } else {
134
+ $result = relevanssi_erase_log_data( $user->ID, $page );
135
+ return array(
136
+ 'items_removed' => $result['items_removed'],
137
+ 'done' => $result['done'],
138
+ 'items_retained' => false,
139
+ 'messages' => array(),
140
+ );
141
+ }
142
+ }
lib/search.php CHANGED
@@ -1674,7 +1674,7 @@ function relevanssi_do_query( &$query ) {
1674
  }
1675
 
1676
  $make_excerpts = get_option( 'relevanssi_excerpts' );
1677
- if ( $query->is_admin ) {
1678
  $make_excerpts = false;
1679
  }
1680
 
1674
  }
1675
 
1676
  $make_excerpts = get_option( 'relevanssi_excerpts' );
1677
+ if ( $query->is_admin && ! defined( 'DOING_AJAX' ) ) {
1678
  $make_excerpts = false;
1679
  }
1680
 
lib/sorting.php ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/sorting.php
4
+ *
5
+ * Sorting functions.
6
+ *
7
+ * @package Relevanssi
8
+ * @author Mikko Saari
9
+ * @license https://wordpress.org/about/gpl/ GNU General Public License
10
+ * @see https://www.relevanssi.com/
11
+ */
12
+
13
+ /**
14
+ * Gets the next key-direction pair from the orderby array.
15
+ *
16
+ * Fetches a key-direction pair from the orderby array. Converts key names to match
17
+ * the post object parameters when necessary and seeds the random generator, if
18
+ * required.
19
+ *
20
+ * @param array $orderby An array of key-direction pairs.
21
+ *
22
+ * @return array A set of 'key', 'dir' for direction and 'compare' for proper
23
+ * comparison method.
24
+ */
25
+ function relevanssi_get_next_key( &$orderby ) {
26
+ if ( ! is_array( $orderby ) || count( $orderby ) < 1 ) {
27
+ // Nothing to see here.
28
+ return array(
29
+ 'key' => null,
30
+ 'dir' => null,
31
+ 'compare' => null,
32
+ );
33
+ }
34
+
35
+ list( $key ) = array_keys( $orderby );
36
+ $dir = $orderby[ $key ];
37
+ unset( $orderby[ $key ] );
38
+
39
+ $key = strtolower( $key );
40
+
41
+ if ( 'rand' === strtolower( $dir ) ) {
42
+ $key = 'rand';
43
+ }
44
+
45
+ // Correcting the key for couple of shorthand cases.
46
+ switch ( $key ) {
47
+ case 'title':
48
+ $key = 'post_title';
49
+ break;
50
+ case 'date':
51
+ $key = 'post_date';
52
+ break;
53
+ case 'modified':
54
+ $key = 'post_modified';
55
+ break;
56
+ case 'parent':
57
+ $key = 'post_parent';
58
+ break;
59
+ case 'type':
60
+ $key = 'post_type';
61
+ break;
62
+ case 'name':
63
+ $key = 'post_name';
64
+ break;
65
+ case 'author':
66
+ $key = 'post_author';
67
+ break;
68
+ case 'relevance':
69
+ $key = 'relevance_score';
70
+ break;
71
+ }
72
+
73
+ $numeric_keys = array( 'menu_order', 'ID', 'post_parent', 'post_author', 'comment_count', 'relevance_score' );
74
+ $date_keys = array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt' );
75
+
76
+ $compare = 'string';
77
+ if ( in_array( $key, $numeric_keys, true ) ) {
78
+ $compare = 'number';
79
+ } elseif ( in_array( $key, $date_keys, true ) ) {
80
+ $compare = 'date';
81
+ }
82
+
83
+ if ( 'rand(' === substr( $key, 0, 5 ) ) {
84
+ $parts = explode( '(', $key );
85
+ $dir = intval( trim( str_replace( ')', '', $parts[1] ) ) );
86
+ $key = 'rand';
87
+ }
88
+ if ( 'rand' === $key ) {
89
+ if ( is_numeric( $dir ) ) {
90
+ // A specific random seed is requested.
91
+ mt_srand( $dir );
92
+ }
93
+ } else {
94
+ $dir = strtolower( $dir );
95
+ if ( 'asc' !== $dir ) {
96
+ $dir = 'desc';
97
+ }
98
+ }
99
+
100
+ $values = array(
101
+ 'key' => $key,
102
+ 'dir' => $dir,
103
+ 'compare' => $compare,
104
+ );
105
+ return $values;
106
+ }
107
+
108
+ /**
109
+ * Gets the values for comparing items for given key.
110
+ *
111
+ * Fetches the key values for the item pair. If random order is required, this
112
+ * function will randomize the order.
113
+ *
114
+ * @global object $wp_query The global WP_Query object.
115
+ *
116
+ * @param string $key The key used.
117
+ * @param object $item_1 The first post object to compare.
118
+ * @param object $item_2 The second post object to compare.
119
+ *
120
+ * @return array Array with the key values: 'key1' and 'key2', respectively.
121
+ */
122
+ function relevanssi_get_compare_values( $key, $item_1, $item_2 ) {
123
+ if ( 'rand' === $key ) {
124
+ do {
125
+ $key1 = rand();
126
+ $key2 = rand();
127
+ } while ( $key1 === $key2 );
128
+ $keys = array(
129
+ 'key1' => $key1,
130
+ 'key2' => $key2,
131
+ );
132
+ return $keys;
133
+ }
134
+
135
+ $key1 = '';
136
+ $key2 = '';
137
+
138
+ if ( 'meta_value' === $key || 'meta_value_num' === $key ) {
139
+ global $wp_query;
140
+ // Get the name of the field from the global WP_Query.
141
+ $key = $wp_query->query_vars['meta_key'];
142
+ if ( ! isset( $key ) ) {
143
+ // The key is not set.
144
+ return array( '', '' );
145
+ }
146
+
147
+ $key1 = get_post_meta( $item_1->ID, $key, true );
148
+ if ( empty( $key1 ) ) {
149
+ /**
150
+ * Adds in a missing sorting value.
151
+ *
152
+ * In some cases the sorting method may not have values for all posts
153
+ * (for example when sorting by 'menu_order'). If you still want to use
154
+ * a sorting method like this, you can use this function to fill in a
155
+ * value (in the case of 'menu_order', for example, one could use
156
+ * PHP_INT_MAX.)
157
+ *
158
+ * @param string $key1 The value to filter.
159
+ * @param string $key The name of the key.
160
+ */
161
+ $key1 = apply_filters( 'relevanssi_missing_sort_key', $key1, $key );
162
+ }
163
+
164
+ $key2 = get_post_meta( $item_2->ID, $key, true );
165
+ if ( empty( $key2 ) ) {
166
+ /**
167
+ * Documented in lib/common.php.
168
+ */
169
+ $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
170
+ }
171
+ } else {
172
+ if ( isset( $item_1->$key ) ) {
173
+ $key1 = relevanssi_strtolower( $item_1->$key );
174
+ } else {
175
+ /**
176
+ * Documented in lib/common.php.
177
+ */
178
+ $key1 = apply_filters( 'relevanssi_missing_sort_key', $key1, $key );
179
+ }
180
+ if ( isset( $item_2->$key ) ) {
181
+ $key2 = relevanssi_strtolower( $item_2->$key );
182
+ } else {
183
+ /**
184
+ * Documented in lib/common.php.
185
+ */
186
+ $key2 = apply_filters( 'relevanssi_missing_sort_key', $key2, $key );
187
+ }
188
+ }
189
+
190
+ $keys = array(
191
+ 'key1' => $key1,
192
+ 'key2' => $key2,
193
+ );
194
+ return $keys;
195
+ }
196
+
197
+ /**
198
+ * Compares two values.
199
+ *
200
+ * Compares two sorting keys using date based comparison, string comparison or
201
+ * numeric comparison.
202
+ *
203
+ * @param string $key1 The first key.
204
+ * @param string $key2 The second key.
205
+ * @param string $compare The comparison method; possible values are 'date' for
206
+ * date comparisons and 'string' for string comparison, everything else is
207
+ * considered a numeric comparison.
208
+ *
209
+ * @return int $val Returns < 0 if key1 is less than key2; > 0 if key1 is greater
210
+ * than key2, and 0 if they are equal.
211
+ */
212
+ function relevanssi_compare_values( $key1, $key2, $compare ) {
213
+ $val = 0;
214
+ if ( 'date' === $compare ) {
215
+ if ( strtotime( $key1 ) > strtotime( $key2 ) ) {
216
+ $val = 1;
217
+ } elseif ( strtotime( $key1 ) < strtotime( $key2 ) ) {
218
+ $val = -1;
219
+ }
220
+ } elseif ( 'string' === $compare ) {
221
+ $val = relevanssi_mb_strcasecmp( $key1, $key2 );
222
+ } else {
223
+ if ( $key1 > $key2 ) {
224
+ $val = 1;
225
+ } elseif ( $key1 < $key2 ) {
226
+ $val = -1;
227
+ }
228
+ }
229
+ return $val;
230
+ }
231
+
232
+ /**
233
+ * Compares values using multiple levels of sorting keys.
234
+ *
235
+ * Comparison function for usort() using multiple levels of sorting methods. If one
236
+ * level produces a tie, the sort will get a next level of sorting methods.
237
+ *
238
+ * @global array $relevanssi_keys An array of sorting keys by level.
239
+ * @global array $relevanssi_dirs An array of sorting directions by level.
240
+ * @global array $relevanssi_compares An array of comparison methods by level.
241
+ *
242
+ * @param object $a A post object.
243
+ * @param object $b A post object.
244
+ *
245
+ * @return int $val Returns < 0 if a is less than b; > 0 if a is greater
246
+ * than b, and 0 if they are equal.
247
+ */
248
+ function relevanssi_cmp_function( $a, $b ) {
249
+ global $relevanssi_keys, $relevanssi_dirs, $relevanssi_compares;
250
+
251
+ $level = -1;
252
+ $val = 0;
253
+
254
+ while ( 0 === $val ) {
255
+ $level++;
256
+ if ( ! isset( $relevanssi_keys[ $level ] ) ) {
257
+ // No more levels; we've hit the bedrock.
258
+ $level--;
259
+ break;
260
+ }
261
+ $compare = $relevanssi_compares[ $level ];
262
+ $compare_values = relevanssi_get_compare_values( $relevanssi_keys[ $level ], $a, $b );
263
+ $val = relevanssi_compare_values( $compare_values['key1'], $compare_values['key2'], $compare );
264
+ }
265
+
266
+ if ( 'desc' === $relevanssi_dirs[ $level ] ) {
267
+ $val = $val * -1;
268
+ }
269
+
270
+ return $val;
271
+ }
272
+
273
+ /**
274
+ * Sorts post objects.
275
+ *
276
+ * Sorts post objects using multiple levels of sorting methods. This function was
277
+ * originally written by Matthew Hood and published in the PHP manual comments.
278
+ * The actual sorting is handled by relevanssi_cmp_function().
279
+ *
280
+ * @global array $relevanssi_keys An array of sorting keys by level.
281
+ * @global array $relevanssi_dirs An array of sorting directions by level.
282
+ * @global array $relevanssi_compares An array of comparison methods by level.
283
+ *
284
+ * @param array $data The posts to sort are in $data[0], used as a reference.
285
+ * @param array $orderby The array of orderby rules with directions.
286
+ */
287
+ function relevanssi_object_sort( &$data, $orderby ) {
288
+ global $relevanssi_keys, $relevanssi_dirs, $relevanssi_compares;
289
+
290
+ $relevanssi_keys = array();
291
+ $relevanssi_dirs = array();
292
+ $relevanssi_compares = array();
293
+
294
+ do {
295
+ $values = relevanssi_get_next_key( $orderby );
296
+ if ( ! empty( $values['key'] ) ) {
297
+ $relevanssi_keys[] = $values['key'];
298
+ $relevanssi_dirs[] = $values['dir'];
299
+ $relevanssi_compares[] = $values['compare'];
300
+ }
301
+ } while ( ! empty( $values['key'] ) );
302
+
303
+ $primary_key = $relevanssi_keys[0];
304
+ if ( 'rand' !== $primary_key && ! isset( $data[0]->$primary_key ) ) {
305
+ // Trying to sort by a non-existent key.
306
+ return;
307
+ }
308
+
309
+ usort( $data, 'relevanssi_cmp_function' );
310
+ }
311
+
312
+ /**
313
+ * Sorts strings by length.
314
+ *
315
+ * A sorting function that sorts strings by length. Uses relevanssi_strlen() to
316
+ * count the string length.
317
+ *
318
+ * @param string $a String A.
319
+ * @param string $b String B.
320
+ *
321
+ * @return int Negative value, if string A is longer; zero, if strings are equally
322
+ * long; positive, if string B is longer.
323
+ */
324
+ function relevanssi_strlen_sort( $a, $b ) {
325
+ return relevanssi_strlen( $b ) - relevanssi_strlen( $a );
326
+ }
lib/tabs/indexing-tab.php CHANGED
@@ -170,7 +170,7 @@ function relevanssi_indexing_tab() {
170
  $public_types = array_merge( $pt_1, $pt_2 );
171
  $post_types = get_post_types();
172
  foreach ( $post_types as $type ) {
173
- if ( in_array( $type, array( 'nav_menu_item', 'revision' ), true ) ) {
174
  continue;
175
  }
176
  $checked = '';
170
  $public_types = array_merge( $pt_1, $pt_2 );
171
  $post_types = get_post_types();
172
  foreach ( $post_types as $type ) {
173
+ if ( in_array( $type, array( 'nav_menu_item', 'revision', 'acf-field', 'acf-field-group', 'oembed_cache', 'customize_changeset', 'custom_css' ), true ) ) {
174
  continue;
175
  }
176
  $checked = '';
lib/tabs/overview-tab.php CHANGED
@@ -64,6 +64,13 @@ function relevanssi_overview_tab() {
64
  <p><?php esc_html_e( "Relevanssi doesn't have a separate search widget. Instead, Relevanssi uses the default search widget. Any standard search form will do!", 'relevanssi' ); ?></p>
65
  </td>
66
  </tr>
 
 
 
 
 
 
 
67
  <tr>
68
  <th scope="row"><?php esc_html_e( 'For more information', 'relevanssi' ); ?></th>
69
  <td>
64
  <p><?php esc_html_e( "Relevanssi doesn't have a separate search widget. Instead, Relevanssi uses the default search widget. Any standard search form will do!", 'relevanssi' ); ?></p>
65
  </td>
66
  </tr>
67
+ <tr>
68
+ <th scope="row"><?php esc_html_e( 'Privacy and GDPR compliance', 'relevanssi' ); ?></th>
69
+ <td>
70
+ <?php // Translators: %1$s opens the link, %2$s closes the link. ?>
71
+ <p><?php printf( esc_html__( '%1$sGDPR Compliance at Relevanssi knowledge base%2$s explains how using Relevanssi affects the GDPR compliance and the privacy policies of your site. Relevanssi also supports the %3$sprivacy policy tool%2$s and the WordPress user data export and erase tools.', 'relevanssi' ), "<a href='https://www.relevanssi.com/knowledge-base/gdpr-compliance/'>", '</a>', "<a href='privacy.php'>" ); ?></p>
72
+ </td>
73
+ </tr>
74
  <tr>
75
  <th scope="row"><?php esc_html_e( 'For more information', 'relevanssi' ); ?></th>
76
  <td>
lib/tabs/synonyms-tab.php CHANGED
@@ -34,7 +34,7 @@ function relevanssi_synonyms_tab() {
34
 
35
  <p class="description"><?php esc_html_e( "Do not go overboard, though, as too many synonyms can make the search confusing: users understand if a search query doesn't match everything, but they get confused if the searches match to unexpected things.", 'relevanssi' ); ?></p>
36
  <br />
37
- <textarea name='relevanssi_synonyms' id='relevanssi_synonyms' rows='9' cols='60'><?php echo esc_textarea( htmlspecialchars( $synonyms ) ); ?></textarea>
38
 
39
  <p class="description"><?php _e( 'The format here is <code>key = value</code>. If you add <code>dog = hound</code> to the list of synonyms, searches for <code>dog</code> automatically become a search for <code>dog hound</code> and will thus match to posts that include either <code>dog</code> or <code>hound</code>. This only works in OR searches: in AND searches the synonyms only restrict the search, as now the search only finds posts that contain <strong>both</strong> <code>dog</code> and <code>hound</code>.', 'relevanssi' ); // WPCS: XSS ok. ?></p>
40
 
34
 
35
  <p class="description"><?php esc_html_e( "Do not go overboard, though, as too many synonyms can make the search confusing: users understand if a search query doesn't match everything, but they get confused if the searches match to unexpected things.", 'relevanssi' ); ?></p>
36
  <br />
37
+ <textarea name='relevanssi_synonyms' id='relevanssi_synonyms' rows='9' cols='60'><?php echo esc_textarea( $synonyms ); ?></textarea>
38
 
39
  <p class="description"><?php _e( 'The format here is <code>key = value</code>. If you add <code>dog = hound</code> to the list of synonyms, searches for <code>dog</code> automatically become a search for <code>dog hound</code> and will thus match to posts that include either <code>dog</code> or <code>hound</code>. This only works in OR searches: in AND searches the synonyms only restrict the search, as now the search only finds posts that contain <strong>both</strong> <code>dog</code> and <code>hound</code>.', 'relevanssi' ); // WPCS: XSS ok. ?></p>
40
 
readme.txt CHANGED
@@ -5,7 +5,7 @@ Tags: search, relevance, better search
5
  Requires at least: 4.0
6
  Tested up to: 5.0
7
  Requires PHP: 5.6
8
- Stable tag: 4.0.9
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -130,9 +130,18 @@ Each document database is full of useless words. All the little words that appea
130
 
131
  == Changelog ==
132
 
 
 
 
 
 
 
 
 
133
  = 4.0.9 =
134
  * Fixes broken tag and category indexing and searching. If you use tags and categories, rebuild the index after updating.
135
- * Fixes phrase highlighting in documents.
 
136
  * New filter: `relevanssi_indexing_restriction` allows filtering posts before indexing.
137
  * New WooCommerce product visibility filtering tool makes WooCommerce product indexing faster.
138
  * MemberPress post controls were loose and showed drafts to searchers. That is now fixed.
@@ -159,17 +168,11 @@ Each document database is full of useless words. All the little words that appea
159
  * User searches page reset buttons fixed.
160
  * WPML language filter fix.
161
 
162
- = 4.0.5 =
163
- * Relevanssi code has been reviewed and modified to follow WordPress coding standards. As a result, there have been minor improvements all around the code to make things more robust and secure.
164
- * Custom field detail is no longer serialized. It's now JSON. If you use custom field detail, rebuild the index and change your code to use json_decode() instead of unserialize().
165
- * `relevanssi_the_tags()` and `relevanssi_get_the_tags()` now have different set of parameters, more in line with `the_tags()` and `get_the_tags()`.
166
- * Taxonomy indexing settings were emptied out if you saved another options tab. That is now fixed.
167
- * Improvements to WPML support; WPML is now less likely to be confused in multisite searches.
168
- * Updated filter: `relevanssi_search_ok` now gets the WP_Query object as a parameter, which is helpful if you're not using the global $wp_query.
169
- * ACF Flexible Content field indexing didn't work properly, possibly due to a change in ACF. That should now work better.
170
-
171
  == Upgrade notice ==
172
 
 
 
 
173
  = 4.0.9 =
174
  * Fixes broken tag and category searching and indexing. Reindex after the update!
175
 
@@ -181,6 +184,3 @@ Each document database is full of useless words. All the little words that appea
181
 
182
  = 4.0.6 =
183
  * Indexing bugs fixed and WPML support corrected.
184
-
185
- = 4.0.5 =
186
- * Codebase review, lots of small improvements everywhere.
5
  Requires at least: 4.0
6
  Tested up to: 5.0
7
  Requires PHP: 5.6
8
+ Stable tag: 4.0.10
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
130
 
131
  == Changelog ==
132
 
133
+ = 4.0.10 =
134
+ * Privacy: If you log search queries, Relevanssi will suggest some additional content to your privacy policy page.
135
+ * Privacy: Relevanssi now supports the new Privacy Policy and Personal Data tools in WordPress 4.9.6.
136
+ * Saving synonyms with quotes worked, but the synonyms showed up wrong.
137
+ * Relevanssi could in some situations override navigation menu links with links to the user profiles or taxonomy terms found in the search. This update fixes that behaviour.
138
+ * Random order works again; using orderby `rand` didn't work properly. The `rand(seed)` format is also supported now.
139
+ * Fixed quotes and apostrophes in Did you mean suggestions.
140
+
141
  = 4.0.9 =
142
  * Fixes broken tag and category indexing and searching. If you use tags and categories, rebuild the index after updating.
143
+ * Phrases were not highlighted correctly on documents. This is now fixed.
144
+ * Shortcode fix: 'wp_show_posts' shouldn't cause problems anymore.
145
  * New filter: `relevanssi_indexing_restriction` allows filtering posts before indexing.
146
  * New WooCommerce product visibility filtering tool makes WooCommerce product indexing faster.
147
  * MemberPress post controls were loose and showed drafts to searchers. That is now fixed.
168
  * User searches page reset buttons fixed.
169
  * WPML language filter fix.
170
 
 
 
 
 
 
 
 
 
 
171
  == Upgrade notice ==
172
 
173
+ = 4.0.10 =
174
+ * Privacy update, with some bug fixes.
175
+
176
  = 4.0.9 =
177
  * Fixes broken tag and category searching and indexing. Reindex after the update!
178
 
184
 
185
  = 4.0.6 =
186
  * Indexing bugs fixed and WPML support corrected.
 
 
 
relevanssi.php CHANGED
@@ -13,7 +13,7 @@
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
- * Version: 4.0.9
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
@@ -39,8 +39,6 @@
39
 
40
  define( 'RELEVANSSI_PREMIUM', false );
41
 
42
- add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), 'relevanssi_action_links' );
43
-
44
  global $relevanssi_variables;
45
  global $wpdb;
46
 
@@ -58,13 +56,16 @@ $relevanssi_variables['database_version'] = 5;
58
  $relevanssi_variables['file'] = __FILE__;
59
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
60
 
61
- require_once 'lib/install.php';
 
 
62
  require_once 'lib/init.php';
 
63
  require_once 'lib/interface.php';
64
- require_once 'lib/indexing.php';
65
- require_once 'lib/stopwords.php';
66
  require_once 'lib/search.php';
67
- require_once 'lib/excerpts-highlights.php';
68
  require_once 'lib/shortcodes.php';
69
- require_once 'lib/common.php';
70
- require_once 'lib/admin-ajax.php';
 
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
+ * Version: 4.0.10
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
39
 
40
  define( 'RELEVANSSI_PREMIUM', false );
41
 
 
 
42
  global $relevanssi_variables;
43
  global $wpdb;
44
 
56
  $relevanssi_variables['file'] = __FILE__;
57
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
58
 
59
+ require_once 'lib/common.php';
60
+ require_once 'lib/excerpts-highlights.php';
61
+ require_once 'lib/indexing.php';
62
  require_once 'lib/init.php';
63
+ require_once 'lib/install.php';
64
  require_once 'lib/interface.php';
65
+ require_once 'lib/log.php';
66
+ require_once 'lib/privacy.php';
67
  require_once 'lib/search.php';
 
68
  require_once 'lib/shortcodes.php';
69
+ require_once 'lib/stopwords.php';
70
+ require_once 'lib/sorting.php';
71
+ require_once 'lib/uninstall.php';