Current File : /home/kelaby89/www/wp/wp-content/plugins/ti-woocommerce-wishlist/public/addtowishlist.class.php
<?php
/**
 * Handles adding products to wishlists, including hooks and shortcodes.
 *
 * @since 1.0.0
 * @package TInvWishlist\Public
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit; // Exit if accessed directly.
}

use DiDom\Document;

/**
 * Manages the addition of products to wishlists.
 */
class TInvWL_Public_AddToWishlist {
	/**
	 * The plugin name.
	 *
	 * @var string
	 */
	private $_name;

	/**
	 * The global product.
	 *
	 * @var object
	 */
	private $product;

	/**
	 * User's wishlists.
	 *
	 * @var array
	 */
	private $user_wishlist;

	/**
	 * Wishlist and product association.
	 *
	 * @var array
	 */
	private $wishlist;

	/**
	 * All products in the wishlist.
	 *
	 * @var array
	 */
	private $all_products;

	/**
	 * Indicates if the button is in a loop.
	 *
	 * @var bool
	 */
	private $is_loop;

	/**
	 * Singleton instance of the class.
	 *
	 * @var TInvWL_Public_AddToWishlist
	 */
	protected static $_instance = null;

	/**
	 * Variation ID for variable products.
	 *
	 * @var mixed
	 */
	private $variation_id;

	/**
	 * Variation IDs for variable products in loops.
	 *
	 * @var array
	 */
	private $variation_ids;

	/**
	 * Returns the singleton instance.
	 *
	 * @param string $plugin_name Plugin name.
	 *
	 * @return TInvWL_Public_AddToWishlist
	 */
	public static function instance( $plugin_name = TINVWL_PREFIX ) {
		if ( is_null( self::$_instance ) ) {
			self::$_instance = new self( $plugin_name );
		}

		return self::$_instance;
	}

	/**
	 * Constructor
	 *
	 * @param string $plugin_name Plugin name.
	 */
	function __construct( $plugin_name ) {
		$this->_name   = $plugin_name;
		$this->is_loop = false;
		$this->define_hooks();
	}

	/**
	 * Defined shortcode and hooks
	 */
	function define_hooks() {
		switch ( tinv_get_option( 'add_to_wishlist', 'position' ) ) {
			case 'before':
				add_action( 'tinvwl_before_add_to_cart_button', 'tinvwl_view_addto_html' );
				add_action( 'tinvwl_single_product_summary', 'tinvwl_view_addto_htmlout' );
				add_action( 'woocommerce_before_add_to_cart_button', 'tinvwl_view_addto_html', 9 );
				add_action( 'woocommerce_single_product_summary', 'tinvwl_view_addto_htmlout', 29 );
				add_action( 'catalog_visibility_before_alternate_add_to_cart_button', 'tinvwl_view_addto_html' );
				break;
			case 'after':
				add_action( 'tinvwl_after_add_to_cart_button', 'tinvwl_view_addto_html' );
				add_action( 'tinvwl_single_product_summary', 'tinvwl_view_addto_htmlout' );
				add_action( 'woocommerce_after_add_to_cart_button', 'tinvwl_view_addto_html', 20 );
				add_action( 'woocommerce_single_product_summary', 'tinvwl_view_addto_htmlout', 31 );
				add_action( 'catalog_visibility_after_alternate_add_to_cart_button', 'tinvwl_view_addto_html' );
				break;
			case 'thumbnails':
				add_action( 'tinvwl_after_thumbnails', 'tinvwl_view_addto_html' );
				add_action( 'woocommerce_product_thumbnails', 'tinvwl_view_addto_html', 21 );
				break;
			case 'summary':
				add_action( 'tinvwl_after_summary', 'tinvwl_view_addto_html' );
				add_action( 'woocommerce_after_single_product_summary', 'tinvwl_view_addto_html', 11 );
				break;
		}
		if ( tinv_get_option( 'add_to_wishlist_catalog', 'show_in_loop' ) ) {
			switch ( tinv_get_option( 'add_to_wishlist_catalog', 'position' ) ) {
				case 'before':
					add_action( 'tinvwl_after_shop_loop_item', 'tinvwl_view_addto_htmlloop' );
					add_action( 'woocommerce_after_shop_loop_item', 'tinvwl_view_addto_htmlloop', 8 );
					add_action( 'uael_woo_products_add_to_cart_before', 'tinvwl_view_addto_htmlloop' );
					break;
				case 'above_thumb':
					add_action( 'tinvwl_above_thumb_loop_item', 'tinvwl_view_addto_htmlloop' );
					add_action( 'woocommerce_before_shop_loop_item', 'tinvwl_view_addto_htmlloop', 9 );
					add_action( 'uael_woo_products_before_summary_wrap', 'tinvwl_view_addto_htmlloop' );
					break;
				case 'shortcode':
					break;
				case 'after':
				default: // Compatibility with previous versions.
					add_action( 'tinvwl_after_shop_loop_item', 'tinvwl_view_addto_htmlloop' );
					add_action( 'woocommerce_after_shop_loop_item', 'tinvwl_view_addto_htmlloop', 20 );
					add_action( 'uael_woo_products_add_to_cart_after', 'tinvwl_view_addto_htmlloop' );
					break;
			}


		}

		// WooCommerce Blocks
		add_filter( 'woocommerce_blocks_product_grid_item_html', array( $this, 'htmloutput_block' ), 9, 3 );
		add_filter( 'woocommerce_product_get_description', array( $this, 'woocommerce_blocks_all_products' ), 10, 2 );
		add_action( 'init', array( $this, 'woocommerce_blocks' ) );

		add_action( 'wp_loaded', array( $this, 'add_to_wishlist' ), 0 );
		if ( is_user_logged_in() && apply_filters( 'tinvwl_allow_data_cookies', true ) ) {
			add_action( 'init', array( $this, 'set_wishlists_data_cookies' ) );
		}
	}

	/**
	 * Set cookies to sync session data across devices
	 *
	 * @return void
	 */
	function set_wishlists_data_cookies() {
		$class   = TInvWL_Public_WishlistCounter::instance();
		$counter = $class->get_counter();
		wc_setcookie( 'tinvwl_wishlists_data_counter', $counter );


		if ( tinv_get_option( 'general', 'product_stats' ) ) {
			global $wpdb;
			$table       = sprintf( '%s%s', $wpdb->prefix, 'tinvwl_items' );
			$table_lists = sprintf( '%s%s', $wpdb->prefix, 'tinvwl_lists' );
			$table_stats = sprintf( '%s%s', $wpdb->prefix, 'tinvwl_analytics' );
			$stats_count = 0;

			$stats_sql = "SELECT SUM(`count`) as `stats_count` FROM (SELECT COUNT(`B`.`ID`) AS `count` FROM `{$table_stats}` AS `A` LEFT JOIN `{$table}` AS `C` ON `C`.`wishlist_id` = `A`.`wishlist_id` AND `C`.`product_id` = `A`.`product_id` AND `C`.`variation_id` = `A`.`variation_id` LEFT JOIN `{$table_lists}` AS `B` ON `C`.`wishlist_id` = `B`.`ID` LEFT JOIN `{$table_lists}` AS `G` ON `C`.`wishlist_id` = `G`.`ID` AND `G`.`author` = 0 WHERE `A`.`product_id` > 0 GROUP BY `A`.`product_id`, `A`.`variation_id` HAVING `count` > 0 LIMIT 0, 9999999) AS `A`";

			$stats_results = $wpdb->get_results( $stats_sql, ARRAY_A );

			if ( ! empty( $stats_results ) ) {
				foreach ( $stats_results as $product_stats ) {
					$stats_count = $product_stats['stats_count'];
				}
			}
			if ( $stats_count ) {
				wc_setcookie( 'tinvwl_wishlists_data_stats', $stats_count );
			}
		}
	}

	/**
	 * Action add product to wishlist
	 *
	 * @return boolean
	 */
	function add_to_wishlist() {
		if ( is_null( filter_input( INPUT_POST, 'tinv_wishlist_id' ) ) ) {
			return false;
		} else {
			remove_action( 'init', 'woocommerce_add_to_cart_action' );
			remove_action( 'wp_loaded', 'WC_Form_Handler::add_to_cart_action', 20 );
		}
		ob_start();
		$post = filter_input_array( INPUT_POST, array(
			'tinv_wishlist_id'   => FILTER_VALIDATE_INT,
			'tinv_wishlist_name' => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
			'product_id'         => FILTER_VALIDATE_INT,
			'product_variation'  => FILTER_VALIDATE_INT,
			'product_type'       => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
			'product_action'     => FILTER_SANITIZE_FULL_SPECIAL_CHARS,
			'redirect'           => FILTER_SANITIZE_URL,
		) );

		$post['original_product_id'] = $post['product_id'];

		$wlp      = null;
		$wishlist = null;
		$data     = array( 'msg' => array() );
		if ( is_user_logged_in() ) {
			$wl       = new TInvWL_Wishlist( $this->_name );
			$wishlist = $wl->add_user_default();
			$wishlist = apply_filters( 'tinvwl_addtowishlist_wishlist', $wishlist );
			if ( empty( $wishlist ) ) {
				$data['status'] = false;
				$data           = apply_filters( 'tinvwl_addtowishlist_return_ajax', $data, $post );
				ob_clean();
				wp_send_json( $data );
			}
			$wlp = new TInvWL_Product( $wishlist, $this->_name );
		} elseif ( tinv_get_option( 'general', 'require_login' ) ) {
			$data['status'] = false;
			$data['icon']   = 'icon_big_times';
			if ( tinv_get_option( 'general', 'redirect_require_login' ) ) {
				$data['msg']            = array();
				$data['force_redirect'] = apply_filters( 'tinvwl_addtowishlist_login_page', add_query_arg( 'tinvwl_redirect', rawurlencode( $post['redirect'] ), wc_get_page_permalink( 'myaccount' ) ), $post );
			} else {
				$data['msg'][]              = __( 'Please, login to add products to Wishlist', 'ti-woocommerce-wishlist' );
				$data['dialog_custom_url']  = apply_filters( 'tinvwl_addtowishlist_login_page', add_query_arg( 'tinvwl_redirect', rawurlencode( $post['redirect'] ), wc_get_page_permalink( 'myaccount' ) ), $post );
				$data['dialog_custom_html'] = '<i class="ftinvwl ftinvwl-key"></i>' . esc_html( __( 'Login', 'ti-woocommerce-wishlist' ) );
			}
			$data['msg'] = array_unique( $data['msg'] );
			$data['msg'] = implode( '<br>', $data['msg'] );
			if ( ! empty( $data['msg'] ) ) {
				$data['msg'] = tinv_wishlist_template_html( 'ti-addedtowishlist-dialogbox.php', apply_filters( 'tinvwl_addtowishlist_dialog_box', $data, $post ) );
			}
			$data = apply_filters( 'tinvwl_addtowishlist_return_ajax', $data, $post );
			ob_clean();
			wp_send_json( $data );
		} else {
			$wl       = new TInvWL_Wishlist( $this->_name );
			$wishlist = $wl->add_sharekey_default();
			$wlp      = new TInvWL_Product( $wishlist );
		}

		$status = true;
		if ( empty( $post['product_id'] ) || apply_filters( 'tinvwl_addtowishlist_not_allowed', false, $post ) ) {
			$status        = false;
			$data['msg'][] = __( 'Something went wrong', 'ti-woocommerce-wishlist' );
		} else {
			$post['product_type'] = apply_filters( 'tinvwl_addtowishlist_modify_type', $post['product_type'], $post );
			$post                 = apply_filters( 'tinvwl_addtowishlist_prepare', $post );
			$form                 = apply_filters( 'tinvwl_addtowishlist_prepare_form', filter_input( INPUT_POST, 'form', FILTER_DEFAULT, FILTER_FORCE_ARRAY ), $_POST, $_FILES );
			if ( empty( $form ) ) {
				$form = array();
			}
			wp_recursive_ksort( $form );
			switch ( $post['product_type'] ) {
				case 'group':
				case 'grouped' :
					$product = $wlp->product_data( $post['product_id'] );
					if ( empty( $product ) ) {
						$status = false;
					} else {
						$variations = $product->get_children();

						foreach ( $variations as $variation_id ) {
							$quantity       = 1;
							$allowed_adding = ! count( $wlp->get_wishlist( array(
								'product_id'   => $post['product_id'],
								'variation_id' => $variation_id,
								'external'     => false,
							) ) );
							if ( tinv_get_option( 'general', 'simple_flow' ) && 'remove' === $post['product_action'] ) {
								if ( $wlp->remove_product_from_wl( 0, $post['product_id'], $variation_id, apply_filters( 'tinvwl_addtowishlist_add_form', $form ) ) ) {
									$data['msg'][]   = apply_filters( 'tinvwl_removed_from_wishlist_text', tinv_get_option( 'general', 'text_removed_from' ) );
									$data['removed'] = true;
									$status          = false;
								}
							} elseif ( ! $allowed_adding ) {
								$data['msg'][] = apply_filters( 'tinvwl_already_in_wishlist_text', tinv_get_option( 'general', 'text_already_in' ) );
								$status        = false;
							} elseif ( $wlp->add_product( apply_filters( 'tinvwl_addtowishlist_add', array(
								'product_id'   => $post['product_id'],
								'variation_id' => $variation_id,
								'quantity'     => $quantity,
							) ) )
							) {
								$data['msg'][] = apply_filters( 'tinvwl_added_to_wishlist_text', tinv_get_option( 'general', 'text_added_to' ) );
							} else {
								$status = false;
							}
						}
					}
					break;
				case 'variable' :
				case 'variation' :
				case 'variable-subscription' :

					if ( $post['product_variation'] ) {
						$variation_id = $post['product_variation'];
					} else {
						$variation_id = absint( array_key_exists( 'variation_id', $form ) ? filter_var( $form['variation_id'], FILTER_VALIDATE_INT ) : 0 );
					}

					$post['original_product_id'] = $variation_id;

					$quantity       = 1;
					$allowed_adding = ! count( $wlp->get_wishlist( array(
						'product_id'   => $post['product_id'],
						'variation_id' => $variation_id,
						'meta'         => apply_filters( 'tinvwl_addtowishlist_add_form', $form ),
						'external'     => false,
					) ) );
					if ( tinv_get_option( 'general', 'simple_flow' ) && 'remove' === $post['product_action'] ) {
						if ( $wlp->remove_product_from_wl( 0, $post['product_id'], $variation_id, apply_filters( 'tinvwl_addtowishlist_add_form', $form ) ) ) {
							$data['msg'][]   = apply_filters( 'tinvwl_removed_from_wishlist_text', tinv_get_option( 'general', 'text_removed_from' ) );
							$data['removed'] = true;
							$status          = false;
						}
					} elseif ( ! $allowed_adding ) {
						$data['msg'][] = apply_filters( 'tinvwl_already_in_wishlist_text', tinv_get_option( 'general', 'text_already_in' ) );
						$status        = false;
					} elseif ( $wlp->add_product( apply_filters( 'tinvwl_addtowishlist_add', array(
						'product_id'   => $post['product_id'],
						'quantity'     => $quantity,
						'variation_id' => $variation_id,
					) ), apply_filters( 'tinvwl_addtowishlist_add_form', $form ) ) ) {
						$data['msg'][] = apply_filters( 'tinvwl_added_to_wishlist_text', tinv_get_option( 'general', 'text_added_to' ) );
					} else {
						$status = false;
					}
					break;
				case 'simple' :
				default:
					$quantity       = 1;
					$allowed_adding = ! count( $wlp->get_wishlist( array(
						'product_id' => $post['product_id'],
						'meta'       => apply_filters( 'tinvwl_addtowishlist_add_form', $form ),
						'external'   => false,
					) ) );
					if ( tinv_get_option( 'general', 'simple_flow' ) && 'remove' === $post['product_action'] ) {
						if ( $wlp->remove_product_from_wl( 0, $post['product_id'], 0, apply_filters( 'tinvwl_addtowishlist_add_form', $form ) ) ) {
							$data['msg'][]   = apply_filters( 'tinvwl_removed_from_wishlist_text', tinv_get_option( 'general', 'text_removed_from' ) );
							$data['removed'] = true;
							$status          = false;
						}
					} elseif ( ! $allowed_adding ) {
						$data['msg'][] = apply_filters( 'tinvwl_already_in_wishlist_text', tinv_get_option( 'general', 'text_already_in' ) );
						$status        = false;
					} elseif ( $wlp->add_product( apply_filters( 'tinvwl_addtowishlist_add', array(
						'product_id' => $post['product_id'],
						'quantity'   => $quantity,
					) ), apply_filters( 'tinvwl_addtowishlist_add_form', $form ) ) ) {
						$data['msg'][] = apply_filters( 'tinvwl_added_to_wishlist_text', tinv_get_option( 'general', 'text_added_to' ) );
					} else {
						$status = false;
					}
					break;
			} // End switch().
		} // End if().
		$data['status']       = $status;
		$data['wishlist_url'] = tinv_url_wishlist_default();

		if ( ! empty( $wishlist ) ) {
			$data['wishlist_url'] = tinv_url_wishlist( $wishlist['ID'] );
		}

		if ( $status && tinv_get_option( 'general', 'redirect' ) && tinv_get_option( 'page', 'wishlist' ) && tinv_get_option( 'general', 'show_notice' ) ) {
			$data['redirect'] = apply_filters( 'tinvwl_addtowishlist_redirect', $data['wishlist_url'] );
		}

		if ( empty( $form ) ) {
			$form = array();
		}

		$data['icon'] = $data['status'] ? 'icon_big_heart_check' : 'icon_big_times';
		$data['msg']  = array_unique( $data['msg'] );
		$data['msg']  = implode( '<br>', $data['msg'] );

		$product = $original_product = wc_get_product( $post['product_id'] );

		if ( $post['original_product_id'] && $post['product_id'] !== $post['original_product_id'] ) {
			$original_product = wc_get_product( $post['original_product_id'] );
		}

		if ( ! empty( $data['msg'] ) ) {
			if ( $original_product ) {
				$data['msg'] = tinvwl_message_placeholders( $data['msg'], $original_product, $wishlist );
			}
			$data['msg']      = apply_filters( 'tinvwl_addtowishlist_message_after', $data['msg'], $data, $post, $form, $product );
			$data['wishlist'] = $wishlist;
			$data['msg']      = tinv_wishlist_template_html( 'ti-addedtowishlist-dialogbox.php', apply_filters( 'tinvwl_addtowishlist_dialog_box', $data, $post ) );
		}
		if ( ! tinv_get_option( 'general', 'show_notice' ) && array_key_exists( 'msg', $data ) ) {
			unset( $data['msg'] );
			if ( ! $status && ! tinv_get_option( 'general', 'simple_flow' ) ) {
				$data['force_redirect'] = $data['wishlist_url'];
			}
		}
		if ( tinv_get_option( 'general', 'simple_flow' ) ) {
			$data['make_remove'] = $data['status'];
		}
		$share_key = false;

		if ( ! is_user_logged_in() ) {
			$share_key = $wishlist['share_key'];
		}
		$data['action']         = 'add_to_wishlist';
		$data['wishlists_data'] = $this->get_wishlists_data( $share_key );
		$data                   = apply_filters( 'tinvwl_addtowishlist_return_ajax', $data, $post, $form, $product );
		ob_clean();
		wp_send_json( $data );
	}

	/**
	 * @param string $share_key
	 *
	 * @return array
	 */
	function get_wishlists_data( $share_key ) {
		global $wpdb;

		$table              = $wpdb->prefix . 'tinvwl_items';
		$table_lists        = $wpdb->prefix . 'tinvwl_lists';
		$table_stats        = $wpdb->prefix . 'tinvwl_analytics';
		$table_translations = $wpdb->prefix . 'icl_translations';
		$table_languages    = $wpdb->prefix . 'icl_languages';

		// Sanitize inputs
		$lang         = sanitize_text_field( filter_input( INPUT_POST, 'lang', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) );
		$lang_default = sanitize_text_field( filter_input( INPUT_POST, 'lang_default', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) );
		$stats        = sanitize_text_field( filter_input( INPUT_POST, 'stats', FILTER_SANITIZE_FULL_SPECIAL_CHARS ) );
		$share_key    = sanitize_text_field( $share_key );

		$data = $products = $wishlists = $results = $analytics = array();

		if ( is_user_logged_in() ) {
			$data['author'] = get_current_user_id();
		}

		if ( ( isset( $data['author'] ) && $data['author'] ) || $share_key ) {
			$default = array(
				'count'    => 99999,
				'field'    => null,
				'offset'   => 0,
				'order'    => 'DESC',
				'order_by' => 'date',
				'external' => true,
				'sql'      => '',
			);

			// Validate order and order_by parameters
			$allowed_orders          = array( 'ASC', 'DESC' );
			$allowed_order_by_fields = array( 'date', 'title', 'ID' ); // Add other allowed fields

			foreach ( $default as $_k => $_v ) {
				if ( array_key_exists( $_k, $data ) ) {
					$default[ $_k ] = $data[ $_k ];
					unset( $data[ $_k ] );
				}
			}

			// Sanitize order parameters
			$default['order']    = in_array( strtoupper( $default['order'] ), $allowed_orders ) ? strtoupper( $default['order'] ) : 'DESC';
			$default['order_by'] = in_array( $default['order_by'], $allowed_order_by_fields ) ? $default['order_by'] : 'date';

			$default['offset'] = absint( $default['offset'] );
			$default['count']  = absint( $default['count'] );

			// Build the base SELECT clause
			if ( $lang ) {
				$select_fields = "{$table}.ID, t.element_id AS product_id, t2.element_id AS variation_id,
                            {$table}.formdata, {$table}.author, {$table}.date, {$table}.quantity,
                            {$table}.price, {$table}.in_stock";
			} else {
				$select_fields = "{$table}.*";
			}

			// Add the wishlist fields to the SELECT clause
			$select_fields .= ", {$table_lists}.ID as wishlist_id, {$table_lists}.status as wishlist_status,
                           {$table_lists}.title as wishlist_title, {$table_lists}.share_key as wishlist_share_key";

			// Build base query
			$sql = "SELECT {$select_fields}
                FROM {$table}
                INNER JOIN {$table_lists} ON {$table}.wishlist_id = {$table_lists}.ID
                AND {$table_lists}.type = 'default'";

			// Add share key condition if present
			if ( $share_key ) {
				$sql .= $wpdb->prepare( " AND {$table_lists}.share_key = %s", $share_key );
			}

			// Add language joins if needed
			if ( $lang ) {
				$language_conditions = array( $lang );
				if ( $lang_default ) {
					$language_conditions[] = $lang_default;
				}

				$languages_in = "'" . implode( "','", array_map( 'esc_sql', $language_conditions ) ) . "'";

				$sql .= " LEFT JOIN {$table_translations} tr ON
                        {$table}.product_id = tr.element_id AND tr.element_type = 'post_product'
                    LEFT JOIN {$table_translations} tr2 ON
                        {$table}.variation_id != 0 AND {$table}.variation_id = tr2.element_id
                        AND tr2.element_type = 'post_product_variation'
                    LEFT JOIN {$table_translations} t ON
                        tr.trid = t.trid AND t.element_type = 'post_product'
                        AND t.language_code IN ({$languages_in})
                    LEFT JOIN {$table_translations} t2 ON
                        {$table}.variation_id != 0 AND tr2.trid = t2.trid
                        AND t2.element_type = 'post_product_variation'
                        AND t2.language_code IN ({$languages_in})
                    JOIN {$table_languages} l ON
                        (t.language_code = l.code OR t2.language_code = l.code) AND l.active = 1";
			}

			// Build WHERE clause
			$where_conditions = array( '1=1' );
			if ( ! empty( $data ) && is_array( $data ) ) {
				foreach ( $data as $field => $value ) {
					if ( is_array( $value ) ) {
						$placeholders       = array_fill( 0, count( $value ), '%s' );
						$where_conditions[] = $wpdb->prepare(
							"{$table}.{$field} IN (" . implode( ',', $placeholders ) . ")",
							$value
						);
					} else {
						$where_conditions[] = $wpdb->prepare(
							"{$table}.{$field} = %s",
							$value
						);
					}
				}
			}

			$sql .= " WHERE " . implode( ' AND ', $where_conditions );

			// Add GROUP BY, ORDER BY, and LIMIT
			$sql .= " GROUP BY {$table}.ID";
			$sql .= " ORDER BY {$default['order_by']} {$default['order']}";
			$sql .= $wpdb->prepare( " LIMIT %d, %d", $default['offset'], $default['count'] );

			// Execute query
			$results = $wpdb->get_results( $sql, ARRAY_A );

			// Process results
			if ( ! empty( $results ) ) {
				foreach ( $results as $product ) {
					$wishlists[ $product['wishlist_id'] ] = array(
						'ID'        => (int) $product['wishlist_id'],
						'title'     => sanitize_text_field( $product['wishlist_title'] ),
						'status'    => sanitize_text_field( $product['wishlist_status'] ),
						'share_key' => sanitize_text_field( $product['wishlist_share_key'] ),
					);
				}

				foreach ( $wishlists as $wishlist ) {
					foreach ( $results as $product ) {
						if ( (int) $wishlist['ID'] !== (int) $product['wishlist_id'] ) {
							continue;
						}

						if ( array_key_exists( $product['product_id'], $products ) ) {
							$products[ $product['product_id'] ][ $wishlist['ID'] ]['in'][] = (int) $product['variation_id'];
						} else {
							$products[ $product['product_id'] ][ $wishlist['ID'] ]         = $wishlist;
							$products[ $product['product_id'] ][ $wishlist['ID'] ]['in'][] = (int) $product['variation_id'];
						}
					}
				}
			}
		}

		// Handle statistics query
		if ( $stats ) {
			$stats_count = 0;
			$analytics   = array();

			$stats_sql = "SELECT A.product_id, A.variation_id, COUNT(B.ID) AS count
                      FROM {$table_stats} AS A
                      LEFT JOIN {$table} AS C ON C.wishlist_id = A.wishlist_id
                          AND C.product_id = A.product_id
                          AND C.variation_id = A.variation_id
                      LEFT JOIN {$table_lists} AS B ON C.wishlist_id = B.ID
                      LEFT JOIN {$table_lists} AS G ON C.wishlist_id = G.ID AND G.author = 0
                      WHERE A.product_id > 0
                      GROUP BY A.product_id, A.variation_id
                      HAVING count > 0
                      LIMIT 0, 9999999";

			$stats_results = $wpdb->get_results( $stats_sql, ARRAY_A );

			if ( ! empty( $stats_results ) ) {
				foreach ( $stats_results as $product_stats ) {
					$analytics[ $product_stats['product_id'] ][ $product_stats['variation_id'] ] = (int) $product_stats['count'];
					$stats_count                                                                 += (int) $product_stats['count'];
				}
			}
		}

		$count = is_array( $results ) ? count( $results ) : 0;

		$response = array(
			'products' => $products,
			'counter'  => $count,
		);

		if ( $lang ) {
			$response['lang'] = sanitize_text_field( $lang );
		}

		if ( $lang_default ) {
			$response['lang_default'] = sanitize_text_field( $lang_default );
		}

		if ( $stats ) {
			$response['stats']       = $analytics;
			$response['stats_count'] = (int) $stats_count;
		}

		return $response;
	}

	/**
	 * Get user wishlist
	 *
	 * @return array
	 */
	function user_wishlists() {
		if ( ! empty( $this->user_wishlist ) ) {
			return $this->user_wishlist;
		}

		$wl = new TInvWL_Wishlist( $this->_name );
		if ( is_user_logged_in() ) {
			$wishlists = $wl->get_by_user_default();
		} else {
			$wishlists = $wl->get_by_sharekey_default();
		}
		$wishlists = array_filter( $wishlists );
		if ( ! empty( $wishlists ) ) {
			$_wishlists = array();
			foreach ( $wishlists as $key => $wishlist ) {
				if ( is_array( $wishlist ) && array_key_exists( 'ID', $wishlist ) ) {
					$_wishlists[ $key ] = array(
						'ID'    => $wishlist['ID'],
						'title' => $wishlist['title'],
						'url'   => tinv_url_wishlist_by_key( $wishlist['share_key'] ),
					);
				}
			}
			$wishlists = $_wishlists;
		}
		$this->user_wishlist = $wishlists;

		return $wishlists;
	}

	/**
	 * Check exists product in user wishlists
	 *
	 * @param object $product Product object.
	 * @param object $wlp Product class, used for local products.
	 *
	 * @return array
	 */
	function user_wishlist( $product, $wlp = null ) {

		$product = apply_filters( 'tinvwl_addtowishlist_check_product', $product );

		$this->wishlist = array();
		$vproduct       = in_array( $product->get_type(), array(
			'variable',
			'variation',
			'variable-subscription',
		) );
		$wlp            = new TInvWL_Product();
		$wishlists      = $this->user_wishlists();
		$ids            = array();
		foreach ( $wishlists as $key => $wishlist ) {
			$ids[] = $wishlist['ID'];
		}
		$ids = array_filter( $ids );

		if ( empty( $ids ) ) {
			return $wishlists;
		}

		if ( ! $this->all_products ) {
			$this->all_products = $wlp->get( array(
				'wishlist_id' => $ids,
				'external'    => false,
				'count'       => 9999999,
			) );
		}

		$products = array();
		foreach ( $this->all_products as $_product ) {
			if ( $_product['product_id'] === $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id() ) {
				$products[] = $_product;
			}
		}

		$in = array();
		if ( ! empty( $products ) ) {
			foreach ( $products as $product ) {
				$in[ $product['wishlist_id'] ][] = $product['variation_id'];
			}
			foreach ( $in as $wishlist_id => $products ) {
				sort( $in[ $wishlist_id ], SORT_NUMERIC );
				if ( empty( $in[ $wishlist_id ] ) && ( $this->is_loop || ! $vproduct ) ) {
					$in[ $wishlist_id ] = true;
				}
			}
		}
		foreach ( $wishlists as $key => $wishlist ) {
			$wishlists[ $key ]['in'] = array_key_exists( $wishlist['ID'], $in ) ? $in[ $wishlist['ID'] ] : false;
		}
		$wishlists      = apply_filters( 'tinvwl_addtowishlist_preparewishlists', $wishlists, $product );
		$this->wishlist = $wishlists;

		return $wishlists;
	}

	/**
	 * Create add button in loop
	 *
	 * @global object $product
	 */
	function htmloutput_loop() {
		global $product;

		if ( $product ) {
			if ( apply_filters( 'tinvwl_allow_addtowishlist_shop_loop_item', true, $product ) ) { // @codingStandardsIgnoreLine WordPress.PHP.StrictInArray.MissingTrueStrict
				$this->is_loop = true;
				$this->htmloutput();
				$this->is_loop = false;
			}
		}
	}

	/**
	 * Create add button if simple product out stock
	 *
	 * @global object $product
	 */
	function htmloutput_out() {
		global $product;

		if ( $product ) {
			$allow = false;
			if ( 'simple' === $product->get_type() ) {
				$allow = ( ( ! $product->is_purchasable() && '' == $product->get_price() ) || ( $product->is_purchasable() && ! $product->is_in_stock() ) );
			}

			if ( in_array( $product->get_type(), array(
				'variable',
				'variable-subscription'
			) ) ) {
				$get_variations       = count( $product->get_children() ) <= apply_filters( 'woocommerce_ajax_variation_threshold', 30, $product );
				$available_variations = $get_variations ? $product->get_available_variations() : false;
				$allow                = ( empty( $available_variations ) && false !== $available_variations );
			}

			if ( apply_filters( 'tinvwl_allow_addtowishlist_single_product_summary', $allow, $product ) ) {
				$this->htmloutput();
			}
		}
	}

	/**
	 * Output page
	 *
	 * @param array $attr Array parameter for shortcode.
	 * @param boolean $is_shortcode Shortcode or action.
	 *
	 * @return boolean
	 * @global object $product
	 *
	 */
	function htmloutput( $attr = array(), $is_shortcode = false ) {
		$attr = apply_filters( 'tinvwl_addtowishlist_out_prepare_attr', $attr );
		//is shortcode
		$this->variation_id = null;
		if ( $is_shortcode ) {
			$position = 'shortcode';

			$product_id = ! empty( $attr['product_id'] ) ? absint( $attr['product_id'] ) : ( ( $this->product instanceof WC_Product ) ? $this->product->get_id() : null );

			$variation_id = ! empty( $attr['variation_id'] ) ? absint( $attr['variation_id'] ) : null;

			if ( 'product_variation' == get_post_type( $product_id ) ) { // WPCS: loose comparison ok.
				$variation_id = $product_id;
				$product_id   = wp_get_post_parent_id( $variation_id );
			}

			$product_data = ( $product_id !== ( ( $this->product instanceof WC_Product ) ? $this->product->get_id() : null ) ) ? wc_get_product( $variation_id ? $variation_id : $product_id ) : $this->product;

			if ( $product_data instanceof WC_Product && 'trash' !== get_post( $product_data->get_id() )->post_status ) {
				$this->product = apply_filters( 'tinvwl_addtowishlist_out_prepare_product', $product_data );
			} else {
				return false;
			}
		} else {

			global $product, $post;

			$_product = $product;
			if ( empty( $product ) && ! empty( $post ) && 'product' === $post->post_type ) {
				$_product = wc_get_product( $post->ID );
			}

			$this->product = apply_filters( 'tinvwl_addtowishlist_out_prepare_product', $_product );

			$position = $this->is_loop ? tinv_get_option( 'add_to_wishlist_catalog', 'position' ) : tinv_get_option( 'add_to_wishlist', 'position' );

		}

		if ( empty( $this->product ) || ! ( $this->product instanceof WC_Product ) || ! apply_filters( 'tinvwl_allow_addtowishlist_single_product', true, $this->product ) ) {
			return false;
		}

		if ( isset( $variation_id ) ) {
			$this->variation_id = $variation_id;
		}

		if ( $this->is_loop && in_array( $this->product->get_type(), array(
				'variable',
				'variable-subscription',
			) ) ) {

			$this->variation_ids = array();

			if ( ! tinv_get_option( 'general', 'simple_flow' ) ) {
				foreach ( $this->product->get_children() as $oid ) {
					$this->variation_ids[] = apply_filters( 'wpml_object_id', $oid, 'product', true );
				}
			}

			$this->variation_ids[] = 0;

			$this->variation_ids = apply_filters( 'tinvwl_wishlist_addtowishlist_button_variation_ids', $this->variation_ids, $this );

			if ( ! isset( $this->variation_id ) ) {
				$this->variation_id = 0;
				$match_attributes   = array();

				foreach ( $this->product->get_default_attributes() as $attribute_name => $value ) {
					$match_attributes[ 'attribute_' . sanitize_title( $attribute_name ) ] = $value;
				}

				if ( $match_attributes ) {

					add_action( 'tinvwl_wishlist_addtowishlist_button', array(
						$this,
						'default_variation_loop'
					), 10, 2 );

					$data_store         = WC_Data_Store::load( 'product' );
					$this->variation_id = $data_store->find_matching_product_variation( $this->product, $match_attributes );
				}
			}
		}
		add_action( 'tinvwl_wishlist_addtowishlist_button', array( $this, 'button' ) );

		$action_class = current_action() ? ' tinvwl-' . current_action() : ' tinvwl-no-action';

		$data = array(
			'class_postion'       => sprintf( 'tinvwl-%s-add-to-cart', $position ) . ( $this->is_loop ? ' tinvwl-loop-button-wrapper' : '' ) . $action_class,
			'product'             => $this->product,
			'variation_id'        => ( $this->is_loop && in_array( ( $this->product->get_type() ), array(
					'variable',
					'variable-subscription',
				) ) ) ? $this->variation_id : ( $this->product->is_type( 'variation' ) ? $this->product->get_id() : 0 ),
			'button_icon'         => tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'icon' ),
			'add_to_wishlist'     => apply_filters( 'tinvwl_added_to_wishlist_text_loop', tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'text' ) ),
			'browse_in_wishlist'  => apply_filters( 'tinvwl_view_wishlist_text', tinv_get_option( 'general', 'text_browse' ) ),
			'product_in_wishlist' => apply_filters( 'tinvwl_already_in_wishlist_text', tinv_get_option( 'general', 'text_already_in' ) ),
			'product_to_wishlist' => apply_filters( 'tinvwl_added_to_wishlist_text', tinv_get_option( 'general', 'text_added_to' ) ),
			'loop'                => $this->is_loop,
		);
		tinv_wishlist_template( 'ti-addtowishlist.php', $data );
	}

	/**
	 * Create button
	 *
	 * @param boolean $echo Return or output.
	 */
	function button( $echo = true ) {
		$content     = apply_filters( 'tinvwl_wishlist_button_before', '' );
		$button_text = apply_filters( 'tinvwl_added_to_wishlist_text_loop', tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'text' ) );
		$text        = ( tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'show_text' ) ) ? $button_text : '';
		$icon        = tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'icon' );
		$icon_color  = tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'icon_style' );
		$icon_class  = '';
		$action      = 'addto';
		if ( empty( $text ) ) {
			$icon_class = ' no-txt';
		} else {
			$content .= '<div class="tinv-wishlist-clear"></div>';
			if ( tinv_get_option( 'general', 'simple_flow' ) ) {
				$text = sprintf( '<span class="tinvwl_add_to_wishlist-text">%s</span><span class="tinvwl_remove_from_wishlist-text">%s</span>', $text, apply_filters( 'tinvwl_remove_from_wishlist_text' . ( $this->is_loop ? '_loop' : '' ), tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'text_remove' ) ) );
			} else {

				$already_on = tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'already_on' ) ? sprintf( '<span class="tinvwl_already_on_wishlist-text">%s</span>', apply_filters( 'tinvwl_already_on_wishlist_text' . ( $this->is_loop ? '_loop' : '' ), tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'text_already_on' ) ) ) : '';

				$text = sprintf( '<span class="tinvwl_add_to_wishlist-text">%s</span>' . $already_on, $text );
			}
		}
		if ( ! empty( $icon ) ) {
			$icon_upload       = tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'icon_upload' );
			$icon_upload_added = tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'icon_upload_added' );
			if ( 'custom' === $icon && ! empty( $icon_upload ) ) {
				$text = sprintf( '<img src="%s" alt="%s"' . ( ! empty( $icon_upload_added ) ? ' class="icon-add-on-wishlist"' : '' ) . '  /> %s', esc_url( $icon_upload ), esc_attr( apply_filters( 'tinvwl_add_to_wishlist_text_loop', tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'text' ) ) ), $text );
				if ( ! empty( $icon_upload_added ) ) {
					$text = sprintf( '<img src="%s" alt="%s" class="icon-already-on-wishlist" /> %s', esc_url( $icon_upload_added ), esc_attr( apply_filters( 'tinvwl_added_to_wishlist_text_loop', tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'text_already_on' ) ) ), $text );
				}
			}
			$icon = 'tinvwl-icon-' . $icon;
			if ( 'custom' !== $icon && $icon_color ) {
				$icon .= ' icon-' . $icon_color;
			}
		}
		$icon .= $icon_class;

		$icon .= tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'already_on' ) ? ' tinvwl-product-already-on-wishlist' : '';

		$icon .= ' ' . tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'class' );

		$icon .= ' tinvwl-position-' . tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'position' );

		$icon .= ( tinv_get_option( 'add_to_wishlist' . ( $this->is_loop ? '_catalog' : '' ), 'show_preloader' ) ) ? ' ftinvwl-animated' : '';

		$icon .= $this->is_loop ? ' tinvwl-loop' : '';

		$product_variation = apply_filters( 'wpml_object_id', ( ( $this->is_loop && in_array( $this->product->get_type(), array(
				'variable',
				'variable-subscription',
			) ) ) ? $this->variation_id : ( $this->product->is_type( 'variation' ) ? $this->product->get_id() : 0 ) ), 'product', true );

		$content .= sprintf( '<a role="button" tabindex="0" name="%s" aria-label="%s" class="tinvwl_add_to_wishlist_button %s" data-tinv-wl-list="[]" data-tinv-wl-product="%s" data-tinv-wl-productvariation="%s" data-tinv-wl-productvariations="%s" data-tinv-wl-producttype="%s" data-tinv-wl-action="add">%s</a>',
			esc_attr( sanitize_title( $button_text ) ),
			$button_text,
			$icon,
			apply_filters( 'wpml_object_id', ( $this->product->is_type( 'variation' ) ? $this->product->get_parent_id() : $this->product->get_id() ), 'product', true ),
			( $product_variation ) ?: 0,
			json_encode( ( $this->is_loop && in_array( $this->product->get_type(), array(
					'variable',
					'variable-subscription',
				) ) ) ? $this->variation_ids : ( $this->product->is_type( 'variation' ) ? array( $this->product->get_id() ) : array() ) ),
			$this->product->get_type(),
			$text );
		$content .= apply_filters( 'tinvwl_wishlist_button_after', '' );

		if ( ! empty( $text ) ) {
			$content .= '<div class="tinv-wishlist-clear"></div>';
		}

		echo apply_filters( 'tinvwl_wishlist_button', $content, $this->wishlist, $this->product, $this->is_loop, $icon, $action, $text ); // WPCS: xss ok.
	}

	/**
	 * Shortcode basic function
	 *
	 * @param array $atts Array parameter from shortcode.
	 *
	 * @return string
	 * @global object $product
	 *
	 */
	function shortcode( $atts = array() ) {
		global $product;

		$default = array(
			'product_id'   => 0,
			'variation_id' => 0,
			'loop'         => 'no',
		);
		if ( $product && is_a( $product, 'WC_Product' ) ) {
			$default['product_id']   = $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id();
			$default['variation_id'] = $product->is_type( 'variation' ) ? $product->get_id() : 0;
		}
		$atts = shortcode_atts( $default, $atts );

		ob_start();
		if ( 'yes' === $atts['loop'] ) {
			$this->is_loop = true;
			$this->htmloutput( $atts, true );
			$this->is_loop = false;
		} else {
			$this->htmloutput( $atts, true );
		}

		return ob_get_clean();
	}

	/**
	 * Registers the WooCommerce blocks.
	 */
	function woocommerce_blocks() {
		/**
		 * Registers the custom product label block.
		 */
		register_block_type(
			'tinvwl/add-to-wishlist',
			[
				'render_callback' => array( $this, 'woocommerce_blocks_render' ),
			]
		);
	}

	/**
	 * Renders the WooCommerce blocks.
	 *
	 * @param array $attributes The block attributes.
	 *
	 * @return string The rendered output.
	 */
	function woocommerce_blocks_render( $attributes ) {
		global $product;

		ob_start();
		echo do_shortcode( '[ti_wishlists_addtowishlist loop=yes]' );
		$output = ob_get_clean();

		return $output;
	}

	/**
	 * Add button to WC Blocks
	 *
	 * @filter woocommerce_blocks_product_grid_item_html
	 */
	function htmloutput_block( $html, $data, $product_object ) {

		global $product;

		$position = tinv_get_option( 'add_to_wishlist_catalog', 'position' );

		if ( ! in_array( $position, array( 'before', 'after', 'above_thumb' ) ) ) {
			return $html;
		}

		$product = $product_object;
		ob_start();
		tinvwl_view_addto_htmlloop();
		$add_to_wishlist = ob_get_clean();

		if ( ! $add_to_wishlist ) {
			return $html;
		}

		$add_to_wishlist_document = new Document();
		$add_to_wishlist_document->load( $add_to_wishlist,
			false, Document::TYPE_HTML, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );
		$add_to_wishlist_element = $add_to_wishlist_document->toElement();

		$product = '';

		$document = new Document();
		$document->load( $html,
			false, Document::TYPE_HTML, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD );

		if ( 'above_thumb' === $position && $document->find( '.wc-block-grid__product-link' ) ) {
			$document->find( '.wc-block-grid__product-link' )[0]->insertSiblingBefore( $add_to_wishlist_element->getNode() );
		}

		if ( 'before' === $position && $document->find( '.wc-block-grid__product-add-to-cart' ) ) {
			$document->find( '.wc-block-grid__product-add-to-cart' )[0]->insertSiblingBefore( $add_to_wishlist_element->getNode() );
		}

		if ( 'after' === $position && $document->find( '.wc-block-grid__product-add-to-cart' ) ) {
			$document->find( '.wc-block-grid__product-add-to-cart' )[0]->insertSiblingAfter( $add_to_wishlist_element->getNode() );
		}

		return $document->html();
	}

	/**
	 * Add button to WC Block All Products
	 *
	 */
	function woocommerce_blocks_all_products( $description, $product_object ) {

		global $product;

		// This is basically the store_api init, but as that calls no action, we need to replicate the logic of its protected function
		// here for the time being. IOK 2020-09-02
		if ( empty( $_SERVER['REQUEST_URI'] ) ) {
			return $description;
		}
		if ( ! did_action( 'rest_api_init' ) ) {
			return $description;
		}
		$request_uri = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) );

		if ( preg_match( '/wc\/store(\/v\d)?\/products/', $request_uri ) !== 1 ) {
			return $description;
		}

		$product = $product_object;
		ob_start();
		echo do_shortcode( '[ti_wishlists_addtowishlist loop=yes]' );
		$add_to_wishlist = ob_get_clean();

		$product = '';

		return $description . $add_to_wishlist;
	}

	/**
	 * Outputs hidden input fields for default variation attributes.
	 *
	 * @param WC_Product $product The product.
	 * @param bool $loop Optional. Whether to enable the loop. Default is false.
	 */
	function default_variation_loop( $product, $loop ) {

		if ( $loop && in_array( $product->get_type(), array(
				'variable',
				'variable-subscription',
			) ) ) {
			$match_attributes = [];
			foreach ( $product->get_default_attributes() as $attribute_name => $value ) {
				$match_attributes[ 'attribute_' . sanitize_title( $attribute_name ) ] = $value;
			}

			foreach ( $match_attributes as $name => $value ) {
				?>
				<input name="<?php echo esc_attr( $name ); ?>" type="hidden" value="<?php echo esc_attr( $value ); ?>"/>
				<?php
			}
		}
	}
}
Page not found – Hello World !