Relevanssi – A Better Search - Version 4.9.0

Version Description

  • New feature: There's now a "Debugging" tab in the Relevanssi settings, letting you see how the Relevanssi index sees posts. This is familiar to Premium users, but is now available in the free version as well.
  • New feature: The SEO Framework plugin is now supported and posts set excluded from the search in SEO Framework settings will be excluded from the index.
  • New feature: There's a new option, "Expand highlights". Enabling it makes Relevanssi expand partial-word highlights to cover the full word. This is useful when doing partial matching and when using a stemmer.
  • New feature: New filter hook relevanssi_excerpt_part allows you to modify the excerpt parts before they are combined together. This doesn't do much in the free version.
  • New feature: Improved compatibility with Oxygen Builder. Relevanssi automatically indexes the Oxygen Builder content and cleans it up. New filter hooks relevanssi_oxygen_section_filters and relevanssi_oxygen_section_content allow easier filtering of Oxygen content to eg. remove unwanted sections.
  • Changed behaviour: The "Uncheck this for non-ASCII highlights" option has been removed. Highlights are now done in a slightly different way that should work in all cases, including for example Cyrillic text, thus this option is no longer necessary.
  • Minor fix: Fixes phrase searching using non-US alphabet.
  • Minor fix: Relevanssi would break admin searching for hierarchical post types. This is now fixed, Relevanssi won't do that anymore.
  • Minor fix: Relevanssi indexing now survives better shortcodes that change the global $post.
  • Minor fix: Warnings about missing relevanssi_update_counts function are now removed.
  • Minor fix: Paid Membership Pro support now takes notice of the "filter queries" setting.
  • Minor fix: OR logic didn't work correctly when two phrases both had the same word (for example "freedom of speech" and "free speech"). The search would always be an AND search in those cases. That has been fixed.
  • Minor fix: Relevanssi no longer blocks the Pretty Links admin page search.
  • Minor fix: The "Respect 'exclude_from_search'" setting did not work if no post type parameter was included in the search parameters.
  • Minor fix: The category inclusion and exclusion setting checkboxes on the Searching tab didn't work. The setting was saved, but the checkboxes wouldn't appear.
Download this release

Release Info

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

Code changes from version 4.8.3 to 4.9.0

lib/admin_scripts.js CHANGED
@@ -126,8 +126,10 @@ jQuery(document).ready(function ($) {
126
  !this.checked
127
  )
128
  $("#tr_excerpt_length").toggleClass("relevanssi_disabled", !this.checked)
 
129
  $("#relevanssi_excerpt_length").attr("disabled", !this.checked)
130
  $("#relevanssi_excerpt_type").attr("disabled", !this.checked)
 
131
  $("#relevanssi_excerpt_allowable_tags").attr("disabled", !this.checked)
132
  $("#relevanssi_excerpt_custom_fields").attr("disabled", !this.checked)
133
  $("#relevanssi_highlight").attr("disabled", !this.checked)
@@ -138,9 +140,9 @@ jQuery(document).ready(function ($) {
138
  $("#relevanssi_hilite_title").attr("disabled", !this.checked)
139
  $("#relevanssi_highlight_docs").attr("disabled", !this.checked)
140
  $("#relevanssi_highlight_comments").attr("disabled", !this.checked)
141
- $("#relevanssi_word_boundaries").attr("disabled", !this.checked)
142
  $("#relevanssi_show_matches").attr("disabled", !this.checked)
143
  $("#relevanssi_show_matches_text").attr("disabled", !this.checked)
 
144
  })
145
 
146
  $("#relevanssi_searchblogs_all").click(function () {
126
  !this.checked
127
  )
128
  $("#tr_excerpt_length").toggleClass("relevanssi_disabled", !this.checked)
129
+ $("#tr_max_excerpts").toggleClass("relevanssi_disabled", !this.checked)
130
  $("#relevanssi_excerpt_length").attr("disabled", !this.checked)
131
  $("#relevanssi_excerpt_type").attr("disabled", !this.checked)
132
+ $("#relevanssi_max_excerpts").attr("disabled", !this.checked)
133
  $("#relevanssi_excerpt_allowable_tags").attr("disabled", !this.checked)
134
  $("#relevanssi_excerpt_custom_fields").attr("disabled", !this.checked)
135
  $("#relevanssi_highlight").attr("disabled", !this.checked)
140
  $("#relevanssi_hilite_title").attr("disabled", !this.checked)
141
  $("#relevanssi_highlight_docs").attr("disabled", !this.checked)
142
  $("#relevanssi_highlight_comments").attr("disabled", !this.checked)
 
143
  $("#relevanssi_show_matches").attr("disabled", !this.checked)
144
  $("#relevanssi_show_matches_text").attr("disabled", !this.checked)
145
+ $("#relevanssi_expand_highlights").attr("disabled", !this.checked)
146
  })
147
 
148
  $("#relevanssi_searchblogs_all").click(function () {
lib/admin_styles.css CHANGED
@@ -107,4 +107,11 @@ table.form-table table.widefat th {
107
 
108
  #redirect_table td {
109
  vertical-align: top;
 
 
 
 
 
 
 
110
  }
107
 
108
  #redirect_table td {
109
  vertical-align: top;
110
+ }
111
+
112
+ #relevanssi_sees_container {
113
+ width: 80%;
114
+ background: white;
115
+ padding: 5px 20px;
116
+ border: thin solid black;
117
  }
lib/class-relevanssi-taxonomy-walker.php CHANGED
@@ -66,7 +66,7 @@ class Relevanssi_Taxonomy_Walker extends Walker_Category_Checklist {
66
  /** This filter is documented in wp-includes/category-template.php */
67
  $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
68
  '<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="' . $name . '[]" id="in-' . $taxonomy . '-' . $category->term_id . '"' .
69
- checked( in_array( strval( $category->term_id ), $args['selected_cats'], true ), true, false ) .
70
  disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
71
  esc_html( apply_filters( 'the_category', $category->name ) ) . '</label>';
72
  }
66
  /** This filter is documented in wp-includes/category-template.php */
67
  $output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
68
  '<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="' . $name . '[]" id="in-' . $taxonomy . '-' . $category->term_id . '"' .
69
+ checked( in_array( intval( $category->term_id ), $args['selected_cats'], true ), true, false ) .
70
  disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
71
  esc_html( apply_filters( 'the_category', $category->name ) ) . '</label>';
72
  }
lib/common.php CHANGED
@@ -509,7 +509,7 @@ function relevanssi_generate_phrase_queries( $phrases, $taxonomies, $custom_fiel
509
  $queries = array();
510
  $phrase = $wpdb->esc_like( $phrase );
511
  $phrase = str_replace( array( '‘', '’', "'", '"', '”', '“', '“', '„', '´' ), '_', $phrase );
512
- $phrase = htmlentities( $phrase );
513
  $phrase = esc_sql( $phrase );
514
 
515
  $excerpt = '';
@@ -900,8 +900,9 @@ function relevanssi_prevent_default_request( $request, $query ) {
900
  $admin_search_ok = false;
901
  }
902
 
903
- if ( $query->is_admin && 'page' === $query->query_vars['post_type'] ) {
904
- // Relevanssi doesn't work on the page screen, so disable.
 
905
  $prevent = false;
906
  $admin_search_ok = false;
907
  }
@@ -1014,14 +1015,22 @@ function relevanssi_tokenize( $string, $remove_stops = true, $min_word_length =
1014
 
1015
  if ( $accept ) {
1016
  $token = relevanssi_mb_trim( $token );
1017
- if ( is_numeric( $token ) ) {
1018
- // $token ends up as an array index, and numbers don't work there.
1019
- $token = " $token";
1020
- }
1021
- if ( ! isset( $tokens[ $token ] ) ) {
1022
- $tokens[ $token ] = 1;
1023
- } else {
1024
- $tokens[ $token ]++;
 
 
 
 
 
 
 
 
1025
  }
1026
  }
1027
 
@@ -2250,3 +2259,228 @@ function relevanssi_launch_ajax_action( $action, $payload_args = array() ) {
2250
  $url = admin_url( 'admin-ajax.php' );
2251
  return wp_remote_post( $url, $args );
2252
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  $queries = array();
510
  $phrase = $wpdb->esc_like( $phrase );
511
  $phrase = str_replace( array( '‘', '’', "'", '"', '”', '“', '“', '„', '´' ), '_', $phrase );
512
+ $phrase = htmlspecialchars( $phrase );
513
  $phrase = esc_sql( $phrase );
514
 
515
  $excerpt = '';
900
  $admin_search_ok = false;
901
  }
902
 
903
+ if ( $query->is_admin && isset( $query->query['fields'] ) && 'id=>parent' === $query->query['fields'] ) {
904
+ // Relevanssi doesn't work on hierarchical post type admin screens,
905
+ // so disable.
906
  $prevent = false;
907
  $admin_search_ok = false;
908
  }
1015
 
1016
  if ( $accept ) {
1017
  $token = relevanssi_mb_trim( $token );
1018
+
1019
+ /**
1020
+ * This explode is done so that a stemmer can return both the
1021
+ * original term and the stemmed term and both can be indexed.
1022
+ */
1023
+ $token_array = explode( ' ', $token );
1024
+ foreach ( $token_array as $token ) {
1025
+ if ( is_numeric( $token ) ) {
1026
+ // $token ends up as an array index, and numbers don't work there.
1027
+ $token = " $token";
1028
+ }
1029
+ if ( ! isset( $tokens[ $token ] ) ) {
1030
+ $tokens[ $token ] = 1;
1031
+ } else {
1032
+ $tokens[ $token ]++;
1033
+ }
1034
  }
1035
  }
1036
 
2259
  $url = admin_url( 'admin-ajax.php' );
2260
  return wp_remote_post( $url, $args );
2261
  }
2262
+
2263
+ /**
2264
+ * Fetches the data and generates the HTML for the "How Relevanssi sees this
2265
+ * post".
2266
+ *
2267
+ * @param int $post_id The post ID.
2268
+ * @param boolean $display If false, add "display: none" style to the element.
2269
+ *
2270
+ * @return string The HTML code for the "How Relevanssi sees this post".
2271
+ */
2272
+ function relevanssi_generate_how_relevanssi_sees( $post_id, $display = true ) {
2273
+ $style = '';
2274
+ if ( ! $display ) {
2275
+ $style = 'style="display: none"';
2276
+ }
2277
+
2278
+ $element = '<div id="relevanssi_sees_container" ' . $style . '>';
2279
+
2280
+ $data = relevanssi_fetch_sees_data( $post_id );
2281
+
2282
+ if ( empty( $data['terms_list'] ) && empty( $data['reason'] ) ) {
2283
+ $element .= '<p>'
2284
+ // Translators: %d is the post ID.
2285
+ . sprintf( __( 'Nothing found for post ID %d.', 'relevanssi' ), $post_id )
2286
+ . '</p>';
2287
+ $element .= '</div>';
2288
+ return $element;
2289
+ }
2290
+
2291
+ if ( ! empty( $data['reason'] ) ) {
2292
+ $element .= '<h3>' . esc_html__( 'Possible reasons this post is not indexed', 'relevanssi' ) . '</h3>';
2293
+ $element .= '<p>' . esc_html( $data['reason'] ) . '</p>';
2294
+ }
2295
+ if ( ! empty( $data['title'] ) ) {
2296
+ $element .= '<h3>' . esc_html__( 'Post title', 'relevanssi' ) . '</h3>';
2297
+ $element .= '<p>' . esc_html( $data['title'] ) . '</p>';
2298
+ }
2299
+ if ( ! empty( $data['content'] ) ) {
2300
+ $element .= '<h3>' . esc_html__( 'Post content', 'relevanssi' ) . '</h3>';
2301
+ $element .= '<p>' . esc_html( $data['content'] ) . '</p>';
2302
+ }
2303
+ if ( ! empty( $data['comment'] ) ) {
2304
+ $element .= '<h3>' . esc_html__( 'Comments', 'relevanssi' ) . '</h3>';
2305
+ $element .= '<p>' . esc_html( $data['comment'] ) . '</p>';
2306
+ }
2307
+ if ( ! empty( $data['tag'] ) ) {
2308
+ $element .= '<h3>' . esc_html__( 'Tags', 'relevanssi' ) . '</h3>';
2309
+ $element .= '<p>' . esc_html( $data['tag'] ) . '</p>';
2310
+ }
2311
+ if ( ! empty( $data['category'] ) ) {
2312
+ $element .= '<h3>' . esc_html__( 'Categories', 'relevanssi' ) . '</h3>';
2313
+ $element .= '<p>' . esc_html( $data['category'] ) . '</p>';
2314
+ }
2315
+ if ( ! empty( $data['taxonomy'] ) ) {
2316
+ $element .= '<h3>' . esc_html__( 'Other taxonomies', 'relevanssi' ) . '</h3>';
2317
+ $element .= '<p>' . esc_html( $data['taxonomy'] ) . '</p>';
2318
+ }
2319
+ if ( ! empty( $data['link'] ) ) {
2320
+ $element .= '<h3>' . esc_html__( 'Links', 'relevanssi' ) . '</h3>';
2321
+ $element .= '<p>' . esc_html( $data['link'] ) . '</p>';
2322
+ }
2323
+ if ( ! empty( $data['author'] ) ) {
2324
+ $element .= '<h3>' . esc_html__( 'Authors', 'relevanssi' ) . '</h3>';
2325
+ $element .= '<p>' . esc_html( $data['author'] ) . '</p>';
2326
+ }
2327
+ if ( ! empty( $data['excerpt'] ) ) {
2328
+ $element .= '<h3>' . esc_html__( 'Excerpt', 'relevanssi' ) . '</h3>';
2329
+ $element .= '<p>' . esc_html( $data['excerpt'] ) . '</p>';
2330
+ }
2331
+ if ( ! empty( $data['customfield'] ) ) {
2332
+ $element .= '<h3>' . esc_html__( 'Custom fields', 'relevanssi' ) . '</h3>';
2333
+ $element .= '<p>' . esc_html( $data['customfield'] ) . '</p>';
2334
+ }
2335
+ if ( ! empty( $data['mysql'] ) ) {
2336
+ $element .= '<h3>' . esc_html__( 'MySQL content', 'relevanssi' ) . '</h3>';
2337
+ $element .= '<p>' . esc_html( $data['mysql'] ) . '</p>';
2338
+ }
2339
+ $element .= '</div>';
2340
+ return $element;
2341
+ }
2342
+
2343
+ /**
2344
+ * Fetches the Relevanssi indexing data for a post.
2345
+ *
2346
+ * @param int $post_id The post ID.
2347
+ *
2348
+ * @global array $relevanssi_variables The Relevanssi global variables array,
2349
+ * used for the database table name.
2350
+ * @global object $wpdb The WordPress database interface.
2351
+ *
2352
+ * @return array The indexed terms for various parts of the post in an
2353
+ * associative array.
2354
+ */
2355
+ function relevanssi_fetch_sees_data( $post_id ) {
2356
+ global $wpdb, $relevanssi_variables;
2357
+
2358
+ $terms_list = $wpdb->get_results(
2359
+ $wpdb->prepare(
2360
+ 'SELECT * FROM ' . $relevanssi_variables['relevanssi_table'] . ' WHERE doc = %d', // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
2361
+ $post_id
2362
+ ),
2363
+ OBJECT
2364
+ );
2365
+
2366
+ $terms['content'] = array();
2367
+ $terms['title'] = array();
2368
+ $terms['comment'] = array();
2369
+ $terms['tag'] = array();
2370
+ $terms['link'] = array();
2371
+ $terms['author'] = array();
2372
+ $terms['category'] = array();
2373
+ $terms['excerpt'] = array();
2374
+ $terms['taxonomy'] = array();
2375
+ $terms['customfield'] = array();
2376
+ $terms['mysql'] = array();
2377
+
2378
+ foreach ( $terms_list as $row ) {
2379
+ if ( $row->content > 0 ) {
2380
+ $terms['content'][] = $row->term;
2381
+ }
2382
+ if ( $row->title > 0 ) {
2383
+ $terms['title'][] = $row->term;
2384
+ }
2385
+ if ( $row->comment > 0 ) {
2386
+ $terms['comment'][] = $row->term;
2387
+ }
2388
+ if ( $row->tag > 0 ) {
2389
+ $terms['tag'][] = $row->term;
2390
+ }
2391
+ if ( $row->link > 0 ) {
2392
+ $terms['link'][] = $row->term;
2393
+ }
2394
+ if ( $row->author > 0 ) {
2395
+ $terms['author'][] = $row->term;
2396
+ }
2397
+ if ( $row->category > 0 ) {
2398
+ $terms['category'][] = $row->term;
2399
+ }
2400
+ if ( $row->excerpt > 0 ) {
2401
+ $terms['excerpt'][] = $row->term;
2402
+ }
2403
+ if ( $row->taxonomy > 0 ) {
2404
+ $terms['taxonomy'][] = $row->term;
2405
+ }
2406
+ if ( $row->customfield > 0 ) {
2407
+ $terms['customfield'][] = $row->term;
2408
+ }
2409
+ if ( $row->mysqlcolumn > 0 ) {
2410
+ $terms['mysql'][] = $row->term;
2411
+ }
2412
+ }
2413
+
2414
+ $reason = get_post_meta( $post_id, '_relevanssi_noindex_reason', true );
2415
+
2416
+ return array(
2417
+ 'content' => implode( ' ', $terms['content'] ),
2418
+ 'title' => implode( ' ', $terms['title'] ),
2419
+ 'comment' => implode( ' ', $terms['comment'] ),
2420
+ 'tag' => implode( ' ', $terms['tag'] ),
2421
+ 'link' => implode( ' ', $terms['link'] ),
2422
+ 'author' => implode( ' ', $terms['author'] ),
2423
+ 'category' => implode( ' ', $terms['category'] ),
2424
+ 'excerpt' => implode( ' ', $terms['excerpt'] ),
2425
+ 'taxonomy' => implode( ' ', $terms['taxonomy'] ),
2426
+ 'customfield' => implode( ' ', $terms['customfield'] ),
2427
+ 'mysql' => implode( ' ', $terms['mysql'] ),
2428
+ 'reason' => $reason,
2429
+ 'terms_list' => $terms_list,
2430
+ );
2431
+ }
2432
+
2433
+ /**
2434
+ * Removes quotes from array keys. Does not keep array values.
2435
+ *
2436
+ * Used to remove phrase quotes from search term array, which have the format
2437
+ * of (term => hits). The number of hits is not needed, so this function
2438
+ * discards it as a side effect.
2439
+ *
2440
+ * @param array $array An array to process.
2441
+ *
2442
+ * @return array The same array with quotes removed from the keys.
2443
+ */
2444
+ function relevanssi_remove_quotes_from_array_keys( $array ) {
2445
+ $array = array_keys( $array );
2446
+ array_walk(
2447
+ $array,
2448
+ function( &$key ) {
2449
+ $key = relevanssi_remove_quotes( $key );
2450
+ }
2451
+ );
2452
+ return array_flip( $array );
2453
+ }
2454
+
2455
+ /**
2456
+ * Removes quotes (", ”, “) from a string.
2457
+ *
2458
+ * @param string $string The string to clean.
2459
+ *
2460
+ * @return string The cleaned string.
2461
+ */
2462
+ function relevanssi_remove_quotes( $string ) {
2463
+ return str_replace( array( '”', '“', '"' ), '', $string );
2464
+ }
2465
+
2466
+ /**
2467
+ * Strips tags from contents, keeping the allowed tags.
2468
+ *
2469
+ * The allowable tags are read from the relevanssi_excerpt_allowable_tags
2470
+ * option. Spaces are added between tags before removing the tags, so that
2471
+ * words don't get stuck together. The function also remove invisible content.
2472
+ *
2473
+ * @see relevanssi_strip_invisibles
2474
+ *
2475
+ * @param string $content The content.
2476
+ *
2477
+ * @return string The content without tags.
2478
+ */
2479
+ function relevanssi_strip_tags( $content ) {
2480
+ $content = relevanssi_strip_invisibles( $content );
2481
+ $content = preg_replace( '/(<\/[^>]+?>)(<[^>\/][^>]*?>)/', '$1 $2', $content );
2482
+ return strip_tags(
2483
+ $content,
2484
+ get_option( 'relevanssi_excerpt_allowable_tags', '' )
2485
+ );
2486
+ }
lib/compatibility/oxygen.php ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/compatibility/oxygen.php
4
+ *
5
+ * Oxygen Builder 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_custom_field_value', 'relevanssi_oxygen_compatibility', 10, 3 );
14
+ add_filter( 'relevanssi_index_custom_fields', 'relevanssi_add_oxygen' );
15
+
16
+ /**
17
+ * Cleans up the Oxygen Builder custom field for Relevanssi consumption.
18
+ *
19
+ * Splits up the big custom field content from ct_builder_shortcodes into
20
+ * sections ([ct_section] tags). Each section can be processed with filters
21
+ * defined with `relevanssi_oxygen_section_filters`, for example to remove
22
+ * sections based on their "nicename" or "ct_category" values. After that the
23
+ * section is passed through the `relevanssi_oxygen_section_content` filter.
24
+ * Finally all shortcode tags are removed, leaving just the content.
25
+ *
26
+ * @param array $value An array of custom field values.
27
+ * @param string $field The name of the custom field. This function only looks
28
+ * at `ct_builder_shortcodes` fields.
29
+ * @param int $post_id The post ID.
30
+ *
31
+ * @return array|null An array of custom field values, null if no value exists.
32
+ */
33
+ function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
34
+ if ( 'ct_builder_shortcodes' === $field ) {
35
+ if ( empty( $value ) ) {
36
+ return null;
37
+ }
38
+
39
+ $content_tags = explode( '[ct_section', $value[0] );
40
+ $page_content = '';
41
+ foreach ( $content_tags as $content ) {
42
+ if ( empty( $content ) ) {
43
+ continue;
44
+ }
45
+ $content = '[ct_section' . $content;
46
+ /**
47
+ * Allows defining filters to remove Oxygen Builder sections.
48
+ *
49
+ * The filters are arrays, with the array key defining the key and
50
+ * the value defining the value. If the filter array is
51
+ * array( 'nicename' => 'Hero BG' ), Relevanssi will look for
52
+ * sections that have "nicename":"Hero BG" in their settings and
53
+ * will remove those.
54
+ *
55
+ * @param array An array of filtering rules, defaults empty.
56
+ *
57
+ * @return array
58
+ */
59
+ $filters = apply_filters(
60
+ 'relevanssi_oxygen_section_filters',
61
+ array()
62
+ );
63
+ array_walk(
64
+ $filters,
65
+ function( $filter ) use ( &$content ) {
66
+ foreach ( $filter as $key => $value ) {
67
+ if ( stristr( $content, '"' . $key . '":"' . $value . '"' ) !== false ) {
68
+ $content = '';
69
+ }
70
+ }
71
+ }
72
+ );
73
+
74
+ $content = preg_replace(
75
+ '/\[.*?\]/',
76
+ ' ',
77
+ /**
78
+ * Filters the Oxygen Builder section content before the
79
+ * shortcode tags are removed.
80
+ *
81
+ * @param string $content The single section content.
82
+ * @param itn $post_id The post ID.
83
+ *
84
+ * @return string
85
+ */
86
+ apply_filters(
87
+ 'relevanssi_oxygen_section_content',
88
+ $content,
89
+ $post_id
90
+ )
91
+ );
92
+
93
+ $page_content .= $content;
94
+ }
95
+
96
+ $value[0] = $page_content;
97
+ }
98
+ return $value;
99
+ }
100
+
101
+ /**
102
+ * Adds the `ct_builder_shortcodes` to the list of indexed custom fields.
103
+ *
104
+ * @param array|boolean $fields An array of custom fields to index, or false.
105
+ *
106
+ * @return array An array of custom fields, including `ct_builder_shortcodes`.
107
+ */
108
+ function relevanssi_add_oxygen( $fields ) {
109
+ if ( ! is_array( $fields ) ) {
110
+ $fields = array();
111
+ }
112
+ $fields[] = 'ct_builder_shortcodes';
113
+ return $fields;
114
+ }
115
+
lib/compatibility/paidmembershippro.php CHANGED
@@ -22,12 +22,16 @@ add_filter( 'relevanssi_post_ok', 'relevanssi_paidmembershippro_compatibility',
22
  * otherwise false.
23
  */
24
  function relevanssi_paidmembershippro_compatibility( $post_ok, $post_id ) {
25
- $status = relevanssi_get_post_status( $post_id );
26
 
27
- if ( 'publish' === $status ) {
28
- // Only apply to published posts, don't apply to drafts.
29
- $current_user = wp_get_current_user();
30
- $post_ok = pmpro_has_membership_access( $post_id, $current_user->ID );
 
 
 
 
31
  }
32
 
33
  return $post_ok;
22
  * otherwise false.
23
  */
24
  function relevanssi_paidmembershippro_compatibility( $post_ok, $post_id ) {
25
+ $pmpro_active = get_option( 'pmpro_filterqueries', 0 );
26
 
27
+ if ( $pmpro_active ) {
28
+ $status = relevanssi_get_post_status( $post_id );
29
+
30
+ if ( 'publish' === $status ) {
31
+ // Only apply to published posts, don't apply to drafts.
32
+ $current_user = wp_get_current_user();
33
+ $post_ok = pmpro_has_membership_access( $post_id, $current_user->ID );
34
+ }
35
  }
36
 
37
  return $post_ok;
lib/compatibility/pretty-links.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/compatibility/pretty-links.php
4
+ *
5
+ * Pretty Links 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_admin_search_ok', 'relevanssi_pretty_links_ok', 10, 2 );
14
+ add_filter( 'relevanssi_prevent_default_request', 'relevanssi_pretty_links_ok', 10, 2 );
15
+ add_filter( 'relevanssi_search_ok', 'relevanssi_pretty_links_ok', 10, 2 );
16
+
17
+ /**
18
+ * Returns false if the query post type is set to 'pretty-link'.
19
+ *
20
+ * @param boolean $ok Whether to allow the query.
21
+ * @param WP_Query $query The WP_Query object.
22
+ *
23
+ * @return boolean False if this is a Pretty Links query.
24
+ */
25
+ function relevanssi_pretty_links_ok( $ok, $query ) {
26
+ if ( 'pretty-link' === $query->query['post_type'] ) {
27
+ $ok = false;
28
+ }
29
+ return $ok;
30
+ }
lib/compatibility/seoframework.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/compatibility/seoframework.php
4
+ *
5
+ * The SEO Framework noindex filtering function.
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_do_not_index', 'relevanssi_seoframework_noindex', 10, 2 );
14
+ add_filter( 'relevanssi_indexing_restriction', 'relevanssi_seoframework_exclude' );
15
+
16
+ /**
17
+ * Blocks indexing of posts marked "Exclude this page from all search queries
18
+ * on this site." in the SEO Framework settings.
19
+ *
20
+ * Attaches to the 'relevanssi_do_not_index' filter hook.
21
+ *
22
+ * @param boolean $do_not_index True, if the post shouldn't be indexed.
23
+ * @param integer $post_id The post ID number.
24
+ *
25
+ * @return string|boolean If the post shouldn't be indexed, this returns
26
+ * 'SEO Framework'. The value may also be a boolean.
27
+ */
28
+ function relevanssi_seoframework_noindex( $do_not_index, $post_id ) {
29
+ $noindex = get_post_meta( $post_id, 'exclude_local_search', true );
30
+ if ( '1' === $noindex ) {
31
+ $do_not_index = 'SEO Framework';
32
+ }
33
+ return $do_not_index;
34
+ }
35
+
36
+ /**
37
+ * Excludes the "noindex" posts from Relevanssi indexing.
38
+ *
39
+ * Adds a MySQL query restriction that blocks posts that have the SEO Framework
40
+ * "Exclude this page from all search queries on this site" setting set to "1"
41
+ * from indexing.
42
+ *
43
+ * @param array $restriction An array with two values: 'mysql' for the MySQL
44
+ * query restriction to modify, 'reason' for the reason of restriction.
45
+ */
46
+ function relevanssi_seoframework_exclude( $restriction ) {
47
+ global $wpdb;
48
+
49
+ $restriction['mysql'] .= " AND post.ID NOT IN (SELECT post_id FROM
50
+ $wpdb->postmeta WHERE meta_key = 'exclude_local_search'
51
+ AND meta_value = '1' ) ";
52
+ $restriction['reason'] .= ' SEO Framework';
53
+ return $restriction;
54
+ }
lib/excerpts-highlights.php CHANGED
@@ -154,15 +154,7 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
154
  * @param string $query The search query.
155
  */
156
  $content = apply_filters( 'relevanssi_excerpt_content', $content, $post, $query );
157
-
158
- // Removes <script>, <embed> &c with content.
159
- $content = relevanssi_strip_invisibles( $content );
160
-
161
- // Add spaces between tags to avoid getting words stuck together.
162
- $content = preg_replace( '/(<\/[^>]+?>)(<[^>\/][^>]*?>)/', '$1 $2', $content );
163
-
164
- // This removes the tags, but leaves the content.
165
- $content = strip_tags( $content, get_option( 'relevanssi_excerpt_allowable_tags', '' ) );
166
 
167
  // Replace linefeeds and carriage returns with spaces.
168
  $content = preg_replace( "/\n\r|\r\n|\n|\r/", ' ', $content );
@@ -172,63 +164,72 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
172
  }
173
 
174
  // Find the appropriate spot from the post.
175
- $excerpt_data = relevanssi_create_excerpt( $content, $terms, $query, $excerpt_length, $excerpt_type );
 
 
 
176
 
 
177
  if ( 'none' !== get_option( 'relevanssi_index_comments' ) ) {
178
- // Use comment content as source material for excerpts.
179
- $comment_content = relevanssi_get_comments( $post->ID );
180
- $comment_content = preg_replace( '/(<\/[^>]+?>)(<[^>\/][^>]*?>)/', '$1 $2', $comment_content );
181
- $comment_content = strip_tags( $comment_content, get_option( 'relevanssi_excerpt_allowable_tags', '' ) );
182
  if ( ! empty( $comment_content ) ) {
183
- $comment_excerpts = relevanssi_create_excerpt(
184
  $comment_content,
185
  $terms,
186
  $query,
187
  $excerpt_length,
188
  $excerpt_type
189
  );
190
- if ( $comment_excerpts[1] > $excerpt_data[1] ) {
191
- // The excerpt created from comments is better than the one created from post data.
192
- $excerpt_data = $comment_excerpts;
 
 
 
 
 
 
 
 
 
193
  }
194
  }
195
  }
196
 
 
197
  if ( 'off' !== get_option( 'relevanssi_index_excerpt' ) ) {
198
- $excerpt_content = $post->post_excerpt;
199
- $excerpt_content = strip_tags( $excerpt_content, get_option( 'relevanssi_excerpt_allowable_tags', '' ) );
200
-
201
  if ( ! empty( $excerpt_content ) ) {
202
- $excerpt_excerpts = relevanssi_create_excerpt(
203
  $excerpt_content,
204
  $terms,
205
  $query,
206
  $excerpt_length,
207
  $excerpt_type
208
  );
209
- if ( $excerpt_excerpts[1] > $excerpt_data[1] ) {
210
- // The excerpt created from post excerpt is the best we found so far.
211
- $excerpt_data = $excerpt_excerpts;
 
 
 
 
 
 
 
 
 
212
  }
213
  }
214
  }
215
 
216
- $excerpt = $excerpt_data[0];
217
- $excerpt = trim( $excerpt );
218
- /**
219
- * Filters the excerpt.
220
- *
221
- * Filters the post excerpt generated by Relevanssi before the highlighting
222
- * is applied.
223
- *
224
- * @param string $excerpt The excerpt.
225
- * @param int $post->ID The post ID.
226
- */
227
- $excerpt = apply_filters( 'relevanssi_excerpt', $excerpt, $post->ID );
228
-
229
- $whole_post_excerpted = false;
230
- if ( $excerpt === $post->post_content ) {
231
- $whole_post_excerpted = true;
232
  }
233
 
234
  /**
@@ -236,43 +237,58 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
236
  *
237
  * @param string $ellipsis Default '...'.
238
  */
239
- $ellipsis = apply_filters( 'relevanssi_ellipsis', '...' );
240
-
241
  $highlight = get_option( 'relevanssi_highlight' );
242
- if ( 'none' !== $highlight ) {
243
- if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
244
- $excerpt = relevanssi_highlight_terms( $excerpt, $query );
245
- }
246
- }
247
 
248
- $excerpt = relevanssi_close_tags( $excerpt );
249
 
250
- $excerpt_is_from_beginning_of_the_post = $excerpt_data[2];
251
- if ( ! $whole_post_excerpted ) {
252
- if ( ! $excerpt_is_from_beginning_of_the_post && ! empty( $excerpt ) ) {
253
- $excerpt = $ellipsis . $excerpt;
254
  }
255
 
256
- if ( ! empty( $excerpt ) ) {
257
- $excerpt = $excerpt . $ellipsis;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  }
 
 
 
 
 
 
259
  }
260
 
261
  if ( null !== $old_global_post ) {
262
  $post = $old_global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
263
  }
264
 
265
- return $excerpt;
266
  }
267
 
268
  /**
269
  * Creates an excerpt from content.
270
  *
271
- * Since 2.6, this function no longer reads in the excerpt length and type from
272
- * the options, but instead receives them as the function parameters. That is
273
- * done to get uniform results in multisite searches. For backwards support,
274
- * calling this function without the length and type parameters falls back into
275
- * reasonable defaults.
 
276
  *
277
  * @param string $content The content.
278
  * @param array $terms The search terms, tokenized.
@@ -284,13 +300,49 @@ function relevanssi_do_excerpt( $t_post, $query, $excerpt_length = null, $excerp
284
  * element 2 is true, if the excerpt is from the start of the content.
285
  */
286
  function relevanssi_create_excerpt( $content, $terms, $query, $excerpt_length = 30, $excerpt_type = 'words' ) {
287
- $best_excerpt_term_hits = -1;
 
 
 
 
 
 
288
 
289
- $excerpt = '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
290
  $content = ' ' . preg_replace( '/\s+/u', ' ', $content );
291
  $content = html_entity_decode( $content );
292
  // Finds all the phrases in the query.
293
  $phrases = relevanssi_extract_phrases( stripslashes( $query ) );
 
294
 
295
  /**
296
  * This process generates an array of terms, which has single terms and all the
@@ -313,27 +365,48 @@ function relevanssi_create_excerpt( $content, $terms, $query, $excerpt_length =
313
  // Sort the longest search terms first, because those are generally more significant.
314
  uksort( $terms, 'relevanssi_strlen_sort' );
315
 
316
- $start = false;
 
317
  if ( 'chars' === $excerpt_type ) {
318
  $prev_count = floor( $excerpt_length / 6 );
319
 
320
- list( $excerpt, $best_excerpt_term_hits, $start ) =
321
  relevanssi_extract_relevant(
322
  array_keys( $terms ),
323
  $content,
324
  $excerpt_length + 1,
325
  $prev_count
326
  );
327
- } else {
328
- list( $excerpt, $best_excerpt_term_hits, $start ) =
329
- relevanssi_extract_relevant_words(
330
- array_keys( $terms ),
331
- $content,
332
- $excerpt_length
333
  );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  }
335
 
336
- return array( $excerpt, $best_excerpt_term_hits, $start );
337
  }
338
 
339
  /**
@@ -437,7 +510,7 @@ function relevanssi_highlight_terms( $content, $query, $in_docs = false ) {
437
  return $content;
438
  }
439
 
440
- $start_emp_token = '**{}[';
441
  $end_emp_token = ']}**';
442
 
443
  if ( function_exists( 'mb_internal_encoding' ) ) {
@@ -513,12 +586,8 @@ function relevanssi_highlight_terms( $content, $query, $in_docs = false ) {
513
 
514
  usort( $terms, 'relevanssi_strlen_sort' );
515
 
516
- $word_boundaries_available = false;
517
- if ( 'on' === get_option( 'relevanssi_word_boundaries', 'off' ) ) {
518
- $word_boundaries_available = true;
519
- }
520
-
521
  $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
 
522
 
523
  foreach ( $terms as $term ) {
524
  $pr_term = preg_quote( $term, '/' );
@@ -531,24 +600,56 @@ function relevanssi_highlight_terms( $content, $query, $in_docs = false ) {
531
  $pr_term
532
  );
533
 
534
- if ( $word_boundaries_available ) {
535
- $regex = "/(\b$pr_term\b)/iu";
536
- if ( 'never' !== get_option( 'relevanssi_fuzzy' ) ) {
537
- $regex = "/(\b$pr_term|$pr_term\b)/iu";
538
- }
539
- $content = preg_replace(
540
- $regex,
541
- $start_emp_token . '\\1' . $end_emp_token,
542
- $content
543
- );
 
 
 
 
 
544
  } else {
545
- $content = preg_replace(
546
- "/($pr_term)/iu",
547
- $start_emp_token . '\\1' . $end_emp_token,
548
- $content
549
- );
550
  }
551
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  if ( preg_match_all( '/<.*>/U', $content, $matches ) > 0 ) {
553
  // Remove highlights from inside HTML tags.
554
  foreach ( $matches as $match ) {
@@ -870,11 +971,6 @@ function relevanssi_count_matches( $words, $complete_text ) {
870
  $lowercase_text = relevanssi_strtolower( $complete_text, 'UTF-8' );
871
  $text = '';
872
 
873
- $word_boundaries_available = false;
874
- if ( 'on' === get_option( 'relevanssi_word_boundaries', 'off' ) ) {
875
- $word_boundaries_available = true;
876
- }
877
-
878
  $count_words = count( $words );
879
  for ( $t = 0; $t < $count_words; $t++ ) {
880
  $word_slice = relevanssi_strtolower(
@@ -893,15 +989,12 @@ function relevanssi_count_matches( $words, $complete_text ) {
893
  $word_slice
894
  );
895
 
896
- if ( $word_boundaries_available ) {
897
- if ( 'never' !== get_option( 'relevanssi_fuzzy' ) ) {
898
- $regex = "/\b$word_slice|$word_slice\b/";
899
- } else {
900
- $regex = "/\b$word_slice\b/";
901
- }
902
  } else {
903
- $regex = "/$word_slice/";
904
  }
 
905
  $lines = preg_split( $regex, $lowercase_text );
906
  if ( $lines && count( $lines ) > 1 ) {
907
  $count_lines = count( $lines );
@@ -1010,10 +1103,7 @@ function relevanssi_extract_relevant( $words, $fulltext, $excerpt_length = 300,
1010
  $startpos -= ( $text_length - $startpos ) / 2;
1011
  }
1012
 
1013
- $substr = 'substr';
1014
- if ( function_exists( 'mb_substr' ) ) {
1015
- $substr = 'mb_substr';
1016
- }
1017
 
1018
  $excerpt = call_user_func( $substr, $fulltext, $startpos, $excerpt_length );
1019
 
@@ -1106,7 +1196,7 @@ function relevanssi_extract_relevant_words( $terms, $content, $excerpt_length =
1106
  $start = true;
1107
  }
1108
 
1109
- return array( $excerpt, $best_excerpt_term_hits, $start );
1110
  }
1111
 
1112
  /**
@@ -1129,7 +1219,7 @@ function relevanssi_add_accent_variations( $word ) {
1129
  'relevanssi_accents_replacement_arrays',
1130
  array(
1131
  'from' => array( 'a', 'c', 'e', 'i', 'o', 'u', 'n' ),
1132
- 'to' => array( '(a|á|à|â)', '(c|ç)', '(e|é|è|ê|ë)', '(i|í|ì|î|ï)', '(o|ó|ò|ô|õ)', '(u|ú|ù|ü|û)', '(n|ñ)' ),
1133
  'from_re' => array( "/(s)('|’)?$/", "/[^\(\|]('|’)/" ),
1134
  'to_re' => array( "(('|’)?\\1|\\1('|’)?)", "?('|’)?" ),
1135
  )
154
  * @param string $query The search query.
155
  */
156
  $content = apply_filters( 'relevanssi_excerpt_content', $content, $post, $query );
157
+ $content = relevanssi_strip_tags( $content );
 
 
 
 
 
 
 
 
158
 
159
  // Replace linefeeds and carriage returns with spaces.
160
  $content = preg_replace( "/\n\r|\r\n|\n|\r/", ' ', $content );
164
  }
165
 
166
  // Find the appropriate spot from the post.
167
+ $excerpts = relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length, $excerpt_type );
168
+ if ( function_exists( 'relevanssi_add_source_to_excerpts' ) ) {
169
+ relevanssi_add_source_to_excerpts( $excerpts, 'content' );
170
+ }
171
 
172
+ $comment_excerpts = array();
173
  if ( 'none' !== get_option( 'relevanssi_index_comments' ) ) {
174
+ $comment_content = relevanssi_strip_tags( relevanssi_get_comments( $post->ID ) );
 
 
 
175
  if ( ! empty( $comment_content ) ) {
176
+ $comment_excerpts = relevanssi_create_excerpts(
177
  $comment_content,
178
  $terms,
179
  $query,
180
  $excerpt_length,
181
  $excerpt_type
182
  );
183
+ if ( function_exists( 'relevanssi_add_source_to_excerpts' ) ) {
184
+ relevanssi_add_source_to_excerpts( $comment_excerpts, 'comments' );
185
+ $comment_excerpts = array_filter(
186
+ $comment_excerpts,
187
+ function( $excerpt ) {
188
+ return $excerpt['hits'];
189
+ }
190
+ );
191
+ } elseif ( is_array( $comment_excerpts ) ) {
192
+ if ( $comment_excerpts[0]['hits'] > $excerpts[0]['hits'] ) {
193
+ $excerpts = $comment_excerpts;
194
+ }
195
  }
196
  }
197
  }
198
 
199
+ $excerpt_excerpts = array();
200
  if ( 'off' !== get_option( 'relevanssi_index_excerpt' ) ) {
201
+ $excerpt_content = relevanssi_strip_tags( $post->post_excerpt );
 
 
202
  if ( ! empty( $excerpt_content ) ) {
203
+ $excerpt_excerpts = relevanssi_create_excerpts(
204
  $excerpt_content,
205
  $terms,
206
  $query,
207
  $excerpt_length,
208
  $excerpt_type
209
  );
210
+ if ( function_exists( 'relevanssi_add_source_to_excerpts' ) ) {
211
+ relevanssi_add_source_to_excerpts( $excerpt_excerpts, 'excerpt' );
212
+ $excerpt_excerpts = array_filter(
213
+ $excerpt_excerpts,
214
+ function( $excerpt ) {
215
+ return $excerpt['hits'];
216
+ }
217
+ );
218
+ } elseif ( is_array( $excerpt_excerpts ) ) {
219
+ if ( $excerpt_excerpts[0]['hits'] > $excerpts[0]['hits'] ) {
220
+ $excerpts = $excerpt_excerpts;
221
+ }
222
  }
223
  }
224
  }
225
 
226
+ if ( function_exists( 'relevanssi_combine_excerpts' ) ) {
227
+ $excerpts = relevanssi_combine_excerpts(
228
+ $post->ID,
229
+ $excerpts,
230
+ $comment_excerpts,
231
+ $excerpt_excerpts
232
+ );
 
 
 
 
 
 
 
 
 
233
  }
234
 
235
  /**
237
  *
238
  * @param string $ellipsis Default '...'.
239
  */
240
+ $ellipsis = apply_filters( 'relevanssi_ellipsis', '...' );
 
241
  $highlight = get_option( 'relevanssi_highlight' );
 
 
 
 
 
242
 
243
+ $excerpt_text = '';
244
 
245
+ foreach ( $excerpts as $excerpt ) {
246
+ $whole_post_excerpted = false;
247
+ if ( $excerpt['text'] === $post->post_content ) {
248
+ $whole_post_excerpted = true;
249
  }
250
 
251
+ if ( 'none' !== $highlight ) {
252
+ if ( ! is_admin() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
253
+ $excerpt['text'] = relevanssi_highlight_terms( $excerpt['text'], $query );
254
+ }
255
+ }
256
+
257
+ $excerpt['text'] = relevanssi_close_tags( $excerpt['text'] );
258
+
259
+ if ( ! $whole_post_excerpted ) {
260
+ if ( ! $excerpt['start'] && ! empty( $excerpt['text'] ) ) {
261
+ $excerpt['text'] = $ellipsis . $excerpt['text'];
262
+ }
263
+
264
+ if ( ! empty( $excerpt['text'] ) ) {
265
+ $excerpt['text'] = $excerpt['text'] . $ellipsis;
266
+ }
267
  }
268
+
269
+ $excerpt_text .= apply_filters(
270
+ 'relevanssi_excerpt_part',
271
+ '<span class="excerpt_part">' . $excerpt['text'] . '</span>',
272
+ $excerpt
273
+ );
274
  }
275
 
276
  if ( null !== $old_global_post ) {
277
  $post = $old_global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
278
  }
279
 
280
+ return $excerpt_text;
281
  }
282
 
283
  /**
284
  * Creates an excerpt from content.
285
  *
286
+ * This is provided for backwards compatibility. The new version of the function
287
+ * supports the Premium capability to return multiple excerpts. Since that
288
+ * changes the return value of the function, this function is provided to
289
+ * return the original return value.
290
+ *
291
+ * @uses relevanssi_create_excerpts()
292
  *
293
  * @param string $content The content.
294
  * @param array $terms The search terms, tokenized.
300
  * element 2 is true, if the excerpt is from the start of the content.
301
  */
302
  function relevanssi_create_excerpt( $content, $terms, $query, $excerpt_length = 30, $excerpt_type = 'words' ) {
303
+ $excerpts = relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length, $excerpt_type );
304
+ usort(
305
+ $excerpts,
306
+ function( $a, $b ) {
307
+ return $b['hits'] - $a['hits'];
308
+ }
309
+ );
310
 
311
+ $excerpt = array(
312
+ 0 => $excerpts[0]['text'],
313
+ 1 => $excerpts[0]['hits'],
314
+ 2 => $excerpts[0]['start'],
315
+ );
316
+ return $excerpt;
317
+ }
318
+
319
+ /**
320
+ * Creates an excerpt from content.
321
+ *
322
+ * Relevanssi Premium has the capability to generate multiple excerpts from one
323
+ * post. While the free version only generates one excerpt per post, this
324
+ * function supports the multiple excerpt behaviour by returning an array of
325
+ * excerpts, even though just one excerpt is returned.
326
+ *
327
+ * @see relevanssi_create_excerpt()
328
+ *
329
+ * @param string $content The content.
330
+ * @param array $terms The search terms, tokenized.
331
+ * @param string $query The search query (not used anymore).
332
+ * @param int $excerpt_length The length of the excerpt, default 30.
333
+ * @param string $excerpt_type Either 'chars' or 'words', default 'words'.
334
+ *
335
+ * @return array An array of excerpts. In each excerpt, there are following
336
+ * parts: 'text' has the excerpt text, 'hits' the number of keyword matches in
337
+ * the excerpt, 'start' is true if the excerpt is from the beginning of the
338
+ * content.
339
+ */
340
+ function relevanssi_create_excerpts( $content, $terms, $query, $excerpt_length = 30, $excerpt_type = 'words' ) {
341
  $content = ' ' . preg_replace( '/\s+/u', ' ', $content );
342
  $content = html_entity_decode( $content );
343
  // Finds all the phrases in the query.
344
  $phrases = relevanssi_extract_phrases( stripslashes( $query ) );
345
+ $terms = relevanssi_remove_quotes_from_array_keys( $terms );
346
 
347
  /**
348
  * This process generates an array of terms, which has single terms and all the
365
  // Sort the longest search terms first, because those are generally more significant.
366
  uksort( $terms, 'relevanssi_strlen_sort' );
367
 
368
+ $excerpts = array();
369
+ $start = false;
370
  if ( 'chars' === $excerpt_type ) {
371
  $prev_count = floor( $excerpt_length / 6 );
372
 
373
+ list( $excerpt_text, $best_excerpt_term_hits, $start ) =
374
  relevanssi_extract_relevant(
375
  array_keys( $terms ),
376
  $content,
377
  $excerpt_length + 1,
378
  $prev_count
379
  );
380
+ $excerpt = array(
381
+ 'text' => $excerpt_text,
382
+ 'hits' => $best_excerpt_term_hits,
383
+ 'start' => $start,
 
 
384
  );
385
+ $excerpts[] = $excerpt;
386
+ } else {
387
+ if ( function_exists( 'relevanssi_extract_multiple_excerpts' ) ) {
388
+ $excerpts = relevanssi_extract_multiple_excerpts(
389
+ array_keys( $terms ),
390
+ $content,
391
+ $excerpt_length
392
+ );
393
+ } else {
394
+ list( $excerpt_text, $best_excerpt_term_hits, $start ) =
395
+ relevanssi_extract_relevant_words(
396
+ array_keys( $terms ),
397
+ $content,
398
+ $excerpt_length
399
+ );
400
+ $excerpt = array(
401
+ 'text' => $excerpt_text,
402
+ 'hits' => $best_excerpt_term_hits,
403
+ 'start' => $start,
404
+ );
405
+ $excerpts[] = $excerpt;
406
+ }
407
  }
408
 
409
+ return $excerpts;
410
  }
411
 
412
  /**
510
  return $content;
511
  }
512
 
513
+ $start_emp_token = '**{[';
514
  $end_emp_token = ']}**';
515
 
516
  if ( function_exists( 'mb_internal_encoding' ) ) {
586
 
587
  usort( $terms, 'relevanssi_strlen_sort' );
588
 
 
 
 
 
 
589
  $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
590
+ $content = str_replace( "\n", ' ', $content );
591
 
592
  foreach ( $terms as $term ) {
593
  $pr_term = preg_quote( $term, '/' );
600
  $pr_term
601
  );
602
 
603
+ $regex = "/([\W])($pr_term)([\W])/iu";
604
+ $three_parts = true;
605
+
606
+ if ( 'never' !== get_option( 'relevanssi_fuzzy' ) ) {
607
+ $regex = "/([\W]{$pr_term}|{$pr_term}[\W])/iu";
608
+ $three_parts = false;
609
+ }
610
+
611
+ if ( 'on' === get_option( 'relevanssi_expand_highlights' ) ) {
612
+ $regex = "/([\w]*{$pr_term}[\W]|[\W]{$pr_term}[\w]*)/iu";
613
+ $three_parts = false;
614
+ }
615
+
616
+ if ( $three_parts ) {
617
+ $replace = '\\1' . $start_emp_token . '\\2' . $end_emp_token . '\\3';
618
  } else {
619
+ $replace = $start_emp_token . '\\1' . $end_emp_token;
 
 
 
 
620
  }
621
 
622
+ $content = trim(
623
+ preg_replace(
624
+ $regex,
625
+ $replace,
626
+ ' ' . $content
627
+ )
628
+ );
629
+
630
+ /**
631
+ * The method here leaves extra spaces inside the highlighting. That
632
+ * is cleaned away here.
633
+ */
634
+ $replace_regex = '/(.)(' . preg_quote( $start_emp_token, '/' ) . ')(\s)/iu';
635
+ $content = preg_replace( $replace_regex, '\1\3\2', $content );
636
+
637
+ $replace_regex = '/^(' . preg_quote( $start_emp_token, '/' ) . ')\s/iu';
638
+ $content = preg_replace( $replace_regex, '\1', $content );
639
+
640
+ $replace_regex = '/(\s)(' . preg_quote( $end_emp_token, '/' ) . ')(.)/iu';
641
+ $content = preg_replace( $replace_regex, '\2\1\3', $content );
642
+
643
+ $replace_regex = '/\s(' . preg_quote( $end_emp_token, '/' ) . ')/iu';
644
+ $content = preg_replace( $replace_regex, '\1', $content );
645
+
646
+ // The starting tokens can get interlaced this way, let's unknot them.
647
+ $content = str_replace(
648
+ substr( $start_emp_token, 0, -1 ) . $start_emp_token . substr( $start_emp_token, -1, 1 ),
649
+ $start_emp_token . $start_emp_token,
650
+ $content
651
+ );
652
+
653
  if ( preg_match_all( '/<.*>/U', $content, $matches ) > 0 ) {
654
  // Remove highlights from inside HTML tags.
655
  foreach ( $matches as $match ) {
971
  $lowercase_text = relevanssi_strtolower( $complete_text, 'UTF-8' );
972
  $text = '';
973
 
 
 
 
 
 
974
  $count_words = count( $words );
975
  for ( $t = 0; $t < $count_words; $t++ ) {
976
  $word_slice = relevanssi_strtolower(
989
  $word_slice
990
  );
991
 
992
+ if ( 'never' !== get_option( 'relevanssi_fuzzy' ) ) {
993
+ $regex = "/[\W]{$word_slice}|{$word_slice}[\W]/iu";
 
 
 
 
994
  } else {
995
+ $regex = "/[\W]{$word_slice}[\W]/iu";
996
  }
997
+
998
  $lines = preg_split( $regex, $lowercase_text );
999
  if ( $lines && count( $lines ) > 1 ) {
1000
  $count_lines = count( $lines );
1103
  $startpos -= ( $text_length - $startpos ) / 2;
1104
  }
1105
 
1106
+ $substr = function_exists( 'mb_substr' ) ? 'mb_substr' : 'substr';
 
 
 
1107
 
1108
  $excerpt = call_user_func( $substr, $fulltext, $startpos, $excerpt_length );
1109
 
1196
  $start = true;
1197
  }
1198
 
1199
+ return array( trim( $excerpt ), $best_excerpt_term_hits, $start );
1200
  }
1201
 
1202
  /**
1219
  'relevanssi_accents_replacement_arrays',
1220
  array(
1221
  'from' => array( 'a', 'c', 'e', 'i', 'o', 'u', 'n' ),
1222
+ 'to' => array( '(?:a|á|à|â)', '(?:c|ç)', '(?:e|é|è|ê|ë)', '(?:i|í|ì|î|ï)', '(?:o|ó|ò|ô|õ)', '(?:u|ú|ù|ü|û)', '(?:n|ñ)' ),
1223
  'from_re' => array( "/(s)('|’)?$/", "/[^\(\|]('|’)/" ),
1224
  'to_re' => array( "(('|’)?\\1|\\1('|’)?)", "?('|’)?" ),
1225
  )
lib/indexing.php CHANGED
@@ -150,7 +150,6 @@ function relevanssi_generate_indexing_query( $valid_status, $extend = false, $re
150
  OR (post.post_parent=0)
151
  )
152
  ))
153
- AND post.ID NOT IN (SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_relevanssi_hide_post' AND meta_value = 'on')
154
  {$restriction['mysql']} ORDER BY post.ID DESC $limit";
155
  } else {
156
  $processed_post_filter = 'r.doc is null';
@@ -174,7 +173,6 @@ function relevanssi_generate_indexing_query( $valid_status, $extend = false, $re
174
  )
175
  )
176
  )
177
- AND post.ID NOT IN (SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_relevanssi_hide_post' AND meta_value = 'on')
178
  {$restriction['mysql']} ORDER BY post.ID DESC $limit";
179
  }
180
 
@@ -1427,13 +1425,13 @@ function relevanssi_index_title( &$insert_data, $post, $min_word_length, $debug
1427
  * Creates indexing queries for post content.
1428
  *
1429
  * @param array $insert_data The INSERT query data. Modified here.
1430
- * @param object $post The post object.
1431
  * @param int $min_word_length The minimum word length.
1432
  * @param boolean $debug If true, print out debug notices.
1433
  *
1434
  * @return int The number of tokens added to the data.
1435
  */
1436
- function relevanssi_index_content( &$insert_data, $post, $min_word_length, $debug ) {
1437
  $n = 0;
1438
 
1439
  /**
@@ -1455,10 +1453,10 @@ function relevanssi_index_content( &$insert_data, $post, $min_word_length, $debu
1455
  /**
1456
  * Filters the post content before indexing.
1457
  *
1458
- * @param string $post->post_content The post content.
1459
- * @param object $post The full post object.
1460
  */
1461
- $contents = apply_filters( 'relevanssi_post_content', $post->post_content, $post );
1462
  if ( $debug ) {
1463
  relevanssi_debug_echo( "\tPost content after relevanssi_post_content:\n$contents" );
1464
  }
@@ -1468,10 +1466,10 @@ function relevanssi_index_content( &$insert_data, $post, $min_word_length, $debu
1468
  *
1469
  * @author Alexander Gieg
1470
  *
1471
- * @param string The additional content.
1472
- * @param object $post The post object.
1473
  */
1474
- $additional_content = trim( apply_filters( 'relevanssi_content_to_index', '', $post ) );
1475
  if ( ! empty( $additional_content ) ) {
1476
  $contents .= ' ' . $additional_content;
1477
 
@@ -1488,9 +1486,22 @@ function relevanssi_index_content( &$insert_data, $post, $min_word_length, $debu
1488
 
1489
  relevanssi_disable_shortcodes();
1490
 
1491
- $post_before_shortcode = $post;
1492
- $contents = do_shortcode( $contents );
1493
- $post = $post_before_shortcode; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
 
 
 
 
 
 
 
 
 
 
 
 
 
1494
 
1495
  unset( $tablepress_controller );
1496
  } else {
@@ -1503,20 +1514,20 @@ function relevanssi_index_content( &$insert_data, $post, $min_word_length, $debu
1503
  /**
1504
  * Filters the post content after shortcodes but before HTML stripping.
1505
  *
1506
- * @param string $contents The post content.
1507
- * @param object $post The full post object.
1508
  */
1509
  $contents = apply_filters(
1510
  'relevanssi_post_content_after_shortcodes',
1511
  $contents,
1512
- $post
1513
  );
1514
 
1515
  $contents = relevanssi_strip_invisibles( $contents );
1516
 
1517
  // Premium feature for better control over internal links.
1518
  if ( function_exists( 'relevanssi_process_internal_links' ) ) {
1519
- $contents = relevanssi_process_internal_links( $contents, $post->ID );
1520
  }
1521
 
1522
  $contents = preg_replace( '/<[a-zA-Z\/][^>]*>/', ' ', $contents );
@@ -1525,10 +1536,10 @@ function relevanssi_index_content( &$insert_data, $post, $min_word_length, $debu
1525
  /**
1526
  * Filters the post content in indexing before tokenization.
1527
  *
1528
- * @param string $contents The post content.
1529
- * @param object $post The full post object.
1530
  */
1531
- $contents = apply_filters( 'relevanssi_post_content_before_tokenize', $contents, $post );
1532
  /** This filter is documented in lib/indexing.php */
1533
  $content_tokens = apply_filters(
1534
  'relevanssi_indexing_tokens',
150
  OR (post.post_parent=0)
151
  )
152
  ))
 
153
  {$restriction['mysql']} ORDER BY post.ID DESC $limit";
154
  } else {
155
  $processed_post_filter = 'r.doc is null';
173
  )
174
  )
175
  )
 
176
  {$restriction['mysql']} ORDER BY post.ID DESC $limit";
177
  }
178
 
1425
  * Creates indexing queries for post content.
1426
  *
1427
  * @param array $insert_data The INSERT query data. Modified here.
1428
+ * @param object $post_object The post object.
1429
  * @param int $min_word_length The minimum word length.
1430
  * @param boolean $debug If true, print out debug notices.
1431
  *
1432
  * @return int The number of tokens added to the data.
1433
  */
1434
+ function relevanssi_index_content( &$insert_data, $post_object, $min_word_length, $debug ) {
1435
  $n = 0;
1436
 
1437
  /**
1453
  /**
1454
  * Filters the post content before indexing.
1455
  *
1456
+ * @param string $post_object->post_content The post content.
1457
+ * @param object $post_object The full post object.
1458
  */
1459
+ $contents = apply_filters( 'relevanssi_post_content', $post_object->post_content, $post_object );
1460
  if ( $debug ) {
1461
  relevanssi_debug_echo( "\tPost content after relevanssi_post_content:\n$contents" );
1462
  }
1466
  *
1467
  * @author Alexander Gieg
1468
  *
1469
+ * @param string The additional content.
1470
+ * @param object $post_object The post object.
1471
  */
1472
+ $additional_content = trim( apply_filters( 'relevanssi_content_to_index', '', $post_object ) );
1473
  if ( ! empty( $additional_content ) ) {
1474
  $contents .= ' ' . $additional_content;
1475
 
1486
 
1487
  relevanssi_disable_shortcodes();
1488
 
1489
+ /**
1490
+ * This needs to be global here, otherwise the safety mechanism doesn't
1491
+ * work correctly.
1492
+ */
1493
+ global $post;
1494
+
1495
+ $global_post_before_shortcode = null;
1496
+ if ( isset( $post ) ) {
1497
+ $global_post_before_shortcode = $post;
1498
+ }
1499
+
1500
+ $contents = do_shortcode( $contents );
1501
+
1502
+ if ( $global_post_before_shortcode ) {
1503
+ $post = $global_post_before_shortcode; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
1504
+ }
1505
 
1506
  unset( $tablepress_controller );
1507
  } else {
1514
  /**
1515
  * Filters the post content after shortcodes but before HTML stripping.
1516
  *
1517
+ * @param string $contents The post content.
1518
+ * @param object $post_object The full post object.
1519
  */
1520
  $contents = apply_filters(
1521
  'relevanssi_post_content_after_shortcodes',
1522
  $contents,
1523
+ $post_object
1524
  );
1525
 
1526
  $contents = relevanssi_strip_invisibles( $contents );
1527
 
1528
  // Premium feature for better control over internal links.
1529
  if ( function_exists( 'relevanssi_process_internal_links' ) ) {
1530
+ $contents = relevanssi_process_internal_links( $contents, $post_object->ID );
1531
  }
1532
 
1533
  $contents = preg_replace( '/<[a-zA-Z\/][^>]*>/', ' ', $contents );
1536
  /**
1537
  * Filters the post content in indexing before tokenization.
1538
  *
1539
+ * @param string $contents The post content.
1540
+ * @param object $post_object The full post object.
1541
  */
1542
+ $contents = apply_filters( 'relevanssi_post_content_before_tokenize', $contents, $post_object );
1543
  /** This filter is documented in lib/indexing.php */
1544
  $content_tokens = apply_filters(
1545
  'relevanssi_indexing_tokens',
lib/init.php CHANGED
@@ -182,6 +182,10 @@ function relevanssi_init() {
182
  require_once 'compatibility/seopress.php';
183
  }
184
 
 
 
 
 
185
  if ( function_exists( 'members_content_permissions_enabled' ) ) {
186
  require_once 'compatibility/members.php';
187
  }
@@ -223,6 +227,14 @@ function relevanssi_init() {
223
  if ( defined( 'NINJA_TABLES_VERSION' ) ) {
224
  require_once 'compatibility/ninjatables.php';
225
  }
 
 
 
 
 
 
 
 
226
  }
227
 
228
  /**
@@ -233,8 +245,6 @@ function relevanssi_init() {
233
  function relevanssi_admin_init() {
234
  global $relevanssi_variables;
235
 
236
- require_once $relevanssi_variables['plugin_dir'] . 'lib/admin-ajax.php';
237
-
238
  add_action( 'admin_enqueue_scripts', 'relevanssi_add_admin_scripts' );
239
  add_filter( 'plugin_action_links_' . $relevanssi_variables['plugin_basename'], 'relevanssi_action_links' );
240
  }
182
  require_once 'compatibility/seopress.php';
183
  }
184
 
185
+ if ( defined( 'THE_SEO_FRAMEWORK_VERSION' ) ) {
186
+ require_once 'compatibility/seoframework.php';
187
+ }
188
+
189
  if ( function_exists( 'members_content_permissions_enabled' ) ) {
190
  require_once 'compatibility/members.php';
191
  }
227
  if ( defined( 'NINJA_TABLES_VERSION' ) ) {
228
  require_once 'compatibility/ninjatables.php';
229
  }
230
+
231
+ if ( defined( 'PRLI_PLUGIN_NAME' ) ) {
232
+ require_once 'compatibility/pretty-links.php';
233
+ }
234
+
235
+ if ( defined( 'CT_VERSION' ) ) {
236
+ require_once 'compatibility/oxygen.php';
237
+ }
238
  }
239
 
240
  /**
245
  function relevanssi_admin_init() {
246
  global $relevanssi_variables;
247
 
 
 
248
  add_action( 'admin_enqueue_scripts', 'relevanssi_add_admin_scripts' );
249
  add_filter( 'plugin_action_links_' . $relevanssi_variables['plugin_basename'], 'relevanssi_action_links' );
250
  }
lib/install.php CHANGED
@@ -88,6 +88,7 @@ function _relevanssi_install() {
88
  add_option( 'relevanssi_excerpt_type', 'words' );
89
  add_option( 'relevanssi_excerpts', 'on' );
90
  add_option( 'relevanssi_exclude_posts', '' );
 
91
  add_option( 'relevanssi_expand_shortcodes', 'on' );
92
  add_option( 'relevanssi_extag', '0' );
93
  add_option( 'relevanssi_fuzzy', 'sometimes' );
@@ -126,7 +127,6 @@ function _relevanssi_install() {
126
  add_option( 'relevanssi_throttle_limit', '500' );
127
  add_option( 'relevanssi_title_boost', $relevanssi_variables['title_boost_default'] );
128
  add_option( 'relevanssi_txt_col', '#ff0000' );
129
- add_option( 'relevanssi_word_boundaries', 'on' );
130
  add_option( 'relevanssi_wpml_only_current', 'on' );
131
 
132
  if ( function_exists( 'relevanssi_premium_install' ) ) {
88
  add_option( 'relevanssi_excerpt_type', 'words' );
89
  add_option( 'relevanssi_excerpts', 'on' );
90
  add_option( 'relevanssi_exclude_posts', '' );
91
+ add_option( 'relevanssi_expand_highlights', 'off' );
92
  add_option( 'relevanssi_expand_shortcodes', 'on' );
93
  add_option( 'relevanssi_extag', '0' );
94
  add_option( 'relevanssi_fuzzy', 'sometimes' );
127
  add_option( 'relevanssi_throttle_limit', '500' );
128
  add_option( 'relevanssi_title_boost', $relevanssi_variables['title_boost_default'] );
129
  add_option( 'relevanssi_txt_col', '#ff0000' );
 
130
  add_option( 'relevanssi_wpml_only_current', 'on' );
131
 
132
  if ( function_exists( 'relevanssi_premium_install' ) ) {
lib/interface.php CHANGED
@@ -107,121 +107,64 @@ function relevanssi_options() {
107
  */
108
  function update_relevanssi_options() {
109
  // phpcs:disable WordPress.Security.NonceVerification
110
- if ( isset( $_REQUEST['relevanssi_content_boost'] ) ) {
111
- $boost = floatval( $_REQUEST['relevanssi_content_boost'] );
112
- update_option( 'relevanssi_content_boost', $boost );
113
- }
114
-
115
- if ( isset( $_REQUEST['relevanssi_title_boost'] ) ) {
116
- $boost = floatval( $_REQUEST['relevanssi_title_boost'] );
117
- update_option( 'relevanssi_title_boost', $boost );
118
- }
119
-
120
- if ( isset( $_REQUEST['relevanssi_comment_boost'] ) ) {
121
- $boost = floatval( $_REQUEST['relevanssi_comment_boost'] );
122
- update_option( 'relevanssi_comment_boost', $boost );
123
- }
124
-
125
- if ( isset( $_REQUEST['relevanssi_min_word_length'] ) ) {
126
- $value = intval( $_REQUEST['relevanssi_min_word_length'] );
127
- if ( 0 === $value ) {
128
- $value = 3;
129
- }
130
- update_option( 'relevanssi_min_word_length', $value );
131
- }
132
-
133
  if ( 'indexing' === $_REQUEST['tab'] ) {
134
- if ( ! isset( $_REQUEST['relevanssi_index_author'] ) ) {
135
- $_REQUEST['relevanssi_index_author'] = 'off';
136
- }
137
-
138
- if ( ! isset( $_REQUEST['relevanssi_index_excerpt'] ) ) {
139
- $_REQUEST['relevanssi_index_excerpt'] = 'off';
140
- }
141
-
142
- if ( ! isset( $_REQUEST['relevanssi_expand_shortcodes'] ) ) {
143
- $_REQUEST['relevanssi_expand_shortcodes'] = 'off';
144
- }
145
-
146
- if ( ! isset( $_REQUEST['relevanssi_index_image_files'] ) ) {
147
- $_REQUEST['relevanssi_index_image_files'] = 'off';
148
- }
149
  }
150
 
151
  if ( 'searching' === $_REQUEST['tab'] ) {
152
- if ( ! isset( $_REQUEST['relevanssi_admin_search'] ) ) {
153
- $_REQUEST['relevanssi_admin_search'] = 'off';
154
- }
155
-
156
- if ( ! isset( $_REQUEST['relevanssi_throttle'] ) ) {
157
- $_REQUEST['relevanssi_throttle'] = 'off';
158
- }
159
-
160
- if ( ! isset( $_REQUEST['relevanssi_disable_or_fallback'] ) ) {
161
- $_REQUEST['relevanssi_disable_or_fallback'] = 'off';
162
- }
163
-
164
- if ( ! isset( $_REQUEST['relevanssi_respect_exclude'] ) ) {
165
- $_REQUEST['relevanssi_respect_exclude'] = 'off';
166
- }
167
-
168
- if ( ! isset( $_REQUEST['relevanssi_wpml_only_current'] ) ) {
169
- $_REQUEST['relevanssi_wpml_only_current'] = 'off';
170
- }
171
-
172
- if ( ! isset( $_REQUEST['relevanssi_polylang_all_languages'] ) ) {
173
- $_REQUEST['relevanssi_polylang_all_languages'] = 'off';
174
- }
175
-
176
- if ( ! isset( $_REQUEST['relevanssi_exact_match_bonus'] ) ) {
177
- $_REQUEST['relevanssi_exact_match_bonus'] = 'off';
178
- }
179
  }
180
 
181
  if ( 'logging' === $_REQUEST['tab'] ) {
182
- if ( ! isset( $_REQUEST['relevanssi_log_queries'] ) ) {
183
- $_REQUEST['relevanssi_log_queries'] = 'off';
184
- }
185
-
186
- if ( ! isset( $_REQUEST['relevanssi_log_queries_with_ip'] ) ) {
187
- $_REQUEST['relevanssi_log_queries_with_ip'] = 'off';
188
- }
189
  }
190
 
191
  if ( 'excerpts' === $_REQUEST['tab'] ) {
192
- if ( ! isset( $_REQUEST['relevanssi_excerpts'] ) ) {
193
- $_REQUEST['relevanssi_excerpts'] = 'off';
194
- }
195
-
196
- if ( ! isset( $_REQUEST['relevanssi_show_matches'] ) ) {
197
- $_REQUEST['relevanssi_show_matches'] = 'off';
198
- }
199
-
200
- if ( ! isset( $_REQUEST['relevanssi_hilite_title'] ) ) {
201
- $_REQUEST['relevanssi_hilite_title'] = 'off';
202
- }
203
-
204
- if ( ! isset( $_REQUEST['relevanssi_highlight_docs'] ) ) {
205
- $_REQUEST['relevanssi_highlight_docs'] = 'off';
206
- }
207
-
208
- if ( ! isset( $_REQUEST['relevanssi_highlight_comments'] ) ) {
209
- $_REQUEST['relevanssi_highlight_comments'] = 'off';
210
- }
211
-
212
- if ( ! isset( $_REQUEST['relevanssi_excerpt_custom_fields'] ) ) {
213
- $_REQUEST['relevanssi_excerpt_custom_fields'] = 'off';
214
- }
215
-
216
- if ( ! isset( $_REQUEST['relevanssi_word_boundaries'] ) ) {
217
- $_REQUEST['relevanssi_word_boundaries'] = 'off';
218
- }
219
- }
220
-
221
- if ( isset( $_REQUEST['relevanssi_excerpt_length'] ) ) {
222
- $value = intval( $_REQUEST['relevanssi_excerpt_length'] );
223
- if ( 0 !== $value ) {
224
- update_option( 'relevanssi_excerpt_length', $value );
225
  }
226
  }
227
 
@@ -232,15 +175,6 @@ function update_relevanssi_options() {
232
  update_option( 'relevanssi_synonyms', $value );
233
  }
234
 
235
- if ( isset( $_REQUEST['relevanssi_show_matches'] ) ) {
236
- update_option( 'relevanssi_show_matches', $_REQUEST['relevanssi_show_matches'] );
237
- }
238
- if ( isset( $_REQUEST['relevanssi_show_matches_text'] ) ) {
239
- $value = $_REQUEST['relevanssi_show_matches_text'];
240
- $value = str_replace( '"', "'", $value );
241
- update_option( 'relevanssi_show_matches_text', $value );
242
- }
243
-
244
  $relevanssi_punct = array();
245
  if ( isset( $_REQUEST['relevanssi_punct_quotes'] ) ) {
246
  $relevanssi_punct['quotes'] = $_REQUEST['relevanssi_punct_quotes'];
@@ -374,103 +308,57 @@ function update_relevanssi_options() {
374
  }
375
  }
376
 
377
- if ( isset( $_REQUEST['relevanssi_admin_search'] ) ) {
378
- update_option( 'relevanssi_admin_search', $_REQUEST['relevanssi_admin_search'], false );
379
- }
380
- if ( isset( $_REQUEST['relevanssi_excerpts'] ) ) {
381
- update_option( 'relevanssi_excerpts', $_REQUEST['relevanssi_excerpts'] );
382
- }
383
- if ( isset( $_REQUEST['relevanssi_excerpt_type'] ) ) {
384
- update_option( 'relevanssi_excerpt_type', $_REQUEST['relevanssi_excerpt_type'] );
385
- }
386
- if ( isset( $_REQUEST['relevanssi_excerpt_allowable_tags'] ) ) {
387
- update_option( 'relevanssi_excerpt_allowable_tags', $_REQUEST['relevanssi_excerpt_allowable_tags'] );
388
- }
389
- if ( isset( $_REQUEST['relevanssi_log_queries'] ) ) {
390
- update_option( 'relevanssi_log_queries', $_REQUEST['relevanssi_log_queries'] );
391
- }
392
- if ( isset( $_REQUEST['relevanssi_log_queries_with_ip'] ) ) {
393
- update_option( 'relevanssi_log_queries_with_ip', $_REQUEST['relevanssi_log_queries_with_ip'] );
394
- }
395
- if ( isset( $_REQUEST['relevanssi_highlight'] ) ) {
396
- update_option( 'relevanssi_highlight', $_REQUEST['relevanssi_highlight'] );
397
- }
398
- if ( isset( $_REQUEST['relevanssi_highlight_docs'] ) ) {
399
- update_option( 'relevanssi_highlight_docs', $_REQUEST['relevanssi_highlight_docs'] );
400
- }
401
- if ( isset( $_REQUEST['relevanssi_highlight_comments'] ) ) {
402
- update_option( 'relevanssi_highlight_comments', $_REQUEST['relevanssi_highlight_comments'] );
403
- }
404
- if ( isset( $_REQUEST['relevanssi_txt_col'] ) ) {
405
- update_option( 'relevanssi_txt_col', $_REQUEST['relevanssi_txt_col'] );
406
- }
407
- if ( isset( $_REQUEST['relevanssi_bg_col'] ) ) {
408
- update_option( 'relevanssi_bg_col', $_REQUEST['relevanssi_bg_col'] );
409
- }
410
- if ( isset( $_REQUEST['relevanssi_css'] ) ) {
411
- update_option( 'relevanssi_css', $_REQUEST['relevanssi_css'] );
412
- }
413
- if ( isset( $_REQUEST['relevanssi_class'] ) ) {
414
- update_option( 'relevanssi_class', $_REQUEST['relevanssi_class'] );
415
- }
416
  if ( isset( $_REQUEST['relevanssi_expst'] ) ) {
417
- update_option( 'relevanssi_exclude_posts', trim( $_REQUEST['relevanssi_expst'], ' ,' ) );
418
- }
419
- if ( isset( $_REQUEST['relevanssi_hilite_title'] ) ) {
420
- update_option( 'relevanssi_hilite_title', $_REQUEST['relevanssi_hilite_title'] );
421
- }
422
- if ( isset( $_REQUEST['relevanssi_index_comments'] ) ) {
423
- update_option( 'relevanssi_index_comments', $_REQUEST['relevanssi_index_comments'], false );
424
- }
425
- if ( isset( $_REQUEST['relevanssi_index_author'] ) ) {
426
- update_option( 'relevanssi_index_author', $_REQUEST['relevanssi_index_author'], false );
427
- }
428
- if ( isset( $_REQUEST['relevanssi_index_excerpt'] ) ) {
429
- update_option( 'relevanssi_index_excerpt', $_REQUEST['relevanssi_index_excerpt'], false );
430
- }
431
- if ( isset( $_REQUEST['relevanssi_index_image_files'] ) ) {
432
- update_option( 'relevanssi_index_image_files', $_REQUEST['relevanssi_index_image_files'], false );
433
- }
434
- if ( isset( $_REQUEST['relevanssi_fuzzy'] ) ) {
435
- update_option( 'relevanssi_fuzzy', $_REQUEST['relevanssi_fuzzy'] );
436
- }
437
- if ( isset( $_REQUEST['relevanssi_expand_shortcodes'] ) ) {
438
- update_option( 'relevanssi_expand_shortcodes', $_REQUEST['relevanssi_expand_shortcodes'], false );
439
- }
440
- if ( isset( $_REQUEST['relevanssi_implicit_operator'] ) ) {
441
- update_option( 'relevanssi_implicit_operator', $_REQUEST['relevanssi_implicit_operator'] );
442
- }
443
- if ( isset( $_REQUEST['relevanssi_omit_from_logs'] ) ) {
444
- update_option( 'relevanssi_omit_from_logs', $_REQUEST['relevanssi_omit_from_logs'] );
445
- }
446
- if ( isset( $_REQUEST['relevanssi_disable_or_fallback'] ) ) {
447
- update_option( 'relevanssi_disable_or_fallback', $_REQUEST['relevanssi_disable_or_fallback'] );
448
- }
449
- if ( isset( $_REQUEST['relevanssi_respect_exclude'] ) ) {
450
- update_option( 'relevanssi_respect_exclude', $_REQUEST['relevanssi_respect_exclude'] );
451
- }
452
- if ( isset( $_REQUEST['relevanssi_throttle'] ) ) {
453
- update_option( 'relevanssi_throttle', $_REQUEST['relevanssi_throttle'] );
454
- }
455
- if ( isset( $_REQUEST['relevanssi_wpml_only_current'] ) ) {
456
- update_option( 'relevanssi_wpml_only_current', $_REQUEST['relevanssi_wpml_only_current'] );
457
- }
458
- if ( isset( $_REQUEST['relevanssi_polylang_all_languages'] ) ) {
459
- update_option( 'relevanssi_polylang_all_languages', $_REQUEST['relevanssi_polylang_all_languages'] );
460
- }
461
- if ( isset( $_REQUEST['relevanssi_word_boundaries'] ) ) {
462
- update_option( 'relevanssi_word_boundaries', $_REQUEST['relevanssi_word_boundaries'] );
463
- }
464
- if ( isset( $_REQUEST['relevanssi_default_orderby'] ) ) {
465
- update_option( 'relevanssi_default_orderby', $_REQUEST['relevanssi_default_orderby'] );
466
- }
467
- if ( isset( $_REQUEST['relevanssi_excerpt_custom_fields'] ) ) {
468
- update_option( 'relevanssi_excerpt_custom_fields', $_REQUEST['relevanssi_excerpt_custom_fields'] );
469
- }
470
- if ( isset( $_REQUEST['relevanssi_exact_match_bonus'] ) ) {
471
- update_option( 'relevanssi_exact_match_bonus', $_REQUEST['relevanssi_exact_match_bonus'] );
472
  }
473
 
 
 
 
 
 
 
 
 
 
 
 
474
  if ( function_exists( 'relevanssi_update_premium_options' ) ) {
475
  relevanssi_update_premium_options();
476
  }
@@ -883,7 +771,7 @@ function relevanssi_options_form() {
883
  ),
884
  array(
885
  'slug' => 'excerpts',
886
- 'name' => __( 'Excerpts and Highlights', 'relevanssi' ),
887
  'require' => 'tabs/excerpts-tab.php',
888
  'callback' => 'relevanssi_excerpts_tab',
889
  'save' => true,
@@ -909,6 +797,13 @@ function relevanssi_options_form() {
909
  'callback' => 'relevanssi_redirects_tab',
910
  'save' => false,
911
  ),
 
 
 
 
 
 
 
912
  );
913
 
914
  /**
@@ -1110,3 +1005,178 @@ function relevanssi_form_tag_weight() {
1110
  </tr>
1111
  <?php
1112
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  */
108
  function update_relevanssi_options() {
109
  // phpcs:disable WordPress.Security.NonceVerification
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
  if ( 'indexing' === $_REQUEST['tab'] ) {
111
+ relevanssi_turn_off_options(
112
+ $_REQUEST,
113
+ array(
114
+ 'relevanssi_expand_shortcodes',
115
+ 'relevanssi_index_author',
116
+ 'relevanssi_index_excerpt',
117
+ 'relevanssi_index_image_files',
118
+ )
119
+ );
120
+ relevanssi_update_intval( $_REQUEST, 'relevanssi_min_word_length', true, 3 );
 
 
 
 
 
121
  }
122
 
123
  if ( 'searching' === $_REQUEST['tab'] ) {
124
+ relevanssi_turn_off_options(
125
+ $_REQUEST,
126
+ array(
127
+ 'relevanssi_admin_search',
128
+ 'relevanssi_disable_or_fallback',
129
+ 'relevanssi_exact_match_bonus',
130
+ 'relevanssi_polylang_all_languages',
131
+ 'relevanssi_respect_exclude',
132
+ 'relevanssi_throttle',
133
+ 'relevanssi_wpml_only_current',
134
+ )
135
+ );
136
+ relevanssi_update_floatval( $_REQUEST, 'relevanssi_content_boost', true, 1 );
137
+ relevanssi_update_floatval( $_REQUEST, 'relevanssi_title_boost', true, 1 );
138
+ relevanssi_update_floatval( $_REQUEST, 'relevanssi_comment_boost', true, 1 );
 
 
 
 
 
 
 
 
 
 
 
 
139
  }
140
 
141
  if ( 'logging' === $_REQUEST['tab'] ) {
142
+ relevanssi_turn_off_options(
143
+ $_REQUEST,
144
+ array(
145
+ 'relevanssi_log_queries',
146
+ 'relevanssi_log_queries_with_ip',
147
+ )
148
+ );
149
  }
150
 
151
  if ( 'excerpts' === $_REQUEST['tab'] ) {
152
+ relevanssi_turn_off_options(
153
+ $_REQUEST,
154
+ array(
155
+ 'relevanssi_excerpt_custom_fields',
156
+ 'relevanssi_excerpts',
157
+ 'relevanssi_expand_highlights',
158
+ 'relevanssi_highlight_comments',
159
+ 'relevanssi_highlight_docs',
160
+ 'relevanssi_hilite_title',
161
+ 'relevanssi_show_matches',
162
+ )
163
+ );
164
+ if ( isset( $_REQUEST['relevanssi_show_matches_text'] ) ) {
165
+ $value = $_REQUEST['relevanssi_show_matches_text'];
166
+ $value = str_replace( '"', "'", $value );
167
+ update_option( 'relevanssi_show_matches_text', $value );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  }
169
  }
170
 
175
  update_option( 'relevanssi_synonyms', $value );
176
  }
177
 
 
 
 
 
 
 
 
 
 
178
  $relevanssi_punct = array();
179
  if ( isset( $_REQUEST['relevanssi_punct_quotes'] ) ) {
180
  $relevanssi_punct['quotes'] = $_REQUEST['relevanssi_punct_quotes'];
308
  }
309
  }
310
 
311
+ $options = array(
312
+ 'relevanssi_admin_search' => false,
313
+ 'relevanssi_bg_col' => true,
314
+ 'relevanssi_class' => true,
315
+ 'relevanssi_css' => true,
316
+ 'relevanssi_default_orderby' => true,
317
+ 'relevanssi_disable_or_fallback' => true,
318
+ 'relevanssi_exact_match_bonus' => true,
319
+ 'relevanssi_excerpt_allowable_tags' => true,
320
+ 'relevanssi_excerpt_custom_fields' => true,
321
+ 'relevanssi_excerpt_type' => true,
322
+ 'relevanssi_excerpts' => true,
323
+ 'relevanssi_expand_shortcodes' => false,
324
+ 'relevanssi_expand_highlights' => true,
325
+ 'relevanssi_expst' => true,
326
+ 'relevanssi_fuzzy' => true,
327
+ 'relevanssi_highlight_comments' => true,
328
+ 'relevanssi_highlight_docs' => true,
329
+ 'relevanssi_highlight' => true,
330
+ 'relevanssi_hilite_title' => true,
331
+ 'relevanssi_implicit_operator' => true,
332
+ 'relevanssi_index_author' => false,
333
+ 'relevanssi_index_comments' => false,
334
+ 'relevanssi_index_excerpt' => false,
335
+ 'relevanssi_index_image_files' => false,
336
+ 'relevanssi_log_queries_with_ip' => true,
337
+ 'relevanssi_log_queries' => true,
338
+ 'relevanssi_omit_from_logs' => true,
339
+ 'relevanssi_polylang_all_languages' => true,
340
+ 'relevanssi_respect_exclude' => true,
341
+ 'relevanssi_show_matches' => true,
342
+ 'relevanssi_throttle' => true,
343
+ 'relevanssi_txt_col' => true,
344
+ 'relevanssi_wpml_only_current' => true,
345
+ );
346
+
 
 
 
347
  if ( isset( $_REQUEST['relevanssi_expst'] ) ) {
348
+ $_REQUEST['relevanssi_expst'] = trim( $_REQUEST['relevanssi_expst'], ' ,' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  }
350
 
351
+ array_walk(
352
+ $options,
353
+ function( $autoload, $option ) {
354
+ if ( isset( $_REQUEST[ $option ] ) ) {
355
+ update_option( $option, $_REQUEST[ $option ], $autoload );
356
+ }
357
+ }
358
+ );
359
+
360
+ relevanssi_update_intval( $_REQUEST, 'relevanssi_excerpt_length', true, 10 );
361
+
362
  if ( function_exists( 'relevanssi_update_premium_options' ) ) {
363
  relevanssi_update_premium_options();
364
  }
771
  ),
772
  array(
773
  'slug' => 'excerpts',
774
+ 'name' => __( 'Excerpts and highlights', 'relevanssi' ),
775
  'require' => 'tabs/excerpts-tab.php',
776
  'callback' => 'relevanssi_excerpts_tab',
777
  'save' => true,
797
  'callback' => 'relevanssi_redirects_tab',
798
  'save' => false,
799
  ),
800
+ array(
801
+ 'slug' => 'debugging',
802
+ 'name' => __( 'Debugging', 'relevanssi' ),
803
+ 'require' => 'tabs/debugging-tab.php',
804
+ 'callback' => 'relevanssi_debugging_tab',
805
+ 'save' => false,
806
+ ),
807
  );
808
 
809
  /**
1005
  </tr>
1006
  <?php
1007
  }
1008
+
1009
+ /**
1010
+ * Turns off options, ie. sets them to "off".
1011
+ *
1012
+ * If the specified options don't exist in the request array, they are set to
1013
+ * "off".
1014
+ *
1015
+ * @param array $request The _REQUEST array, passed as reference.
1016
+ * @param array $options An array of option names.
1017
+ */
1018
+ function relevanssi_turn_off_options( &$request, $options ) {
1019
+ array_walk(
1020
+ $options,
1021
+ function( $option ) use ( &$request ) {
1022
+ if ( ! isset( $request[ $option ] ) ) {
1023
+ $request[ $option ] = 'off';
1024
+ }
1025
+ }
1026
+ );
1027
+ }
1028
+
1029
+ /**
1030
+ * Returns 'on' if option exists and value is not 'off', otherwise 'off'.
1031
+ *
1032
+ * @param array $request An array of option values.
1033
+ * @param string $option The key to check.
1034
+ *
1035
+ * @return string 'on' or 'off'.
1036
+ */
1037
+ function relevanssi_off_or_on( $request, $option ) {
1038
+ if ( isset( $request[ $option ] ) && 'off' !== $request[ $option ] ) {
1039
+ return 'on';
1040
+ }
1041
+ return 'off';
1042
+ }
1043
+
1044
+ /**
1045
+ * Returns an imploded string if the option exists and is an array, an empty
1046
+ * string otherwise.
1047
+ *
1048
+ * @param array $request An array of option values.
1049
+ * @param string $option The key to check.
1050
+ * @param string $glue The glue string for implode(), default ','.
1051
+ *
1052
+ * @return string Imploded string or an empty string.
1053
+ */
1054
+ function relevanssi_implode( $request, $option, $glue = ',' ) {
1055
+ if ( isset( $request[ $option ] ) && is_array( $request[ $option ] ) ) {
1056
+ return implode( $glue, $request[ $option ] );
1057
+ }
1058
+ return '';
1059
+ }
1060
+
1061
+ /**
1062
+ * Returns the intval of the option if it exists, null otherwise.
1063
+ *
1064
+ * @param array $request An array of option values.
1065
+ * @param string $option The key to check.
1066
+ *
1067
+ * @return int|null Integer value of the option, or null.
1068
+ */
1069
+ function relevanssi_intval( $request, $option ) {
1070
+ if ( isset( $request[ $option ] ) ) {
1071
+ return intval( $request[ $option ] );
1072
+ }
1073
+ return null;
1074
+ }
1075
+
1076
+ /**
1077
+ * Returns a legal value.
1078
+ *
1079
+ * @param array $request An array of option values.
1080
+ * @param string $option The key to check.
1081
+ * @param array $values The legal values.
1082
+ * @param string $default The default value.
1083
+ *
1084
+ * @return string|null A legal value or the default value, null if the option
1085
+ * isn't set.
1086
+ */
1087
+ function relevanssi_legal_value( $request, $option, $values, $default ) {
1088
+ $value = null;
1089
+ if ( isset( $request[ $option ] ) ) {
1090
+ $value = $default;
1091
+ if ( in_array( $request[ $option ], $values, true ) ) {
1092
+ $value = $request[ $option ];
1093
+ }
1094
+ }
1095
+ return $value;
1096
+ }
1097
+
1098
+ /**
1099
+ * Sets an on/off option according to the request value.
1100
+ *
1101
+ * @param array $request An array of option values.
1102
+ * @param string $option The key to check.
1103
+ * @param boolean $autoload Should the option autoload, default true.
1104
+ */
1105
+ function relevanssi_update_off_or_on( $request, $option, $autoload = true ) {
1106
+ relevanssi_update_legal_value(
1107
+ $request,
1108
+ $option,
1109
+ array( 'off', 'on' ),
1110
+ 'off',
1111
+ $autoload
1112
+ );
1113
+ }
1114
+
1115
+ /**
1116
+ * Sets an option after sanitizing and unslashing the value.
1117
+ *
1118
+ * @param array $request An array of option values.
1119
+ * @param string $option The key to check.
1120
+ * @param boolean $autoload Should the option autoload, default true.
1121
+ */
1122
+ function relevanssi_update_sanitized( $request, $option, $autoload = true ) {
1123
+ if ( isset( $request[ $option ] ) ) {
1124
+ $value = sanitize_text_field( wp_unslash( $request[ $option ] ) );
1125
+ update_option( $option, $value, $autoload );
1126
+ }
1127
+ }
1128
+
1129
+ /**
1130
+ * Sets an option after doing intval.
1131
+ *
1132
+ * @param array $request An array of option values.
1133
+ * @param string $option The key to check.
1134
+ * @param boolean $autoload Should the option autoload, default true.
1135
+ * @param int $default The default value if intval() fails, default 0.
1136
+ */
1137
+ function relevanssi_update_intval( $request, $option, $autoload = true, $default = 0 ) {
1138
+ if ( isset( $request[ $option ] ) ) {
1139
+ $value = intval( $request[ $option ] );
1140
+ if ( ! $value ) {
1141
+ $value = $default;
1142
+ }
1143
+ update_option( $option, $value, $autoload );
1144
+ }
1145
+ }
1146
+
1147
+ /**
1148
+ * Sets an option after doing floatval.
1149
+ *
1150
+ * @param array $request An array of option values.
1151
+ * @param string $option The key to check.
1152
+ * @param boolean $autoload Should the option autoload, default true.
1153
+ * @param int $default The default value if floatval() fails, default 0.
1154
+ */
1155
+ function relevanssi_update_floatval( $request, $option, $autoload = true, $default = 0 ) {
1156
+ if ( isset( $request[ $option ] ) ) {
1157
+ $value = floatval( $request[ $option ] );
1158
+ if ( ! $value ) {
1159
+ $value = $default;
1160
+ }
1161
+ update_option( $option, $value, $autoload );
1162
+ }
1163
+ }
1164
+
1165
+ /**
1166
+ * Sets an option with one of the listed legal values.
1167
+ *
1168
+ * @param array $request An array of option values.
1169
+ * @param string $option The key to check.
1170
+ * @param array $values The legal values.
1171
+ * @param string $default The default value.
1172
+ * @param boolean $autoload Should the option autoload, default true.
1173
+ */
1174
+ function relevanssi_update_legal_value( $request, $option, $values, $default, $autoload = true ) {
1175
+ if ( isset( $request[ $option ] ) ) {
1176
+ $value = $default;
1177
+ if ( in_array( $request[ $option ], $values, true ) ) {
1178
+ $value = $request[ $option ];
1179
+ }
1180
+ update_option( $option, $value, $autoload );
1181
+ }
1182
+ }
lib/search-query-restrictions.php CHANGED
@@ -36,7 +36,7 @@ function relevanssi_process_query_args( $args ) {
36
  }
37
 
38
  if ( $args['sentence'] ) {
39
- $query = str_replace( array( '"', '“', '”' ), '', $query );
40
  $query = '"' . $query . '"';
41
  }
42
 
@@ -79,13 +79,11 @@ function relevanssi_process_query_args( $args ) {
79
  $phrase_query_restrictions = $phrases;
80
  }
81
 
82
- if ( $args['post_type'] || $args['include_attachments'] ) {
83
- $query_restrictions .= relevanssi_process_post_type(
84
- $args['post_type'],
85
- $args['admin_search'],
86
- $args['include_attachments']
87
- );
88
- }
89
 
90
  if ( $args['post_status'] ) {
91
  $query_restrictions .= relevanssi_process_post_status( $args['post_status'] );
@@ -560,10 +558,17 @@ function relevanssi_process_post_status( $post_status ) {
560
  */
561
  function relevanssi_add_phrase_restrictions( $query_restrictions, $phrase_queries, $term, $operator ) {
562
  if ( 'OR' === $operator ) {
563
- foreach ( $phrase_queries['or'] as $phrase_terms => $restriction ) {
564
- if ( relevanssi_stripos( $phrase_terms, $term ) !== false ) {
565
- $query_restrictions .= ' AND ' . $restriction;
566
- }
 
 
 
 
 
 
 
567
  }
568
  } else {
569
  $query_restrictions .= $phrase_queries['and'];
36
  }
37
 
38
  if ( $args['sentence'] ) {
39
+ $query = relevanssi_remove_quotes( $query );
40
  $query = '"' . $query . '"';
41
  }
42
 
79
  $phrase_query_restrictions = $phrases;
80
  }
81
 
82
+ $query_restrictions .= relevanssi_process_post_type(
83
+ $args['post_type'],
84
+ $args['admin_search'],
85
+ $args['include_attachments']
86
+ );
 
 
87
 
88
  if ( $args['post_status'] ) {
89
  $query_restrictions .= relevanssi_process_post_status( $args['post_status'] );
558
  */
559
  function relevanssi_add_phrase_restrictions( $query_restrictions, $phrase_queries, $term, $operator ) {
560
  if ( 'OR' === $operator ) {
561
+ $or_queries = array_filter(
562
+ $phrase_queries['or'],
563
+ function ( $terms ) use ( $term ) {
564
+ return relevanssi_stripos( $terms, $term ) !== false;
565
+ },
566
+ ARRAY_FILTER_USE_KEY
567
+ );
568
+ if ( $or_queries ) {
569
+ $query_restrictions .= ' AND ( '
570
+ . implode( ' OR ', array_values( $or_queries ) )
571
+ . ' ) ';
572
  }
573
  } else {
574
  $query_restrictions .= $phrase_queries['and'];
lib/search.php CHANGED
@@ -497,7 +497,7 @@ function relevanssi_search( $args ) {
497
 
498
  if ( $exact_match_bonus ) {
499
  $post = relevanssi_get_post( $match->doc );
500
- $clean_q = str_replace( array( '"', '”', '“' ), '', $q_no_synonyms );
501
  if ( $post && $clean_q ) {
502
  if ( stristr( $post->post_title, $clean_q ) !== false ) {
503
  $match->weight *= $exact_match_boost['title'];
@@ -1529,7 +1529,6 @@ function relevanssi_wp_date_query_from_query_vars( $query ) {
1529
  * @return array|boolean The meta query object or false, if no meta query
1530
  * parameters can be parsed.
1531
  */
1532
-
1533
  function relevanssi_meta_query_from_query_vars( $query ) {
1534
  $meta_query = false;
1535
  if ( ! empty( $query->query_vars['meta_query'] ) ) {
@@ -1585,4 +1584,4 @@ function relevanssi_meta_query_from_query_vars( $query ) {
1585
  $meta_query[] = $build_meta_query;
1586
  }
1587
  return $meta_query;
1588
- }
497
 
498
  if ( $exact_match_bonus ) {
499
  $post = relevanssi_get_post( $match->doc );
500
+ $clean_q = relevanssi_remove_quotes( $q_no_synonyms );
501
  if ( $post && $clean_q ) {
502
  if ( stristr( $post->post_title, $clean_q ) !== false ) {
503
  $match->weight *= $exact_match_boost['title'];
1529
  * @return array|boolean The meta query object or false, if no meta query
1530
  * parameters can be parsed.
1531
  */
 
1532
  function relevanssi_meta_query_from_query_vars( $query ) {
1533
  $meta_query = false;
1534
  if ( ! empty( $query->query_vars['meta_query'] ) ) {
1584
  $meta_query[] = $build_meta_query;
1585
  }
1586
  return $meta_query;
1587
+ }
lib/tabs/debugging-tab.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * /lib/tabs/debugging-tab.php
4
+ *
5
+ * Prints out the Debugging tab in Relevanssi settings.
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
+ * Prints out the debugging tab in Relevanssi settings.
15
+ */
16
+ function relevanssi_debugging_tab() {
17
+ $how_relevanssi_sees = '';
18
+ $current_post_id = 0;
19
+ if ( isset( $_REQUEST['post_id'] ) ) {
20
+ wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
21
+ if ( intval( $_REQUEST['post_id'] ) > 0 ) {
22
+ $current_post_id = intval( $_REQUEST['post_id'] );
23
+ $how_relevanssi_sees = relevanssi_generate_how_relevanssi_sees( intval( $current_post_id ) );
24
+ }
25
+ }
26
+ wp_nonce_field( 'relevanssi_how_relevanssi_sees', '_relevanssi_nonce', true, true );
27
+ ?>
28
+ <h2><?php esc_html_e( 'Debugging', 'relevanssi' ); ?></h2>
29
+
30
+ <p><?php esc_html_e( 'In order to figure out problems with indexing posts, you can test how Relevanssi sees the post by entering the post ID number in the field below.', 'relevanssi' ); ?></p>
31
+ <?php // Translators: %1$s starts the link, %2$s closes it. ?>
32
+ <p><?php printf( esc_html__( 'In Relevanssi Premium, you can find this feature for each post on the post edit page. %1$sBuy Relevanssi Premium here%2$s.', 'relevanssi' ), '<a href="https://www.relevanssi.com/buy-premium/">', '</a>' ); ?></p>
33
+ <p><label for="post_id"><?php esc_html_e( 'The post ID', 'relevanssi' ); ?></label>:
34
+ <input type="text" name="post_id" id="post_id"
35
+ <?php
36
+ if ( $current_post_id > 0 ) {
37
+ echo 'value="' . esc_attr( $current_post_id ) . '"';
38
+ }
39
+ ?>
40
+ /></p>
41
+ <p>
42
+ <input
43
+ type='submit' name='submit'
44
+ value='<?php esc_attr_e( 'Check the post', 'relevanssi' ); ?>'
45
+ class='button button-primary' />
46
+ </p>
47
+ <?php echo $how_relevanssi_sees; // phpcs:ignore WordPress.Security.EscapeOutput ?>
48
+ <?php
49
+ }
lib/tabs/excerpts-tab.php CHANGED
@@ -29,8 +29,8 @@ function relevanssi_excerpts_tab() {
29
  $highlight_coms = get_option( 'relevanssi_highlight_comments' );
30
  $show_matches = get_option( 'relevanssi_show_matches' );
31
  $show_matches_text = get_option( 'relevanssi_show_matches_text' );
32
- $word_boundaries = get_option( 'relevanssi_word_boundaries' );
33
  $index_fields = get_option( 'relevanssi_index_fields' );
 
34
 
35
  if ( '#' !== substr( $txt_col, 0, 1 ) ) {
36
  $txt_col = '#' . $txt_col;
@@ -49,7 +49,7 @@ function relevanssi_excerpts_tab() {
49
  $highlight_docs = relevanssi_check( $highlight_docs );
50
  $highlight_coms = relevanssi_check( $highlight_coms );
51
  $show_matches = relevanssi_check( $show_matches );
52
- $word_boundaries = relevanssi_check( $word_boundaries );
53
  $excerpt_chars = relevanssi_select( $excerpt_type, 'chars' );
54
  $excerpt_words = relevanssi_select( $excerpt_type, 'words' );
55
  $highlight_none = relevanssi_select( $highlight, 'no' );
@@ -136,6 +136,11 @@ function relevanssi_excerpts_tab() {
136
  <p class="description"><?php esc_html_e( "Using words is much faster than characters. Don't use characters, unless you have a really good reason and your posts are short.", 'relevanssi' ); ?></p>
137
  </td>
138
  </tr>
 
 
 
 
 
139
  <tr id="tr_excerpt_allowable_tags"
140
  <?php
141
  if ( empty( $excerpts ) ) {
@@ -356,20 +361,20 @@ function relevanssi_excerpts_tab() {
356
  </tr>
357
  <tr>
358
  <th scope="row">
359
- <?php esc_html_e( 'Highlighting problems with non-ASCII alphabet?', 'relevanssi' ); ?>
360
  </th>
361
  <td>
362
- <label for='relevanssi_word_boundaries'>
363
- <input type='checkbox' name='relevanssi_word_boundaries' id='relevanssi_word_boundaries' <?php echo esc_html( $word_boundaries ); ?>
364
  <?php
365
  if ( empty( $excerpts ) ) {
366
  echo "disabled='disabled'";
367
  }
368
  ?>
369
  />
370
- <?php esc_html_e( 'Uncheck this if you use non-ASCII characters', 'relevanssi' ); ?>
371
  </label>
372
- <p class="description"><?php esc_html_e( "If you use non-ASCII characters (like Cyrillic alphabet) and the highlights don't work, unchecking this option may make the highlights work.", 'relevanssi' ); ?></p>
373
  </td>
374
  </tr>
375
  </table>
29
  $highlight_coms = get_option( 'relevanssi_highlight_comments' );
30
  $show_matches = get_option( 'relevanssi_show_matches' );
31
  $show_matches_text = get_option( 'relevanssi_show_matches_text' );
 
32
  $index_fields = get_option( 'relevanssi_index_fields' );
33
+ $expand_highlights = get_option( 'relevanssi_expand_highlights' );
34
 
35
  if ( '#' !== substr( $txt_col, 0, 1 ) ) {
36
  $txt_col = '#' . $txt_col;
49
  $highlight_docs = relevanssi_check( $highlight_docs );
50
  $highlight_coms = relevanssi_check( $highlight_coms );
51
  $show_matches = relevanssi_check( $show_matches );
52
+ $expand_highlights = relevanssi_check( $expand_highlights );
53
  $excerpt_chars = relevanssi_select( $excerpt_type, 'chars' );
54
  $excerpt_words = relevanssi_select( $excerpt_type, 'words' );
55
  $highlight_none = relevanssi_select( $highlight, 'no' );
136
  <p class="description"><?php esc_html_e( "Using words is much faster than characters. Don't use characters, unless you have a really good reason and your posts are short.", 'relevanssi' ); ?></p>
137
  </td>
138
  </tr>
139
+ <?php
140
+ if ( function_exists( 'relevanssi_form_max_excerpts' ) ) {
141
+ relevanssi_form_max_excerpts( $excerpts );
142
+ }
143
+ ?>
144
  <tr id="tr_excerpt_allowable_tags"
145
  <?php
146
  if ( empty( $excerpts ) ) {
361
  </tr>
362
  <tr>
363
  <th scope="row">
364
+ <?php esc_html_e( 'Expand highlights', 'relevanssi' ); ?>
365
  </th>
366
  <td>
367
+ <label for='relevanssi_expand_highlights'>
368
+ <input type='checkbox' name='relevanssi_expand_highlights' id='relevanssi_expand_highlights' <?php echo esc_html( $expand_highlights ); ?>
369
  <?php
370
  if ( empty( $excerpts ) ) {
371
  echo "disabled='disabled'";
372
  }
373
  ?>
374
  />
375
+ <?php esc_html_e( 'Expand highlights to cover full words', 'relevanssi' ); ?>
376
  </label>
377
+ <p class="description"><?php esc_html_e( 'When a highlight matches part of the word, if this option is enabled, the highlight will be expanded to highlight the whole word.', 'relevanssi' ); ?></p>
378
  </td>
379
  </tr>
380
  </table>
lib/uninstall.php CHANGED
@@ -70,6 +70,7 @@ function relevanssi_uninstall_free() {
70
  delete_option( 'relevanssi_excerpt_type' );
71
  delete_option( 'relevanssi_excerpts' );
72
  delete_option( 'relevanssi_exclude_posts' );
 
73
  delete_option( 'relevanssi_expand_shortcodes' );
74
  delete_option( 'relevanssi_extag' );
75
  delete_option( 'relevanssi_fuzzy' );
@@ -111,7 +112,6 @@ function relevanssi_uninstall_free() {
111
  delete_option( 'relevanssi_title_boost' );
112
  delete_option( 'relevanssi_trim_logs' );
113
  delete_option( 'relevanssi_txt_col' );
114
- delete_option( 'relevanssi_word_boundaries' );
115
  delete_option( 'relevanssi_wpml_only_current' );
116
 
117
  // Unused options, removed in case they are still left.
@@ -130,6 +130,7 @@ function relevanssi_uninstall_free() {
130
  delete_option( 'relevanssi_custom_taxonomies' );
131
  delete_option( 'relevanssi_taxonomies_to_index' );
132
  delete_option( 'relevanssi_highlight_docs_external' );
 
133
 
134
  global $wpdb;
135
  $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key = '_relevanssi_noindex_reason'" );
70
  delete_option( 'relevanssi_excerpt_type' );
71
  delete_option( 'relevanssi_excerpts' );
72
  delete_option( 'relevanssi_exclude_posts' );
73
+ delete_option( 'relevanssi_expand_highlights' );
74
  delete_option( 'relevanssi_expand_shortcodes' );
75
  delete_option( 'relevanssi_extag' );
76
  delete_option( 'relevanssi_fuzzy' );
112
  delete_option( 'relevanssi_title_boost' );
113
  delete_option( 'relevanssi_trim_logs' );
114
  delete_option( 'relevanssi_txt_col' );
 
115
  delete_option( 'relevanssi_wpml_only_current' );
116
 
117
  // Unused options, removed in case they are still left.
130
  delete_option( 'relevanssi_custom_taxonomies' );
131
  delete_option( 'relevanssi_taxonomies_to_index' );
132
  delete_option( 'relevanssi_highlight_docs_external' );
133
+ delete_option( 'relevanssi_word_boundaries' );
134
 
135
  global $wpdb;
136
  $wpdb->query( "DELETE FROM $wpdb->postmeta WHERE meta_key = '_relevanssi_noindex_reason'" );
readme.txt CHANGED
@@ -3,9 +3,9 @@ Contributors: msaari
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search, product search, woocommerce search
5
  Requires at least: 4.9
6
- Tested up to: 5.5.1
7
  Requires PHP: 7.0
8
- Stable tag: 4.8.3
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
@@ -133,6 +133,23 @@ Each document database is full of useless words. All the little words that appea
133
  * John Calahan for extensive 4.0 beta testing.
134
 
135
  == Changelog ==
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  = 4.8.3 =
137
  * New feature: Both `relevanssi_fuzzy_query` and `relevanssi_term_where` now get the current search term as a parameter.
138
  * Minor fix: Relevanssi database tables don't have PRIMARY keys, only UNIQUE keys. In case this is a problem (for example on Digital Ocean servers), deactivate and activate Relevanssi to fix the problem.
@@ -186,6 +203,9 @@ Each document database is full of useless words. All the little words that appea
186
  * Minor fix: User Access Manager showed drafts in search results for all users. This is now fixed.
187
 
188
  == Upgrade notice ==
 
 
 
189
  = 4.8.3 =
190
  * Small bug fixes and database improvements.
191
 
3
  Donate link: https://www.relevanssi.com/buy-premium/
4
  Tags: search, relevance, better search, product search, woocommerce search
5
  Requires at least: 4.9
6
+ Tested up to: 5.5.3
7
  Requires PHP: 7.0
8
+ Stable tag: 4.9.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
133
  * John Calahan for extensive 4.0 beta testing.
134
 
135
  == Changelog ==
136
+ = 4.9.0 =
137
+ * New feature: There's now a "Debugging" tab in the Relevanssi settings, letting you see how the Relevanssi index sees posts. This is familiar to Premium users, but is now available in the free version as well.
138
+ * New feature: The SEO Framework plugin is now supported and posts set excluded from the search in SEO Framework settings will be excluded from the index.
139
+ * New feature: There's a new option, "Expand highlights". Enabling it makes Relevanssi expand partial-word highlights to cover the full word. This is useful when doing partial matching and when using a stemmer.
140
+ * New feature: New filter hook `relevanssi_excerpt_part` allows you to modify the excerpt parts before they are combined together. This doesn't do much in the free version.
141
+ * New feature: Improved compatibility with Oxygen Builder. Relevanssi automatically indexes the Oxygen Builder content and cleans it up. New filter hooks `relevanssi_oxygen_section_filters` and `relevanssi_oxygen_section_content` allow easier filtering of Oxygen content to eg. remove unwanted sections.
142
+ * Changed behaviour: The "Uncheck this for non-ASCII highlights" option has been removed. Highlights are now done in a slightly different way that should work in all cases, including for example Cyrillic text, thus this option is no longer necessary.
143
+ * Minor fix: Fixes phrase searching using non-US alphabet.
144
+ * Minor fix: Relevanssi would break admin searching for hierarchical post types. This is now fixed, Relevanssi won't do that anymore.
145
+ * Minor fix: Relevanssi indexing now survives better shortcodes that change the global `$post`.
146
+ * Minor fix: Warnings about missing `relevanssi_update_counts` function are now removed.
147
+ * Minor fix: Paid Membership Pro support now takes notice of the "filter queries" setting.
148
+ * Minor fix: OR logic didn't work correctly when two phrases both had the same word (for example "freedom of speech" and "free speech"). The search would always be an AND search in those cases. That has been fixed.
149
+ * Minor fix: Relevanssi no longer blocks the Pretty Links admin page search.
150
+ * Minor fix: The "Respect 'exclude_from_search'" setting did not work if no post type parameter was included in the search parameters.
151
+ * Minor fix: The category inclusion and exclusion setting checkboxes on the Searching tab didn't work. The setting was saved, but the checkboxes wouldn't appear.
152
+
153
  = 4.8.3 =
154
  * New feature: Both `relevanssi_fuzzy_query` and `relevanssi_term_where` now get the current search term as a parameter.
155
  * Minor fix: Relevanssi database tables don't have PRIMARY keys, only UNIQUE keys. In case this is a problem (for example on Digital Ocean servers), deactivate and activate Relevanssi to fix the problem.
203
  * Minor fix: User Access Manager showed drafts in search results for all users. This is now fixed.
204
 
205
  == Upgrade notice ==
206
+ = 4.9.0 =
207
+ * New debugging feature, lots of minor fixes.
208
+
209
  = 4.8.3 =
210
  * Small bug fixes and database improvements.
211
 
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.8.3
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
@@ -67,7 +67,7 @@ $relevanssi_variables['database_version'] = 5;
67
  $relevanssi_variables['file'] = __FILE__;
68
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
69
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
70
- $relevanssi_variables['plugin_version'] = '4.8.3';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';
13
  * Plugin Name: Relevanssi
14
  * Plugin URI: https://www.relevanssi.com/
15
  * Description: This plugin replaces WordPress search with a relevance-sorting search.
16
+ * Version: 4.9.0
17
  * Author: Mikko Saari
18
  * Author URI: http://www.mikkosaari.fi/
19
  * Text Domain: relevanssi
67
  $relevanssi_variables['file'] = __FILE__;
68
  $relevanssi_variables['plugin_dir'] = plugin_dir_path( __FILE__ );
69
  $relevanssi_variables['plugin_basename'] = plugin_basename( __FILE__ );
70
+ $relevanssi_variables['plugin_version'] = '4.9.0';
71
 
72
  require_once 'lib/admin-ajax.php';
73
  require_once 'lib/common.php';