<?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' );
add_filter( 'relevanssi_post_ok', 'relevanssi_variation_post_ok', 10, 2 );

/**
 * 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;
}

/**
 * Checks the parent product status for product variations.
 *
 * @param bool $ok      Whether the post is OK to return in search.
 * @param int  $post_id The post ID.
 *
 * @return bool
 */
function relevanssi_variation_post_ok( $ok, $post_id ) : bool {
	$post_type = relevanssi_get_post_type( $post_id );
	if ( 'product_variation' === $post_type ) {
		$parent = get_post_parent( $post_id );
		return apply_filters( 'relevanssi_post_ok', $ok, $parent->ID );
	}
	return $ok;
}
