HEX
Server: Apache/2.4.65 (Debian)
System: Linux kubikelcreative 5.10.0-35-amd64 #1 SMP Debian 5.10.237-1 (2025-05-19) x86_64
User: www-data (33)
PHP: 8.4.13
Disabled: NONE
Upload Files
File: /var/www/indoadvisory/wp/wp-content/plugins/polylang-wc/frontend/frontend.php
<?php
/**
 * @package Polylang-WC
 */

/**
 * Manages WooCommerce specific translations on the frontend.
 *
 * @since 0.1
 */
class PLLWC_Frontend {

	/**
	 * Constructor.
	 *
	 * @since 0.1
	 */
	public function __construct() {
		if ( did_action( 'pll_language_defined' ) ) {
			$this->init();
		} else {
			add_action( 'pll_language_defined', array( $this, 'init' ), 1 );
			add_action( 'woocommerce_init', array( $this, 'override_countries' ), 1 );

			// Set the language early if a form has been posted with a language value.
			if ( ! empty( $_REQUEST['lang'] ) && $lang = PLL()->model->get_language( sanitize_key( $_REQUEST['lang'] ) ) ) {  // phpcs:ignore WordPress.Security.NonceVerification
				PLL()->curlang = $lang;
				$GLOBALS['text_direction'] = $lang->is_rtl ? 'rtl' : 'ltr'; // phpcs:ignore WordPress.WP.GlobalVariablesOverride
				do_action( 'pll_language_defined', $lang->slug, $lang );
			}
		}
	}

	/**
	 * Setups actions filters once the language is defined.
	 *
	 * @since 0.1
	 *
	 * @return void
	 */
	public function init() {
		PLLWC_Filter_WC_Pages::init();

		// Filters the product search form.
		if ( is_callable( array( PLL()->filters_search, 'get_search_form' ) ) ) {
			add_filter( 'get_product_search_form', array( PLL()->filters_search, 'get_search_form' ), 99 );
			add_filter( 'render_block_woocommerce/product-search', array( PLL()->filters_search, 'get_search_form' ) );
		}

		if ( ! PLL()->options['force_lang'] ) {
			if ( ! get_option( 'permalink_structure' ) ) {
				// Fix product page when using plain permalinks and the language is set from the content.
				add_filter( 'pll_check_canonical_url', array( $this, 'pll_check_canonical_url' ) );
				add_filter( 'pll_translation_url', array( $this, 'pll_translation_url' ), 10, 2 );
			} else {
				// Fix shop link when using pretty permalinks and the language is set from the content.
				add_filter( 'post_type_archive_link', array( $this, 'post_type_archive_link' ), 99, 2 ); // After Polylang.
			}

			// Add the language input field to forms to detect the language before wp_loaded is fired.
			$actions = array(
				'woocommerce_login_form_start', // Login.
				'woocommerce_register_form_start', // Register.
				'woocommerce_before_cart_table', // Cart.
				'woocommerce_before_add_to_cart_button', // Product.
				'woocommerce_lostpassword_form', // Lost password.
			);

			foreach ( $actions as $action ) {
				add_action( $action, array( $this, 'language_form_field' ) );
			}

			add_filter( 'woocommerce_get_remove_url', array( $this, 'add_lang_query_arg' ) );
		}

		// Translates home url in widgets.
		add_filter( 'pll_home_url_white_list', array( $this, 'home_url_white_list' ) );

		// Layered nav chosen attributes.
		add_filter( 'woocommerce_product_query_tax_query', array( $this, 'product_tax_query' ) );

		if ( PLL()->options['force_lang'] > 1 ) {
			add_filter( 'home_url', array( $this, 'fix_widget_price_filter' ), 10, 2 );
		}

		// Object cache compatibility.
		add_filter( 'woocommerce_shortcode_products_query', array( $this, 'shortcode_products_query' ) ); // Since WC 3.0.2.
		add_filter( 'woocommerce_get_product_subcategories_cache_key', array( $this, 'get_product_subcategories_cache_key' ) );

		// Ajax endpoint.
		add_filter( 'woocommerce_ajax_get_endpoint', array( $this, 'ajax_get_endpoint' ), 10, 2 );

		// Handles Coming Soon for product and taxonomy pages.
		add_filter( 'woocommerce_is_extension_store_page', array( $this, 'is_store_page' ) );
	}

	/**
	 * Replaces WooCommerce countries class by our own when language is set from the content.
	 *
	 * @since 1.9.2
	 *
	 * @return void
	 */
	public function override_countries() {
		WC()->countries = new PLLWC_Countries();
	}

	/**
	 * Fixes the canonical redirection from the shop page to the product archive when using plain permalinks and the language is set from the content
	 *
	 * @since 0.3.2
	 *
	 * @param string $redirect_url Redirect url.
	 * @return string|false
	 */
	public function pll_check_canonical_url( $redirect_url ) {
		if ( is_post_type_archive( 'product' ) ) {
			return false;
		}
		return $redirect_url;
	}

	/**
	 * Fixes the translation url of the shop page (product archive) when using plain permalinks and the language is set from the content.
	 *
	 * @since 0.3.2
	 *
	 * @param string $url  Translation url.
	 * @param string $lang Language code.
	 * @return string
	 */
	public function pll_translation_url( $url, $lang ) {
		if ( is_post_type_archive( 'product' ) ) {
			$lang = PLL()->model->get_language( $lang );

			if ( $lang ) {
				if ( PLL()->options['hide_default'] && 'page' === get_option( 'show_on_front' ) && PLL()->options['default_lang'] === $lang->slug ) {
					$pages = pll_languages_list( array( 'fields' => 'page_on_front' ) );
					if ( in_array( wc_get_page_id( 'shop' ), $pages ) ) {
						return $lang->get_home_url();
					}
				}

				$url = get_post_type_archive_link( 'product' );
				$url = PLL()->links_model->switch_language_in_link( $url, $lang );
				$url = PLL()->links_model->remove_paged_from_link( $url );
			}
		}
		return $url;
	}

	/**
	 * Fixes the shop link when using pretty permalinks and the language is set from the content.
	 *
	 * This fixes the widget layered nav which calls get_post_type_archive_link( 'product' ).
	 *
	 * @since 0.4.6
	 *
	 * @param string $link      Post type archive link.
	 * @param string $post_type Post type name.
	 * @return string Modified link.
	 */
	public function post_type_archive_link( $link, $post_type ) {
		return 'product' === $post_type ? wc_get_page_permalink( 'shop' ) : $link;
	}

	/**
	 * Outputs the hidden language input field.
	 *
	 * @since 0.3.5
	 *
	 * @return void
	 */
	public function language_form_field() {
		printf( '<input type="hidden" name="lang" value="%s" />', esc_attr( pll_current_language() ) );
	}

	/**
	 * Adds a lang query arg to the url.
	 *
	 * @since 0.5
	 *
	 * @param string $url URL to modify.
	 * @return string
	 */
	public function add_lang_query_arg( $url ) {
		return add_query_arg( 'lang', pll_current_language(), $url );
	}

	/**
	 * Fixes the home url in widgets
	 *
	 * @since 0.5
	 *
	 * @param string[][] $arr List of files and functions to whitelist for the home_url filter.
	 * @return string[][]
	 */
	public function home_url_white_list( $arr ) {
		$arr = array_merge(
			$arr,
			array( array( 'file' => 'abstract-wc-widget.php' ) )
		);

		// Avoid a redirect when the language is set from the content.
		if ( PLL()->options['force_lang'] > 0 ) {
			$arr = array_merge(
				$arr,
				array( array( 'file' => 'class-wc-widget-product-categories.php' ) )
			);
		}

		if ( PLL()->options['force_lang'] > 1 ) {
			$arr = array_merge(
				$arr,
				array( array( 'file' => 'class-wc-widget-price-filter.php' ) )
			);
		}

		return $arr;
	}

	/**
	 * Fixes the layered nav chosen attributes when shared slugs are in the query,
	 * otherwise the query would look for products in all attributes in all languages which always returns an empty result.
	 *
	 * @since 0.5
	 *
	 * @param array $tax_query Tax query parameter in WP_Query.
	 * @return array
	 */
	public function product_tax_query( $tax_query ) {
		foreach ( $tax_query as $k => $q ) {
			if ( is_array( $q ) && ! empty( $q['field'] ) && 'slug' === $q['field'] ) {
				$terms = get_terms( array( 'taxonomy' => $q['taxonomy'], 'slug' => $q['terms'] ) );
				if ( is_array( $terms ) ) {
					$tax_query[ $k ]['terms'] = wp_list_pluck( $terms, 'term_taxonomy_id' );
					$tax_query[ $k ]['field'] = 'term_taxonomy_id';
				}
			}
		}
		return $tax_query;
	}

	/**
	 * Filters the form action url of the widget price filter for subdomains and multiple domains.
	 *
	 * @since 0.5
	 *
	 * @param string $url  Form action url.
	 * @param string $path Path.
	 * @return string
	 */
	public function fix_widget_price_filter( $url, $path ) {
		global $wp;

		if ( ! empty( $wp->request ) && trailingslashit( $wp->request ) === $path && ! empty( PLL()->curlang ) ) {
			$url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang );
		}

		return $url;
	}

	/**
	 * Adds the language to the shortcodes query args to get one cache key per language.
	 * Needed for WC 3.0, Requires WC 3.0.2+
	 *
	 * @since 0.7.4
	 *
	 * @param array $args WP_Query arguments.
	 * @return array
	 */
	public function shortcode_products_query( $args ) {
		if ( empty( PLL()->curlang ) ) {
			return $args;
		}

		$args['tax_query'][] = array(
			'taxonomy' => 'language',
			'field'    => 'term_taxonomy_id',
			'terms'    => PLL()->curlang->get_tax_prop( 'language', 'term_taxonomy_id' ),
			'operator' => 'IN',
		);

		return $args;
	}

	/**
	 * Makes the product subcategories cache key language dependent.
	 *
	 * @since 1.2.3
	 *
	 * @param string $cache_key WooCommerce product subcategories cache key.
	 * @return string
	 */
	public function get_product_subcategories_cache_key( $cache_key ) {
		$curlang = pll_current_language();
		return $cache_key . '-' . $curlang;
	}

	/**
	 * Make sure that the ajax endpoint is in the right language.
	 * Required since WC 3.2.
	 *
	 * @since 0.9.1
	 *
	 * @param string $url     Ajax endpoint.
	 * @param string $request Ajax endpoint request.
	 * @return string
	 */
	public function ajax_get_endpoint( $url, $request ) {
		// Remove wc-ajax to avoid the value %%endpoint%% to be encoded by add_query_arg (used in plain permalinks).
		$url = remove_query_arg( 'wc-ajax', $url );
		if ( ! empty( PLL()->curlang ) ) {
			$url = PLL()->links_model->switch_language_in_link( $url, PLL()->curlang );
		}
		return add_query_arg( 'wc-ajax', $request, $url );
	}

	/**
	 * Tells that we're on a store page if it's a product or taxonomy page.
	 *
	 * @since 2.1
	 *
	 * @param bool $is_store_page Whether or not we're on a store page.
	 * @return bool
	 */
	public function is_store_page( $is_store_page ) {
		if ( is_product() || is_product_taxonomy() ) {
			return true;
		}

		return $is_store_page;
	}
}