JezK
Edit File: meta-data.php
<?php /** * CartFlows Flows ajax actions. * * @package CartFlows */ namespace CartflowsAdmin\AdminCore\Ajax; // Exit if accessed directly. if ( ! defined( 'ABSPATH' ) ) { exit; } use CartflowsAdmin\AdminCore\Ajax\AjaxBase; use CartflowsAdmin\AdminCore\Inc\AdminHelper; /** * Class Steps. */ class MetaData extends AjaxBase { /** * Instance * * @access private * @var object Class object. * @since 1.0.0 */ private static $instance; /** * Initiator * * @since 1.0.0 * @return object initialized object of class. */ public static function get_instance() { if ( ! isset( self::$instance ) ) { self::$instance = new self(); } return self::$instance; } /** * Register ajax events. * * @since 1.0.0 * @return void */ public function register_ajax_events() { $ajax_events = array( 'json_search_products', 'json_search_coupons', ); $this->init_ajax_events( $ajax_events ); } /** * Clone step with its meta. */ public function json_search_products() { if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) { return; } check_ajax_referer( 'cartflows_json_search_products', 'security' ); global $wpdb; if ( ! isset( $_POST['term'] ) ) { return; } $term = ! empty( $_POST['term'] ) ? sanitize_text_field( wp_unslash( $_POST['term'] ) ) : ''; // CartFlows supported product types. $supported_product_types = apply_filters( 'cartflows_supported_product_types_for_search', array( 'simple', 'variable', 'variation', 'subscription', 'variable-subscription', 'subscription_variation', 'course' ) ); // Allowed product types. if ( isset( $_POST['allowed_products'] ) && ! empty( $_POST['allowed_products'] ) ) { $allowed_product_types = sanitize_text_field( ( wp_unslash( $_POST['allowed_products'] ) ) ); $allowed_product_types = $this->sanitize_data_attributes( $allowed_product_types ); $supported_product_types = $allowed_product_types; } // Include product types. if ( isset( $_POST['include_products'] ) && ! empty( $_POST['include_products'] ) ) { $include_product_types = sanitize_text_field( ( wp_unslash( $_POST['include_products'] ) ) ); $include_product_types = $this->sanitize_data_attributes( $include_product_types ); $supported_product_types = array_merge( $supported_product_types, $include_product_types ); } // Exclude product types. if ( isset( $_POST['exclude_products'] ) && ! empty( $_POST['exclude_products'] ) ) { $excluded_product_types = sanitize_text_field( ( wp_unslash( $_POST['exclude_products'] ) ) ); $excluded_product_types = $this->sanitize_data_attributes( $excluded_product_types ); $supported_product_types = array_diff( $supported_product_types, $excluded_product_types ); } // Get all products data. $data = \WC_Data_Store::load( 'product' ); $ids = $data->search_products( $term, '', true, false, 11 ); // Get all product objects. $product_objects = array_filter( array_map( 'wc_get_product', $ids ), 'wc_products_array_filter_readable' ); // Remove the product objects whose product type are not in supported array. $product_objects = array_filter( $product_objects, function ( $arr ) use ( $supported_product_types ) { return $arr && is_a( $arr, 'WC_Product' ) && in_array( $arr->get_type(), $supported_product_types, true ); } ); $products_found = array(); foreach ( $product_objects as $product_object ) { $formatted_name = $product_object->get_name(); $managing_stock = $product_object->managing_stock(); $is_in_stock = $product_object->is_in_stock(); $product_type = $product_object->get_type(); $availibility_text = $this->get_stock_availability_text( $product_object ); $product_price_range = $this->get_formatted_product_price_range( $product_object ); if ( $managing_stock && ! empty( $_GET['display_stock'] ) ) { $stock_amount = $product_object->get_stock_quantity(); /* Translators: %d stock amount */ $formatted_name .= ' – ' . sprintf( __( 'Stock: %d', 'cartflows' ), wc_format_stock_quantity_for_display( $stock_amount, $product_object ) ); } array_push( $products_found, array( 'value' => $product_object->get_id(), 'label' => $formatted_name, 'original_price' => \Cartflows_Helper::get_product_original_price( $product_object ), 'product_name' => $product_object->get_name(), 'product_desc' => $product_object->get_short_description(), 'product_image' => get_the_post_thumbnail_url( $product_object->get_id() ), 'product_type' => $product_type, 'type_label' => $this->get_product_type_label( $product_object ), 'stock_status' => $availibility_text, 'in_stock' => $is_in_stock, 'price_range' => $product_price_range, ) ); } wp_send_json( $products_found ); } /** * Function to search coupons */ public function json_search_coupons() { if ( ! current_user_can( 'cartflows_manage_flows_steps' ) ) { return; } check_ajax_referer( 'cartflows_json_search_coupons', 'security' ); global $wpdb; if ( ! isset( $_POST['term'] ) ) { return; } $term = ! empty( $_POST['term'] ) ? sanitize_text_field( wp_unslash( $_POST['term'] ) ) : ''; if ( empty( $term ) ) { die(); } $posts = wp_cache_get( 'wcf_search_coupons', 'wcf_funnel_Cart' ); if ( false === $posts ) { $posts = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}posts WHERE post_type = %s AND post_title LIKE %s AND post_status = %s", 'shop_coupon', $wpdb->esc_like( $term ) . '%', 'publish' ) ); // db call ok. wp_cache_set( 'wcf_search_coupons', $posts, 'wcf_funnel_Cart' ); } $coupons_found = array(); $all_discount_types = wc_get_coupon_types(); if ( $posts ) { foreach ( $posts as $post ) { $discount_type = get_post_meta( $post->ID, 'discount_type', true ); if ( ! empty( $all_discount_types[ $discount_type ] ) ) { array_push( $coupons_found, array( 'value' => get_the_title( $post->ID ), 'label' => get_the_title( $post->ID ) . ' (Type: ' . $all_discount_types[ $discount_type ] . ')', ) ); } } } wp_send_json( $coupons_found ); } /** * Function to sanitize the product type data attribute. * * @param array $product_types product types. */ public function sanitize_data_attributes( $product_types = array() ) { if ( ! is_array( $product_types ) ) { $product_types = explode( ',', $product_types ); } // Sanitize the excluded types against valid product types. foreach ( $product_types as $index => $value ) { $product_types[ $index ] = strtolower( trim( $value ) ); } return $product_types; } /** * Function to get the stock availability text for a given product. * * This function checks the stock status of a product and returns a string indicating its availability. * The availability text is translated to ensure it is user-friendly and consistent with the plugin's language. * * @param \WC_Product $product The product object to check the stock availability for. * @return string The stock availability text for the product. */ public function get_stock_availability_text( $product ) { $availability_text = ''; if ( ! is_a( $product, 'WC_Product' ) ) { return ''; // Return empty if product is not valid. } $availability = $product->get_availability(); if ( ! empty( $availability['class'] ) ) { switch ( $availability['class'] ) { case 'available-on-backorder': $availability_text = __( 'On backorder', 'cartflows' ); break; case 'in-stock': $availability_text = __( 'In stock', 'cartflows' ); break; case 'out-of-stock': $availability_text = __( 'Out of stock', 'cartflows' ); break; default: break; } } return $availability_text; } /** * Get a short, human readable label for product types that behave differently in a funnel. * * Returned as its own field rather than appended to the product name, because the name is * also used for the selected-product chip. Only types that need distinguishing are labelled; * everything else returns an empty string so no tag is rendered. * * @since 3.1.4 * @param \WC_Product $product The product object. * @return string Label to show beside the product, or an empty string. */ public function get_product_type_label( $product ) { if ( ! is_a( $product, 'WC_Product' ) ) { return ''; } $labels = apply_filters( 'cartflows_product_type_labels_for_search', array( 'bundle' => __( 'Bundle', 'cartflows' ), ) ); $product_type = $product->get_type(); return isset( $labels[ $product_type ] ) ? $labels[ $product_type ] : ''; } /** * Function to generate a formatted price range for a product. * * This function determines the price range for a product based on its type. For variable products, it calculates the price range from the minimum and maximum prices of its variations. For other product types, it uses the product's price or regular price if available. * The function returns a formatted string representing the price range. * * @param \WC_Product $product The product object to generate the price range for. * @return string The formatted price range for the product. */ public function get_formatted_product_price_range( $product ) { $original_price = 0; $product_type = $product->get_type(); if ( 'variable' === $product_type ) { // Code for variation product. $variation_price_range = $product->get_variation_prices( true ); $price = array(); $min_price = isset( $variation_price_range['price'] ) ? current( $variation_price_range['price'] ) : null; $max_price = isset( $variation_price_range['price'] ) ? end( $variation_price_range['price'] ) : null; $min_reg_price = isset( $variation_price_range['regular_price'] ) ? current( $variation_price_range['regular_price'] ) : null; $max_reg_price = isset( $variation_price_range['regular_price'] ) ? end( $variation_price_range['regular_price'] ) : null; if ( $min_price !== $max_price && ! is_null( $min_price ) && ! is_null( $max_price ) ) { // Build the range from each bound directly so WooCommerce's screen-reader "Price range: ... through ..." text is not appended. $original_price = html_entity_decode( wp_strip_all_tags( wc_price( $min_price ) ) ) . ' – ' . html_entity_decode( wp_strip_all_tags( wc_price( $max_price ) ) ); } if ( $min_reg_price !== $max_reg_price && ! is_null( $min_reg_price ) && ! is_null( $max_reg_price ) ) { if ( ! empty( $original_price ) ) { $original_price = html_entity_decode( wp_strip_all_tags( wc_price( $min_reg_price ) ) ) . ' – ' . html_entity_decode( wp_strip_all_tags( wc_price( $max_reg_price ) ) ); } } } elseif ( 'bundle' === $product_type && method_exists( $product, 'get_bundle_price' ) ) { /* * A bundle's own price is only its container base price, which is 0 whenever the * bundled items are priced individually. Use the bundle's min/max price instead so * the selector shows what the customer would actually pay, as it does for variable * products. Guarded on the method so this stays inert without Product Bundles. */ $min_price = $product->get_bundle_price( 'min', false ); $max_price = $product->get_bundle_price( 'max', false ); if ( '' !== $min_price && null !== $min_price ) { $original_price = html_entity_decode( wp_strip_all_tags( wc_price( $min_price ) ) ); if ( '' !== $max_price && null !== $max_price && (float) $max_price !== (float) $min_price ) { $original_price .= ' – ' . html_entity_decode( wp_strip_all_tags( wc_price( $max_price ) ) ); } } } else { $price = $product->get_price(); $original_price = ! empty( $price ) ? $price : $product->get_regular_price(); $original_price = html_entity_decode( wp_strip_all_tags( wc_price( $original_price ) ) ); } return $original_price; } }