3c85cdf4 by Jeff Balicki

qa

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent f5c15107
Showing 90 changed files with 4898 additions and 0 deletions
This diff could not be displayed because it is too large.
jQuery(document).ready(function ($) {
$("#build_index").click(function () {
$("#relevanssi-progress").show()
$("#results").show()
$("#relevanssi-timer").show()
$("#relevanssi-indexing-instructions").show()
$("#stateoftheindex").html(relevanssi.reload_state)
$("#indexing_button_instructions").hide()
var results = document.getElementById("results")
results.value = ""
var data = {
action: "relevanssi_truncate_index",
security: nonce.indexing_nonce,
}
intervalID = window.setInterval(relevanssiUpdateClock, 1000)
console.log("Truncating index.")
results.value += relevanssi.truncating_index + " "
jQuery.post(ajaxurl, data, function (response) {
truncate_response = JSON.parse(response)
console.log("Truncate index: " + truncate_response)
if (truncate_response == true) {
results.value += relevanssi.done + "\n"
}
var data = {
action: "relevanssi_count_posts",
}
console.log("Counting posts.")
results.value += relevanssi.counting_posts + " "
jQuery.post(ajaxurl, data, function (response) {
count_response = JSON.parse(response)
console.log("Counted " + count_response + " posts.")
var post_total = parseInt(count_response)
results.value += count_response + " " + relevanssi.posts_found + "\n"
var args = {
completed: 0,
total: post_total,
offset: 0,
total_seconds: 0,
limit: relevanssi_params.indexing_limit,
adjust: relevanssi_params.indexing_adjust,
extend: false,
security: nonce.indexing_nonce,
}
process_indexing_step(args)
})
})
})
})
p.important {
color: #992000;
}
table.form-table table.widefat th {
padding-left: 8px;
}
#relevanssi_min_word_length {
width: 3em;
}
#relevanssi_trim_logs, #relevanssi_trim_click_logs {
width: 4em;
}
#index_field_input {
margin-top: 1em;
}
#indexing_tab #results {
display: none;
width: 100%;
}
#relevanssi-progress {
display: none;
margin-bottom: 2em;
width: 100%;
height: 20px;
background-color: white;
}
.rpi-indicator {
width: 0;
height: 20px;
background-color: #afe240;
}
.relevanssi-weights-table {
min-width: 400px;
}
.relevanssi-weights-table td {
padding: 0;
}
.relevanssi-weights-table td.col-2, .relevanssi-weights-table th.col-2 {
width: 25%;
}
.rpi-progress {
display: none;
margin: 0.5em 0 2em 0;
width: 100%;
height: 20px;
background-color: white;
}
.rpi-progress div {
width: 0;
height: 20px;
background-color: #afe240;
}
#relevanssi_results {
display: none;
width: 100%;
}
#relevanssi_show_pdf_errors {
text-decoration: underline;
cursor: pointer;
color: #0073aa;
}
#relevanssi_pdf_errors {
display: none;
}
.visually_hidden {
margin: -1px;
padding: 0;
width: 1px;
height: 1px;
overflow: hidden;
clip: rect(0 0 0 0);
clip: rect(0,0,0,0);
position: absolute;
}
.relevanssi_disabled, .relevanssi_disabled td, .relevanssi_disabled th, .relevanssi_disabled p {
color: #999;
}
#relevanssi-timer {
display: none;
}
#category_inclusion_checklist ul.children, #category_exclusion_checklist ul.children {
margin-left: 1.5em;
}
#relevanssi_filter_list {
display: none;
}
#redirect_table td {
vertical-align: top;
}
#relevanssi_sees_container, #relevanssi_db_view_container {
width: 80%;
background: white;
padding: 5px 20px;
border: thin solid black;
overflow: scroll;
}
\ No newline at end of file
<?php
/**
* /lib/class-relevanssi-taxonomy-walker.php
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* A taxonomy walker used in Relevanssi interface.
*
* This is needed for wp_terms_checklist() in the Relevanssi admin interface to
* control the way the taxonomies are listed.
*/
class Relevanssi_Taxonomy_Walker extends Walker_Category_Checklist {
/**
* Name of the input element.
*
* @var string $name Name of the input element.
*/
public $name;
/**
* Creates a single element of the list.
*
* @see Walker::start_el()
*
* @param string $output Used to append additional content (passed by reference).
* @param object $category Category data object.
* @param int $depth Optional. Depth of category in reference to parents. Default 0.
* @param array $args Optional. An array of arguments. See wp_list_categories(). Default empty array.
* @param int $id Optional. ID of the current category. Default 0.
*/
public function start_el( &$output, $category, $depth = 0, $args = array(), $id = 0 ) {
if ( empty( $args['taxonomy'] ) ) {
$taxonomy = 'category';
} else {
$taxonomy = $args['taxonomy'];
}
$name = $this->name;
if ( ! isset( $args['popular_cats'] ) ) {
$args['popular_cats'] = array();
}
if ( ! isset( $args['selected_cats'] ) ) {
$args['selected_cats'] = array();
}
$class = '';
$inner_class = '';
if ( ! empty( $args['list_only'] ) ) {
$aria_checked = 'false';
$inner_class = 'category';
$output .= "\n" . '<li' . $class . '>' .
'<div class="' . $inner_class . '" data-term-id=' . $category->term_id .
' tabindex="0" role="checkbox" aria-checked="' . $aria_checked . '">' .
/** This filter is documented in wp-includes/category-template.php */
esc_html( apply_filters( 'the_category', $category->name, '', '' ) ) . '</div>';
} else {
$output .= "\n<li id='{$taxonomy}-{$category->term_id}'$class>" .
'<label class="selectit"><input value="' . $category->term_id . '" type="checkbox" name="' . $name . '[]" id="in-' . $taxonomy . '-' . $category->term_id . '"' .
checked( in_array( intval( $category->term_id ), $args['selected_cats'], true ), true, false ) .
disabled( empty( $args['disabled'] ), false, false ) . ' /> ' .
/** This filter is documented in wp-includes/category-template.php */
esc_html( apply_filters( 'the_category', $category->name, '', '' ) ) . '</label>';
}
}
}
<?php
/**
* /lib/compatibility/acf.php
*
* Advanced Custom Fields compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_action( 'acf/render_field_settings', 'relevanssi_acf_exclude_setting' );
add_filter( 'relevanssi_search_ok', 'relevanssi_acf_relationship_fields' );
add_filter( 'relevanssi_index_custom_fields', 'relevanssi_acf_exclude_fields', 10, 2 );
/**
* Disables Relevanssi in the ACF Relationship field post search.
*
* We don't want to use Relevanssi on the ACF Relationship field post searches, so
* this function disables it (on the 'relevanssi_search_ok' hook).
*
* @param boolean $search_ok Block the search or not.
*
* @return boolean False, if this is an ACF Relationship field search, pass the
* parameter unchanged otherwise.
*/
function relevanssi_acf_relationship_fields( $search_ok ) {
// phpcs:disable WordPress.Security.NonceVerification
if ( isset( $_REQUEST['action'] )
&& is_string( $_REQUEST['action'] )
&& 'acf' === substr( $_REQUEST['action'], 0, 3 ) ) {
$search_ok = false;
}
return $search_ok;
}
/**
* Indexes the human-readable value of "choice" options list from ACF.
*
* @author Droz Raphaël
*
* @param array $insert_data The insert data array.
* @param int $post_id The post ID.
* @param string $field_name Name of the field.
* @param string $field_value The field value.
*
* @return int Number of tokens indexed.
*/
function relevanssi_index_acf( &$insert_data, $post_id, $field_name, $field_value ) {
if ( ! is_admin() ) {
include_once ABSPATH . 'wp-admin/includes/plugin.php'; // Otherwise is_plugin_active() will cause a fatal error.
}
if ( ! function_exists( 'is_plugin_active' ) ) {
return 0;
}
if ( ! is_plugin_active( 'advanced-custom-fields/acf.php' ) && ! is_plugin_active( 'advanced-custom-fields-pro/acf.php' ) ) {
return 0;
}
if ( ! function_exists( 'get_field_object' ) ) {
return 0; // ACF is active, but not loaded.
}
$field_object = get_field_object( $field_name, $post_id );
if ( ! isset( $field_object['choices'] ) ) {
return 0; // Not a "select" field.
}
if ( is_array( $field_value ) ) {
return 0; // Not handled (currently).
}
if ( ! isset( $field_object['choices'][ $field_value ] ) ) {
return 0; // Value does not exist.
}
$n = 0;
/**
* Filters the field value before it is used to save the insert data.
*
* The value is used as an array key, so it needs to be an integer or a
* string. If your custom field values are arrays or objects, use this
* filter hook to convert them into strings.
*
* @param mixed $field_content The ACF field value.
* @param string $field_name The ACF field name.
* @param int $post_id The post ID.
*
* @return string|int The field value.
*/
$value = apply_filters(
'relevanssi_acf_field_value',
$field_object['choices'][ $field_value ],
$field_name,
$post_id
);
if ( $value && ( is_integer( $value ) || is_string( $value ) ) ) {
$min_word_length = get_option( 'relevanssi_min_word_length', 3 );
/** This filter is documented in lib/indexing.php */
$value_tokens = apply_filters( 'relevanssi_indexing_tokens', relevanssi_tokenize( $value, true, $min_word_length, 'indexing' ), 'custom_field' );
foreach ( $value_tokens as $token => $count ) {
++$n;
if ( ! isset( $insert_data[ $token ]['customfield'] ) ) {
$insert_data[ $token ]['customfield'] = 0;
}
$insert_data[ $token ]['customfield'] += $count;
// Premium indexes more detail about custom fields.
if ( function_exists( 'relevanssi_customfield_detail' ) ) {
$insert_data = relevanssi_customfield_detail( $insert_data, $token, $count, $field_name );
}
}
}
return $n;
}
/**
* Adds a Relevanssi exclude setting to ACF fields.
*
* @param array $field The field object array.
*/
function relevanssi_acf_exclude_setting( $field ) {
if ( ! function_exists( 'acf_render_field_setting' ) ) {
return;
}
if ( 'clone' === $field['type'] ) {
return;
}
acf_render_field_setting(
$field,
array(
'label' => __( 'Exclude from Relevanssi index', 'relevanssi' ),
'instructions' => __( 'If this setting is enabled, Relevanssi will not index the value of this field for posts.', 'relevanssi' ),
'name' => 'relevanssi_exclude',
'type' => 'true_false',
'ui' => 1,
),
true
);
}
/**
* Excludes ACF fields based on the exclude setting.
*
* Hooks on to relevanssi_index_custom_fields.
*
* @param array $fields The list of custom fields to index.
* @param int $post_id The post ID.
*
* @return array Filtered list of custom fields.
*/
function relevanssi_acf_exclude_fields( $fields, $post_id ) {
$included_fields = array();
$excluded_fields = array();
/**
* Filters the types of ACF fields to exclude from indexing.
*
* By default, blocks 'repeater', 'flexible_content' and 'group' are
* excluded from Relevanssi indexing. You can add other field types here.
*
* @param array $excluded_field_types The field types to exclude.
*/
$blocked_field_types = apply_filters(
'relevanssi_blocked_field_types',
array( 'repeater', 'flexible_content', 'group' )
);
global $post;
foreach ( $fields as $field ) {
$global_post = $post; // ACF fields can change the global $post.
$field_object = get_field_object( $field );
$post = $global_post; // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
if ( ! $field_object || ! is_array( $field_object ) ) {
$field_id = relevanssi_acf_get_field_id( $field, $post_id );
if ( ! $field_id ) {
// No field ID -> not an ACF field. Include.
$included_fields[] = $field;
} else {
/*
* This field has a field ID, but get_field_object() does not
* return a field object. This may be a clone field, in which
* case we can try to get the field object from the field ID.
* Clone fields have keys like field_xxx_field_yyy, where the
* field_yyy is the part we need.
*/
$field_id = preg_replace( '/.*_(field_.*)/', '$1', $field_id );
$field_object = get_field_object( $field_id );
}
}
if ( $field_object ) {
/**
* Filters the ACF field object.
*
* If the filter returns a false value, Relevanssi will not index
* the field.
*
* @param array $field_object The field object.
* @param int $post_id The post ID.
*
* @return array The filtered field object.
*/
$field_object = apply_filters(
'relevanssi_acf_field_object',
$field_object,
$post_id
);
if ( ! $field_object ) {
continue;
}
if ( isset( $field_object['relevanssi_exclude'] ) && 1 === $field_object['relevanssi_exclude'] ) {
continue;
}
if ( relevanssi_acf_is_parent_excluded( $field_object ) ) {
continue;
}
if ( isset( $field_object['type'] ) && in_array( $field_object['type'], $blocked_field_types, true ) ) {
continue;
}
$included_fields[] = $field;
}
}
return $included_fields;
}
/**
* Checks if the field has an excluded parent field.
*
* If the field has a "parent" value set, this function gets the parent field
* post based on the post ID in the "parent" value. This is done recursively
* until we reach the top or find an excluded parent.
*
* @param array $field_object The field object.
*
* @return bool Returns true if the post has an excluded parent.
*/
function relevanssi_acf_is_parent_excluded( $field_object ) {
if ( isset( $field_object['parent'] ) ) {
$parent = $field_object['parent'];
if ( $parent ) {
$parent_field_post = get_post( $parent );
if ( $parent_field_post ) {
$parent_object = get_field_object( $parent_field_post->post_name );
if ( $parent_object ) {
if ( isset( $parent_object['relevanssi_exclude'] ) && 1 === $parent_object['relevanssi_exclude'] ) {
return true;
}
return relevanssi_acf_is_parent_excluded( $parent_object );
}
}
}
}
return false;
}
/**
* Gets the field ID from the field name.
*
* The field ID is stored in the postmeta table with the field name prefixed
* with an underscore as the key.
*
* @param string $field_name The field name.
* @param int $post_id The post ID.
*
* @return string The field ID.
*/
function relevanssi_acf_get_field_id( $field_name, $post_id ) {
global $wpdb;
$field_id = $wpdb->get_var(
$wpdb->prepare(
"SELECT meta_value FROM $wpdb->postmeta
WHERE post_id = %d
AND meta_key = %s",
$post_id,
'_' . $field_name
)
);
return $field_id;
}
<?php
/**
* /lib/compatibility/aioseo.php
*
* All-in-One SEO noindex filtering function.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_do_not_index', 'relevanssi_aioseo_noindex', 10, 2 );
add_filter( 'relevanssi_indexing_restriction', 'relevanssi_aioseo_exclude' );
add_action( 'relevanssi_indexing_tab_advanced', 'relevanssi_aioseo_form', 20 );
add_action( 'relevanssi_indexing_options', 'relevanssi_aioseo_options' );
/**
* Blocks indexing of posts marked "noindex" in the All-in-One SEO settings.
*
* Attaches to the 'relevanssi_do_not_index' filter hook.
*
* @param boolean $do_not_index True, if the post shouldn't be indexed.
* @param integer $post_id The post ID number.
*
* @return string|boolean If the post shouldn't be indexed, this returns
* 'aioseo_seo'. The value may also be a boolean.
*/
function relevanssi_aioseo_noindex( bool $do_not_index, int $post_id ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $do_not_index;
}
$noindex_posts = relevanssi_aioseo_get_noindex_posts();
if ( in_array( $post_id, $noindex_posts, true ) ) {
$do_not_index = 'All-in-One SEO';
}
return $do_not_index;
}
/**
* Excludes the "noindex" posts from Relevanssi indexing.
*
* Adds a MySQL query restriction that blocks posts that have the aioseo SEO
* "noindex" setting set to "1" from indexing.
*
* @param array $restriction An array with two values: 'mysql' for the MySQL
* query restriction to modify, 'reason' for the reason of restriction.
*/
function relevanssi_aioseo_exclude( array $restriction ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $restriction;
}
global $wpdb;
$restriction['mysql'] .= " AND post.ID NOT IN (SELECT post_id FROM
{$wpdb->prefix}aioseo_posts WHERE robots_noindex = '1' ) ";
$restriction['reason'] .= ' All-in-One SEO';
return $restriction;
}
/**
* Fetches the post IDs where robots_noindex is set to 1 in the aioseo_posts
* table.
*
* @return array An array of post IDs.
*/
function relevanssi_aioseo_get_noindex_posts() {
global $wpdb, $relevanssi_aioseo_noindex_cache;
if ( ! empty( $relevanssi_aioseo_noindex_cache ) ) {
return $relevanssi_aioseo_noindex_cache;
}
$relevanssi_aioseo_noindex_cache = $wpdb->get_col( "SELECT post_id FROM {$wpdb->prefix}aioseo_posts WHERE 'robots_noindex' = '1'" );
return $relevanssi_aioseo_noindex_cache;
}
/**
* Prints out the form fields for disabling the feature.
*/
function relevanssi_aioseo_form() {
$seo_noindex = get_option( 'relevanssi_seo_noindex' );
$seo_noindex = relevanssi_check( $seo_noindex );
?>
<tr>
<th scope="row">
<label for='relevanssi_seo_noindex'><?php esc_html_e( 'Use All-in-One SEO noindex', 'relevanssi' ); ?></label>
</th>
<td>
<label for='relevanssi_seo_noindex'>
<input type='checkbox' name='relevanssi_seo_noindex' id='relevanssi_seo_noindex' <?php echo esc_attr( $seo_noindex ); ?> />
<?php esc_html_e( 'Use All-in-One SEO noindex.', 'relevanssi' ); ?>
</label>
<p class="description"><?php esc_html_e( 'If checked, Relevanssi will not index posts marked as "No index" in All-in-One SEO settings.', 'relevanssi' ); ?></p>
</td>
</tr>
<?php
}
/**
* Saves the SEO No index option.
*
* @param array $request An array of option values from the request.
*/
function relevanssi_aioseo_options( array $request ) {
relevanssi_update_off_or_on( $request, 'relevanssi_seo_noindex', true );
}
<?php
/**
* /lib/compatibility/avada.php
*
* Avada theme compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter(
'fusion_live_search_query_args',
function ( $args ) {
$args['relevanssi'] = true;
return $args;
}
);
<?php
/**
* /lib/compatibility/bricks.php
*
* Bricks theme compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'bricks/posts/query_vars', 'relevanssi_bricks_enable', 10 );
add_filter( 'relevanssi_custom_field_value', 'relevanssi_bricks_values', 10, 2 );
add_filter( 'relevanssi_index_custom_fields', 'relevanssi_add_bricks' );
add_filter( 'option_relevanssi_index_fields', 'relevanssi_bricks_fix_none_setting' );
add_action( 'save_post', 'relevanssi_insert_edit', 99, 1 );
/**
* Enables Relevanssi in the query when the 's' query var is set.
*
* @param array $query_vars The query variables.
*
* @return array The query variables with the Relevanssi toggle enabled.
*/
function relevanssi_bricks_enable( $query_vars ) {
if ( isset( $query_vars['s'] ) ) {
$query_vars['relevanssi'] = true;
}
return $query_vars;
}
/**
* Adds the `_bricks_page_content_2` to the list of indexed custom fields.
*
* @param array|boolean $fields An array of custom fields to index, or false.
*
* @return array An array of custom fields, including `_bricks_page_content_2`.
*/
function relevanssi_add_bricks( $fields ) {
if ( ! is_array( $fields ) ) {
$fields = array();
}
if ( ! in_array( '_bricks_page_content_2', $fields, true ) ) {
$fields[] = '_bricks_page_content_2';
}
return $fields;
}
/**
* Includes only text from _bricks_page_content_2 custom field.
*
* This function goes through the multilevel array of _bricks_page_content_2
* and only picks up the "text" elements inside it, discarding everything else.
*
* @param array $value An array of custom field values.
* @param string $field The name of the custom field.
*
* @return array An array containing a string with all the values concatenated
* together.
*/
function relevanssi_bricks_values( $value, $field ) {
if ( '_bricks_page_content_2' !== $field ) {
return $value;
}
$content = '';
array_walk_recursive(
$value,
function ( $text, $key ) use ( &$content ) {
if ( 'text' === $key ) {
$content .= ' ' . $text;
}
}
);
return array( $content );
}
/**
* Makes sure the Bricks builder shortcode is included in the index, even when
* the custom field setting is set to 'none'.
*
* @param string $value The custom field indexing setting value. The parameter
* is ignored, Relevanssi disables this filter and then checks the option to
* see what the value is.
*
* @return string If value is undefined, it's set to '_bricks_page_content_2'.
*/
function relevanssi_bricks_fix_none_setting( $value ) {
if ( ! $value ) {
$value = '_bricks_page_content_2';
}
return $value;
}
<?php
/**
* /lib/compatibility/elementor.php
*
* Elementor page builder compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_search_ok', 'relevanssi_block_elementor_library', 10, 2 );
/**
* Blocks Relevanssi from interfering with the Elementor Library searches.
*
* @param bool $ok Should Relevanssi be allowed to process the query.
* @param WP_Query $query The WP_Query object.
*
* @return bool Returns false, if this is an Elementor library search.
*/
function relevanssi_block_elementor_library( bool $ok, WP_Query $query ): bool {
if ( 'elementor_library' === $query->query_vars['post_type'] ) {
$ok = false;
}
return $ok;
}
<?php
/**
* /lib/compatibility/fibosearch.php
*
* Fibo Search compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'dgwt/wcas/search_query/args', 'relevanssi_enable_relevanssi_in_fibo' );
/**
* Adds the 'relevanssi' parameter to the Fibo Search.
*
* Uses the dgwt/wcas/search_query_args filter hook to modify the search query.
*
* @param array $args The search arguments.
*
* @return array
*/
function relevanssi_enable_relevanssi_in_fibo( $args ) {
$args['relevanssi'] = true;
return $args;
}
<?php
/**
* /lib/compatibility/groups.php
*
* Groups compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_groups_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* Only applies to published posts.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_groups_compatibility( $post_ok, $post_id ) {
$status = relevanssi_get_post_status( $post_id );
if ( 'publish' === $status ) {
// Only apply to published posts, don't apply to drafts.
$current_user = wp_get_current_user();
$post_ok = Groups_Post_Access::user_can_read_post( $post_id, $current_user->ID );
}
return $post_ok;
}
<?php
/**
* /lib/compatibility/gutenberg.php
*
* Gutenberg compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_content', 'relevanssi_gutenberg_block_rendering', 10, 2 );
/**
* Registers rest_after_insert_{post_type} actions for all indexed post types.
*
* Runs on `admin_init` action hook and registers the function
* `relevanssi_save_gutenberg_postdata` for all indexed post types.
*
* @see relevanssi_save_gutenberg_postdata
*/
function relevanssi_register_gutenberg_actions() {
if ( ! RELEVANSSI_PREMIUM ) {
return;
}
$index_post_types = get_option( 'relevanssi_index_post_types', array() );
array_walk(
$index_post_types,
function ( $post_type ) {
if ( 'bogus' !== $post_type ) {
add_action(
'rest_after_insert_' . $post_type,
'relevanssi_save_gutenberg_postdata'
);
}
}
);
}
/**
* Renders Gutenberg blocks.
*
* Renders all sorts of Gutenberg blocks, including reusable blocks and ACF
* blocks. Also enables basic Gutenberg deindexing: you can add an extra CSS
* class 'relevanssi_noindex' to a block to stop it from being indexed by
* Relevanssi. This function is essentially the same as core do_blocks().
*
* @see do_blocks()
*
* @param string $content The post content.
* @param object $post_object The post object.
*
* @return string The post content with the rendered content added.
*/
function relevanssi_gutenberg_block_rendering( $content, $post_object ) {
/**
* Filters whether the blocks are rendered or not.
*
* If this filter returns false, the blocks in this post are not rendered,
* and the post content is returned as such.
*
* @param boolean If true, render the blocks. Default true.
* @param object The post object.
*/
if ( ! apply_filters( 'relevanssi_render_blocks', true, $post_object ) ) {
return $content;
}
$blocks = parse_blocks( $content );
$output = '';
foreach ( $blocks as $block ) {
/**
* Filters the Gutenberg block before it is rendered.
*
* If the block is non-empty after the filter and it's className
* parameter is not 'relevanssi_noindex', it will be passed on to the
* render_block() function for rendering.
*
* @see render_block
*
* @param array $block The Gutenberg block element.
*/
$block = apply_filters( 'relevanssi_block_to_render', $block );
if ( ! $block ) {
continue;
}
if (
isset( $block['attrs']['className'] )
&& false !== strstr( $block['attrs']['className'], 'relevanssi_noindex' )
) {
continue;
}
$block = relevanssi_process_inner_blocks( $block );
/**
* Filters the Gutenberg block after it is rendered.
*
* The value is the output from render_block( $block ). Feel free to
* modify it as you wish.
*
* @see render_block
*
* @param string The rendered block content.
* @param array $block The Gutenberg block being rendered.
*
* @return string The filtered block content.
*/
$output .= apply_filters( 'relevanssi_rendered_block', render_block( $block ), $block );
}
// If there are blocks in this content, we shouldn't run wpautop() on it later.
$priority = has_filter( 'the_content', 'wpautop' );
if ( false !== $priority && doing_filter( 'the_content' ) && has_blocks( $content ) ) {
remove_filter( 'the_content', 'wpautop', $priority );
add_filter( 'the_content', '_restore_wpautop_hook', $priority + 1 );
}
return $output;
}
/**
* Runs recursively through inner blocks to filter them.
*
* Runs relevanssi_block_to_render and the relevanssi_noindex CSS class check
* on all inner blocks. If inner blocks are filtered out, they will be removed
* with empty blocks of the type "core/fake". Removing the inner blocks causes
* problems; that's why they are replaced. The blocks are rendered here;
* everything will be rendered once at the top level.
*
* @param array $block A Gutenberg block.
*
* @return array The filtered block.
*/
function relevanssi_process_inner_blocks( $block ) {
$innerblocks_to_keep = array();
$empty_block = array(
'blockName' => 'core/fake',
'attrs' => array(),
'innerHTML' => '',
'innerBlocks' => array(),
);
foreach ( $block['innerBlocks'] as $inner_block ) {
/* Filter documented in /lib/compatibility/gutenberg.php. */
$inner_block = apply_filters( 'relevanssi_block_to_render', $inner_block );
if ( ! $inner_block ) {
$innerblocks_to_keep[] = $empty_block;
continue;
}
if (
isset( $inner_block['attrs']['className'] )
&& false !== strstr( $inner_block['attrs']['className'], 'relevanssi_noindex' )
) {
$innerblocks_to_keep[] = $empty_block;
continue;
}
$inner_block = relevanssi_process_inner_blocks( $inner_block );
$innerblocks_to_keep[] = $inner_block;
}
$block['innerBlocks'] = $innerblocks_to_keep;
return $block;
}
<?php
/**
* /lib/compatibility/jetsmartfilters.php
*
* JetSmartFilters compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_action( 'pre_get_posts', 'relevanssi_jetsmartfilters', 9999 );
/**
* Makes JetSmartFilters use posts from Relevanssi.
*
* @param WP_Query $wp_query The wp_query object.
*/
function relevanssi_jetsmartfilters( $wp_query ) {
if (
! isset( $wp_query->query['jet_smart_filters'] )
|| empty( $wp_query->query['s'] )
) {
return;
}
$args = array(
's' => $wp_query->query['s'],
'fields' => 'ids',
'posts_per_page' => -1,
'relevanssi' => true,
);
$relevanssi_query = new WP_Query( $args );
$results = ! empty( $relevanssi_query->posts )
? $relevanssi_query->posts
: array( 0 );
$wp_query->set( 'post__in', $results );
$wp_query->set( 'post_type', 'any' );
$wp_query->set( 'post_status', 'any' );
$wp_query->set( 'orderby', 'post__in' );
$wp_query->set( 'order', 'DESC' );
$wp_query->set( 's', false );
}
<?php
/**
* /lib/compatibility/memberpress.php
*
* Memberpress compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_memberpress_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_memberpress_compatibility( $post_ok, $post_id ) {
$post = get_post( $post_id );
if ( MeprRule::is_locked( $post ) ) {
$post_ok = false;
}
return $post_ok;
}
<?php
/**
* /lib/compatibility/members.php
*
* Members compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_members_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* Only applies to private posts and only if the "content permissions" feature
* is enabled.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_members_compatibility( $post_ok, $post_id ) {
$status = relevanssi_get_post_status( $post_id );
if ( 'private' === $status ) {
if ( members_content_permissions_enabled() ) {
$post_ok = members_can_current_user_view_post( $post_id );
}
}
return $post_ok;
}
<?php
/**
* /lib/compatibility/ninjatables.php
*
* Ninja Tables compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_content', 'relevanssi_index_ninja_tables' );
/**
* Indexes Ninja Tables table contents.
*
* Uses regular expression matching to find all the Ninja Tables shortcodes in
* the post content and then uses relevanssi_index_ninja_table() to convert the
* tables into strings.
*
* @uses $wpdb WordPress database abstraction.
* @see relevanssi_index_ninja_table()
*
* @param string $content The post content.
*
* @return string Post content with the Ninja Tables data.
*/
function relevanssi_index_ninja_tables( $content ) {
$m = preg_match_all(
'/.*\[ninja_tables.*?id=["\'](\d+)["\'].*?\]/im',
$content,
$matches,
PREG_PATTERN_ORDER
);
if ( ! $m ) {
return $content;
}
foreach ( $matches[1] as $table_id ) {
$content .= ' ' . relevanssi_index_ninja_table( $table_id );
}
return $content;
}
/**
* Creates a string containing a Ninja Table table contents.
*
* The string contains the caption and the values from each row. The table
* title and description are also included, if they are set visible on the
* frontend.
*
* @uses $wpdb WordPress database abstraction.
*
* @param int $table_id The table ID.
*
* @return string The table content as a string.
*/
function relevanssi_index_ninja_table( $table_id ) {
global $wpdb;
$table_post = get_post( $table_id );
$table_settings = get_post_meta( $table_id, '_ninja_table_settings', true );
$table_contents = '';
if ( isset( $table_settings['show_description'] ) && '1' === $table_settings['show_description'] ) {
$table_contents .= ' ' . $table_post->post_content;
}
if ( isset( $table_settings['show_title'] ) && '1' === $table_settings['show_title'] ) {
$table_contents .= ' ' . $table_post->post_title;
}
$table_contents .= ' ' . get_post_meta( $table_id, '_ninja_table_caption', true );
$rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT value FROM {$wpdb->prefix}ninja_table_items WHERE table_id=%d",
$table_id
)
);
foreach ( $rows as $row ) {
$array_values = array_map(
function ( $value ) {
if ( is_object( $value ) ) {
return '';
}
return strval( $value );
},
array_values( get_object_vars( json_decode( $row->value ) ) )
);
$table_contents .= ' ' . implode( ' ', $array_values );
}
return $table_contents;
}
<?php
/**
* /lib/compatibility/oxygen.php
*
* Oxygen Builder compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_custom_field_value', 'relevanssi_oxygen_compatibility', 10, 3 );
add_filter( 'relevanssi_index_custom_fields', 'relevanssi_add_oxygen' );
add_filter( 'option_relevanssi_index_fields', 'relevanssi_oxygen_fix_none_setting' );
add_filter( 'relevanssi_oxygen_section_content', 'relevanssi_oxygen_code_block' );
add_filter( 'relevanssi_oxygen_section_content', 'relevanssi_oxygen_rich_text' );
add_action( 'save_post', 'relevanssi_insert_edit', 99, 1 );
/**
* Cleans up the Oxygen Builder custom field for Relevanssi consumption.
*
* Splits up the big custom field content from ct_builder_shortcodes into
* sections ([ct_section] tags). Each section can be processed with filters
* defined with `relevanssi_oxygen_section_filters`, for example to remove
* sections based on their "nicename" or "ct_category" values. After that the
* section is passed through the `relevanssi_oxygen_section_content` filter.
* Finally all shortcode tags are removed, leaving just the content.
*
* @param array $value An array of custom field values.
* @param string $field The name of the custom field. This function only looks
* at `ct_builder_shortcodes` fields.
* @param int $post_id The post ID.
*
* @return array|null An array of custom field values, null if no value exists.
*/
function relevanssi_oxygen_compatibility( $value, $field, $post_id ) {
if ( 'ct_builder_json' === $field ) {
$json = array();
foreach ( $value as $row ) {
$json[] = json_decode( $row );
}
$content = '';
if ( isset( $json[0]->children ) ) {
foreach ( $json[0]->children as $child ) {
$content .= relevanssi_process_oxygen_child( $child );
}
}
$value[0] = $content;
return $value;
}
if ( 'ct_builder_shortcodes_revisions_dates' === $field ) {
return '';
}
if ( 'ct_builder_shortcodes_revisions' === $field ) {
return '';
}
if ( 'ct_builder_shortcodes' === $field ) {
if ( version_compare( CT_VERSION, '4.0', '>=' ) ) {
return null;
}
if ( empty( $value ) ) {
return null;
}
$content_tags = explode( '[ct_section', $value[0] );
$page_content = '';
foreach ( $content_tags as $content ) {
if ( empty( $content ) ) {
continue;
}
if ( '[' !== substr( $content, 0, 1 ) ) {
$content = '[ct_section' . $content;
}
/**
* Allows defining filters to remove Oxygen Builder sections.
*
* The filters are arrays, with the array key defining the key and
* the value defining the value. If the filter array is
* array( 'nicename' => 'Hero BG' ), Relevanssi will look for
* sections that have "nicename":"Hero BG" in their settings and
* will remove those.
*
* @param array An array of filtering rules, defaults empty.
*
* @return array
*/
$filters = apply_filters(
'relevanssi_oxygen_section_filters',
array()
);
array_walk(
$filters,
function ( $filter ) use ( &$content ) {
foreach ( $filter as $key => $value ) {
if ( stristr( $content, '"' . $key . '":"' . $value . '"' ) !== false ) {
$content = '';
}
}
}
);
$content = preg_replace(
array(
'/\[oxygen.*?\]/',
'/\[\/?ct_.*?\]/',
'/\[\/?oxy_.*?\]/',
),
' ',
/**
* Filters the Oxygen Builder section content before the
* Oxygen Builder shortcode tags are removed.
*
* @param string $content The single section content.
* @param int $post_id The post ID.
*
* @return string
*/
apply_filters(
'relevanssi_oxygen_section_content',
$content,
$post_id
)
);
$page_content .= $content;
}
$page_content = relevanssi_do_shortcode( $page_content );
$value[0] = $page_content;
}
return $value;
}
/**
* Recursively processes the Oxygen JSON data.
*
* This function extracts all the ct_content data from the JSON. All elements
* are run through the relevanssi_oxygen_element filter hook. You can use that
* filter hook to modify or to eliminate elements from the JSON.
*
* @param array $child The child element array.
*
* @return string The content from the child and the grandchildren.
*/
function relevanssi_process_oxygen_child( $child ): string {
/**
* Filters the Oxygen JSON child element.
*
* If the filter returns an empty value, the child element and all its
* children will be ignored.
*
* @param array $child The JSON child element.
*/
$child = apply_filters( 'relevanssi_oxygen_element', $child );
if ( empty( $child ) ) {
return '';
}
$child_content = ' ';
if ( isset( $child->options->ct_content ) ) {
$child_content .= $child->options->ct_content;
}
if ( isset( $child->options->original->{'code-php'} ) ) {
// For code and HTML blocks, strip all tags.
$child_content .= wp_strip_all_tags( $child->options->original->{'code-php'} );
}
if ( isset( $child->children ) ) {
foreach ( $child->children as $grandchild ) {
$child_content .= relevanssi_process_oxygen_child( $grandchild );
}
}
return $child_content;
}
/**
* Adds the Oxygen custom field to the list of indexed custom fields.
*
* @param array|boolean $fields An array of custom fields to index, or false.
*
* @return array An array of custom fields, including `ct_builder_json` or
* `ct_builder_shortcodes`.
*/
function relevanssi_add_oxygen( $fields ) {
$oxygen_field = version_compare( CT_VERSION, '4.0', '>=' )
? 'ct_builder_json'
: 'ct_builder_shortcodes';
if ( ! is_array( $fields ) ) {
$fields = array();
}
if ( ! in_array( $oxygen_field, $fields, true ) ) {
$fields[] = $oxygen_field;
}
return $fields;
}
/**
* Makes sure the Oxygen builder shortcode is included in the index, even when
* the custom field setting is set to 'none'.
*
* @param string $value The custom field indexing setting value. The parameter
* is ignored, Relevanssi disables this filter and then checks the option to
* see what the value is.
*
* @return string If value is undefined, it's set to 'ct_builder_json' or
* 'ct_builder_shortcodes'.
*/
function relevanssi_oxygen_fix_none_setting( $value ) {
if ( ! $value ) {
$value = version_compare( CT_VERSION, '4.0', '>=' )
? 'ct_builder_json'
: 'ct_builder_shortcodes';
}
return $value;
}
/**
* Indexes the Base64 encoded PHP & HTML code block contents.
*
* @param string $content The section content from the
* relevanssi_oxygen_section_content filter hook.
*
* @return string $content The content with the decoded code block content
* added to the end.
*/
function relevanssi_oxygen_code_block( $content ) {
if ( preg_match_all( '/\[ct_code_block.*?ct_code_block\]/', $content, $matches ) ) {
foreach ( $matches[0] as $match ) {
if ( preg_match_all( '/"code-php":"(.*?)"/', $match, $block_matches ) ) {
foreach ( $block_matches[1] as $encoded_text ) {
$content .= ' ' . base64_decode( $encoded_text ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions
}
}
}
}
return $content;
}
/**
* Removes the Oxygen rich text shortcode.
*
* @param string $content The content of the Oxygen section.
*
* @return string The content with the oxy_rich_text shortcodes removed.
*/
function relevanssi_oxygen_rich_text( $content ) {
$content = preg_replace( '/\[\/?oxy_rich_text.*?\]/im', '', $content );
return $content;
}
<?php
/**
* /lib/compatibility/paidmembershippro.php
*
* Paid Membership Pro compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_paidmembershippro_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_paidmembershippro_compatibility( $post_ok, $post_id ) {
$pmpro_active = get_option( 'pmpro_filterqueries', 0 );
if ( $pmpro_active ) {
$status = relevanssi_get_post_status( $post_id );
if ( 'publish' === $status ) {
// Only apply to published posts, don't apply to drafts.
$current_user = wp_get_current_user();
$post_ok = pmpro_has_membership_access( $post_id, $current_user->ID );
}
}
return $post_ok;
}
<?php
/**
* /lib/compatibility/polylang.php
*
* Polylang compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_modify_wp_query', 'relevanssi_polylang_filter' );
add_filter( 'relevanssi_where', 'relevanssi_polylang_where_include_terms' );
add_filter( 'relevanssi_hits_filter', 'relevanssi_polylang_term_filter' );
/**
* Removes the Polylang language filters.
*
* If the Polylang allow all option ('relevanssi_polylang_all_languages') is
* enabled this removes the Polylang language filter. By default Polylang
* filters the languages using a taxonomy query.
*
* @param object $query WP_Query object we need to clean up.
*/
function relevanssi_polylang_filter( $query ) {
$polylang_allow_all = get_option( 'relevanssi_polylang_all_languages' );
if ( 'on' === $polylang_allow_all ) {
$ok_queries = array();
if ( ! isset( $query->tax_query ) ) {
// No tax query set, backing off.
return;
}
if ( ! isset( $query->tax_query->queries ) || ! is_array( $query->tax_query->queries ) ) {
// No tax query set, backing off.
return;
}
foreach ( $query->tax_query->queries as $tax_query ) {
if ( isset( $tax_query['taxonomy'] ) && 'language' !== $tax_query['taxonomy'] ) {
// Not a language tax query.
$ok_queries[] = $tax_query;
}
}
$query->tax_query->queries = $ok_queries;
if ( isset( $query->query_vars['tax_query'] ) ) {
// Tax queries can be here as well, so let's sweep this one too.
$ok_queries = array();
foreach ( $query->query_vars['tax_query'] as $tax_query ) {
if ( isset( $tax_query['taxonomy'] ) ) {
if ( 'language' !== $tax_query['taxonomy'] ) {
$ok_queries[] = $tax_query;
}
} else {
// Relation parameter most likely.
$ok_queries[] = $tax_query;
}
}
$query->query_vars['tax_query'] = $ok_queries;
}
if ( isset( $query->query_vars['taxonomy'] ) && 'language' === $query->query_vars['taxonomy'] ) {
// Another way to set the taxonomy.
unset( $query->query_vars['taxonomy'] );
unset( $query->query_vars['term'] );
}
}
return $query;
}
/**
* Allows taxonomy terms in language-restricted searches.
*
* This is a bit of a hack, where the language taxonomy WHERE clause is modified
* on the go to allow all posts with the post ID -1 (which means taxonomy terms
* and users). This may break suddenly in updates, but I haven't come up with a
* better way so far.
*
* @param string $where The WHERE clause to modify.
*
* @return string The WHERE clause with additional filtering included.
*
* @since 2.1.6
*/
function relevanssi_polylang_where_include_terms( $where ) {
global $wpdb;
$current_language = substr( get_locale(), 0, 2 );
if ( function_exists( 'pll_current_language' ) ) {
$current_language = pll_current_language();
}
$languages = get_terms( array( 'taxonomy' => 'language' ) );
$language_id = 0;
foreach ( $languages as $language ) {
if (
! is_wp_error( $language ) &&
$language instanceof WP_Term &&
$language->slug === $current_language
) {
$language_id = intval( $language->term_id );
break;
}
}
// Language ID should now have current language ID.
if ( 0 !== $language_id ) {
// Do a simple search-and-replace to modify the query.
$where = preg_replace( '/\s+/', ' ', $where );
$where = preg_replace( '/\(\s/', '(', $where );
$where = str_replace(
"AND relevanssi.doc IN (SELECT DISTINCT(tr.object_id) FROM {$wpdb->prefix}term_relationships AS tr WHERE tr.term_taxonomy_id IN ($language_id))",
"AND (relevanssi.doc IN ( SELECT DISTINCT(tr.object_id) FROM {$wpdb->prefix}term_relationships AS tr WHERE tr.term_taxonomy_id IN ($language_id)) OR (relevanssi.doc = -1))",
$where
);
}
return $where;
}
/**
* Filters out taxonomy terms in the wrong language.
*
* If all languages are not allowed, this filter goes through the results and
* removes the taxonomy terms in the wrong language. This can't be done in the
* original query because the term language information is slightly hard to
* find.
*
* @param array $hits The found posts are in $hits[0].
*
* @return array The $hits array with the unwanted posts removed.
*
* @since 2.1.6
*/
function relevanssi_polylang_term_filter( $hits ) {
$polylang_allow_all = get_option( 'relevanssi_polylang_all_languages' );
if ( 'on' !== $polylang_allow_all ) {
$current_language = substr( get_locale(), 0, 2 );
if ( function_exists( 'pll_current_language' ) ) {
$current_language = pll_current_language();
}
$accepted_hits = array();
foreach ( $hits[0] as $hit ) {
$original_hit = $hit;
if ( is_numeric( $hit ) ) {
// In case "fields" is set to "ids", fetch the post object we need.
$original_hit = $hit;
$hit = get_post( $hit );
}
if ( ! isset( $hit->post_content ) && isset( $hit->ID ) ) {
// The "fields" is set to "id=>parent".
$original_hit = $hit;
$hit = get_post( $hit->ID );
}
if ( isset( $hit->ID ) && -1 === $hit->ID && isset( $hit->term_id ) ) {
$term_id = intval( $hit->term_id );
$translations = pll_get_term_translations( $term_id );
if (
isset( $translations[ $current_language ] ) &&
$translations[ $current_language ] === $term_id
) {
$accepted_hits[] = $original_hit;
}
} else {
$accepted_hits[] = $original_hit;
}
}
$hits[0] = $accepted_hits;
}
return $hits;
}
/**
* Returns the term_taxonomy_id matching the Polylang language based on locale.
*
* @param string $locale The locale string for the language.
*
* @return int The term_taxonomy_id for the language; 0 if nothing is found.
*/
function relevanssi_get_language_term_taxonomy_id( $locale ) {
global $wpdb, $relevanssi_language_term_ids;
if ( isset( $relevanssi_language_term_ids[ $locale ] ) ) {
return $relevanssi_language_term_ids[ $locale ];
}
$languages = $wpdb->get_results(
"SELECT term_taxonomy_id, description FROM $wpdb->term_taxonomy " .
"WHERE taxonomy = 'language'"
);
$term_id = 0;
foreach ( $languages as $row ) {
$description = unserialize( $row->description ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions
if ( $description['locale'] === $locale ) {
$term_id = $row->term_taxonomy_id;
break;
}
}
$relevanssi_language_term_ids[ $locale ] = $term_id;
return $term_id;
}
<?php
/**
* /lib/compatibility/pretty-links.php
*
* Pretty Links compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_admin_search_ok', 'relevanssi_pretty_links_ok', 10, 2 );
add_filter( 'relevanssi_prevent_default_request', 'relevanssi_pretty_links_ok', 10, 2 );
add_filter( 'relevanssi_search_ok', 'relevanssi_pretty_links_ok', 10, 2 );
/**
* Returns false if the query post type is set to 'pretty-link'.
*
* @param boolean $ok Whether to allow the query.
* @param WP_Query $query The WP_Query object.
*
* @return boolean False if this is a Pretty Links query.
*/
function relevanssi_pretty_links_ok( $ok, $query ) {
if ( isset( $query->query['post_type'] ) && 'pretty-link' === $query->query['post_type'] ) {
$ok = false;
}
return $ok;
}
<?php
/**
* /lib/compatibility/product-gtin-ean-upc-isbn-for-woocommerce.php.php
*
* Adds Product GTIN (EAN, UPC, ISBN) for WooCommerce support for Relevanssi.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_action( 'pre_option_wpm_pgw_search_by_code', 'relevanssi_disable_gtin_code' );
add_filter( 'relevanssi_index_custom_fields', 'relevanssi_add_wpm_gtin_code' );
add_filter( 'option_relevanssi_index_fields', 'relevanssi_wpm_pgw_fix_none_setting' );
/**
* Disables the 'wpm_pgw_search_by_code' option.
*
* If this option is enabled, it will break Relevanssi search when there's a
* match for the code.
*
* @return string 'no'.
*/
function relevanssi_disable_gtin_code() {
return 'no';
}
/**
* Adds the `_wpm_gtin_code` to the list of indexed custom fields.
*
* @param array|boolean $fields An array of custom fields to index, or false.
*
* @return array An array of custom fields, including `_wpm_gtin_code`.
*/
function relevanssi_add_wpm_gtin_code( $fields ) {
if ( ! is_array( $fields ) ) {
$fields = array();
}
if ( ! in_array( '_wpm_gtin_code', $fields, true ) ) {
$fields[] = '_wpm_gtin_code';
}
return $fields;
}
/**
* Makes sure the GTIN code is included in the index, even when the custom field
* setting is set to 'none'.
*
* @param string $value The custom field indexing setting value. The parameter
* is ignored, Relevanssi disables this filter and then checks the option to
* see what the value is.
*
* @return string If value is undefined, it's set to '_wpm_gtin_code'.
*/
function relevanssi_wpm_pgw_fix_none_setting( $value ) {
if ( ! $value ) {
$value = '_wpm_gtin_code';
}
return $value;
}
<?php
/**
* /lib/compatibility/rankmath.php
*
* Rank Math noindex filtering function.
*
* @package Relevanssi
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_do_not_index', 'relevanssi_rankmath_noindex', 10, 2 );
add_filter( 'relevanssi_indexing_restriction', 'relevanssi_rankmath_exclude' );
add_action( 'relevanssi_indexing_tab_advanced', 'relevanssi_rankmath_form', 20 );
add_action( 'relevanssi_indexing_options', 'relevanssi_rankmath_options' );
/**
* Blocks indexing of posts marked "noindex" in the Rank Math settings.
*
* Attaches to the 'relevanssi_do_not_index' filter hook.
*
* @param boolean $do_not_index True, if the post shouldn't be indexed.
* @param integer $post_id The post ID number.
*
* @return string|boolean If the post shouldn't be indexed, this returns
* 'RankMath'. The value may also be a boolean.
*/
function relevanssi_rankmath_noindex( $do_not_index, $post_id ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $do_not_index;
}
$noindex = get_post_meta( $post_id, 'rank_math_robots', true );
if ( is_array( $noindex ) && in_array( 'noindex', $noindex, true ) ) {
$do_not_index = 'RankMath';
}
return $do_not_index;
}
/**
* Excludes the "noindex" posts from Relevanssi indexing.
*
* Adds a MySQL query restriction that blocks posts that have the Rank Math
* "rank_math_robots" setting set to something that includes "noindex".
*
* @param array $restriction An array with two values: 'mysql' for the MySQL
* query restriction to modify, 'reason' for the reason of restriction.
*/
function relevanssi_rankmath_exclude( $restriction ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $restriction;
}
global $wpdb;
// Backwards compatibility code for 2.8.0, remove at some point.
if ( is_string( $restriction ) ) {
$restriction = array(
'mysql' => $restriction,
'reason' => '',
);
}
$restriction['mysql'] .= " AND post.ID NOT IN (SELECT post_id FROM
$wpdb->postmeta WHERE meta_key = 'rank_math_robots'
AND meta_value LIKE '%noindex%' ) ";
$restriction['reason'] .= ' Rank Math';
return $restriction;
}
/**
* Prints out the form fields for disabling the feature.
*/
function relevanssi_rankmath_form() {
$seo_noindex = get_option( 'relevanssi_seo_noindex' );
$seo_noindex = relevanssi_check( $seo_noindex );
?>
<tr>
<th scope="row">
<label for='relevanssi_seo_noindex'><?php esc_html_e( 'Use Rank Math SEO noindex', 'relevanssi' ); ?></label>
</th>
<td>
<label for='relevanssi_seo_noindex'>
<input type='checkbox' name='relevanssi_seo_noindex' id='relevanssi_seo_noindex' <?php echo esc_attr( $seo_noindex ); ?> />
<?php esc_html_e( 'Use Rank Math SEO noindex.', 'relevanssi' ); ?>
</label>
<p class="description"><?php esc_html_e( 'If checked, Relevanssi will not index posts marked as "No index" in Rank Math SEO settings.', 'relevanssi' ); ?></p>
</td>
</tr>
<?php
}
/**
* Saves the SEO No index option.
*
* @param array $request An array of option values from the request.
*/
function relevanssi_rankmath_options( array $request ) {
relevanssi_update_off_or_on( $request, 'relevanssi_seo_noindex', true );
}
<?php
/**
* /lib/compatibility/restrictcontentpro.php
*
* Restrict Content Pro compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_restrictcontentpro_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_restrictcontentpro_compatibility( $post_ok, $post_id ) {
if ( ! $post_ok ) {
return $post_ok;
}
$post_ok = rcp_user_can_access( get_current_user_id(), $post_id );
return $post_ok;
}
<?php
/**
* /lib/compatibility/seoframework.php
*
* The SEO Framework noindex filtering function.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_do_not_index', 'relevanssi_seoframework_noindex', 10, 2 );
add_filter( 'relevanssi_indexing_restriction', 'relevanssi_seoframework_exclude' );
add_action( 'relevanssi_indexing_tab_advanced', 'relevanssi_seoframework_form', 20 );
add_action( 'relevanssi_indexing_options', 'relevanssi_seoframework_options' );
/**
* Blocks indexing of posts marked "Exclude this page from all search queries
* on this site." in the SEO Framework settings.
*
* Attaches to the 'relevanssi_do_not_index' filter hook.
*
* @param boolean $do_not_index True, if the post shouldn't be indexed.
* @param integer $post_id The post ID number.
*
* @return string|boolean If the post shouldn't be indexed, this returns
* 'SEO Framework'. The value may also be a boolean.
*/
function relevanssi_seoframework_noindex( $do_not_index, $post_id ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $do_not_index;
}
$noindex = get_post_meta( $post_id, 'exclude_local_search', true );
if ( '1' === $noindex ) {
$do_not_index = 'SEO Framework';
}
return $do_not_index;
}
/**
* Excludes the "noindex" posts from Relevanssi indexing.
*
* Adds a MySQL query restriction that blocks posts that have the SEO Framework
* "Exclude this page from all search queries on this site" setting set to "1"
* from indexing.
*
* @param array $restriction An array with two values: 'mysql' for the MySQL
* query restriction to modify, 'reason' for the reason of restriction.
*/
function relevanssi_seoframework_exclude( $restriction ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $restriction;
}
global $wpdb;
$restriction['mysql'] .= " AND post.ID NOT IN (SELECT post_id FROM
$wpdb->postmeta WHERE meta_key = 'exclude_local_search'
AND meta_value = '1' ) ";
$restriction['reason'] .= ' SEO Framework';
return $restriction;
}
/**
* Prints out the form fields for disabling the feature.
*/
function relevanssi_seoframework_form() {
$seo_noindex = get_option( 'relevanssi_seo_noindex' );
$seo_noindex = relevanssi_check( $seo_noindex );
?>
<tr>
<th scope="row">
<label for='relevanssi_seo_noindex'><?php esc_html_e( 'Use SEO Framework noindex', 'relevanssi' ); ?></label>
</th>
<td>
<label for='relevanssi_seo_noindex'>
<input type='checkbox' name='relevanssi_seo_noindex' id='relevanssi_seo_noindex' <?php echo esc_attr( $seo_noindex ); ?> />
<?php esc_html_e( 'Use SEO Framework noindex.', 'relevanssi' ); ?>
</label>
<p class="description"><?php esc_html_e( 'If checked, Relevanssi will not index posts marked as "No index" in SEO Framework settings.', 'relevanssi' ); ?></p>
</td>
</tr>
<?php
}
/**
* Saves the SEO No index option.
*
* @param array $request An array of option values from the request.
*/
function relevanssi_seoframework_options( array $request ) {
relevanssi_update_off_or_on( $request, 'relevanssi_seo_noindex', true );
}
<?php
/**
* /lib/compatibility/seopress.php
*
* SEOPress noindex filtering function.
*
* @package Relevanssi
* @author Benjamin Denis
* @source ./yoast-seo.php (Mikko Saari)
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_do_not_index', 'relevanssi_seopress_noindex', 10, 2 );
add_filter( 'relevanssi_indexing_restriction', 'relevanssi_seopress_exclude' );
add_action( 'relevanssi_indexing_tab_advanced', 'relevanssi_seopress_form', 20 );
add_action( 'relevanssi_indexing_options', 'relevanssi_seopress_options' );
/**
* Blocks indexing of posts marked "noindex" in the SEOPress settings.
*
* Attaches to the 'relevanssi_do_not_index' filter hook.
*
* @param boolean $do_not_index True, if the post shouldn't be indexed.
* @param integer $post_id The post ID number.
*
* @return string|boolean If the post shouldn't be indexed, this returns
* 'seopress'. The value may also be a boolean.
*/
function relevanssi_seopress_noindex( $do_not_index, $post_id ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $do_not_index;
}
$noindex = get_post_meta( $post_id, '_seopress_robots_index', true );
if ( 'yes' === $noindex ) {
$do_not_index = 'SEOPress';
}
return $do_not_index;
}
/**
* Excludes the "noindex" posts from Relevanssi indexing.
*
* Adds a MySQL query restriction that blocks posts that have the SEOPress
* "noindex" setting set to "1" from indexing.
*
* @param array $restriction An array with two values: 'mysql' for the MySQL
* query restriction to modify, 'reason' for the reason of restriction.
*/
function relevanssi_seopress_exclude( $restriction ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $restriction;
}
global $wpdb;
// Backwards compatibility code for 2.8.0, remove at some point.
if ( is_string( $restriction ) ) {
$restriction = array(
'mysql' => $restriction,
'reason' => '',
);
}
$restriction['mysql'] .= " AND post.ID NOT IN (SELECT post_id FROM
$wpdb->postmeta WHERE meta_key = '_seopress_robots_index'
AND meta_value = 'yes' ) ";
$restriction['reason'] .= 'SEOPress';
return $restriction;
}
/**
* Prints out the form fields for disabling the feature.
*/
function relevanssi_seopress_form() {
$seo_noindex = get_option( 'relevanssi_seo_noindex' );
$seo_noindex = relevanssi_check( $seo_noindex );
?>
<tr>
<th scope="row">
<label for='relevanssi_seo_noindex'><?php esc_html_e( 'Use SEOPress noindex', 'relevanssi' ); ?></label>
</th>
<td>
<label for='relevanssi_seo_noindex'>
<input type='checkbox' name='relevanssi_seo_noindex' id='relevanssi_seo_noindex' <?php echo esc_attr( $seo_noindex ); ?> />
<?php esc_html_e( 'Use SEOPress noindex.', 'relevanssi' ); ?>
</label>
<p class="description"><?php esc_html_e( 'If checked, Relevanssi will not index posts marked as "No index" in SEOPress settings.', 'relevanssi' ); ?></p>
</td>
</tr>
<?php
}
/**
* Saves the SEO No index option.
*
* @param array $request An array of option values from the request.
*/
function relevanssi_seopress_options( array $request ) {
relevanssi_update_off_or_on( $request, 'relevanssi_seo_noindex', true );
}
<?php
/**
* /lib/compatibility/simplemembership.php
*
* Simple Membership compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_simplemembership_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_simplemembership_compatibility( $post_ok, $post_id ) {
$access_ctrl = SwpmAccessControl::get_instance();
$post = get_post( $post_id );
$post_ok = $access_ctrl->can_i_read_post( $post );
return $post_ok;
}
<?php
/**
* /lib/compatibility/tablepress.php
*
* TablePress compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Enables TablePress shortcodes for Relevanssi indexing.
*
* @return null|object The TablePress controller.
*/
function relevanssi_enable_tablepress_shortcodes() {
$my_tablepress_controller = null;
if ( defined( 'TABLEPRESS_ABSPATH' ) ) {
if ( ! isset( TablePress::$model_options ) ) {
include_once TABLEPRESS_ABSPATH . 'classes/class-model.php';
include_once TABLEPRESS_ABSPATH . 'models/model-options.php';
TablePress::$model_options = new TablePress_Options_Model();
}
$my_tablepress_controller = TablePress::load_controller( 'frontend' );
$my_tablepress_controller->init_shortcodes();
}
return $my_tablepress_controller;
}
add_filter( 'relevanssi_post_content', 'relevanssi_table_filter' );
/**
* Replaces the [table_filter] shortcodes with [table].
*
* The shortcode filter extension adds a [table_filter] shortcode which is not
* compatible with Relevanssi. This function switches those to the normal
* [table] shortcode which works better.
*
* @param string $content The post content.
*
* @return string The fixed post content.
*/
function relevanssi_table_filter( $content ) {
$content = str_replace( '[table_filter', '[table', $content );
return $content;
}
<?php
/**
* /lib/compatibility/useraccessmanager.php
*
* User Access Manager compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_useraccessmanager_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_useraccessmanager_compatibility( $post_ok, $post_id ) {
$status = relevanssi_get_post_status( $post_id );
if ( 'publish' === $status ) {
// Only apply to published posts, don't apply to drafts.
// phpcs:disable WordPress.NamingConventions.ValidVariableName
global $userAccessManager;
$type = relevanssi_get_post_type( $post_id );
$post_ok = $userAccessManager->getAccessHandler()->checkObjectAccess( $type, $post_id );
// phpcs:enable WordPress.NamingConventions.ValidVariableName
}
return $post_ok;
}
<?php
/**
* /lib/compatibility/woocommerce.php
*
* WooCommerce compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_indexing_restriction', 'relevanssi_woocommerce_restriction' );
add_filter( 'relevanssi_admin_search_blocked_post_types', 'relevanssi_woocommerce_admin_search_blocked_post_types' );
add_filter( 'relevanssi_modify_wp_query', 'relevanssi_woocommerce_filters' );
/**
* This action solves the problems introduced by adjust_posts_count() in
* WooCommerce version 4.4.0.
*/
add_action( 'woocommerce_before_shop_loop', 'relevanssi_wc_reset_loop' );
RELEVANSSI_PREMIUM && add_filter( 'relevanssi_match', 'relevanssi_sku_boost' );
/**
* Resets the WC post loop in search queries.
*
* Hooks on to woocommerce_before_shop_loop.
*/
function relevanssi_wc_reset_loop() {
global $wp_query;
if ( $wp_query->is_search ) {
wc_reset_loop();
}
}
/**
* Applies the WooCommerce product visibility filter.
*
* @param array $restriction An array with two values: 'mysql' for the MySQL
* query restriction to modify, 'reason' for the reason of restriction.
*/
function relevanssi_woocommerce_restriction( $restriction ) {
// Backwards compatibility code for 2.8.0, remove at some point.
if ( is_string( $restriction ) ) {
$restriction = array(
'mysql' => $restriction,
'reason' => '',
);
}
$restriction['mysql'] .= relevanssi_woocommerce_indexing_filter();
$restriction['reason'] .= 'WooCommerce';
return $restriction;
}
/**
* WooCommerce product visibility filtering for indexing.
*
* This filter is applied before the posts are selected for indexing, so this will
* skip all the excluded posts right away.
*
* @since 4.0.9 (2.1.5)
* @global $wpdb The WordPress database interface.
*
* @return string $restriction The query restriction for the WooCommerce filtering.
*/
function relevanssi_woocommerce_indexing_filter() {
global $wpdb;
$restriction = '';
$woocommerce_blocks = array(
'outofstock' => false,
'exclude-from-catalog' => false,
'exclude-from-search' => true,
);
/**
* Controls the WooCommerce product visibility filtering.
*
* @param array $woocommerce_blocks Has three keys: 'outofstock',
* 'exclude-from-catalog' and 'exclude-from-search', matching three different
* product visibility settings. If the filter sets some of these to 'true',
* those posts will be filtered in the indexing.
*/
$woocommerce_blocks = apply_filters( 'relevanssi_woocommerce_indexing', $woocommerce_blocks );
$term_taxonomy_id_array = array();
if ( $woocommerce_blocks['outofstock'] ) {
$out_of_stock = get_term_by( 'slug', 'outofstock', 'product_visibility', OBJECT );
if ( $out_of_stock && isset( $out_of_stock->term_taxonomy_id ) ) {
$term_taxonomy_id_array[] = $out_of_stock->term_taxonomy_id;
}
}
if ( $woocommerce_blocks['exclude-from-catalog'] ) {
$exclude_from_catalog = get_term_by( 'slug', 'exclude-from-catalog', 'product_visibility', OBJECT );
if ( $exclude_from_catalog && isset( $exclude_from_catalog->term_taxonomy_id ) ) {
$term_taxonomy_id_array[] = $exclude_from_catalog->term_taxonomy_id;
}
}
if ( $woocommerce_blocks['exclude-from-search'] ) {
$exclude_from_search = get_term_by( 'slug', 'exclude-from-search', 'product_visibility', OBJECT );
if ( $exclude_from_search && isset( $exclude_from_search->term_taxonomy_id ) ) {
$term_taxonomy_id_array[] = $exclude_from_search->term_taxonomy_id;
}
}
if ( ! empty( $term_taxonomy_id_array ) ) {
$term_taxonomy_id_string = implode( ',', $term_taxonomy_id_array );
$restriction .= " AND post.ID NOT IN (SELECT object_id FROM $wpdb->term_relationships WHERE object_id = post.ID AND term_taxonomy_id IN ($term_taxonomy_id_string)) ";
}
return $restriction;
}
/**
* SKU weight boost.
*
* Increases the weight for matches in the _sku custom field. The amount of
* boost can be adjusted with the `relevanssi_sku_boost` filter hook. The
* default is 2.
*
* @param object $match_object The match object.
*
* @return object The match object.
*/
function relevanssi_sku_boost( $match_object ) {
$custom_field_detail = json_decode( $match_object->customfield_detail );
if ( null !== $custom_field_detail && isset( $custom_field_detail->_sku ) ) {
/**
* Filters the SKU boost value.
*
* @param float The boost multiplier, default 2.
*/
$match_object->weight *= apply_filters( 'relevanssi_sku_boost', 2 );
}
return $match_object;
}
/**
* Adds blocked WooCommerce post types to the list of blocked post types.
*
* Stops Relevanssi from taking over the admin search for the WooCommerce
* blocked post types using the relevanssi_admin_search_blocked_post_types
* filter hook.
*
* @param array $post_types The list of blocked post types.
* @return array
*/
function relevanssi_woocommerce_admin_search_blocked_post_types( array $post_types ): array {
$woo_post_types = array(
'shop_coupon',
'shop_order',
'shop_order_refund',
'wc_order_status',
'wc_order_email',
'shop_webhook',
);
return array_merge( $post_types, $woo_post_types );
}
/**
* Relevanssi support for WooCommerce filtering.
*
* @param WP_Query $query The WP_Query object.
* @return WP_Query The WP_Query object.
*/
function relevanssi_woocommerce_filters( $query ) {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
$min_price = isset( $_REQUEST['min_price'] ) ? intval( $_REQUEST['min_price'] ) : false;
$max_price = isset( $_REQUEST['max_price'] ) ? intval( $_REQUEST['max_price'] ) : false;
$meta_query = $query->get( 'meta_query' );
if ( $min_price ) {
$meta_query[] = array(
'key' => '_price',
'value' => $min_price,
'compare' => '>=',
'type' => 'NUMERIC',
);
}
if ( $max_price ) {
$meta_query[] = array(
'key' => '_price',
'value' => $max_price,
'compare' => '<=',
'type' => 'NUMERIC',
);
}
if ( $meta_query ) {
$query->set( 'meta_query', $meta_query );
}
foreach ( array( 'product_tag', 'product_cat', 'product_brand' ) as $taxonomy ) {
$value = isset( $_REQUEST[ $taxonomy ] ) ? intval( $_REQUEST[ $taxonomy ] ) : false;
if ( $value ) {
$tax_query = $query->get( 'tax_query' );
if ( ! is_array( $tax_query ) ) {
$tax_query = array();
}
$tax_query[] = array(
'taxonomy' => $taxonomy,
'field' => 'term_id',
'terms' => $value,
);
$query->set( 'tax_query', $tax_query );
}
}
if ( 'no' === get_option( 'woocommerce_attribute_lookup_enabled' ) ) {
return $query;
}
$chosen_attributes = array();
if ( ! empty( $_GET ) ) {
foreach ( $_GET as $key => $value ) {
if ( 0 === strpos( $key, 'filter_' ) ) {
$attribute = wc_sanitize_taxonomy_name( str_replace( 'filter_', '', $key ) );
$taxonomy = wc_attribute_taxonomy_name( $attribute );
$filter_terms = ! empty( $value ) ? explode( ',', wc_clean( wp_unslash( $value ) ) ) : array();
if ( empty( $filter_terms ) || ! taxonomy_exists( $taxonomy ) || ! wc_attribute_taxonomy_id_by_name( $attribute ) ) {
continue;
}
$query_type = ! empty( $_GET[ 'query_type_' . $attribute ] ) && in_array( $_GET[ 'query_type_' . $attribute ], array( 'and', 'or' ), true )
? wc_clean( wp_unslash( $_GET[ 'query_type_' . $attribute ] ) )
: '';
$chosen_attributes[ $taxonomy ]['terms'] = array_map( 'sanitize_title', $filter_terms );
$chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : apply_filters( 'woocommerce_layered_nav_default_query_type', 'and' );
}
}
}
$tax_query = $query->get( 'tax_query' );
if ( ! is_array( $tax_query ) ) {
$tax_query = array();
}
foreach ( $chosen_attributes as $taxonomy => $data ) {
$tax_query[] = array(
'taxonomy' => $taxonomy,
'field' => 'slug',
'terms' => $data['terms'],
'operator' => 'and' === $data['query_type'] ? 'AND' : 'IN',
'include_children' => false,
);
}
$query->set( 'tax_query', $tax_query );
return $query;
}
/**
* Provides layered navigation term counts based on Relevanssi searches.
*
* Hooks onto woocommerce_get_filtered_term_product_counts_query to provide
* accurate term counts.
*
* @param array $query The MySQL query parts.
*
* @return array The modified query.
*/
function relevanssi_filtered_term_product_counts_query( $query ) {
if ( defined( 'BeRocket_AJAX_filters_version' ) ) {
return $query;
}
global $relevanssi_variables, $wpdb;
if ( false !== stripos( $query['select'], 'product_or_parent_id' ) ) {
$query['from'] = str_replace( 'FROM ', "FROM {$relevanssi_variables['relevanssi_table']} AS relevanssi, ", $query['from'] );
$query['where'] = str_replace( 'WHERE ', " WHERE relevanssi.doc = $wpdb->posts.ID AND ", $query['where'] );
$query['where'] = preg_replace( '/\(\w+posts.post_title LIKE(.*?)\)\)/', 'relevanssi.term LIKE\1)', $query['where'] );
$query['where'] = preg_replace( array( '/OR \(\w+posts.post_excerpt LIKE .*?\)/', '/OR \(\w+posts.post_content LIKE .*?\)/' ), '', $query['where'] );
} else {
$query['select'] = 'SELECT COUNT( DISTINCT( relevanssi.doc ) ) AS term_count, terms.term_id AS term_count_id';
$query['from'] = "FROM {$relevanssi_variables['relevanssi_table']} AS relevanssi, $wpdb->posts";
$query['where'] = str_replace( 'WHERE ', " WHERE relevanssi.doc = $wpdb->posts.ID AND ", $query['where'] );
$query['where'] = preg_replace( '/\(\w+posts.post_title LIKE(.*?)\)\)/', 'relevanssi.term LIKE\1)', $query['where'] );
$query['where'] = preg_replace( array( '/OR \(\w+posts.post_excerpt LIKE .*?\)/', '/OR \(\w+posts.post_content LIKE .*?\)/' ), '', $query['where'] );
}
return $query;
}
<?php
/**
* /lib/compatibility/wp-file-download.php
*
* WP File Download compatibility features. Compatibility with WPFD checked for
* version 4.5.4.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_content_to_index', 'relevanssi_wpfd_content', 10, 2 );
add_action( 'wpfd_file_indexed', 'relevanssi_wpfd_index' );
/**
* Adds the WPFD indexed content to wpfd_file posts.
*
* Fetches the words from wpfd_words. wpfd_index.tid is the post ID, wpfd_index.id is
* then used to get the wpfd_docs.id, that is used to get the wpfd_vectors.did which
* can then be used to fetch the correct words from wpfd_words. This function is
* hooked onto relevanssi_content_to_index filter hook.
*
* @param string $content The post content as a string.
* @param object $post The post object.
*
* @return string The post content with the words added to the end.
*/
function relevanssi_wpfd_content( $content, $post ) {
$wpfd_search_config = get_option( '_wpfd_global_search_config', null );
if ( 'wpfd_file' === $post->post_type ) {
if ( $wpfd_search_config && isset( $wpfd_search_config['plain_text_search'] ) && $wpfd_search_config['plain_text_search'] ) {
global $wpdb;
$words = $wpdb->get_col(
"SELECT word
FROM {$wpdb->prefix}wpfd_words, {$wpdb->prefix}wpfd_docs, {$wpdb->prefix}wpfd_index, {$wpdb->prefix}wpfd_vectors " .
"WHERE {$wpdb->prefix}wpfd_index.tid = {$post->ID} " . // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared,WordPress.DB.PreparedSQL.NotPrepared
" AND {$wpdb->prefix}wpfd_docs.index_id = {$wpdb->prefix}wpfd_index.id
AND {$wpdb->prefix}wpfd_docs.id = {$wpdb->prefix}wpfd_vectors.did
AND {$wpdb->prefix}wpfd_vectors.wid = {$wpdb->prefix}wpfd_words.id"
);
$content .= implode( ' ', $words );
}
}
return $content;
}
/**
* Runs Relevanssi indexing after WPFD indexing is done.
*
* @param int $wpfd_id The WPFD post index.
*/
function relevanssi_wpfd_index( $wpfd_id ) {
global $wpdb;
$post_id = $wpdb->get_var( $wpdb->prepare( "SELECT tid FROM {$wpdb->prefix}wpfd_index WHERE id=%d", $wpfd_id ) );
relevanssi_insert_edit( $post_id );
}
<?php
/**
* /lib/compatibility/wp-members.php
*
* WP-Members compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_wpmembers_compatibility', 10, 2 );
/**
* Checks whether the post type is blocked.
*
* Allows all logged-in users to see posts. For non-logged-in users, checks if
* the post is blocked by the _wpmem_block custom field, or if the post type is
* blocked in the $wpmem global.
*
* @param bool $post_ok Whether the user is allowed to see the post.
* @param int|string $post_id The post ID.
*
* @return bool
*/
function relevanssi_wpmembers_compatibility( bool $post_ok, $post_id ): bool {
global $wpmem;
if ( is_user_logged_in() ) {
return $post_ok;
}
$post_meta = get_post_meta( $post_id, '_wpmem_block', true );
$post_type = isset( $wpmem->block[ relevanssi_get_post_type( $post_id ) ] )
? $wpmem->block[ relevanssi_get_post_type( $post_id ) ]
: 0;
if ( '1' === $post_meta ) {
$post_ok = false;
} elseif ( '1' === $post_type && '0' !== $post_meta ) {
$post_ok = false;
}
return $post_ok;
}
<?php
/**
* /lib/compatibility/wp-search-suggest.php
*
* WP Search Suggest compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'wpss_search_results', 'relevanssi_wpss_support', 10, 2 );
/**
* Adds Relevanssi results to WP Search Suggest dropdown.
*
* @param array $title_list List of post titles.
* @param object $query The WP_Query object.
*
* @return array List of post titles.
*/
function relevanssi_wpss_support( $title_list, $query ) {
$query = relevanssi_do_query( $query );
return wp_list_pluck( $query->posts, 'post_title' );
}
<?php
/**
* /lib/compatibility/wpjvpostreadinggroups.php
*
* WP JV Post Reading Groups compatibility features.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_post_ok', 'relevanssi_wpjvpostreadinggroups_compatibility', 10, 2 );
/**
* Checks whether the user is allowed to see the post.
*
* @param boolean $post_ok Can the post be shown to the user.
* @param int $post_id The post ID.
*
* @return boolean $post_ok True if the user is allowed to see the post,
* otherwise false.
*/
function relevanssi_wpjvpostreadinggroups_compatibility( $post_ok, $post_id ) {
$post_ok = wp_jv_prg_user_can_see_a_post( get_current_user_id(), $post_id );
return $post_ok;
}
<?php
/**
* /lib/compatibility/wpml.php
*
* WPML filtering function.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_hits_filter', 'relevanssi_wpml_filter', 9 );
add_filter( 'relevanssi_tag_before_tokenize', 'relevanssi_wpml_term_fix', 10, 4 );
add_action( 'relevanssi_pre_index_taxonomies', 'relevanssi_disable_wpml_terms' );
add_action( 'relevanssi_post_index_taxonomies', 'relevanssi_enable_wpml_terms' );
/**
* Filters posts based on WPML language.
*
* Attaches to 'relevanssi_hits_filter' to restrict WPML searches to the current
* language. Whether this filter is used or not depends on the option
* 'relevanssi_wpml_only_current'. Thanks to rvencu for the initial code.
*
* @global object $sitepress The WPML global object.
*
* @param array $data Index 0 has the array of results, index 1 has the search query.
*
* @return array $data The whole parameter array, with the filtered posts in the index 0.
*/
function relevanssi_wpml_filter( $data ) {
$filter_enabled = get_option( 'relevanssi_wpml_only_current' );
if ( 'on' === $filter_enabled ) {
$wpml_post_type_setting = apply_filters( 'wpml_setting', false, 'custom_posts_sync_option' );
$wpml_taxonomy_setting = apply_filters( 'wpml_setting', false, 'taxonomies_sync_option' );
$current_blog_language = get_bloginfo( 'language' );
$filtered_hits = array();
foreach ( $data[0] as $hit ) {
$original_hit = $hit;
$object_array = relevanssi_get_an_object( $hit );
$hit = $object_array['object'];
if ( isset( $hit->blog_id ) && function_exists( 'switch_to_blog' ) ) {
// This is a multisite search.
switch_to_blog( $hit->blog_id );
if ( function_exists( 'icl_object_id' ) ) {
// Reset the WPML cache when blog is switched, otherwise WPML
// will be confused.
global $wpml_post_translations;
$wpml_post_translations->reload();
}
}
global $sitepress;
// Check if WPML is used.
if ( function_exists( 'icl_object_id' ) && ! function_exists( 'pll_is_translated_post_type' ) ) {
if ( $sitepress->is_translated_post_type( $hit->post_type ) ) {
$fallback_to_default = false;
if ( isset( $wpml_post_type_setting[ $hit->post_type ] ) && '2' === $wpml_post_type_setting[ $hit->post_type ] ) {
$fallback_to_default = true;
}
$id = apply_filters( 'wpml_object_id', $hit->ID, $hit->post_type, $fallback_to_default );
// This is a post in a translated post type.
if ( intval( $hit->ID ) === intval( $id ) ) {
// The post exists in the current language, and can be included.
$filtered_hits[] = $original_hit;
}
} elseif ( isset( $hit->term_id ) ) {
$fallback_to_default = false;
if ( isset( $wpml_taxonomy_setting[ $hit->post_type ] ) && '2' === $wpml_taxonomy_setting[ $hit->post_type ] ) {
$fallback_to_default = true;
}
if ( ! isset( $hit->post_type ) ) {
// This is a term object, not a Relevanssi-generated post object.
$hit->post_type = $hit->taxonomy;
}
$id = apply_filters( 'wpml_object_id', $hit->term_id, $hit->post_type, $fallback_to_default );
if ( intval( $hit->term_id ) === intval( $id ) ) {
// The post exists in the current language, and can be included.
$filtered_hits[] = $original_hit;
}
} else {
// This is not a translated post type, so include all posts.
$filtered_hits[] = $original_hit;
}
} elseif ( get_bloginfo( 'language' ) === $current_blog_language ) {
// If there is no WPML but the target blog has identical language with current blog,
// we use the hits. Note en-US is not identical to en-GB!
$filtered_hits[] = $original_hit;
}
if ( isset( $hit->blog_id ) && function_exists( 'restore_current_blog' ) ) {
restore_current_blog();
}
}
// A bit of foolproofing, avoid a warning if someone passes this filter bad data.
$query = '';
if ( isset( $data[1] ) ) {
$query = $data[1];
}
return array( $filtered_hits, $query );
}
return $data;
}
/**
* Fixes translated term indexing for WPML.
*
* WPML indexed translated terms based on current admin language, not the post
* language. This filter changes the term indexing to match the post language.
*
* @param string $term_content All terms in the taxonomy as a string.
* @param array $terms All the term objects in the current taxonomy.
* @param string $taxonomy The taxonomy name.
* @param int $post_id The post ID.
*
* @return string The term names as a string.
*/
function relevanssi_wpml_term_fix( string $term_content, array $terms, string $taxonomy, int $post_id ) {
$post_language = apply_filters( 'wpml_post_language_details', null, $post_id );
if ( ! is_wp_error( $post_language ) ) {
$term_content = '';
global $sitepress;
remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
foreach ( $terms as $term ) {
$term = get_term(
apply_filters(
'wpml_object_id',
$term->term_id,
$taxonomy,
true,
$post_language['language_code']
),
$taxonomy
);
$term_content .= ' ' . $term->name;
}
add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
}
return $term_content;
}
/**
* Disables WPML term filtering.
*
* This function disables the WPML term filtering, so that Relevanssi can index
* the terms in the correct language.
*/
function relevanssi_disable_wpml_terms() {
global $sitepress;
remove_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1 );
}
/**
* Enables WPML term filtering.
*
* This function enables the WPML term filtering.
*/
function relevanssi_enable_wpml_terms() {
global $sitepress;
add_filter( 'get_term', array( $sitepress, 'get_term_adjust_id' ), 1, 1 );
}
<?php
/**
* /lib/compatibility/yoast-seo.php
*
* Yoast SEO noindex filtering function.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_filter( 'relevanssi_do_not_index', 'relevanssi_yoast_noindex', 10, 2 );
add_filter( 'relevanssi_indexing_restriction', 'relevanssi_yoast_exclude' );
add_action( 'relevanssi_indexing_tab_advanced', 'relevanssi_yoast_form', 20 );
add_action( 'relevanssi_indexing_options', 'relevanssi_yoast_options' );
/**
* Blocks indexing of posts marked "noindex" in the Yoast SEO settings.
*
* Attaches to the 'relevanssi_do_not_index' filter hook.
*
* @param boolean $do_not_index True, if the post shouldn't be indexed.
* @param integer $post_id The post ID number.
*
* @return string|boolean If the post shouldn't be indexed, this returns
* 'yoast_seo'. The value may also be a boolean.
*/
function relevanssi_yoast_noindex( $do_not_index, $post_id ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $do_not_index;
}
$noindex = get_post_meta( $post_id, '_yoast_wpseo_meta-robots-noindex', true );
if ( '1' === $noindex ) {
$do_not_index = 'Yoast SEO';
}
return $do_not_index;
}
/**
* Excludes the "noindex" posts from Relevanssi indexing.
*
* Adds a MySQL query restriction that blocks posts that have the Yoast SEO
* "noindex" setting set to "1" from indexing.
*
* @param array $restriction An array with two values: 'mysql' for the MySQL
* query restriction to modify, 'reason' for the reason of restriction.
*/
function relevanssi_yoast_exclude( $restriction ) {
if ( 'on' !== get_option( 'relevanssi_seo_noindex' ) ) {
return $restriction;
}
global $wpdb;
// Backwards compatibility code for 2.8.0, remove at some point.
if ( is_string( $restriction ) ) {
$restriction = array(
'mysql' => $restriction,
'reason' => '',
);
}
$restriction['mysql'] .= " AND post.ID NOT IN (SELECT post_id FROM
$wpdb->postmeta WHERE meta_key = '_yoast_wpseo_meta-robots-noindex'
AND meta_value = '1' ) ";
$restriction['reason'] .= ' Yoast SEO';
return $restriction;
}
/**
* Prints out the form fields for disabling the feature.
*/
function relevanssi_yoast_form() {
$seo_noindex = get_option( 'relevanssi_seo_noindex' );
$seo_noindex = relevanssi_check( $seo_noindex );
?>
<tr>
<th scope="row">
<label for='relevanssi_seo_noindex'><?php esc_html_e( 'Use Yoast SEO noindex', 'relevanssi' ); ?></label>
</th>
<td>
<label for='relevanssi_seo_noindex'>
<input type='checkbox' name='relevanssi_seo_noindex' id='relevanssi_seo_noindex' <?php echo esc_attr( $seo_noindex ); ?> />
<?php esc_html_e( 'Use Yoast SEO noindex.', 'relevanssi' ); ?>
</label>
<p class="description"><?php esc_html_e( 'If checked, Relevanssi will not index posts marked as "No index" in Yoast SEO settings.', 'relevanssi' ); ?></p>
</td>
</tr>
<?php
}
/**
* Saves the SEO No index option.
*
* @param array $request An array of option values from the request.
*/
function relevanssi_yoast_options( array $request ) {
relevanssi_update_off_or_on( $request, 'relevanssi_seo_noindex', true );
}
<?php
/**
* /lib/debug.php
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_action( 'wp', 'relevanssi_debug_post' );
/**
* Checks if Relevanssi debug mode is enabled.
*
* Debug mode is enabled by setting RELEVANSSI_DEBUG to true or with the
* 'relevanssi_debug' query parameter if the debug mode is allowed from the
* settings.
*
* @return boolean True if debug mode is enabled, false if not.
*/
function relevanssi_is_debug(): bool {
$debug = false;
if ( defined( 'RELEVANSSI_DEBUG' ) && RELEVANSSI_DEBUG ) {
$debug = true;
}
if ( isset( $_REQUEST['relevanssi_debug'] ) && 'on' === get_option( 'relevanssi_debugging_mode' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
$debug = true;
}
return $debug;
}
/**
* Adds the debug information to the search results.
*
* Displays the found posts.
*
* @param array $posts The search results.
*/
function relevanssi_debug_posts( $posts ) {
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<h2>Posts</h2>';
foreach ( $posts as $post ) {
if ( ! is_object( $post ) ) {
echo "$post\n";
} else {
echo "<p>$post->ID: $post->post_title<br />";
echo "$post->post_type$post->post_status$post->relevance_score<br />";
property_exists( $post, 'relevanssi_link' ) && print( "relevanssi_link: $post->relevanssi_link<br />" );
echo 'the_permalink(): ';
the_permalink( $post->ID );
echo '<br />get_permalink(): ' . get_permalink( $post );
echo '</p>';
}
}
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Prints out an array in a preformatted block.
*
* @param array $array_value The array to print.
* @param string $title The title for the array.
*/
function relevanssi_debug_array( $array_value, $title ) {
echo '<h2>' . esc_html( $title ) . '</h2>';
echo '<pre>';
print_r( $array_value ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r
echo '</pre>';
}
/**
* Prints out a string in a preformatted block.
*
* @param string $str The string to print.
* @param string $title The title for the string.
*/
function relevanssi_debug_string( $str, $title ) {
echo '<h2>' . esc_html( $title ) . '</h2>';
echo '<pre>' . esc_html( $str ) . '</pre>';
}
/**
* Prints out the Relevanssi debug information for a post.
*
* This function is called by the 'wp' action, so it's executed on every page
* load.
*/
function relevanssi_debug_post() {
if ( ! is_singular() || ! relevanssi_is_debug() ) {
return;
}
global $post;
echo '<h1>' . esc_html( $post->post_title ) . ' (' . intval( $post->ID ) . ')</h1>';
echo '<h2>Index</h2>';
echo relevanssi_generate_how_relevanssi_sees( $post->ID, true, 'post' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<h2>Database</h2>';
echo '<pre>' . relevanssi_generate_db_post_view( $post->ID ) . '</pre>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
relevanssi_debug_array( get_post_meta( $post->ID ), 'Post meta' );
exit();
}
/**
* Generates the debugging view for a post.
*
* @param int $post_id ID of the post.
*
* @return string The debugging view in a div container.
*/
function relevanssi_generate_db_post_view( int $post_id ) {
global $wpdb;
$element = '<div id="relevanssi_db_view_container">';
$post_object = get_post( $post_id );
if ( ! $post_object ) {
$element .= '<p>' . esc_html__( 'Post not found', 'relevanssi' ) . '</p>';
$element .= '</div>';
return $element;
}
$element .= '<p>' . esc_html( $post_object->post_content ) . '</p>';
$element .= '</div>';
return $element;
}
/**
* Prints out the Relevanssi debug information for search settings.
*/
function relevanssi_debug_search_settings() {
// phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
echo '<h2>Relevanssi searching settings</h2>';
echo '<p>';
$value = get_option( 'relevanssi_fuzzy' );
echo "relevanssi_fuzzy: $value<br />";
$value = get_option( 'relevanssi_implicit_operator' );
echo "relevanssi_implicit_operator: $value<br />";
$value = get_option( 'relevanssi_disable_or_fallback' );
echo "relevanssi_disable_or_fallback: $value<br />";
$value = get_option( 'relevanssi_throttle' );
echo "relevanssi_throttle: $value<br />";
$value = get_option( 'relevanssi_throttle_limit' );
echo "relevanssi_throttle_limit: $value<br />";
$value = get_option( 'relevanssi_default_orderby' );
echo "relevanssi_default_orderby: $value<br />";
echo '</p>';
// phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Returns true if RELEVANSSI_DEBUG, WP_DEBUG and WP_DEBUG_DISPLAY are true.
*
* @return bool True if debug mode is on.
*/
function relevanssi_log_debug(): bool {
return defined( 'RELEVANSSI_DEBUG' ) && RELEVANSSI_DEBUG
&& defined( 'WP_DEBUG' ) && WP_DEBUG
&& defined( 'WP_DEBUG_DISPLAY' ) && WP_DEBUG_DISPLAY;
}
<?php
/**
* /lib/didyoumean.php
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Generates the Did you mean suggestions.
*
* A wrapper function that prints out the Did you mean suggestions. If Premium
* is available, will use relevanssi_premium_didyoumean(), otherwise the
* relevanssi_simple_didyoumean() is used.
*
* @param string $query The query.
* @param string $pre Printed out before the suggestion.
* @param string $post Printed out after the suggestion.
* @param int $n Maximum number of search results found for the
* suggestions to show up. Default 5.
* @param boolean $echoed If true, echo out. Default true.
*
* @return string|null The suggestion HTML element.
*/
function relevanssi_didyoumean( $query, $pre, $post, $n = 5, $echoed = true ) {
if ( function_exists( 'relevanssi_premium_didyoumean' ) ) {
$result = relevanssi_premium_didyoumean( $query, $pre, $post, $n );
} else {
$result = relevanssi_simple_didyoumean( $query, $pre, $post, $n );
}
if ( $echoed ) {
echo $result; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
return $result;
}
/**
* Generates the Did you mean suggestions HTML code.
*
* Uses relevanssi_simple_generate_suggestion() to come up with a suggestion,
* then wraps that up with HTML code.
*
* @global object $wpdb The WordPress database interface.
* @global array $relevanssi_variables The Relevanssi global variables.
* @global object $wp_query The WP_Query object.
*
* @param string $query The query.
* @param string $pre Printed out before the suggestion.
* @param string $post Printed out after the suggestion.
* @param int $n Maximum number of search results found for the
* suggestions to show up. Default 5.
*
* @return string|null The suggestion HTML code, null if nothing found.
*/
function relevanssi_simple_didyoumean( $query, $pre, $post, $n = 5 ) {
global $wp_query;
$total_results = $wp_query->found_posts;
if ( $total_results > $n ) {
return null;
}
$suggestion = relevanssi_simple_generate_suggestion( $query );
$result = null;
if ( $suggestion ) {
$url = trailingslashit( get_bloginfo( 'url' ) );
$url = esc_attr(
add_query_arg(
array( 's' => rawurlencode( $suggestion ) ),
$url
)
);
/**
* Filters the 'Did you mean' suggestion URL.
*
* @param string $url The URL for the suggested search query.
* @param string $query The search query.
* @param string $suggestion The suggestion.
*/
$url = apply_filters(
'relevanssi_didyoumean_url',
$url,
$query,
$suggestion
);
// Escape the suggestion to avoid XSS attacks.
$suggestion = htmlspecialchars( $suggestion );
/**
* Filters the complete 'Did you mean' suggestion.
*
* @param string The suggestion HTML code.
*/
$result = apply_filters(
'relevanssi_didyoumean_suggestion',
"$pre<a href='$url'>$suggestion</a>$post"
);
}
return $result;
}
/**
* Generates the 'Did you mean' suggestions. Can be used to correct any queries.
*
* Uses the Relevanssi search logs as source material for corrections. If there
* are no logged search queries, can't do anything.
*
* @global object $wpdb The WordPress database interface.
* @global array $relevanssi_variables The Relevanssi global variables, used
* for table names.
*
* @param string $query The query to correct.
*
* @return string Corrected query, empty if nothing found.
*/
function relevanssi_simple_generate_suggestion( $query ) {
global $wpdb, $relevanssi_variables;
/**
* The minimum limit of occurrances to include a word.
*
* To save resources, only words with more than this many occurrances are
* fed for the spelling corrector. If there are problems with the spelling
* corrector, increasing this value may fix those problems.
*
* @param int $number The number of occurrances must be more than this
* value, default 2.
*/
$count = apply_filters( 'relevanssi_get_words_having', 2 );
if ( ! is_numeric( $count ) ) {
$count = 2;
}
$q = 'SELECT query, count(query) as c, AVG(hits) as a FROM '
. $relevanssi_variables['log_table'] . ' WHERE hits > ' . $count
. ' GROUP BY query ORDER BY count(query) DESC';
/**
* Filters the MySQL query used to fetch potential suggestions from the log.
*
* @param string $q MySQL query for fetching the suggestions.
*/
$q = apply_filters( 'relevanssi_didyoumean_query', $q );
$data = get_transient( 'relevanssi_didyoumean_query' );
if ( empty( $data ) ) {
$data = $wpdb->get_results( $q ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
set_transient( 'relevanssi_didyoumean_query', $data, MONTH_IN_SECONDS );
}
$query = htmlspecialchars_decode( $query, ENT_QUOTES );
$tokens = relevanssi_tokenize( $query, true, -1, 'search_query' );
$suggestions_made = false;
$suggestion = '';
foreach ( $tokens as $token => $count ) {
/**
* Filters the tokens for Did you mean suggestions.
*
* You can use this filter hook to modify the tokens before Relevanssi
* tries to come up with Did you mean suggestions for them. If you
* return an empty string, the token will be skipped and no suggestion
* will be made for the token.
*
* @param string $token An individual word from the search query.
*
* @return string The token.
*/
$token = apply_filters( 'relevanssi_didyoumean_token', trim( $token ) );
if ( ! $token ) {
continue;
}
$closest = '';
$distance = -1;
foreach ( $data as $row ) {
if ( $row->c < 2 ) {
break;
}
if ( $token === $row->query ) {
$closest = '';
break;
} elseif ( strlen( $token ) < 255 && strlen( $row->query ) < 255 ) {
// The levenshtein() function has a max length of 255
// characters. The function uses strlen(), so we must use
// too, instead of relevanssi_strlen().
$lev = levenshtein( $token, $row->query );
if ( $lev < 3 && ( $lev < $distance || $distance < 0 ) ) {
if ( $row->a > 0 ) {
$distance = $lev;
$closest = $row->query;
if ( $lev < 2 ) {
break; // get the first with distance of 1 and go.
}
}
}
}
}
if ( ! empty( $closest ) ) {
$query = str_ireplace( $token, $closest, $query, $replacement_count );
if ( $replacement_count > 0 ) {
$suggestions_made = true;
}
}
}
if ( $suggestions_made ) {
$suggestion = $query;
}
return $suggestion;
}
<?php
/**
* /lib/install.php
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Installs Relevanssi on a new plugin if Relevanssi is network active.
*
* Hooks on to 'wpmu_new_blog' and 'wp_initialize_site' action hooks and runs
* '_relevanssi_install' on the new blog.
*
* @param int|object $blog Either the blog ID (if 'wpmu_new_blog') or new site
* object (if 'wp_initialize_site').
*/
function relevanssi_new_blog( $blog ) {
if ( is_int( $blog ) ) {
$blog_id = $blog;
} else {
$blog_id = $blog->id;
}
if ( is_plugin_active_for_network( 'relevanssi-premium/relevanssi.php' ) || is_plugin_active_for_network( 'relevanssi/relevanssi.php' ) ) {
switch_to_blog( $blog_id );
_relevanssi_install();
restore_current_blog();
}
}
/**
* Runs _relevanssi_install() on one blog or for the whole network.
*
* If Relevanssi is network active, this installs Relevanssi on all blogs in the
* network, running the _relevanssi_install() function.
*
* @param boolean $network_wide If true, install on all sites. Default false.
*/
function relevanssi_install( $network_wide = false ) {
if ( $network_wide ) {
$args = array(
'spam' => 0,
'deleted' => 0,
'archived' => 0,
'fields' => 'ids',
);
$blog_ids = get_sites( $args );
foreach ( $blog_ids as $blog_id ) {
switch_to_blog( $blog_id );
_relevanssi_install();
restore_current_blog();
}
} else {
_relevanssi_install();
}
}
/**
* Installs Relevanssi on the blog.
*
* Adds Relevanssi options and sets their default values and generates the
* database tables.
*
* @global array $relevanssi_variables The global Relevanssi variables array.
*/
function _relevanssi_install() {
global $relevanssi_variables;
add_option( 'relevanssi_admin_search', 'off' );
add_option( 'relevanssi_bg_col', '#ffaf75' );
add_option( 'relevanssi_cat', '0' );
add_option( 'relevanssi_class', 'relevanssi-query-term' );
add_option( 'relevanssi_comment_boost', $relevanssi_variables['comment_boost_default'] );
add_option( 'relevanssi_content_boost', $relevanssi_variables['content_boost_default'] );
add_option( 'relevanssi_css', 'text-decoration: underline; color: #ff0000' );
add_option( 'relevanssi_db_version', '0' );
add_option( 'relevanssi_debugging_mode', 'off' );
add_option( 'relevanssi_default_orderby', 'relevance' );
add_option( 'relevanssi_disable_or_fallback', 'off' );
add_option( 'relevanssi_exact_match_bonus', 'on' );
add_option( 'relevanssi_excat', '0' );
add_option( 'relevanssi_excerpt_allowable_tags', '' );
add_option( 'relevanssi_excerpt_custom_fields', 'off' );
add_option( 'relevanssi_excerpt_length', '30' );
add_option( 'relevanssi_excerpt_specific_fields', 'off' );
add_option( 'relevanssi_excerpt_type', 'words' );
add_option( 'relevanssi_excerpts', 'on' );
add_option( 'relevanssi_exclude_posts', '' );
add_option( 'relevanssi_expand_highlights', 'off' );
add_option( 'relevanssi_expand_shortcodes', 'on' );
add_option( 'relevanssi_extag', '0' );
add_option( 'relevanssi_fuzzy', 'always' );
add_option( 'relevanssi_highlight', 'strong' );
add_option( 'relevanssi_highlight_comments', 'off' );
add_option( 'relevanssi_highlight_docs', 'off' );
add_option( 'relevanssi_hilite_title', '' );
add_option( 'relevanssi_implicit_operator', 'OR' );
add_option( 'relevanssi_index_author', '' );
add_option( 'relevanssi_index_comments', 'none' );
add_option( 'relevanssi_index_excerpt', 'off' );
add_option( 'relevanssi_index_fields', '' );
add_option( 'relevanssi_index_image_files', 'on' );
add_option( 'relevanssi_index_post_types', array( 'post', 'page' ) );
add_option( 'relevanssi_index_taxonomies_list', array() );
add_option( 'relevanssi_indexed', '' );
add_option( 'relevanssi_log_queries', 'off' );
add_option( 'relevanssi_log_queries_with_ip', 'off' );
add_option( 'relevanssi_min_word_length', '3' );
add_option( 'relevanssi_omit_from_logs', '' );
add_option( 'relevanssi_polylang_all_languages', 'off' );
add_option(
'relevanssi_punctuation',
array(
'quotes' => 'replace',
'hyphens' => 'replace',
'ampersands' => 'replace',
)
);
add_option( 'relevanssi_respect_exclude', 'on' );
add_option( 'relevanssi_seo_noindex', 'on' );
add_option( 'relevanssi_show_matches', '' );
add_option( 'relevanssi_show_matches_text', '(Search hits: %body% in body, %title% in title, %categories% in categories, %tags% in tags, %taxonomies% in other taxonomies, %comments% in comments. Score: %score%)' );
add_option( 'relevanssi_stopwords', array() );
add_option( 'relevanssi_synonyms', array() );
add_option( 'relevanssi_throttle', 'on' );
add_option( 'relevanssi_throttle_limit', '500' );
add_option( 'relevanssi_title_boost', $relevanssi_variables['title_boost_default'] );
add_option( 'relevanssi_txt_col', '#ff0000' );
add_option( 'relevanssi_wpml_only_current', 'on' );
if ( function_exists( 'relevanssi_premium_install' ) ) {
// Do some Relevanssi Premium additions.
relevanssi_premium_install();
}
/**
* Runs after Relevanssi options are added in the installation process.
*
* This action hook can be used to adjust the options to set your own default
* settings, for example.
*/
do_action( 'relevanssi_update_options' );
relevanssi_create_database_tables( $relevanssi_variables['database_version'] );
}
<?php
/**
* /lib/phrases.php
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Extracts phrases from the search query.
*
* Finds all phrases wrapped in quotes (curly or straight) from the search
* query.
*
* @param string $query The search query.
*
* @return array An array of phrases (strings).
*/
function relevanssi_extract_phrases( string $query ) {
// iOS uses “” or „“ as the default quotes, so Relevanssi needs to
// understand those as well.
$normalized_query = str_replace( array( '”', '“', '„' ), '"', $query );
$pos = relevanssi_stripos( $normalized_query, '"' );
$phrases = array();
while ( false !== $pos ) {
if ( $pos + 2 > relevanssi_strlen( $normalized_query ) ) {
$pos = false;
continue;
}
$start = relevanssi_stripos( $normalized_query, '"', $pos );
$end = false;
if ( false !== $start ) {
$end = relevanssi_stripos( $normalized_query, '"', $start + 2 );
}
if ( false === $end ) {
// Just one " in the query.
$pos = $end;
continue;
}
$phrase = relevanssi_substr(
$normalized_query,
$start + 1,
$end - $start - 1
);
$phrase = trim( $phrase );
// Do not count single-word phrases as phrases.
if ( relevanssi_is_multiple_words( $phrase ) ) {
$phrases[] = $phrase;
}
$pos = $end + 1;
}
return $phrases;
}
/**
* Generates the MySQL code for restricting the search to phrase hits.
*
* This function uses relevanssi_extract_phrases() to figure out the phrases in
* the search query, then generates MySQL queries to restrict the search to the
* posts containing those phrases in the title, content, taxonomy terms or meta
* fields.
*
* @global array $relevanssi_variables The global Relevanssi variables.
*
* @param string $search_query The search query.
* @param string $operator The search operator (AND or OR).
*
* @return string $queries If not phrase hits are found, an empty string;
* otherwise MySQL queries to restrict the search.
*/
function relevanssi_recognize_phrases( $search_query, $operator = 'AND' ) {
global $relevanssi_variables;
$phrases = relevanssi_extract_phrases( $search_query );
$all_queries = array();
if ( 0 === count( $phrases ) ) {
return $all_queries;
}
/**
* Filters the custom fields for phrase matching.
*
* If you don't want the phrase matching to target custom fields, you can
* have this filter hook return an empty array.
*
* @param array $custom_fields An array of custom field names.
*/
$custom_fields = apply_filters( 'relevanssi_phrase_custom_fields', relevanssi_get_custom_fields() );
/**
* Filters the taxonomies for phrase matching.
*
* If you don't want the phrase matching to target taxonomies, you can have
* this filter hook return an empty array.
*
* @param array $taxonomies An array of taxonomy names.
*/
$taxonomies = apply_filters( 'relevanssi_phrase_taxonomies', get_option( 'relevanssi_index_taxonomies_list', array() ) );
$excerpts = get_option( 'relevanssi_index_excerpt', 'off' );
$phrase_queries = array();
$queries = array();
if (
isset( $relevanssi_variables['phrase_targets'] ) &&
is_array( $relevanssi_variables['phrase_targets'] )
) {
$non_targeted_phrases = array();
foreach ( $phrases as $phrase ) {
if (
isset( $relevanssi_variables['phrase_targets'][ $phrase ] ) &&
function_exists( 'relevanssi_targeted_phrases' )
) {
$queries = relevanssi_targeted_phrases( $phrase );
} else {
$non_targeted_phrases[] = $phrase;
}
}
$phrases = $non_targeted_phrases;
}
$queries = array_merge(
$queries,
relevanssi_generate_phrase_queries(
$phrases,
$taxonomies,
$custom_fields,
$excerpts
)
);
$phrase_queries = array();
foreach ( $queries as $phrase => $p_queries ) {
$pq_array = array();
foreach ( $p_queries as $query ) {
$pq_array[] = "relevanssi.{$query['target']} IN {$query['query']}";
}
$p_queries = implode( ' OR ', $pq_array );
$all_queries[] = "($p_queries)";
$phrase_queries[ $phrase ] = $p_queries;
}
$operator = strtoupper( $operator );
if ( 'AND' !== $operator && 'OR' !== $operator ) {
$operator = 'AND';
}
if ( ! empty( $all_queries ) ) {
$all_queries = ' AND ( ' . implode( ' ' . $operator . ' ', $all_queries ) . ' ) ';
}
return array(
'and' => $all_queries,
'or' => $phrase_queries,
);
}
/**
* Generates the phrase queries from phrases.
*
* Takes in phrases and a bunch of parameters and generates the MySQL queries
* that restrict the main search query to only posts that have the phrase.
*
* @param array $phrases A list of phrases to handle.
* @param array $taxonomies An array of taxonomy names to use.
* @param array|string $custom_fields A list of custom field names to use,
* "visible", or "all".
* @param string $excerpts If 'on', include excerpts.
*
* @global object $wpdb The WordPress database interface.
*
* @return array An array of queries sorted by phrase.
*/
function relevanssi_generate_phrase_queries(
array $phrases,
array $taxonomies,
$custom_fields,
string $excerpts
): array {
global $wpdb;
$status = relevanssi_valid_status_array();
// Add "inherit" to the list of allowed statuses to include attachments.
if ( ! strstr( $status, 'inherit' ) ) {
$status .= ",'inherit'";
}
$phrase_queries = array();
foreach ( $phrases as $phrase ) {
$queries = array();
$phrase = $wpdb->esc_like( $phrase );
$phrase = str_replace( array( '‘', '’', "'", '"', '”', '“', '“', '„', '´' ), '_', $phrase );
$title_phrase = $phrase;
$phrase = htmlspecialchars( $phrase );
/**
* Filters each phrase before it's passed through esc_sql() and used in
* the MySQL query. You can use this filter hook to for example run
* htmlentities() on the phrase in case your database needs that.
*
* @param string $phrase The phrase after quotes are replaced with a
* MySQL wild card and the phrase has been passed through esc_like() and
* htmlspecialchars().
*/
$phrase = esc_sql( apply_filters( 'relevanssi_phrase', $phrase ) );
$excerpt = '';
if ( 'on' === $excerpts ) {
$excerpt = "OR post_excerpt LIKE '%$phrase%'";
}
$query = "(SELECT ID FROM $wpdb->posts
WHERE (post_content LIKE '%$phrase%'
OR post_title LIKE '%$title_phrase%' $excerpt)
AND post_status IN ($status))";
$queries[] = array(
'query' => $query,
'target' => 'doc',
);
if ( ! empty( $taxonomies ) ) {
$taxonomies_escaped = implode( "','", array_map( 'esc_sql', $taxonomies ) );
$taxonomies_sql = "AND s.taxonomy IN ('$taxonomies_escaped')";
$query = "(SELECT ID FROM
$wpdb->posts as p,
$wpdb->term_relationships as r,
$wpdb->term_taxonomy as s, $wpdb->terms as t
WHERE r.term_taxonomy_id = s.term_taxonomy_id
AND s.term_id = t.term_id AND p.ID = r.object_id
$taxonomies_sql
AND t.name LIKE '%$phrase%' AND p.post_status IN ($status))";
$queries[] = array(
'query' => $query,
'target' => 'doc',
);
}
if ( ! empty( $custom_fields ) ) {
$keys = '';
if ( is_array( $custom_fields ) ) {
if ( ! in_array( '_relevanssi_pdf_content', $custom_fields, true ) ) {
array_push( $custom_fields, '_relevanssi_pdf_content' );
}
if ( strpos( implode( ' ', $custom_fields ), '%' ) ) {
// ACF repeater fields involved.
$custom_fields_regexp = str_replace( '%', '.+', implode( '|', $custom_fields ) );
$keys = "AND m.meta_key REGEXP ('$custom_fields_regexp')";
} else {
$custom_fields_escaped = implode(
"','",
array_map(
'esc_sql',
$custom_fields
)
);
$keys = "AND m.meta_key IN ('$custom_fields_escaped')";
}
}
if ( 'visible' === $custom_fields ) {
$keys = "AND (m.meta_key NOT LIKE '\_%' OR m.meta_key = '_relevanssi_pdf_content')";
}
$query = "(SELECT ID
FROM $wpdb->posts AS p, $wpdb->postmeta AS m
WHERE p.ID = m.post_id
$keys
AND m.meta_value LIKE '%$phrase%'
AND p.post_status IN ($status))";
$queries[] = array(
'query' => $query,
'target' => 'doc',
);
}
/**
* Filters the phrase queries.
*
* Relevanssi Premium uses this filter hook to add Premium-specific
* phrase queries.
*
* @param array $queries The MySQL queries for phrase matching.
* @param string $phrase The current phrase.
* @param string $status A string containing post statuses.
*
* @return array An array of phrase queries, where each query is an
* array that has the actual MySQL query in 'query' and the target
* column ('doc' or 'item') in the Relevanssi index table in 'target'.
*/
$queries = apply_filters( 'relevanssi_phrase_queries', $queries, $phrase, $status );
$phrase_queries[ $phrase ] = $queries;
}
return $phrase_queries;
}
<?php
/**
* /lib/privacy.php
*
* Privacy policy features.
*
* @since 4.0.10
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_action( 'admin_init', 'relevanssi_register_privacy_policy' );
add_filter( 'wp_privacy_personal_data_exporters', 'relevanssi_register_exporter', 10 );
add_filter( 'wp_privacy_personal_data_erasers', 'relevanssi_register_eraser', 10 );
/**
* Registers the Relevanssi privacy policy information.
*
* @since 4.0.10
*/
function relevanssi_register_privacy_policy() {
if ( ! function_exists( 'wp_add_privacy_policy_content' ) ) {
return;
}
$name = 'Relevanssi';
if ( RELEVANSSI_PREMIUM ) {
$name .= ' Premium';
}
$content = '';
if ( 'on' === get_option( 'relevanssi_log_queries' ) ) {
$content = '<h2>' . __( 'What personal data we collect and why we collect it' ) . '</h2>';
if ( 'on' === get_option( 'relevanssi_log_queries_with_ip' ) ) {
$content .= '<h3>' . __( 'IP address for searches', 'relevanssi' ) . '</h3>';
$content .= '<p>' . __( 'All searches performed using the internal site search are logged in the database, including the following information: the search query, the number of hits found, user ID for users who are logged in, date and time and the IP address. The IP address is stored for security and auditing purposes.', 'relevanssi' ) . '</p>';
} else {
$content .= '<p>' . __( 'All searches performed using the internal site search are logged in the database, including the following information: the search query, the number of hits found, user ID for users who are logged in and date and time.', 'relevanssi' ) . '</p>';
}
$interval = intval( get_option( 'relevanssi_trim_logs' ) );
$content .= '<h2>' . __( 'How long we retain your data' ) . '</h2>';
if ( $interval > 0 ) {
// Translators: %d is the number of days.
$content .= '<p>' . sprintf( __( 'The search logs are stored for %d days before they are automatically removed.', 'relevanssi' ), $interval ) . '</p>';
} else {
$content .= '<p>' . __( 'The search logs are stored indefinitely.', 'relevanssi' ) . '</p>';
}
}
wp_add_privacy_policy_content( $name, $content );
}
/**
* Registers the Relevanssi data exporter.
*
* @since 4.0.10
*
* @param array $exporters The exporters array.
*
* @return array The exporters array, with Relevanssi added.
*/
function relevanssi_register_exporter( $exporters ) {
$exporters['relevanssi'] = array(
'exporter_friendly_name' => __( 'Relevanssi Search Logs' ),
'callback' => 'relevanssi_privacy_exporter',
);
return $exporters;
}
/**
* Registers the Relevanssi data eraser.
*
* @since 4.0.10
*
* @param array $erasers The erasers array.
*
* @return array The erasers array, with Relevanssi added.
*/
function relevanssi_register_eraser( $erasers ) {
$erasers['relevanssi'] = array(
'eraser_friendly_name' => __( 'Relevanssi Search Logs' ),
'callback' => 'relevanssi_privacy_eraser',
);
return $erasers;
}
/**
* Exports the log entries based on user email.
*
* @since 4.0.10
*
* @param string $email_address The user email address.
* @param int $page The page number, default 1.
*
* @return array Two-item array: 'done' is a Boolean that tells if the exporter is
* done, 'data' contains the actual data.
*/
function relevanssi_privacy_exporter( $email_address, $page = 1 ) {
$user = get_user_by( 'email', $email_address );
if ( ! $user ) {
// No user found.
return array(
'done' => true,
'data' => array(),
);
} else {
$result = relevanssi_export_log_data( $user->ID, $page );
return array(
'done' => $result['done'],
'data' => $result['data'],
);
}
}
/**
* Erases the log entries based on user email.
*
* @since 4.0.10
*
* @param string $email_address The user email address.
* @param int $page The page number, default 1.
*
* @return array Four-item array: 'items_removed' is a Boolean that tells if
* something was removed, 'done' is a Boolean that tells if the eraser is done,
* 'items_retained' is always false, 'messages' is always an empty array.
*/
function relevanssi_privacy_eraser( $email_address, $page = 1 ) {
$user = get_user_by( 'email', $email_address );
if ( ! $user ) {
// No user found.
return array(
'items_removed' => false,
'done' => true,
'items_retained' => false,
'messages' => array(),
);
} else {
$result = relevanssi_erase_log_data( $user->ID, $page );
return array(
'items_removed' => $result['items_removed'],
'done' => $result['done'],
'items_retained' => false,
'messages' => array(),
);
}
}
<?php
/**
* /lib/shortcodes.php
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
add_shortcode( 'search', 'relevanssi_shortcode' );
add_shortcode( 'noindex', 'relevanssi_noindex_shortcode' );
add_shortcode( 'searchform', 'relevanssi_search_form' );
/**
* Creates a link to search results.
*
* Using this is generally not a brilliant idea, actually. Google doesn't like
* it if you create links to internal search results.
*
* Usage: [search term='tomato']tomatoes[/search] would create a link like this:
* <a href="/?s=tomato">tomatoes</a>
*
* Set 'phrase' to something else than 'not' to make the search term a phrase.
*
* @global object $wpdb The WordPress database interface.
*
* @param array $atts The shortcode attributes. If 'term' is set, will use
* it as the search term, otherwise the content word is used as the term.
* @param string $content The content inside the shortcode tags.
*
* @return string A link to search results.
*/
function relevanssi_shortcode( $atts, $content ) {
$attributes = shortcode_atts(
array(
'term' => false,
'phrase' => 'not',
),
$atts
);
$term = $attributes['term'];
$phrase = $attributes['phrase'];
if ( false !== $term ) {
$term = rawurlencode( relevanssi_strtolower( $term ) );
} else {
$term = rawurlencode( wp_strip_all_tags( relevanssi_strtolower( $content ) ) );
}
if ( 'not' !== $phrase ) {
$term = '%22' . $term . '%22';
}
$link = get_bloginfo( 'url' ) . "/?s=$term";
$pre = "<a rel='nofollow' href='$link'>"; // rel='nofollow' for Google.
$post = '</a>';
return $pre . do_shortcode( $content ) . $post;
}
/**
* Does nothing.
*
* In normal use, the [noindex] shortcode does nothing.
*
* @param array $atts The shortcode attributes. Not used.
* @param string $content The content inside the shortcode tags.
*
* @return string The shortcode content.
*/
function relevanssi_noindex_shortcode( $atts, $content ) {
return do_shortcode( $content );
}
/**
* Returns nothing.
*
* During indexing, the [noindex] shortcode returns nothing.
*
* @return string An empty string.
*/
function relevanssi_noindex_shortcode_indexing() {
return '';
}
/**
* Returns a search form.
*
* Returns a search form generated by get_search_form(). Any attributes passed to the
* shortcode will be passed onto the search form, for example like this:
*
* [searchform post_types='post,product']
*
* This would add a
*
* <input type="hidden" name="post_types" value="post,product" />
*
* to the search form.
*
* @param array $atts The shortcode attributes.
*
* @return string A search form.
*/
function relevanssi_search_form( $atts ) {
$form = get_search_form( false );
if ( is_array( $atts ) ) {
$additional_fields = array();
foreach ( $atts as $key => $value ) {
if ( 'dropdown' === substr( $key, 0, 8 ) ) {
$key = 'dropdown';
}
if ( 'checklist' === substr( $key, 0, 9 ) ) {
$key = 'checklist';
}
if ( 'post_type_boxes' === $key ) {
$post_types = explode( ',', $value );
if ( is_array( $post_types ) ) {
$post_type_objects = get_post_types( array(), 'objects' );
$additional_fields[] = '<div class="post_types"><strong>Post types</strong>: ';
foreach ( $post_types as $post_type ) {
$checked = '';
if ( '*' === substr( $post_type, 0, 1 ) ) {
$post_type = substr( $post_type, 1 );
$checked = ' checked="checked" ';
}
if ( isset( $post_type_objects[ $post_type ] ) ) {
$additional_fields[] = '<span class="post_type post_type_' . $post_type . '">'
. '<input type="checkbox" name="post_types[]" value="' . $post_type . '"' . $checked . '/> '
. $post_type_objects[ $post_type ]->name . '</span>';
}
}
$additional_fields[] = '</div>';
}
} elseif ( 'dropdown' === $key && 'post_type' === $value ) {
$field = '<select name="post_type">';
$types = get_option( 'relevanssi_index_post_types', array() );
if ( ! is_array( $types ) ) {
$types = array();
}
foreach ( $types as $type ) {
if ( post_type_exists( $type ) ) {
$object = get_post_type_object( $type );
$field .= '<option value="' . $type . '">' . $object->labels->singular_name . '</option>';
}
}
$field .= '</select>';
$additional_fields[] = $field;
} elseif ( 'dropdown' === $key && 'post_type' !== $value ) {
$name = $value;
if ( 'category' === $value ) {
$name = 'cat';
}
if ( 'post_tag' === $value ) {
$name = 'tag';
}
$args = array(
'taxonomy' => $value,
'echo' => 0,
'hide_if_empty' => true,
'show_option_none' => __( 'None' ),
'name' => $name,
'option_none_value' => 0,
);
$additional_fields[] = wp_dropdown_categories( $args );
} elseif ( 'checklist' === $key && 'post_type' !== $value ) {
$name = $value;
if ( 'category' === $value ) {
$name = 'cat';
}
if ( 'post_tag' === $value ) {
$name = 'tag';
}
$args = array(
'taxonomy' => $value,
'echo' => 0,
);
if ( ! function_exists( 'wp_terms_checklist' ) ) {
include ABSPATH . 'wp-admin/includes/template.php';
}
$checklist = wp_terms_checklist( 0, $args );
$checklist = str_replace( 'post_category', 'cats', $checklist );
$checklist = str_replace( 'tax_input[post_tag]', 'tags', $checklist );
$checklist = str_replace( "disabled='disabled'", '', $checklist );
$checklist = preg_replace( '/tax_input\[(.*?)\]/', '\1', $checklist );
$additional_fields[] = $checklist;
} else {
$key = esc_attr( $key );
$value = esc_attr( $value );
$additional_fields[] = "<input type='hidden' name='$key' value='$value' />";
}
}
$form = str_replace( '</form>', implode( "\n", $additional_fields ) . '</form>', $form );
}
/**
* Filters the Relevanssi shortcode search form before it's used.
*
* @param string $form The form HTML code.
* @param array $atts The shortcode attributes.
*/
return apply_filters( 'relevanssi_search_form', $form, $atts );
}
<?php
/**
* /lib/tabs/attachments-tab.php
*
* Prints out the Attachments tab in Relevanssi settings.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Prints out the attachments tab in Relevanssi settings.
*/
function relevanssi_attachments_tab() {
?>
<h2><?php esc_html_e( 'Indexing attachment content', 'relevanssi' ); ?></h2>
<p><?php esc_html_e( 'With Relevanssi Premium, you can index the text contents of attachments (PDFs, Word documents, Open Office documents and many other types). The contents of the attachments are processed on an external service, which makes the feature reliable and light on your own server performance.', 'relevanssi' ); ?></p>
<?php // Translators: %1$s starts the link, %2$s closes it. ?>
<p><?php printf( esc_html__( 'In order to access this and many other delightful Premium features, %1$sbuy Relevanssi Premium here%2$s.', 'relevanssi' ), '<a href="https://www.relevanssi.com/buy-premium/">', '</a>' ); ?></p>
<?php
}
<?php
/**
* /lib/tabs/debugging-tab.php
*
* Prints out the Debugging tab in Relevanssi settings.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Prints out the debugging tab in Relevanssi settings.
*/
function relevanssi_debugging_tab() {
$how_relevanssi_sees = '';
$db_post_view = '';
$current_post_id = 0;
$current_db_post_id = 0;
$selected = 'post';
if ( isset( $_REQUEST['post_id'] ) ) {
wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
$type = 'post';
if ( isset( $_REQUEST['type'] ) ) {
if ( 'term' === $_REQUEST['type'] ) {
$type = 'term';
$selected = 'term';
}
if ( 'user' === $_REQUEST['type'] ) {
$type = 'user';
$selected = 'user';
}
}
if ( intval( $_REQUEST['post_id'] ) > 0 ) {
$current_post_id = intval( $_REQUEST['post_id'] );
$how_relevanssi_sees = relevanssi_generate_how_relevanssi_sees(
intval( $current_post_id ),
true,
$type
);
}
}
if ( isset( $_REQUEST['db_post_id'] ) ) {
wp_verify_nonce( '_relevanssi_nonce', 'relevanssi_how_relevanssi_sees' );
if ( intval( $_REQUEST['db_post_id'] ) > 0 ) {
$current_db_post_id = intval( $_REQUEST['db_post_id'] );
$db_post_view = relevanssi_generate_db_post_view( $current_db_post_id );
}
}
wp_nonce_field( 'relevanssi_how_relevanssi_sees', '_relevanssi_nonce', true, true );
?>
<h2><?php esc_html_e( 'Debugging', 'relevanssi' ); ?></h2>
<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>
<?php
if ( RELEVANSSI_PREMIUM ) {
?>
<p><?php esc_html_e( 'You can also check user profiles and taxonomy terms by choosing the type from the dropdown.', 'relevanssi' ); ?></p>
<?php
}
if ( ! RELEVANSSI_PREMIUM ) {
// Translators: %1$s starts the link, %2$s closes it.
printf( '<p>' . esc_html__( 'In Relevanssi Premium, you can find this feature for each post on the post edit page. %1$sBuy Relevanssi Premium here%2$s.', 'relevanssi' ) . '</p>', '<a href="https://www.relevanssi.com/buy-premium/">', '</a>' );
}
?>
<p><label for="post_id"><?php esc_html_e( 'The ID', 'relevanssi' ); ?></label>:
<input type="text" name="post_id" id="post_id"
<?php
if ( $current_post_id > 0 ) {
echo 'value="' . esc_attr( $current_post_id ) . '"';
}
?>
/>
<?php
if ( RELEVANSSI_PREMIUM ) {
?>
<select name="type">
<option value="post"
<?php if ( 'post' === $selected ) { ?>
selected="selected"
<?php } ?>><?php esc_html_e( 'Post', 'relevanssi' ); ?></option>
<option value="term"
<?php if ( 'term' === $selected ) { ?>
selected="selected"
<?php } ?>><?php esc_html_e( 'Taxonomy term', 'relevanssi' ); ?></option>
<option value="user"
<?php if ( 'user' === $selected ) { ?>
selected="selected"
<?php } ?>><?php esc_html_e( 'User', 'relevanssi' ); ?></option>
</select>
<?php
}
?>
</p>
<p>
<input
type='submit' name='submit'
value='<?php esc_attr_e( 'Check the post', 'relevanssi' ); ?>'
class='button button-primary' />
</p>
<?php echo $how_relevanssi_sees; // phpcs:ignore WordPress.Security.EscapeOutput ?>
<h2><?php esc_html_e( 'What does the post look like in the database?', 'relevanssi' ); ?></h2>
<p><?php esc_html_e( "This feature will show you how the post looks like in the database. It can sometimes be very helpful for debugging why a post isn't indexed the way you expect it to be.", 'relevanssi' ); ?></p>
<p><label for="db_post_id"><?php esc_html_e( 'The ID', 'relevanssi' ); ?></label>:
<input type="text" name="db_post_id" id="db_post_id"
<?php
if ( $current_db_post_id > 0 ) {
echo 'value="' . esc_attr( $current_db_post_id ) . '"';
}
?>
/>
</p>
<p>
<input
type='submit' name='submit'
value='<?php esc_attr_e( 'Check the post', 'relevanssi' ); ?>'
class='button button-primary' />
</p>
<?php echo $db_post_view; // phpcs:ignore WordPress.Security.EscapeOutput ?>
<h2><?php esc_html_e( 'Debugging information', 'relevanssi' ); ?></h2>
<?php
global $wpdb;
$max_allowed_packet = $wpdb->get_var( 'SELECT @@global.max_allowed_packet' );
$max_allowed_packet = round( $max_allowed_packet / 1024 / 1024, 2 );
echo '<p>max_allowed_packet: ' . $max_allowed_packet . 'M</p>'; // phpcs:ignore WordPress.Security.EscapeOutput
$indexing_query = relevanssi_generate_indexing_query(
relevanssi_valid_status_array(),
false,
relevanssi_post_type_restriction(),
'LIMIT 0'
);
?>
<p><?php esc_html_e( 'Indexing query', 'relevanssi' ); ?>:</p>
<?php
echo '<code>' . $indexing_query . '</code>'; // phpcs:ignore WordPress.Security.EscapeOutput
?>
<?php do_action( 'relevanssi_debugging_tab' ); ?>
<h2><?php esc_html_e( 'Debugging mode', 'relevanssi' ); ?></h2>
<?php
$enable_debugging_mode = relevanssi_check( get_option( 'relevanssi_debugging_mode' ) );
?>
<fieldset>
<legend class="screen-reader-text"><?php esc_html_e( 'Enable the debugging mode.', 'relevanssi' ); ?></legend>
<label for='relevanssi_debugging_mode'>
<input type='checkbox' name='relevanssi_debugging_mode' id='relevanssi_debugging_mode' <?php echo esc_html( $enable_debugging_mode ); ?> />
<?php esc_html_e( 'Enable the debugging mode.', 'relevanssi' ); ?>
</label>
<p class="description"><?php esc_html_e( "Relevanssi support may ask you to enable the debugging mode. When you check this box, it's possible to see debugging information from the front-end.", 'relevanssi' ); ?></p>
</fieldset>
<?php
}
<?php
/**
* /lib/tabs/logging-tab.php
*
* Prints out the Logging tab in Relevanssi settings.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Prints out the logging tab in Relevanssi settings.
*
* @global $wpdb The WordPress database interface.
*/
function relevanssi_logging_tab() {
global $wpdb;
$log_queries = get_option( 'relevanssi_log_queries' );
$log_queries = relevanssi_check( $log_queries );
$log_queries_with_ip = get_option( 'relevanssi_log_queries_with_ip' );
$log_queries_with_ip = relevanssi_check( $log_queries_with_ip );
$omit_from_logs = get_option( 'relevanssi_omit_from_logs' );
$trim_logs = get_option( 'relevanssi_trim_logs' );
?>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<?php esc_html_e( 'Enable logs', 'relevanssi' ); ?>
</th>
<td>
<fieldset>
<legend class="screen-reader-text"><?php esc_html_e( 'Keep a log of user queries.', 'relevanssi' ); ?></legend>
<label for='relevanssi_log_queries'>
<input type='checkbox' name='relevanssi_log_queries' id='relevanssi_log_queries' <?php echo esc_html( $log_queries ); ?> />
<?php esc_html_e( 'Keep a log of user queries.', 'relevanssi' ); ?>
</label>
</fieldset>
<p class="description">
<?php
// Translators: %1$s is the name of the "User searches" page, %2$s is the name of the database table.
printf(
esc_html__( "If enabled, Relevanssi will log user queries. The logs can be examined under '%1\$s' on the Dashboard admin menu and are stored in the %2\$s database table.", 'relevanssi' ),
esc_html__( 'User searches', 'relevanssi' ),
esc_html( $wpdb->prefix . 'relevanssi_log' )
);
?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Log user IP', 'relevanssi' ); ?>
</th>
<td>
<fieldset>
<legend class="screen-reader-text"><?php esc_html_e( "Log the user's IP with the queries.", 'relevanssi' ); ?></legend>
<label for='relevanssi_log_queries_with_ip'>
<input type='checkbox' name='relevanssi_log_queries_with_ip' id='relevanssi_log_queries_with_ip' <?php echo esc_html( $log_queries_with_ip ); ?> />
<?php esc_html_e( "Log the user's IP with the queries.", 'relevanssi' ); ?>
</label>
</fieldset>
<p class="description"><?php esc_html_e( "If enabled, Relevanssi will log user's IP adress with the queries. Note that this may be illegal where you live, and in EU will create a person registry that falls under the GDPR.", 'relevanssi' ); ?></p>
</td>
</tr>
<tr>
<th scope="row">
<label for='relevanssi_omit_from_logs'><?php esc_html_e( 'Exclude users', 'relevanssi' ); ?></label>
</th>
<td>
<input type='text' name='relevanssi_omit_from_logs' id='relevanssi_omit_from_logs' size='60' value='<?php echo esc_attr( $omit_from_logs ); ?>' />
<p class="description"><?php esc_html_e( 'Comma-separated list of numeric user IDs or user login names that will not be logged.', 'relevanssi' ); ?></p>
</td>
</tr>
<?php
if ( function_exists( 'relevanssi_form_hide_branding' ) ) {
relevanssi_form_hide_branding();
}
?>
<tr>
<th scope="row">
<label for='relevanssi_trim_logs'><?php esc_html_e( 'Trim logs', 'relevanssi' ); ?></label>
</th>
<td>
<input type='number' name='relevanssi_trim_logs' id='relevanssi_trim_logs' value='<?php echo esc_attr( $trim_logs ); ?>' />
<?php esc_html_e( 'How many days of logs to keep in the database.', 'relevanssi' ); ?>
<?php
if ( '0' === $trim_logs ) {
echo '<p class="description">';
esc_html_e( "Big log database table will eventually start to slow down the search, so it's a good idea to use some level of automatic log trimming.", 'relevanssi' );
echo '</p>';
} else {
echo '<p class="description">';
// Translators: %d is the setting for no trim (probably 0).
printf( esc_html__( 'Set to %d for no trimming.', 'relevanssi' ), 0 );
echo '</p>';
}
?>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Export logs', 'relevanssi' ); ?>
</th>
<td>
<?php submit_button( __( 'Export the log as a CSV file', 'relevanssi' ), 'secondary', 'relevanssi_export' ); ?>
<p class="description"><?php esc_html_e( 'Push the button to export the search log as a CSV file.', 'relevanssi' ); ?></p>
</td>
</tr>
</table>
<?php
if ( function_exists( 'relevanssi_click_tracking_interface' ) ) {
relevanssi_click_tracking_interface();
} else {
?>
<h3><?php esc_html_e( 'Click tracking', 'relevanssi' ); ?></h3>
<p><?php esc_html_e( 'Relevanssi Premium has a click tracking feature where you can track which posts are clicked from the search results. That way you can tell what is your most interesting content and how the search is actually used to access posts.', 'relevanssi' ); ?></p>
<?php
}
}
<?php
/**
* /lib/tabs/overview-tab.php
*
* Prints out the Overview tab in Relevanssi settings.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Prints out the overview tab in Relevanssi settings.
*
* @global array $relevanssi_variables The global Relevanssi variables array.
*/
function relevanssi_overview_tab() {
global $relevanssi_variables;
$this_page = '?page=' . plugin_basename( $relevanssi_variables['file'] );
?>
<h2><?php esc_html_e( 'Welcome to Relevanssi!', 'relevanssi' ); ?></h2>
<table class="form-table" role="presentation">
<?php
if ( ! is_plugin_active_for_network( plugin_basename( $relevanssi_variables['file'] ) ) && function_exists( 'relevanssi_form_api_key' ) ) {
relevanssi_form_api_key();
}
if ( function_exists( 'relevanssi_form_hide_post_controls' ) ) {
relevanssi_form_hide_post_controls();
}
if ( function_exists( 'relevanssi_form_do_not_call_home' ) ) {
relevanssi_form_do_not_call_home();
}
if ( function_exists( 'relevanssi_form_update_translations' ) ) {
relevanssi_form_update_translations();
}
?>
<tr>
<th scope="row"><?php esc_html_e( 'Getting started', 'relevanssi' ); ?></th>
<td>
<p><?php esc_html_e( "You've already installed Relevanssi. That's a great first step towards good search experience!", 'relevanssi' ); ?></p>
<ol>
<?php if ( 'done' !== get_option( 'relevanssi_indexed' ) ) : ?>
<?php // Translators: %1$s opens the link, %2$s is the anchor text, %3$s closes the link. ?>
<li><p><?php printf( esc_html__( 'Now, you need an index. Head over to the %1$s%2$s%3$s tab to set up the basic indexing options and to build the index.', 'relevanssi' ), "<a href='" . esc_attr( $this_page ) . "&amp;tab=indexing'>", esc_html__( 'Indexing', 'relevanssi' ), '</a>' ); ?></p>
<p><?php esc_html_e( 'You need to check at least the following options:', 'relevanssi' ); ?><br />
&ndash; <?php esc_html_e( 'Make sure the post types you want to include in the index are indexed.', 'relevanssi' ); ?><br />
<?php // Translators: %s is '_sku'. ?>
&ndash; <?php printf( esc_html__( 'Do you use custom fields to store content you want included? If so, add those too. WooCommerce user? You probably want to include %s.', 'relevanssi' ), '<code>_sku</code>' ); ?></p>
<p><?php esc_html_e( "Then just save the options and build the index. First time you have to do it manually, but after that, it's fully automatic: all changes are reflected in the index without reindexing. (That said, it's a good idea to rebuild the index once a year.)", 'relevanssi' ); ?></p>
</li>
<?php else : ?>
<li><p><?php esc_html_e( 'Great, you already have an index!', 'relevanssi' ); ?></p></li>
<?php endif; ?>
<li>
<?php // Translators: %1$s opens the link, %2$s is the anchor text, %3$s closes the link. ?>
<p><?php printf( esc_html__( 'On the %1$s%2$s%3$s tab, choose whether you want the default operator to be AND (less results, but more precise) or OR (more results, less precise).', 'relevanssi' ), "<a href='" . esc_attr( $this_page ) . "&amp;tab=searching'>", esc_html__( 'Searching', 'relevanssi' ), '</a>' ); ?></p>
</li>
<li>
<?php // Translators: %1$s opens the link, %2$s is the anchor text, %3$s closes the link. ?>
<p><?php printf( esc_html__( 'The next step is the %1$s%2$s%3$s tab, where you can enable the custom excerpts that show the relevant part of post in the search results pages.', 'relevanssi' ), "<a href='" . esc_attr( $this_page ) . "&amp;tab=excerpts'>", esc_html__( 'Excerpts and highlights', 'relevanssi' ), '</a>' ); ?></p>
<p><?php esc_html_e( 'There are couple of options related to that, so if you want highlighting in the results, you can adjust the styles for that to suit the look of your site.', 'relevanssi' ); ?></p>
</li>
<li>
<p><?php esc_html_e( "That's about it! Now you should have Relevanssi up and running. The rest of the options is mostly fine-tuning.", 'relevanssi' ); ?></p>
</li>
</ol>
<p><?php esc_html_e( "Relevanssi doesn't have a separate search widget. Instead, Relevanssi uses the default search widget. Any standard search form will do!", 'relevanssi' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Relevanssi Live Ajax Search', 'relevanssi' ); ?></th>
<td>
<?php // Translators: %1$s opens the link, %2$s closes it. ?>
<p><?php printf( esc_html__( 'If you want a live search results, you can use the Relevanssi Live Ajax Search plugin. %1$sYou can find it in the plugin repository%2$s. It will make your search forms show instant results, powered by Relevanssi.', 'relevanssi' ), "<a href='https://wordpress.org/plugins/relevanssi-live-ajax-search/'>", '</a>' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Privacy and GDPR compliance', 'relevanssi' ); ?></th>
<td>
<?php // Translators: %1$s and %3$s open the links, %2$s closes them. ?>
<p><?php printf( esc_html__( '%1$sGDPR Compliance at Relevanssi knowledge base%2$s explains how using Relevanssi affects the GDPR compliance and the privacy policies of your site. Relevanssi also supports the %3$sprivacy policy tool%2$s and the WordPress user data export and erase tools.', 'relevanssi' ), "<a href='https://www.relevanssi.com/knowledge-base/gdpr-compliance/'>", '</a>', "<a href='privacy.php'>" ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'For more information', 'relevanssi' ); ?></th>
<td>
<p><?php esc_html_e( "Relevanssi uses the WordPress contextual help. Click 'Help' on the top right corner for more information on many Relevanssi topics.", 'relevanssi' ); ?></p>
<?php // Translators: %1$s opens the link, %2$s closes the link. ?>
<p><?php printf( esc_html__( '%1$sRelevanssi knowledge base%2$s has lots of information about advanced Relevanssi use, including plenty of code samples.', 'relevanssi' ), "<a href='https://www.relevanssi.com/knowledge-base/'>", '</a>' ); ?></p>
</td>
</tr>
<tr>
<th scope="row"><?php esc_html_e( 'Do you like Relevanssi?', 'relevanssi' ); ?></th>
<td>
<p><?php esc_html_e( 'If you do, the best way to show your appreciation is to spread the word and perhaps give us a good review on WordPress.org.', 'relevanssi' ); ?></p>
<?php // Translators: %1$s opens the link, %2$s closes the link. ?>
<p><?php printf( esc_html__( 'If you like Relevanssi, leaving a five-star review on WordPress.org will help others discover Relevanssi. %1$sYou can add your review here%2$s.', 'relevanssi' ), "<a href='https://wordpress.org/support/plugin/relevanssi/reviews/#new-post'>", '</a>' ); ?></p>
</td>
</tr>
<?php if ( ! RELEVANSSI_PREMIUM ) { ?>
<tr>
<th scope="row">
<?php esc_html_e( 'Buy Relevanssi Premium', 'relevanssi' ); ?>
</th>
<td>
<p><a href="https://www.relevanssi.com/buy-premium"><?php esc_html_e( 'Buy Relevanssi Premium now', 'relevanssi' ); ?></a>
<?php // Translators: %1$s is the coupon code, %2$s is the year it expires. ?>
<?php printf( esc_html__( 'use coupon code %1$s for 20%% discount (valid at least until the end of %2$s)', 'relevanssi' ), '<strong>FREE2023</strong>', '2023' ); ?></p>
<p><?php esc_html_e( 'Here are some improvements Relevanssi Premium offers:', 'relevanssi' ); ?></p>
<ul class="relevanssi_ul">
<li><?php esc_html_e( 'PDF content indexing', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'A Related posts feature', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Index and search user profile pages', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Index and search taxonomy term pages', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Multisite searches across many subsites', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'WP CLI commands', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Adjust weights separately for each post type and taxonomy', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Internal link anchors can be search terms for the target posts', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Index and search any columns in the wp_posts database', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Hide Relevanssi branding from the User Searches page on a client installation', 'relevanssi' ); ?></li>
<li><?php esc_html_e( 'Redirect search queries to custom URLs', 'relevanssi' ); ?></li>
</ul>
</td>
</tr>
<?php } // End if ( ! RELEVANSSI_PREMIUM ). ?>
</table>
<?php
}
<?php
/**
* /lib/tabs/redirects-tab.php
*
* Prints out the Redirects tab in Relevanssi settings.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Prints out the redirects tab in Relevanssi settings.
*/
function relevanssi_redirects_tab() {
?>
<h2><?php esc_html_e( 'Redirects', 'relevanssi' ); ?></h2>
<p><?php esc_html_e( 'With Relevanssi Premium, you can set up redirects. These are keywords that automatically redirect the user to certain page, without going through the usual search process. For example, you could set it up so that all searches for "job" automatically lead to your "Careers" page.', 'relevanssi' ); ?></p>
<?php // Translators: %1$s starts the link, %2$s closes it. ?>
<p><?php printf( esc_html__( 'In order to access this and many other delightful Premium features, %1$sbuy Relevanssi Premium here%2$s.', 'relevanssi' ), '<a href="https://www.relevanssi.com/buy-premium/">', '</a>' ); ?></p>
<?php
}
<?php
/**
* /lib/tabs/search-tab.php
*
* Prints out the search tab in Relevanssi settings.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Prints out the search tab in Relevanssi settings.
*/
function relevanssi_search_tab() {
?>
<p><?php esc_html_e( 'You can use this search to perform Relevanssi searches without any restrictions from WordPress. You can search all post types here.', 'relevanssi' ); ?></p>
<form action="" method="get">
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for='s'><?php esc_html_e( 'Search terms', 'relevanssi' ); ?></label>
</th>
<td>
<input type='text' name='s' id='s' size='60' />
</td>
</tr>
<tr>
<th scope="row">
<label for='post_types'><?php esc_html_e( 'Post type', 'relevanssi' ); ?></label>
</th>
<td>
<select name='post_types' id='post_types'>
<option value="any"><?php esc_html_e( 'Any', 'relevanssi' ); ?></option>
<?php
echo implode(
' ',
array_map( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
function ( $post_type ) {
$pt = get_post_type_object( $post_type );
if ( $pt ) {
$post_type_value = esc_attr( $post_type );
$post_type_name = esc_html( $pt->labels->singular_name );
return "<option value='{$post_type_value}'>{$post_type_name}</option>";
}
return null;
},
get_option( 'relevanssi_index_post_types', array() )
)
);
if ( 'on' === get_option( 'relevanssi_index_users' ) ) {
?>
<option value='user'><?php esc_html_e( 'Users', 'relevanssi' ); ?></option>
<?php
}
?>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for='posts_per_page'><?php esc_html_e( 'Posts per page', 'relevanssi' ); ?></label>
</th>
<td>
<select name='posts_per_page' id='posts_per_page'>
<option value='0'><?php esc_html_e( 'All', 'relevanssi' ); ?></option>
<option>10</option>
<option>50</option>
<option>100</option>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for='args'><?php esc_html_e( 'Search parameters', 'relevanssi' ); ?></label>
</th>
<td>
<input type='text' name='args' id='args' size='60' />
<?php // Translators: example query string. ?>
<p class='description'><?php printf( esc_html__( 'Use query parameter formatting here, the same that would appear on search page results URL. For example %s.', 'relevanssi' ), '<code>posts_per_page=10&post_types=page&from=2018-01-01</code>' ); ?></p>
</td>
</tr>
<tr>
<td>
</td>
<td>
<input type='submit' name='search' id='search' value='<?php echo esc_html_x( 'Search', 'button action', 'relevanssi' ); ?>' class='button' />
</td>
</tr>
</table>
</form>
<div id='results'></div>
<?php
}
<?php
/**
* /lib/tabs/stopwords-tab.php
*
* Prints out the Stopwords tab in Relevanssi settings.
*
* @package Relevanssi
* @author Mikko Saari
* @license https://wordpress.org/about/gpl/ GNU General Public License
* @see https://www.relevanssi.com/
*/
/**
* Prints out the stopwords tab in Relevanssi settings.
*/
function relevanssi_stopwords_tab() {
if ( class_exists( 'Polylang', false ) && ! relevanssi_get_current_language() ) {
relevanssi_polylang_all_languages_stopwords();
return;
}
?>
<h3 id="stopwords"><?php esc_html_e( 'Stopwords', 'relevanssi' ); ?></h3>
<?php
relevanssi_show_stopwords();
?>
<h3 id="bodystopwords"><?php esc_html_e( 'Content stopwords', 'relevanssi' ); ?></h3>
<?php
if ( function_exists( 'relevanssi_show_body_stopwords' ) ) {
relevanssi_show_body_stopwords();
} else {
printf(
'<p>%s</p>',
esc_html__(
'Content stopwords are a premium feature where you can set stopwords that only apply to the post content. Those stopwords will still be indexed if they appear in post titles, tags, categories, custom fields or other parts of the post. To use content stopwords, you need Relevanssi Premium.',
'relevanssi'
)
);
}
/**
* Filters whether the common words list is displayed or not.
*
* The list of 25 most common words is displayed by default, but if the
* index is big, displaying the list can take a long time. This filter can
* be used to turn the list off.
*
* @param boolean If true, show the list; if false, don't show it.
*/
if ( apply_filters( 'relevanssi_display_common_words', true ) ) {
relevanssi_common_words( 25 );
}
}
/**
* Displays a list of stopwords.
*
* Displays the list of stopwords and gives the controls for adding new
* stopwords.
*/
function relevanssi_show_stopwords() {
printf(
'<p>%s</p>',
esc_html__(
'Enter a word here to add it to the list of stopwords. The word will automatically be removed from the index, so re-indexing is not necessary. You can enter many words at the same time, separate words with commas.',
'relevanssi'
)
);
?>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<label for="addstopword"><p><?php esc_html_e( 'Stopword(s) to add', 'relevanssi' ); ?>
</th>
<td>
<textarea name="addstopword" id="addstopword" rows="2" cols="80"></textarea>
<p><input type="submit" value="<?php esc_attr_e( 'Add', 'relevanssi' ); ?>" class='button' /></p>
</td>
</tr>
</table>
<p><?php esc_html_e( "Here's a list of stopwords in the database. Click a word to remove it from stopwords. Removing stopwords won't automatically return them to index, so you need to re-index all posts after removing stopwords to get those words back to index.", 'relevanssi' ); ?></p>
<table class="form-table" role="presentation">
<tr>
<th scope="row">
<?php esc_html_e( 'Current stopwords', 'relevanssi' ); ?>
</th>
<td>
<ul>
<?php
$stopwords = array_map( 'stripslashes', relevanssi_fetch_stopwords() );
sort( $stopwords );
$exportlist = htmlspecialchars( implode( ', ', $stopwords ) );
array_walk(
$stopwords,
function ( $term ) {
printf( '<li style="display: inline;"><input type="submit" name="removestopword" value="%s"/></li>', esc_attr( $term ) );
}
);
?>
</ul>
<p>
<input
type="submit"
id="removeallstopwords"
name="removeallstopwords"
value="<?php esc_attr_e( 'Remove all stopwords', 'relevanssi' ); ?>"
class='button'
/>
<input
type="submit"
id="repopulatestopwords"
name="repopulatestopwords"
value="<?php esc_attr_e( 'Add default stopwords', 'relevanssi' ); ?>"
class='button'
/>
</p>
</td>
</tr>
<tr>
<th scope="row">
<?php esc_html_e( 'Exportable list of stopwords', 'relevanssi' ); ?>
</th>
<td>
<label for="stopwords" class="screen-reader-text"><?php esc_html_e( 'Exportable list of stopwords', 'relevanssi' ); ?></label>
<textarea name="stopwords" id="stopwords" rows="2" cols="80"><?php echo esc_textarea( $exportlist ); ?></textarea>
<p class="description"><?php esc_html_e( 'You can copy the list of stopwords here if you want to back up the list, copy it to a different blog or otherwise need the list.', 'relevanssi' ); ?></p>
</td>
</tr>
</table>
<?php
}
/**
* Displays an error message when Polylang is in all languages mode.
*/
function relevanssi_polylang_all_languages_stopwords() {
?>
<h3 id="stopwords"><?php esc_html_e( 'Stopwords', 'relevanssi' ); ?></h3>
<p class="description"><?php esc_html_e( 'You are using Polylang and are in "Show all languages" mode. Please select a language before adjusting the stopword settings.', 'relevanssi' ); ?></p>
<?php
}