Plugin Directory

Changeset 3419727


Ignore:
Timestamp:
12/15/2025 06:03:00 AM (4 months ago)
Author:
akashmalik
Message:

Improvements and bug fixes in block pages

Location:
gratisfaction-all-in-one-loyalty-contests-referral-program-for-woocommerce/trunk
Files:
2 added
6 edited

Legend:

Unmodified
Added
Removed
  • gratisfaction-all-in-one-loyalty-contests-referral-program-for-woocommerce/trunk/grconnect.php

    r3404633 r3419727  
    33/**
    44 * @package Gratisfaction Connect
    5  * @version 4.5.5
     5 * @version 4.6.0
    66 */
    77/*
     
    99  Plugin URI: http://appsmav.com
    1010  Description: Loyalty + Referral + Rewards + Birthdays and Anniversaries + Giveaways + Sweepstakes.
    11   Version: 4.5.5
     11  Version: 4.6.0
    1212  Author: Appsmav
    1313  Author URI: http://appsmav.com
     
    3434// Load PHP compatibility polyfills for older versions
    3535if (!function_exists('array_column')) {
    36     include_once(GR_PLUGIN_BASE_PATH . '/includes/compat/array_column.php');
     36    $array_column_file = GR_PLUGIN_BASE_PATH . '/includes/compat/array_column.php';
     37    if (file_exists($array_column_file)) {
     38        include_once($array_column_file);
     39    }
    3740}
    3841if (!function_exists('hash_equals')) {
    39     include_once(GR_PLUGIN_BASE_PATH . '/includes/compat/hash_equals.php');
     42    $hash_equals_file = GR_PLUGIN_BASE_PATH . '/includes/compat/hash_equals.php';
     43    if (file_exists($hash_equals_file)) {
     44        include_once($hash_equals_file);
     45    }
    4046}
    4147
     
    4753        const REDEEM_COUPON = 'GRPAYPOINTS';
    4854
    49         public static $_plugin_version  = '4.5.5';
     55        public static $_plugin_version  = '4.6.0';
    5056        public static $_callback_url = 'https://gratisfaction.appsmav.com/';
    5157        public static $_api_version  = 'newapi/v2/';
     
    5965        {
    6066            try {
     67                // Include required files first (before any hooks that might use them)
     68                $this->include_files();
     69               
    6170                // register actions
    6271                add_action('admin_init', [$this, 'admin_init']);
    6372                add_action('admin_menu', [$this, 'add_menu']);
    6473                add_action('plugins_loaded', [$this, 'woohook_init']);
     74               
     75                // Enqueue global JavaScript for cart/checkout
     76                // Use priority 5 to ensure it runs early enough
     77                add_action('wp_enqueue_scripts', [$this, 'gr_enqueue_apply_discount_handler'], 5);
    6578
    6679                register_activation_hook(__FILE__, [$this, 'activate_endpoints']);
     
    7689                add_filter('woocommerce_cart_totals_coupon_label', [$this, 'coupon_label']);
    7790                add_filter('woocommerce_coupon_is_valid', [$this, 'validate_apply_coupon']);
     91               
     92                // Clear success message when GR coupon is removed
     93                add_action('woocommerce_removed_coupon', [$this, 'clear_success_message_on_coupon_removal'], 10, 1);
    7894
    7995                // display points on a separate tab on user's account page
     
    88104                add_action('rest_api_init', [$this, 'register_rest_routes'], 10);
    89105
     106                // AJAX handlers for block-based cart/checkout
     107                add_action('wp_ajax_get_gr_loyalty_points_blocks', [$this, 'gr_ajax_get_loyalty_points_blocks']);
     108                add_action('wp_ajax_nopriv_get_gr_loyalty_points_blocks', [$this, 'gr_ajax_get_loyalty_points_blocks']);
     109
    90110                // Handle plugin upgrades
    91                 add_action('upgrader_process_complete', [$this, 'gr_handle_plugin_upgrade'], 10, 2);
     111                add_action('upgrader_process_complete', [$this, 'gr_handle_plugin_upgrade'], 10, 2);               
    92112            } catch (Exception $ex) {
    93113            }
     
    103123                    // Check if our plugin was updated
    104124                    $plugin_path = plugin_basename(__FILE__);
    105                     if (isset($options['plugins']) && is_array($options['plugins']) && in_array($plugin_path, $options['plugins'], true)) {
     125                    if (isset($options['plugins']) && is_array($options['plugins']) && appsmav_in_array($plugin_path, $options['plugins'], true)) {
    106126
    107127                        // Safely get plugin data
     
    140160        {
    141161            try {
     162                // Only register REST routes if WP_REST_Controller exists (WordPress 4.4+)
     163                if (!class_exists('Grwoo_API')) {
     164                    return;
     165                }
    142166                $route = new Grwoo_API();
    143167                $route->register_apis();
     
    169193        }
    170194
     195        /**
     196         * Enqueue global JavaScript handler for Apply Points button
     197         * Works for both shortcode and block-based cart/checkout pages
     198         *
     199         * @return void
     200         */
     201        public function gr_enqueue_apply_discount_handler()
     202        {
     203            try {
     204                // Ensure WooCommerce is active
     205                if (!function_exists('is_cart') || !function_exists('is_checkout')) {
     206                    return;
     207                }
     208               
     209                // Check if this is a cart or checkout page (traditional or block)
     210                $is_cart_page = is_cart() || is_checkout();
     211               
     212                // Check if this is a product page (for "Restrictions apply" tooltip)
     213                $is_product_page = function_exists('is_product') && is_product();
     214               
     215                // Additional check for pages with cart/checkout shortcodes
     216                $has_cart_shortcode = false;
     217                global $post;
     218                if (!$is_cart_page && isset($post->post_content)) {
     219                    // Check if page content has woocommerce cart or checkout shortcode
     220                    $has_cart_shortcode = (
     221                        appsmav_strpos($post->post_content, '[woocommerce_cart') !== false ||
     222                        appsmav_strpos($post->post_content, '[woocommerce_checkout') !== false
     223                    );
     224                }
     225               
     226                // Additional check for pages with cart/checkout BLOCKS (Gutenberg)
     227                $has_cart_block = false;
     228                if (!$is_cart_page && !$has_cart_shortcode && function_exists('has_block') && isset($post->ID)) {
     229                    // Check if page has WooCommerce cart or checkout blocks
     230                    $has_cart_block = (
     231                        has_block('woocommerce/cart', $post->ID) ||
     232                        has_block('woocommerce/checkout', $post->ID)
     233                    );
     234                }
     235               
     236                // Additional check for block-based cart/checkout pages using sanitized URL
     237                $is_cart_checkout_url = false;
     238                if (isset($_SERVER['REQUEST_URI'])) {
     239                    $request_uri = sanitize_text_field(appsmav_unslash($_SERVER['REQUEST_URI']));
     240                   
     241                    // Get actual cart/checkout slugs from WooCommerce (works with custom slugs & translations)
     242                    $cart_page_id = function_exists('wc_get_page_id') ? wc_get_page_id('cart') : 0;
     243                    $checkout_page_id = function_exists('wc_get_page_id') ? wc_get_page_id('checkout') : 0;
     244                   
     245                    $cart_slug = ($cart_page_id > 0) ? get_post_field('post_name', $cart_page_id) : 'cart';
     246                    $checkout_slug = ($checkout_page_id > 0) ? get_post_field('post_name', $checkout_page_id) : 'checkout';
     247                   
     248                    // Check URL with actual slugs (handles custom slugs, translations, subdirectories)
     249                    $is_cart_checkout_url = (appsmav_strpos($request_uri, '/' . $cart_slug) !== false) ||
     250                                           (appsmav_strpos($request_uri, '/' . $checkout_slug) !== false) ||
     251                                           (appsmav_strpos($request_uri, 'page_id=' . $cart_page_id) !== false) ||
     252                                           (appsmav_strpos($request_uri, 'page_id=' . $checkout_page_id) !== false);
     253                }
     254               
     255                // Combine all cart/checkout detection methods (4-layer detection)
     256                $is_cart_or_checkout = $is_cart_page || $has_cart_shortcode || $has_cart_block || $is_cart_checkout_url;
     257               
     258                // Exit early if not a cart/checkout/product page
     259                if (!$is_cart_or_checkout && !$is_product_page) {
     260                    return;
     261                }
     262
     263                // Check if this is a block-based page
     264                $is_block_page = gr_is_cart_or_checkout_block();
     265               
     266                // Enqueue FSE-compatible styles for cart/checkout pages
     267                // Styles moved from inline to CSS file for FSE theme.json compatibility
     268                wp_enqueue_style(
     269                    'gr-frontend-styles',
     270                    plugins_url('/css/gr-frontend.css', __FILE__),
     271                    array(),
     272                    self::$_plugin_version
     273                );
     274               
     275                // Prepare script dependencies
     276                $dependencies = array('jquery');
     277                // Script dependency on jquery only (WC Blocks loaded independently)
     278
     279                // Enqueue the script (consolidated frontend cart/checkout handler)
     280                $script_url = plugins_url('/js/gr-apply-discount.js', __FILE__);
     281               
     282                wp_enqueue_script(
     283                    'gr-apply-discount-handler',
     284                    $script_url,
     285                    $dependencies,
     286                    self::$_plugin_version,
     287                    true
     288                );
     289
     290                // Get the current GR coupon code from session (safe fallback)
     291                $redeem_coupon = self::REDEEM_COUPON;
     292                try {
     293                    if (!empty(WC()->session)) {
     294                        $session_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
     295                        if (!empty($session_coupon)) {
     296                            $redeem_coupon = $session_coupon;
     297                        }
     298                    }
     299                } catch (Exception $coupon_ex) {
     300                    // Use default if session not available
     301                    $redeem_coupon = self::REDEEM_COUPON;
     302                }
     303
     304                // Get WooCommerce cart and checkout URLs
     305                $cart_url = '';
     306                $checkout_url = '';
     307                if (function_exists('wc_get_cart_url')) {
     308                    $cart_url = wc_get_cart_url();
     309                }
     310                if (function_exists('wc_get_checkout_url')) {
     311                    $checkout_url = wc_get_checkout_url();
     312                }
     313
     314                // Pass data to JavaScript with properly escaped values
     315                wp_localize_script('gr-apply-discount-handler', 'grconnect_vars', array(
     316                    'ajax_url' => esc_url(admin_url('admin-ajax.php')),
     317                    'apply_nonce' => wp_create_nonce('apply_gr_discount'),
     318                    'remove_nonce' => wp_create_nonce('remove_gr_discount'),
     319                    'blocks_nonce' => wp_create_nonce('gr_blocks_loyalty_data'),
     320                    'cart_details_nonce' => wp_create_nonce('gr_nonce'),
     321                    'gr_coupon' => (string) $redeem_coupon, // Authoritative GR coupon code for exact matching
     322                    'coupon_removed_message' => esc_html__('Coupon removed successfully.', 'gratisfaction'),
     323                    'is_cart_page' => $is_cart_or_checkout, // Detected in PHP - handles all cases
     324                    'is_block_page' => $is_block_page, // Block page detection from PHP
     325                    'cart_url' => esc_url($cart_url), // Official WooCommerce cart URL
     326                    'checkout_url' => esc_url($checkout_url), // Official WooCommerce checkout URL
     327                    'use_php_hooks' => $has_cart_shortcode // TRUE ONLY for shortcode pages, NOT official cart/checkout
     328                ));
     329               
     330                // If block page, also pass grBlocksData for block-specific functionality
     331                if ($is_block_page) {
     332                    // Generate fresh nonce for blocks
     333                    $fresh_nonce = wp_create_nonce('gr_blocks_loyalty_data');
     334                   
     335                    wp_localize_script('gr-apply-discount-handler', 'grBlocksData', array(
     336                        'ajax_url' => esc_url(admin_url('admin-ajax.php')),
     337                        'nonce' => $fresh_nonce,
     338                        'cart_url' => esc_url(wc_get_cart_url()),
     339                        'is_checkout' => is_checkout() ? '1' : '0'
     340                    ));
     341                }
     342            } catch (Exception $ex) {
     343                // Silently catch exception
     344            }
     345        }
     346       
    171347        public function get_discount_error_message($message, $message_code, $coupon)
    172348        {
     
    175351                    return $message;
    176352
     353                // Safe object method call - verify coupon is object and has get_code method
     354                if (empty($coupon) || !is_object($coupon) || !method_exists($coupon, 'get_code'))
     355                    return $message;
     356                   
    177357                $coupon = appsmav_strtolower($coupon->get_code());
    178358                $redeem_coupon = appsmav_strtolower(WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON));
     
    190370                    return $message;
    191371
     372                // Safe object method call - verify coupon is object and has get_code method
     373                if (empty($coupon) || !is_object($coupon) || !method_exists($coupon, 'get_code'))
     374                    return $message;
     375                   
    192376                $coupon = appsmav_strtolower($coupon->get_code());
     377                $redeem_coupon = appsmav_strtolower(WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON));
     378                if ($coupon === $redeem_coupon) {
     379                    // Message code 1 = Coupon applied successfully
     380                    if (WC_Coupon::WC_COUPON_SUCCESS === $message_code) {
     381                        // Return message from session - WooCommerce will display it via notice system
     382                        // For classic pages: WooCommerce displays notices automatically during fragment refresh
     383                        // For block pages: JavaScript handles display (but filter still needs to return message for consistency)
     384                        return __(WC()->session->get('gr_redeemed_status_msg'), 'gratisfaction');
     385                    }
     386                   
     387                    // Message code 2 = Coupon removed successfully
     388                    if (2 === $message_code) {
     389                        // Return translatable message from language files
     390                        return __('Coupon removed successfully.', 'gratisfaction');
     391                    }
     392                }
     393            } catch (Exception $ex) {
     394
     395            }
     396            return $message;
     397        }
     398
     399        /**
     400         * Clear success message when GR coupon is removed
     401         * This prevents "Reward added Successfully" from showing after coupon removal
     402         *
     403         * @param string $coupon_code The coupon code that was removed
     404         */
     405        public function clear_success_message_on_coupon_removal($coupon_code)
     406        {
     407            try {
     408                if (empty(WC()->session)) {
     409                    return;
     410                }
     411               
     412                // Check if the removed coupon is our GR coupon
    193413                $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
    194                 if ($coupon === appsmav_strtolower($redeem_coupon)) {
    195                     if (WC_Coupon::WC_COUPON_SUCCESS === $message_code)
    196                         return __(WC()->session->get('gr_redeemed_status_msg'), 'gratisfaction');
     414                if (appsmav_strtolower($coupon_code) === appsmav_strtolower($redeem_coupon)) {
     415                    // Clear the success message from session
     416                    WC()->session->set('gr_redeemed_status_msg', '');
     417                   
     418                    // Also clear WooCommerce notices that contain the success message
     419                    if (function_exists('wc_get_notices') && function_exists('wc_clear_notices') && function_exists('wc_add_notice')) {
     420                        $all_notices = wc_get_notices();
     421                       
     422                        // Clear all notices
     423                        wc_clear_notices();
     424                       
     425                        // Re-add only notices that are NOT the "Reward added" message
     426                        if (!empty($all_notices)) {
     427                            foreach ($all_notices as $notice_type => $notices) {
     428                                if (is_array($notices)) {
     429                                    foreach ($notices as $notice) {
     430                                        $notice_text = is_array($notice) && isset($notice['notice']) ? $notice['notice'] : $notice;
     431                                        $notice_text_lower = appsmav_strtolower($notice_text);
     432                                       
     433                                        // Skip if this is the "Reward added" success message
     434                                        if (appsmav_strpos($notice_text_lower, 'reward added') !== false ||
     435                                            appsmav_strpos($notice_text_lower, 'added successfully') !== false) {
     436                                            continue; // Don't re-add this notice
     437                                        }
     438                                       
     439                                        // Re-add other notices
     440                                        wc_add_notice($notice_text, $notice_type);
     441                                    }
     442                                }
     443                            }
     444                        }
     445                    }
    197446                }
    198447            } catch (Exception $ex) {
    199 
    200             }
    201             return $message;
     448                // Silently fail
     449            }
    202450        }
    203451
     
    287535                if (empty($ratio))
    288536                    $ratio = self::gr_get_currency_ratio();
     537
     538                // Prevent division by zero
     539                if (empty($ratio) || $ratio == 0) {
     540                    $ratio = 1;
     541                }
    289542
    290543                // Set redeem points in descriptions
     
    371624                    if ($is_blocked_role)
    372625                    {
    373                         echo WC()->session->get('no_records_found', 'No Activites Found');
     626                        echo esc_html(WC()->session->get('no_records_found', 'No Activites Found'));
    374627                        return;
    375628                    }
     
    395648
    396649                if(!empty($resp))
    397                     $resp = json_decode($resp, true);
     650                    $resp = appsmav_json_decode($resp, true);
     651
     652                // Ensure $resp is always an array to prevent "Cannot access offset of type string on string" error
     653                if(!is_array($resp))
     654                    $resp = array();
    398655
    399656                if(!empty($resp['error']))
     
    420677                    $this->get_settings_api();
    421678
    422                 echo $style.'<div class="rewardsActivities"><h3>'.WC()->session->get('label_life_time_points', 'My Life Time Points').'</h3><ul class="pointsCon clearfix">
    423                         <li><label>'.WC()->session->get('label_available_points', 'Redeemable points').'</label><span class="titlePoints">'.$resp['user_points'].'</span></li>
    424                         <li><label>'.WC()->session->get('label_exclusion_points', 'Latest Exclusion period points').'</label><span class="titlePoints">'.$resp['exclusion_points'].'</span></li>
    425                         <li><label>'.WC()->session->get('label_total_points', 'Total points').'</label><span class="titlePoints">'.$resp['total_points'].'</span></li>
    426                         <li><label>'.WC()->session->get('label_redeemed_points', 'Redeemed points').'</label><span class="titlePoints">'.$resp['redeem_points'].'</span></li>
     679                echo $style.'<div class="rewardsActivities"><h3>'.esc_html(WC()->session->get('label_life_time_points', 'My Life Time Points')).'</h3><ul class="pointsCon clearfix">
     680                        <li><label>'.esc_html(WC()->session->get('label_available_points', 'Redeemable points')).'</label><span class="titlePoints">'.absint($resp['user_points']).'</span></li>
     681                        <li><label>'.esc_html(WC()->session->get('label_exclusion_points', 'Latest Exclusion period points')).'</label><span class="titlePoints">'.absint($resp['exclusion_points']).'</span></li>
     682                        <li><label>'.esc_html(WC()->session->get('label_total_points', 'Total points')).'</label><span class="titlePoints">'.absint($resp['total_points']).'</span></li>
     683                        <li><label>'.esc_html(WC()->session->get('label_redeemed_points', 'Redeemed points')).'</label><span class="titlePoints">'.absint($resp['redeem_points']).'</span></li>
    427684                        </ul></div>';
    428685            }
    429686            catch(Exception $e)
    430687            {
    431                 echo WC()->session->get('no_records_found', 'No Activites Found');
     688                echo esc_html(WC()->session->get('no_records_found', 'No Activites Found'));
    432689            }
    433690
     
    601858
    602859                $order = new WC_Order($order_id);
     860                if (empty($order) || !($order instanceof WC_Order)) {
     861                    return;
     862                }
    603863
    604864                $param = [];
     
    610870                    $user_email = $ordered_user->get('user_email');
    611871
    612                 //$param['user_email'] = $user_email;
    613                 $param['roles'] = (empty($ordered_user) || !is_object($ordered_user)) ? [] : $ordered_user->roles;
     872                $param['roles'] = ($ordered_user instanceof WP_User) ? $ordered_user->roles : [];
    614873
    615874                if (version_compare( WC_VERSION, '3.7', '<' ))
     
    628887                {
    629888                    $points = appsmav_explode('_', $gr_applied_points);
    630                     $param['redeem_points']  = empty($points['1']) ? 0 : $points['1'];
    631                     $param['redeem_charges'] = empty($points['2']) ? 0 : $points['2'];
     889                    // Ensure $points is array and has required elements (explode returns numeric indices: 0, 1, 2...)
     890                    if (is_array($points) && count($points) >= 3) {
     891                        $param['redeem_points']  = isset($points[1]) ? absint($points[1]) : 0;
     892                        $param['redeem_charges'] = isset($points[2]) ? floatval($points[2]) : 0;
     893                    } else {
     894                        $param['redeem_points'] = 0;
     895                        $param['redeem_charges'] = 0;
     896                    }
    632897                }
    633898
     
    640905                $param['subtotal'] = $order->get_subtotal();
    641906                $param['total'] = $order->get_total();
    642                 //$param['total'] = $order->get_total() - $order->get_total_refunded();
    643 
    644                 // Full refund, set total amount for points deduction.
    645 //                if ($param['total'] <= 0)
    646 //                    $param['total'] = $order->get_total();
    647 
    648907                $param['refunded'] = $order->get_total_refunded();
    649908                $param['shipping'] = $order->get_shipping_total();
     
    656915                if (!empty($refunds) && is_array($refunds)) {
    657916                    foreach ($refunds as $key => $refund) {
    658                         if (empty($refund) || !is_object($refund)) {
     917                        if (empty($refund) || !($refund instanceof WC_Order_Refund)) {
    659918                            continue;
    660919                        }
     920
    661921                        $refundData[$key]['refund'] = self::sanitize_refund_data_for_api($refund->get_data());
     922
    662923                        $refund_items = $refund->get_items();
    663924                        if (!empty($refund_items) && is_array($refund_items)) {
    664925                            foreach ($refund_items as $item_id => $item) {
    665                                 if (empty($item) || !is_object($item)) {
     926                                if (empty($item) || !($item instanceof WC_Order_Item)) {
    666927                                    continue;
    667928                                }
     929
    668930                                $refundData[$key]['line_items'][$item_id] = self::sanitize_refund_data_for_api($item->get_data());
    669931                            }
     
    700962                {
    701963                    $order_data = $order->get_data();
    702                     $param['name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
     964                    $billing_data = isset($order_data['billing']) && is_array($order_data['billing']) ? $order_data['billing'] : array();
     965                    $param['name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
    703966                    $param['number'] = empty($order_data['number']) ? '' : $order_data['number'];
    704                     $param['first_name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
    705                     $param['last_name'] = empty($order_data['billing']['last_name']) ? '' : $order_data['billing']['last_name'];
    706                     $param['postcode'] = empty($order_data['billing']['postcode']) ? '' : $order_data['billing']['postcode'];
    707                     $param['country'] = empty($order_data['billing']['country']) ? '' : $order_data['billing']['country'];
    708                 }
    709 
    710                 $param['created_date'] = $order->get_date_created()->format('c');
     967                    $param['first_name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
     968                    $param['last_name'] = empty($billing_data['last_name']) ? '' : $billing_data['last_name'];
     969                    $param['postcode'] = empty($billing_data['postcode']) ? '' : $billing_data['postcode'];
     970                    $param['country'] = empty($billing_data['country']) ? '' : $billing_data['country'];
     971                }
     972
     973                // Safe date handling - prevent fatal if get_date_created() returns null
     974                $date_created = $order->get_date_created();
     975                $param['created_date'] = (is_object($date_created) && method_exists($date_created, 'format'))
     976                    ? $date_created->format('c')
     977                    : current_time('c');
    711978                $param['user_ip'] = $order->get_customer_ip_address();
    712979                $param['email'] = !empty($user_email) ? $user_email : $order->get_billing_email();
     
    722989
    723990                        if( appsmav_get_parent_id($order) === 0 && get_post_meta( $order_id, 'has_wcmp_sub_order', true ) == '1'){
    724                             $param['comment'] = 'Main WCMp Order Id ' . str_replace('wc-', '', sanitize_text_field($_REQUEST['order_status'])) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
     991                            $order_status_safe = isset($_REQUEST['order_status']) ? sanitize_text_field($_REQUEST['order_status']) : $param['order_status'];
     992                            $param['comment'] = 'Main WCMp Order Id ' . appsmav_str_replace('wc-', '', $order_status_safe) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
    725993                            $param['total'] = 0;
    726994                            $param['subtotal'] = 0;
     
    7331001                    {
    7341002                        if( appsmav_get_parent_id($order) === 0 && get_post_meta( $order_id, 'has_sub_order', true ) == '1'){
    735                             $param['comment'] = 'Main Dokan Order Id ' . str_replace('wc-', '', sanitize_text_field($_REQUEST['order_status'])) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
     1003                            $order_status_safe = isset($_REQUEST['order_status']) ? sanitize_text_field($_REQUEST['order_status']) : $param['order_status'];
     1004                            $param['comment'] = 'Main Dokan Order Id ' . appsmav_str_replace('wc-', '', $order_status_safe) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
    7361005                            $param['total'] = 0;
    7371006                            $param['subtotal'] = 0;
     
    7681037                // Set up the settings for this plugin
    7691038                $order = new WC_Order($order_id);
     1039                if (empty($order) || !($order instanceof WC_Order)) {
     1040                    return;
     1041                }
    7701042
    7711043                $param = [];
     
    8651137                {
    8661138                    $order_data = $order->get_data();
    867                     $param['name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
     1139                    $billing_data = isset($order_data['billing']) && is_array($order_data['billing']) ? $order_data['billing'] : array();
     1140                    $param['name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
    8681141                    $param['number'] = empty($order_data['number']) ? '' : $order_data['number'];
    869                     $param['first_name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
    870                     $param['last_name'] = empty($order_data['billing']['last_name']) ? '' : $order_data['billing']['last_name'];
    871                     $param['postcode'] = empty($order_data['billing']['postcode']) ? '' : $order_data['billing']['postcode'];
    872                     $param['country'] = empty($order_data['billing']['country']) ? '' : $order_data['billing']['country'];
     1142                    $param['first_name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
     1143                    $param['last_name'] = empty($billing_data['last_name']) ? '' : $billing_data['last_name'];
     1144                    $param['postcode'] = empty($billing_data['postcode']) ? '' : $billing_data['postcode'];
     1145                    $param['country'] = empty($billing_data['country']) ? '' : $billing_data['country'];
    8731146                }
    8741147
     
    8801153                $param['comment'] = 'Order Id - ' . $order_id . ' From ' . get_option('siteurl');
    8811154                $param['status'] = 'Add';
    882                 $param['created_date'] = $order->get_date_created()->format('c');
     1155                // Safe date handling - prevent fatal if get_date_created() returns null
     1156                $date_created = $order->get_date_created();
     1157                $param['created_date'] = (is_object($date_created) && method_exists($date_created, 'format'))
     1158                    ? $date_created->format('c')
     1159                    : current_time('c');
    8831160                $param['user_ip'] = $order->get_customer_ip_address();
    8841161
     
    9021179
    9031180                        if( appsmav_get_parent_id($order) === 0 && get_post_meta( $order_id, 'has_wcmp_sub_order', true ) == '1'){
    904                             $param['comment'] = 'Main WCMp Order Id ' . str_replace('wc-', '', sanitize_text_field($_REQUEST['order_status'])) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
     1181                            $order_status_safe = isset($_REQUEST['order_status']) ? sanitize_text_field($_REQUEST['order_status']) : $param['order_status'];
     1182                            $param['comment'] = 'Main WCMp Order Id ' . appsmav_str_replace('wc-', '', $order_status_safe) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
    9051183                            $param['total'] = 0;
    9061184                            $param['subtotal'] = 0;
     
    9131191                    {
    9141192                        if( appsmav_get_parent_id($order) === 0 && get_post_meta( $order_id, 'has_sub_order', true ) == '1'){
    915                             $param['comment'] = 'Main Dokan Order Id ' . str_replace('wc-', '', sanitize_text_field($_REQUEST['order_status'])) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
     1193                            $order_status_safe = isset($_REQUEST['order_status']) ? sanitize_text_field($_REQUEST['order_status']) : $param['order_status'];
     1194                            $param['comment'] = 'Main Dokan Order Id ' . appsmav_str_replace('wc-', '', $order_status_safe) . ' - ' . $order_id . ' From ' . get_option('grconnect_shop_id', 0).' total '.$param['total'];
    9161195                            $param['total'] = 0;
    9171196                            $param['subtotal'] = 0;
     
    9511230                // Set up the settings for this plugin
    9521231                $user = get_userdata($customer_id);
     1232                if (empty($user) || !($user instanceof WP_User)) {
     1233                    return;
     1234                }
     1235
    9531236                $shop_id = get_option('grconnect_shop_id', 0);
    9541237
     
    9651248                $param['first_name'] = empty($user->first_name) ? '' : $user->first_name;
    9661249                $param['last_name'] = empty($user->last_name) ? '' : $user->last_name;
    967                 $param['user_ip'] = empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR'];
     1250                $param['user_ip'] = !empty($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(appsmav_unslash($_SERVER['REMOTE_ADDR'])) : '';
    9681251                $param['customer_id'] = $customer_id;
    9691252                $param['id_shop'] = $shop_id;
     
    9811264
    9821265                if(!empty($resp))
    983                     $resp = json_decode($resp, true);
     1266                    $resp = appsmav_json_decode($resp, true);
     1267
     1268                // Ensure $resp is always an array to prevent "Cannot access offset of type string on string" error
     1269                if(!is_array($resp))
     1270                    $resp = array();
    9841271
    9851272                if(!empty($resp['error']))
     
    10091296
    10101297                $user = get_userdata($customer_id);
     1298                if (empty($user) || !($user instanceof WP_User)) {
     1299                    return;
     1300                }
    10111301
    10121302                $param['email'] = $user->user_email;
     
    10151305                $param['last_name'] = empty($user->last_name) ? '' : $user->last_name;
    10161306
    1017                 $param['user_ip'] = empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR'];
     1307                $param['user_ip'] = !empty($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(appsmav_unslash($_SERVER['REMOTE_ADDR'])) : '';
    10181308                $param['customer_id'] = $customer_id;
    10191309                $param['id_shop'] = $grShopId;
     
    10311321
    10321322                if(!empty($resp))
    1033                     $resp = json_decode($resp, true);
     1323                    $resp = appsmav_json_decode($resp, true);
     1324
     1325                // Ensure $resp is always an array to prevent "Cannot access offset of type string on string" error
     1326                if (!is_array($resp))
     1327                    $resp = array();
    10341328
    10351329                if(!empty($resp['error']))
     
    10621356                // Set up the settings for this plugin
    10631357                $action = isset($_REQUEST['action']) ? sanitize_text_field($_REQUEST['action']) : '';
    1064                 if(!empty($action) && $action == 'woocommerce_delete_refund')
    1065                 {
     1358                if (!empty($action) && $action === 'woocommerce_delete_refund') {
    10661359                    $refund = new WC_Order_Refund($refund_id);
    1067                     if (empty($refund) || !is_object($refund)) {
     1360                    if (empty($refund) || !($refund instanceof WC_Order_Refund)) {
    10681361                        return;
    10691362                    }
     1363
    10701364                    $parent_id = appsmav_get_parent_id($refund);
    10711365                    if (empty($parent_id)) {
    10721366                        return;
    10731367                    }
     1368
    10741369                    $order = new WC_Order($parent_id);
    1075                     if (empty($order) || !is_object($order)) {
     1370                    if (empty($order) || !($order instanceof WC_Order)) {
    10761371                        return;
    10771372                    }
     
    11051400                    }
    11061401
    1107                     $param['created_date'] = $order->get_date_created()->format('c');
     1402                    // Safe date handling - prevent fatal if get_date_created() returns null
     1403                    $date_created = $order->get_date_created();
     1404                    $param['created_date'] = (is_object($date_created) && method_exists($date_created, 'format'))
     1405                        ? $date_created->format('c')
     1406                        : current_time('c');
    11081407                    $param['user_ip'] = $order->get_customer_ip_address();
    11091408                    $param['email'] = !empty($email) ? $email : $order->get_billing_email();
     
    11261425                    {
    11271426                        $order_data = $order->get_data();
    1128                         $param['name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
     1427                        $billing_data = isset($order_data['billing']) && is_array($order_data['billing']) ? $order_data['billing'] : array();
     1428                        $param['name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
    11291429                        $param['number'] = empty($order_data['number']) ? '' : $order_data['number'];
    1130                         $param['first_name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
    1131                         $param['last_name'] = empty($order_data['billing']['last_name']) ? '' : $order_data['billing']['last_name'];
    1132                         $param['postcode'] = empty($order_data['billing']['postcode']) ? '' : $order_data['billing']['postcode'];
    1133                         $param['country'] = empty($order_data['billing']['country']) ? '' : $order_data['billing']['country'];
     1430                        $param['first_name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
     1431                        $param['last_name'] = empty($billing_data['last_name']) ? '' : $billing_data['last_name'];
     1432                        $param['postcode'] = empty($billing_data['postcode']) ? '' : $billing_data['postcode'];
     1433                        $param['country'] = empty($billing_data['country']) ? '' : $billing_data['country'];
    11341434                    }
    11351435
     
    11871487                $grCampId  = get_option('grconnect_secret');
    11881488                $grPayload = get_option('grconnect_payload');
    1189                 if (empty($grShopId) || empty($grAppId) || empty($grCampId) || empty($grPayload) || empty($_REQUEST['refund_amount']))
     1489               
     1490                // Check refund amount exists and is valid
     1491                $refund_amount = isset($_REQUEST['refund_amount']) ? sanitize_text_field(appsmav_unslash($_REQUEST['refund_amount'])) : '';
     1492               
     1493                if (empty($grShopId) || empty($grAppId) || empty($grCampId) || empty($grPayload) || empty($refund_amount))
    11901494                    return;
    11911495
    11921496                // Set up the settings for this plugin
    11931497                $order = new WC_Order($order_id);
    1194                 if (empty($order) || !is_object($order)) {
     1498                if (empty($order) || !($order instanceof WC_Order)) {
    11951499                    return;
    11961500                }
     
    12281532                if (!empty($refunds) && is_array($refunds)) {
    12291533                    foreach ($refunds as $key => $refund) {
    1230                         if (empty($refund) || !is_object($refund)) {
     1534                        if (empty($refund) || !($refund instanceof WC_Order_Refund)) {
    12311535                            continue;
    12321536                        }
     
    12661570                $param['tax'] = $order->get_total_tax();
    12671571
    1268                 $param['created_date'] = $order->get_date_created()->format('c');
     1572                // Safe date handling - prevent fatal if get_date_created() returns null
     1573                $date_created = $order->get_date_created();
     1574                $param['created_date'] = (is_object($date_created) && method_exists($date_created, 'format'))
     1575                    ? $date_created->format('c')
     1576                    : current_time('c');
    12691577                $param['user_ip'] = $order->get_customer_ip_address();
    12701578                $param['email'] = !empty($user_email) ? $user_email : $order->get_billing_email();
     
    12861594                {
    12871595                    $order_data = $order->get_data();
    1288                     $param['name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
     1596                    $billing_data = isset($order_data['billing']) && is_array($order_data['billing']) ? $order_data['billing'] : array();
     1597                    $param['name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
    12891598                    $param['number'] = empty($order_data['number']) ? '' : $order_data['number'];
    1290                     $param['first_name'] = empty($order_data['billing']['first_name']) ? '' : $order_data['billing']['first_name'];
    1291                     $param['last_name'] = empty($order_data['billing']['last_name']) ? '' : $order_data['billing']['last_name'];
    1292                     $param['postcode'] = empty($order_data['billing']['postcode']) ? '' : $order_data['billing']['postcode'];
    1293                     $param['country'] = empty($order_data['billing']['country']) ? '' : $order_data['billing']['country'];
     1599                    $param['first_name'] = empty($billing_data['first_name']) ? '' : $billing_data['first_name'];
     1600                    $param['last_name'] = empty($billing_data['last_name']) ? '' : $billing_data['last_name'];
     1601                    $param['postcode'] = empty($billing_data['postcode']) ? '' : $billing_data['postcode'];
     1602                    $param['country'] = empty($billing_data['country']) ? '' : $billing_data['country'];
    12941603                }
    12951604
     
    14251734
    14261735                            $order = new WC_Order($order_id);
    1427                             if (empty($order))
     1736                            if (empty($order) || !($order instanceof WC_Order) || empty($order->get_id()))
    14281737                                throw new Exception('Order details not found');
    14291738
     
    14391748                            foreach($couponsArr as $coupon_code)
    14401749                            {
    1441                                 $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
    1442                                 if(appsmav_strtolower($coupon_code) === appsmav_strtolower($redeem_coupon))
     1750                                $coupon_code_lower = appsmav_strtolower($coupon_code);
     1751                                $redeem_coupon = appsmav_strtolower(WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON));
     1752                                if($coupon_code_lower === $redeem_coupon)
    14431753                                {
    14441754                                    WC()->session->set('gr_applied_points', '');
     
    14531763
    14541764                //pbp appeding more variable is the config files
     1765                // Cache session object for performance and safety
     1766                $wc_session = WC()->session;
     1767                $wc_cart = WC()->cart;
     1768               
    14551769                $cart_count = 0;
    1456                 if (!empty(WC()->cart)) {
    1457                     $cart_items = WC()->cart->get_cart();
    1458                     $cart_count = (is_array($cart_items) || $cart_items instanceof Countable) ? count($cart_items) : 0;
    1459 
    1460                     //Reset the session values if discount is not there
    1461                     if (empty(WC()->session->get('gr_user_max_discount', 0))) {
     1770                if (!empty($wc_cart)) {
     1771                    $cart_items = $wc_cart->get_cart();
     1772                    $cart_count = (is_array($cart_items) || ($cart_items instanceof Countable)) ? count($cart_items) : 0;
     1773
     1774                    // Validate session exists before accessing
     1775                    if (!empty($wc_session)) {
    14621776                        self::gr_calc_point_value();
    14631777                    }
     
    14651779
    14661780                // Do not move this to above
    1467                 $discounted_amount = WC()->session->get('gr_user_max_discount', 0);
    1468                 $is_discount_applied  = WC()->session->get('gr_discount_applied', 0);
     1781                // Safely retrieve session values with fallbacks
     1782                $discounted_amount = !empty($wc_session) ? $wc_session->get('gr_user_max_discount', 0) : 0;
     1783                $is_discount_applied = !empty($wc_session) ? $wc_session->get('gr_discount_applied', 0) : 0;
    14691784                $cart_url = !empty(wc_get_cart_url()) ? esc_url(wc_get_cart_url()) : '';
    1470                 $gr_sdk_version = !empty(WC()->session->get('gr_sdk_version', 0)) ? WC()->session->get('gr_sdk_version', 0) : self::$_plugin_version;
    1471                 $gr_widget_config_version = !empty(WC()->session->get('gr_widget_config_version', 0)) ? WC()->session->get('gr_widget_config_version', 0) : WC()->session->get('gr_api_sess', 0);
     1785                $gr_sdk_version = !empty($wc_session) && $wc_session->get('gr_sdk_version', 0) ? $wc_session->get('gr_sdk_version', 0) : self::$_plugin_version;
     1786                $gr_widget_config_version = !empty($wc_session) && $wc_session->get('gr_widget_config_version', 0) ? $wc_session->get('gr_widget_config_version', 0) : (!empty($wc_session) ? $wc_session->get('gr_api_sess', 0) : 0);
     1787
     1788                // Check actual cart state for is_discount_applied on page load
     1789                // Session flags can be stale, so verify coupon is actually in cart
     1790                if (!empty($wc_session) && !empty($wc_cart)) {
     1791                    $redeem_coupon_raw = $wc_session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
     1792                    // Sanitize coupon code to prevent potential issues
     1793                    $redeem_coupon = sanitize_text_field($redeem_coupon_raw);
     1794                    $cart_has_coupon = $wc_cart->has_discount($redeem_coupon);
     1795                   
     1796                    // CART IS SOURCE OF TRUTH: If coupon is in cart, it's applied
     1797                    // Override session flag with actual cart state for accuracy
     1798                    if ($cart_has_coupon) {
     1799                        $is_discount_applied = 1;
     1800                    } else {
     1801                        // Coupon not in cart, ensure flag is cleared
     1802                        $is_discount_applied = 0;
     1803                    }
     1804                }
    14721805
    14731806                $config = [
     
    14921825                        'sdk_version' => esc_js($gr_sdk_version),
    14931826                        'version' => esc_js($gr_widget_config_version),
    1494 
    1495                     ],
     1827                        'ajax_url' => esc_js(admin_url('admin-ajax.php'))
     1828                    ]
    14961829                ];
    14971830
     
    15081841                    }
    15091842                }
    1510 
    1511                 echo '<script>var AMGRConfig = ' . wp_json_encode($config) . ';
     1843               
     1844                // Safely retrieve session values with validation
     1845                // Include currency_ratio so SDK can calculate points worth correctly for multi-currency stores
     1846                $currency_ratio = self::gr_get_currency_ratio();
     1847               
     1848                // Get current WooCommerce currency for display (overrides app.json currency)
     1849                $current_currency = get_woocommerce_currency();
     1850                $currency_symbol = get_woocommerce_currency_symbol($current_currency);
     1851               
     1852                $config['pay_points'] = [
     1853                    'redeem_restrict_terms' => !empty($wc_session) ? esc_js($wc_session->get('gr_pay_redeem_restrict_terms', '')) : '',
     1854                    'label_redeem_restriction_apply' => !empty($wc_session) ? esc_js($wc_session->get('gr_label_redeem_restriction_apply', '')) : '',
     1855                    'paybypoints_coupon' => !empty($wc_session) ? esc_js($wc_session->get('gr_paybypoints_coupon', self::REDEEM_COUPON)) : esc_js(self::REDEEM_COUPON),
     1856                    'currency_ratio' => esc_js($currency_ratio),
     1857                    'current_currency' => esc_js($current_currency),
     1858                    'current_currency_symbol' => esc_js($currency_symbol)
     1859                ];
     1860
     1861                // Output config and load SDK script
     1862                echo '<script type="text/javascript">
     1863                var AMGRConfig = ' . appsmav_json_encode($config) . ';
    15121864                (function(d, s, id) {
    15131865                    var js, amjs = d.getElementsByTagName(s)[0];
    15141866                    if (d.getElementById(id)) return;
    1515                     js = d.createElement(s); js.id = id; js.async = true;
    1516                     js.src = "' . esc_url(self::$_c_sdk_url) . '?v=' . esc_attr($gr_sdk_version) . '";
     1867                    js = d.createElement(s);
     1868                    js.id = id;
     1869                    js.async = true;
     1870                    js.src = ' . appsmav_json_encode(esc_url(self::$_c_sdk_url) . '?v=' . esc_attr($gr_sdk_version)) . ';
    15171871                    amjs.parentNode.insertBefore(js, amjs);
    15181872                }(document, "script", "gratisfaction-sdk"));
     
    15431897                    $order_id = sanitize_text_field($_GET['order-received']);
    15441898                } elseif (isset($_GET['key']) && !empty($_GET['key']) && version_compare( WC_VERSION, '5.9', '>=' )) {
    1545                     $order_id = wc_get_order_id_by_order_key( sanitize_text_field($_GET['key']) );
     1899                    $order_id = wc_get_order_id_by_order_key( sanitize_text_field(appsmav_unslash($_GET['key'])) );
    15461900                } else {
    1547                     $url = $_SERVER['SERVER_NAME'] . $_SERVER['REQUEST_URI'];
    1548                     $template_name = strpos($url,'/order-received/') === false ? '/view-order/' : '/order-received/';
    1549                     if (strpos($url,$template_name) !== false) {
    1550                         $start = strpos($url,$template_name);
    1551                         $first_part = substr($url, $start+appsmav_strlen($template_name));
    1552                         $order_id = substr($first_part, 0, strpos($first_part, '/'));
     1901                    $server_name = !empty($_SERVER['SERVER_NAME']) ? sanitize_text_field(appsmav_unslash($_SERVER['SERVER_NAME'])) : '';
     1902                    $request_uri = !empty($_SERVER['REQUEST_URI']) ? esc_url_raw(appsmav_unslash($_SERVER['REQUEST_URI'])) : '';
     1903                    $url = $server_name . $request_uri;
     1904                   
     1905                    // Get WooCommerce endpoint slugs (works with custom endpoints & translations)
     1906                    $order_received_slug = function_exists('get_option') ? get_option('woocommerce_checkout_order_received_endpoint', 'order-received') : 'order-received';
     1907                    $view_order_slug = function_exists('get_option') ? get_option('woocommerce_myaccount_view_order_endpoint', 'view-order') : 'view-order';
     1908                   
     1909                    // Determine which endpoint is in the URL
     1910                    $template_name = (appsmav_strpos($url, '/' . $order_received_slug . '/') !== false) ? '/' . $order_received_slug . '/' : '/' . $view_order_slug . '/';
     1911                   
     1912                    if (appsmav_strpos($url, $template_name) !== false) {
     1913                        $start = appsmav_strpos($url, $template_name);
     1914                        $first_part = appsmav_substr($url, $start+appsmav_strlen($template_name));
     1915                        $order_id = appsmav_substr($first_part, 0, appsmav_strpos($first_part, '/'));
    15531916                    }
    15541917                }
     
    16261989            try {
    16271990                $app_config = gr_get_app_config();
    1628                 if (empty($app_config['points']['loyalty_campaign_enabled']) || empty($app_config['reviews']['global_review_enabled']) || empty($app_config['reviews']['is_testimonial_enabled'])) {
     1991                if (empty(appsmav_get_nested_array_value($app_config, ['points', 'loyalty_campaign_enabled'], 0)) ||
     1992                    empty(appsmav_get_nested_array_value($app_config, ['reviews', 'global_review_enabled'], 0)) ||
     1993                    empty(appsmav_get_nested_array_value($app_config, ['reviews', 'is_testimonial_enabled'], 0))) {
    16291994                    return;
    16301995                }
    16311996
    16321997                $post_meta = get_post_meta($post_id);
    1633                 if (!isset($post_meta['email']) || empty($post_meta['email'][0]) || self::_isActiveCampaign() === false)
     1998                // Safe array access - check if email exists and is array before accessing index
     1999                $email_meta = isset($post_meta['email']) && is_array($post_meta['email']) ? $post_meta['email'] : array();
     2000                if (empty($email_meta[0]) || self::_isActiveCampaign() === false)
    16342001                    return;
    16352002
     
    16472014                $review_details['comment_ID'] = !empty($post->ID) ? $post->ID : 0;
    16482015                $review_details['comment_post_ID'] = !empty($post->ID) ? $post->ID : 0;
    1649                 $review_details['comment_author_email'] = !empty($post_meta['email'][0]) ? $post_meta['email'][0] : '';
     2016                $review_details['comment_author_email'] = (isset($post_meta['email']) && is_array($post_meta['email']) && isset($post_meta['email'][0])) ? $post_meta['email'][0] : '';
    16502017                $review_details['comment_date'] = !empty($post->post_date) ? $post->post_date : '';
    16512018
     
    16532020                $review_details['comment_approved'] = !empty($post->post_status == 'publish') ? 1 : 0;
    16542021                $review_details['comment_status'] = $comment_status;
    1655                 $review_details['rating'] = !empty($post_meta['star_rating'][0]) ? $post_meta['star_rating'][0] : '';
     2022                $review_details['rating'] = (isset($post_meta['star_rating']) && is_array($post_meta['star_rating']) && isset($post_meta['star_rating'][0])) ? $post_meta['star_rating'][0] : '';
    16562023                $review_details['product_key'] = !empty($testimonial_key) ? $testimonial_key : '';
    16572024                $review_details['product_url'] = !empty($testimonial_url) ? $testimonial_url : '';
     
    16612028
    16622029                // Check the user role is allowed to proceed
    1663                 $user = get_user_by('email', $post_meta['email'][0]);
     2030                $user_email = (isset($post_meta['email']) && is_array($post_meta['email']) && isset($post_meta['email'][0])) ? $post_meta['email'][0] : '';
     2031                $user = !empty($user_email) ? get_user_by('email', $user_email) : false;
    16642032                if (!empty($user)) {
    16652033                    $is_blocked_role = self::is_restricted_user_role($user->roles);
     
    17392107
    17402108                // Render the settings template
    1741                 if(in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins'))))
     2109                if(appsmav_in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins'))))
    17422110                {
    17432111                    $frame_url = 'about:blank';
     
    17862154                        $this->get_settings_api(1);
    17872155                    }
     2156                   
    17882157                    self::gr_calc_point_value();
    17892158                }
     
    18132182                    $gr_pbp_auto_apply = WC()->session->get('gr_pbp_auto_apply', 0);
    18142183                    $gr_pbp_auto_apply_done = WC()->session->get('gr_pbp_auto_apply_done', 0); // First time only we need to set automatically
    1815                     if (empty($gr_pbp_auto_apply_done) && !empty($gr_pbp_auto_apply))
     2184                    $manually_removed = WC()->session->get('gr_user_manually_removed_discount', 0);
     2185                   
     2186                    // Only auto-apply if: not done yet, auto-apply is enabled, AND user didn't manually remove discount
     2187                    if (empty($gr_pbp_auto_apply_done) && !empty($gr_pbp_auto_apply) && empty($manually_removed))
    18162188                    {
    1817                         $applied_coupons = WC()->cart->applied_coupons;
    1818                         if (empty($applied_coupons))
     2189                        $cart_object = !empty(WC()->cart) ? WC()->cart : null;
     2190                        if (!empty($cart_object)) {
     2191                            $applied_coupons = $cart_object->applied_coupons;
     2192                        } else {
     2193                            $applied_coupons = array();
     2194                        }
     2195                        if (empty($applied_coupons) && !empty($cart_object))
    18192196                        {
    1820                             WC()->cart->add_discount($redeem_coupon);
     2197                            if (method_exists($cart_object, 'apply_coupon')) {
     2198                                $cart_object->apply_coupon($redeem_coupon);
     2199                            } else {
     2200                                $cart_object->add_discount($redeem_coupon);
     2201                            }
    18212202                            WC()->session->set('gr_pbp_auto_apply_done', 1);
    18222203                            WC()->session->set('gr_discount_applied', 1);
     
    18412222                }
    18422223
    1843                 $email = sanitize_email( $_POST['grconnect_login_email'] );
     2224                $email = isset($_POST['grconnect_login_email']) ? sanitize_email($_POST['grconnect_login_email']) : '';
    18442225                if(empty($email) || !filter_var($email, FILTER_VALIDATE_EMAIL))
    18452226                    throw new Exception("Please enter valid email");
     
    18562237                $params['email'] = $email;
    18572238                $params['admin_email'] = $adminEmail;
    1858                 $params['password'] = sanitize_text_field( $_POST['grconnect_login_pwd'] );
     2239                $params['password'] = isset($_POST['grconnect_login_pwd']) ? sanitize_text_field($_POST['grconnect_login_pwd']) : '';
    18592240                $params['shop_url'] = get_option('siteurl');
    18602241                $params['plugin_version'] = self::$_plugin_version;
     
    18662247
    18672248                if(!empty($resp))
    1868                     $resp = json_decode($resp, true);
     2249                    $resp = appsmav_json_decode($resp, true);
     2250
     2251                // Ensure $resp is always an array to prevent "Cannot access offset of type string on string" error
     2252                if(!is_array($resp))
     2253                    $resp = array();
    18692254
    18702255                if(empty($resp['error']) && !empty($resp['id_shop']))
     
    19092294            }
    19102295
    1911             die(json_encode($res));
     2296            echo appsmav_json_encode($res);
     2297            wp_die();
    19122298        }
    19132299
     
    19432329
    19442330                if(!empty($res))
    1945                     $res = json_decode($res, true);
     2331                    $res = appsmav_json_decode($res, true);
     2332
     2333                // Ensure $res is always an array to prevent "Cannot access offset of type string on string" error
     2334                if(!is_array($res))
     2335                    $res = array();
    19462336
    19472337                if(!empty($res['is_shop']) && $res['is_shop'] == 1)
     
    20032393                    $params['date_format'] = 'd/m/Y'; //Dummy$p['grappsmav_reg_date_format'];
    20042394                    $params['exclusion_period'] = 0; //$p['grconnect_reg_exclusion_period'];
    2005                     $params['app_lang'] = str_replace('-', '_', get_bloginfo('language'));
     2395                    $params['app_lang'] = appsmav_str_replace('-', '_', get_bloginfo('language'));
    20062396
    20072397                    $myaccount_page_id = get_option('woocommerce_myaccount_page_id');
     
    20182408
    20192409                    if(!empty($res))
    2020                         $res = json_decode($res, true);
     2410                        $res = appsmav_json_decode($res, true);
     2411
     2412                    // Ensure $res is always an array to prevent "Cannot access offset of type string on string" error
     2413                    if(!is_array($res))
     2414                        $res = array();
    20212415
    20222416                    if(empty($res['error']) && !empty($res['id_shop']))
     
    20572451            }
    20582452
    2059             die(json_encode($res));
     2453            echo appsmav_json_encode($res);
     2454            wp_die();
    20602455        }
    20612456
     
    20852480                     $shop_credentials['payload'] = get_option('grconnect_payload', 0);
    20862481                 }
    2087                  // Get client IP address with fallback to empty string if not available
    2088                  $ip_address = !empty($_SERVER['REMOTE_ADDR']) ? filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP) : '';
    2089                  // Prepare and execute API request to get auto-login token
     2482                // Get client IP address with fallback to empty string if not available
     2483                $ip_address = !empty($_SERVER['REMOTE_ADDR']) ? filter_var(appsmav_unslash($_SERVER['REMOTE_ADDR']), FILTER_VALIDATE_IP) : '';
     2484                // Prepare and execute API request to get auto-login token
    20902485                     $httpObj = (new HttpRequestHandler)
    20912486                     ->setPostData([
     
    20992494                 // Process API response if not empty
    21002495                 if (!empty($resp)) {
    2101                      $response = json_decode($resp, true);
     2496                     $response = appsmav_json_decode($resp, true);
    21022497                     // Check if JSON is valid and is an array
    2103                      if (json_last_error() === JSON_ERROR_NONE && is_array($response)) {
     2498                     if (is_array($response)) {
    21042499                         if (empty($response['error']) && !empty($response['token'])) {
    21052500                             $token = $response['token'];
     
    21262521                     }
    21272522                 }
    2128              } catch (Exception $ex) {
    2129                  $res['message'] = 'Error: ' . $ex->getMessage();
    2130                  $res['error'] = 1;
    2131              }
    2132              die(json_encode($res));
    2133          }
    2134 
     2523            } catch (Exception $ex) {
     2524                $res['message'] = 'Error: ' . $ex->getMessage();
     2525                $res['error'] = 1;
     2526            }
     2527           
     2528            echo appsmav_json_encode($res);
     2529            wp_die();
     2530        }
     2531       
    21352532        public function gr_show_redeem_points_lable()
    21362533        {
     
    21912588                self::gr_calc_point_value();
    21922589
    2193                 echo '<style>.gr_rewards_remove_discount{opacity:.6}#gr_checkout_redeem_lable{text-align:right;}.grPointsRedeem{padding:10px;border:1px dashed;}</style>';
    2194 
    21952590                if(WC()->session->get('gr_user_max_discount', 0) > 0 && WC()->session->get('gr_user_deduct_points', 0) >= 1)
    21962591                {
    21972592                    $discount = WC()->session->get('gr_user_max_discount', 0);
    21982593                    $points = WC()->session->get('gr_user_deduct_points', 0);
    2199                     $redeem_point_lable = str_replace('{points}', $points, $redeem_point_lable);
    2200                     $redeem_point_lable = str_replace('{points_value}', wc_price($discount), $redeem_point_lable);
    2201 
    2202                     $point_lable = ($points > 1) ? WC()->session->get('gr_points_lable', '') : WC()->session->get('gr_point_lable', '');
    2203                     $redeem_point_lable = '<p class="grPointsRedeem" id="gr_checkout_lable_top">' . str_replace('{points_label}', $point_lable, $redeem_point_lable);
    2204 
     2594                    $label = WC()->session->get('gr_redeem_point_per_dollar_lable', '');
    22052595                    $extra_pay_points = WC()->session->get('gr_user_extra_pay_points', 0);
    2206                     $extra_pay_apply = '';
    2207                     $extra_pay_confirm = 'display:none;';
    2208                     if (!empty($extra_pay_points))
    2209                     {
    2210                         $extra_pay_apply = 'display:none;';
    2211                         $extra_pay_confirm = '';
    2212                         $extra_point_lable = ($extra_pay_points > 1) ? WC()->session->get('gr_points_lable', '') : WC()->session->get('gr_point_lable', '');
    2213 
    22142596                        $extra_points_info = WC()->session->get('gr_redeem_extra_point_info', '');
    2215                         $extra_points_info = str_replace('{points}', $extra_pay_points, $extra_points_info);
    2216                         $extra_points_info = str_replace('{points_label}', $extra_point_lable, $extra_points_info);
    2217                         $redeem_point_lable .= '<span style="color:red;"><br>'.$extra_points_info.'</span>';
    2218                     }
    2219 
    2220                     $redeem_point_lable .= '</p>';
    2221 
    2222                     // add 'Apply Discount' button
    2223                     if(WC()->session->get('gr_user_applied_discount', 0) == 0)
    2224                     {
    2225                         $btn_agree = WC()->session->get('gr_btn_redeem_confirm', '');
    2226                         $redeem_point_lable .= '<form class="gr_apply_discount" action="' . esc_url(get_permalink(wc_get_page_id('cart'))) . '" method="post">';
    2227                         $redeem_point_lable .= '<input type="hidden" name="gr_rewards_apply_discount" class="gr_rewards_apply_discount" value="1" />';
    2228                         $redeem_point_lable .= '<input type="submit" class="button gr_rewards_apply_discount_confirm" style="'.$extra_pay_confirm.'" value="'.$btn_agree.'" />';
    2229                         $redeem_point_lable .= '<input type="submit" class="button gr_rewards_apply_discount" style="'.$extra_pay_apply.'" value="' . WC()->session->get('gr_redeem_btn_text', '') . '" />';
    2230                         $redeem_point_lable .= '</form>';
    2231 
     2597                    $is_applied = (WC()->session->get('gr_user_applied_discount', 0) != 0);
     2598                   
     2599                    // Prepare data array
     2600                    $render_data = array(
     2601                        'discount' => $discount,
     2602                        'points' => $points,
     2603                        'label' => $label,
     2604                        'extra_pay_points' => $extra_pay_points,
     2605                        'extra_points_info' => $extra_points_info,
     2606                        'is_applied' => $is_applied,
     2607                        'is_checkout' => $is_checkout_page,
     2608                        'is_block_page' => false // Classic page
     2609                    );
     2610                   
     2611                    // Use shared rendering method
     2612                    $redeem_point_lable = $this->gr_render_loyalty_banner_html($render_data);
     2613                   
     2614                    // Handle session reset for non-applied state (lines 2184-2198)
     2615                    if(!$is_applied) {
    22322616                        WC()->session->set('gr_user_max_discount', 0);
    22332617                        WC()->session->set('gr_user_deduct_points', 0);
    2234                     }
    2235                     else
    2236                     {
    2237                         $redeem_point_lable = '';
    2238 
    2239                         if(WC()->session->get('gr_user_applied_discount', 0) != $discount)
    2240                         {
     2618                    } else {
     2619                        $redeem_point_lable = '';
     2620                       
     2621                        if(WC()->session->get('gr_user_applied_discount', 0) != $discount) {
    22412622                            WC()->session->set('gr_user_max_discount', $discount);
    22422623                            WC()->session->set('gr_user_deduct_points', $points);
    2243 
    22442624                            $gr_user_max_discount = WC()->session->get('gr_user_max_discount', 0);
    22452625                            WC()->session->set('gr_user_applied_discount', (!empty($gr_user_max_discount) ? $gr_user_max_discount : 0));
    22462626                        }
    2247 
    22482627                    }
    22492628
    22502629                    echo '<div id="gr_checkout_redeem_lable">' . $redeem_point_lable . '</div>';
    2251 
    2252                     wc_enqueue_js("
    2253                         var gr_busy = false;
    2254                         jQuery('body').on('click', '.gr_rewards_apply_discount', function(e) {
    2255                             e.preventDefault();
    2256                             jQuery('#gr_checkout_lable_top').next('.error-msg').remove();
    2257 
    2258                             if(gr_busy)
    2259                                 return false;
    2260 
    2261                             gr_busy = true;
    2262                             jQuery.post(
    2263                                 '" . admin_url('admin-ajax.php') . "',
    2264                                 {
    2265                                     action:'apply_gr_discount',
    2266                                     security: '". wp_create_nonce('apply_gr_discount') ."'
    2267                                 },
    2268                                 function(response){
    2269                                     gr_busy = false;
    2270 
    2271                                     if (typeof response.success != 'undefined' && response.success === false) {
    2272                                         var errorMessage = (typeof response.data == 'undefined') ? 'Sorry, you are not allowed!' : response.data;
    2273                                         jQuery('#gr_checkout_lable_top').after('<span class=\"error-msg\">'+ errorMessage +'</span>');
    2274                                         return false;
    2275                                     }
    2276 
    2277                                     if('".$is_checkout_page."' == '1')
    2278                                     {
    2279                                         jQuery('#gr_checkout_redeem_lable').hide();
    2280                                         jQuery('body').trigger('update_checkout');
    2281                                         return false;
    2282                                     }
    2283                                     else
    2284                                     {
    2285                                         var obj = jQuery(\"[name='update_cart']\");
    2286                                         jQuery('body').trigger('wc_update_cart');
    2287 
    2288                                         if(obj.length > 0)
    2289                                             jQuery('body').trigger('wc_update_cart');
    2290                                     }
    2291                                 }, 'json');
    2292                             return false;
    2293                         });
    2294 
    2295                         jQuery('body').on('click', '.gr_rewards_apply_discount_confirm', function(e) {
    2296                             e.preventDefault();
    2297 
    2298                             jQuery('.gr_rewards_apply_discount_confirm').hide();
    2299                             jQuery('.gr_rewards_apply_discount').fadeIn('slow');
    2300                             return false;
    2301                         });
    2302                     ");
     2630                   
     2631                    // Mark banner as displayed to prevent duplicates from other hooks
     2632                    $banner_displayed = true;
    23032633                }
    23042634            }
    23052635            catch(Exception $e)
    23062636            {
    2307 
    2308             }
     2637            }
     2638        }
     2639
     2640        /**
     2641         * PURE PHP: Inject banner into WooCommerce Blocks (cart/checkout)
     2642         * Intercepts block rendering and adds banner HTML before the block
     2643         * Works for ALL block-based cart pages (official + custom URLs)
     2644         *
     2645         * @param string $block_content The rendered block content
     2646         * @param array $block The block data (name, attributes, etc.)
     2647         * @return string Modified block content with banner injected
     2648         */
     2649        public function gr_inject_banner_into_blocks($block_content, $block)
     2650        {
     2651            try {
     2652                // Prevent multiple injections per request (banner should appear only once)
     2653                static $banner_injected = false;
     2654                if ($banner_injected) {
     2655                    return $block_content;
     2656                }
     2657               
     2658                // Only process cart and checkout blocks
     2659                if (!isset($block['blockName'])) {
     2660                    return $block_content;
     2661                }
     2662               
     2663                $block_name = $block['blockName'];
     2664               
     2665                // Target WooCommerce cart and checkout blocks
     2666                if ($block_name !== 'woocommerce/cart' && $block_name !== 'woocommerce/checkout') {
     2667                    return $block_content;
     2668                }
     2669               
     2670                // Skip ONLY pages with shortcodes (they use traditional hooks)
     2671                // Shortcode pages have [woocommerce_cart] which internally renders blocks
     2672                // We must detect shortcode pages and let traditional hooks handle them
     2673                // This prevents double injection: render_block + traditional hooks
     2674                // BUT: Official block pages (/cart/, /checkout/) should still use render_block
     2675                global $post;
     2676               
     2677                // Skip pages with cart/checkout shortcodes (they use traditional hooks)
     2678                if (isset($post->post_content)) {
     2679                    $has_shortcode = (
     2680                        appsmav_strpos($post->post_content, '[woocommerce_cart') !== false ||
     2681                        appsmav_strpos($post->post_content, '[woocommerce_checkout') !== false
     2682                    );
     2683                   
     2684                    if ($has_shortcode) {
     2685                        return $block_content; // Let traditional hooks handle shortcode pages
     2686                    }
     2687                }
     2688                // If no shortcode found, this is a pure block page - proceed with render_block injection
     2689               
     2690                // Check if we should show the banner (same logic as gr_show_redeem_points_lable)
     2691                if (
     2692                    empty(WC()->session) || WC()->session->get('gr_loyalty_campaign_enabled', 0) != 1 ||
     2693                    WC()->session->get('redeem_point_enabled') == 0 || self::_isActiveCampaign() === false
     2694                    || empty(WC()->cart)
     2695                ) {
     2696                    return $block_content;
     2697                }
     2698               
     2699                // Show redeem label only for points
     2700                if(WC()->session->get('gr_pbp_mode', 'points') != 'points') {
     2701                    return $block_content;
     2702                }
     2703               
     2704                // Check user permissions
     2705                if(is_user_logged_in())
     2706                {
     2707                    $user = wp_get_current_user();
     2708                    $is_blocked_role = self::is_restricted_user_role($user->roles);
     2709                    if ($is_blocked_role)
     2710                        return $block_content;
     2711                }
     2712                else if (WC()->session->get('gr_disable_non_loggedin', 0) == 1)
     2713                {
     2714                    return $block_content;
     2715                }
     2716               
     2717                // Check if coupon is applied (same logic as gr_show_redeem_points_lable)
     2718                $items = WC()->cart->get_cart();
     2719                $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
     2720               
     2721                if(!WC()->cart->has_discount($redeem_coupon))
     2722                {
     2723                    WC()->session->set('gr_user_applied_discount', 0);
     2724                    WC()->session->set('gr_discount_applied', 0);
     2725                }
     2726                else if(WC()->session->get('gr_user_applied_discount', 0) <= 0 || empty($items))
     2727                {
     2728                    WC()->cart->remove_coupon($redeem_coupon);
     2729                    WC()->session->set('gr_user_max_discount', 0);
     2730                    WC()->session->set('gr_user_deduct_points', 0);
     2731                }
     2732               
     2733                // Calculate point value
     2734                self::gr_calc_point_value();
     2735               
     2736                // Generate banner HTML if points available
     2737                if(WC()->session->get('gr_user_max_discount', 0) > 0 && WC()->session->get('gr_user_deduct_points', 0) >= 1)
     2738                {
     2739                    $discount = WC()->session->get('gr_user_max_discount', 0);
     2740                    $points = WC()->session->get('gr_user_deduct_points', 0);
     2741                    $label = WC()->session->get('gr_redeem_point_per_dollar_lable', '');
     2742                    $extra_pay_points = WC()->session->get('gr_user_extra_pay_points', 0);
     2743                    $extra_points_info = WC()->session->get('gr_redeem_extra_point_info', '');
     2744                    $is_applied = (WC()->session->get('gr_user_applied_discount', 0) != 0);
     2745                   
     2746                    $is_checkout_page = ($block_name === 'woocommerce/checkout') ? 1 : 0;
     2747                   
     2748                    // Prepare data array
     2749                    $render_data = array(
     2750                        'discount' => $discount,
     2751                        'points' => $points,
     2752                        'label' => $label,
     2753                        'extra_pay_points' => $extra_pay_points,
     2754                        'extra_points_info' => $extra_points_info,
     2755                        'is_applied' => $is_applied,
     2756                        'is_checkout' => $is_checkout_page,
     2757                        'is_block_page' => true // Block page
     2758                    );
     2759                   
     2760                    // Use shared rendering method
     2761                    $redeem_point_lable = $this->gr_render_loyalty_banner_html($render_data);
     2762                   
     2763                    // Handle session reset for non-applied state
     2764                    if(!$is_applied) {
     2765                        WC()->session->set('gr_user_max_discount', 0);
     2766                        WC()->session->set('gr_user_deduct_points', 0);
     2767                    } else {
     2768                        $redeem_point_lable = '';
     2769                       
     2770                        if(WC()->session->get('gr_user_applied_discount', 0) != $discount) {
     2771                            WC()->session->set('gr_user_max_discount', $discount);
     2772                            WC()->session->set('gr_user_deduct_points', $points);
     2773                            $gr_user_max_discount = WC()->session->get('gr_user_max_discount', 0);
     2774                            WC()->session->set('gr_user_applied_discount', (!empty($gr_user_max_discount) ? $gr_user_max_discount : 0));
     2775                        }
     2776                    }
     2777                   
     2778                    // Inject banner BEFORE the block content
     2779                    $banner_html = '<div id="gr_checkout_redeem_lable">' . $redeem_point_lable . '</div>';
     2780                    $block_content = $banner_html . $block_content;
     2781                   
     2782                    // Mark banner as injected to prevent duplicates
     2783                    $banner_injected = true;
     2784                }
     2785               
     2786                return $block_content;
     2787               
     2788            } catch(Exception $e) {
     2789                // Silently fail - return original block content
     2790                return $block_content;
     2791            }
     2792        }
     2793
     2794        /**
     2795         * Render loyalty points HTML for both shortcode and blocks
     2796         * BUSINESS LOGIC: Separated from presentation layer
     2797         *
     2798         * @param array $data Associative array with keys:
     2799         *   - discount (float): Discount amount
     2800         *   - points (int): Points to redeem
     2801         *   - label (string): Label template from session
     2802         *   - extra_pay_points (int): Extra points needed
     2803         *   - extra_points_info (string): Extra points message
     2804         *   - is_applied (bool): Whether discount is already applied
     2805         *   - is_checkout (bool): Whether this is checkout page
     2806         *   - is_block_page (bool): Whether this is a block page (optional, defaults to false)
     2807         * @return string HTML output (not echoed)
     2808         */
     2809        public function gr_render_loyalty_banner_html($data) {
     2810            // Extract and validate data
     2811            $discount = isset($data['discount']) ? floatval($data['discount']) : 0;
     2812            $points = isset($data['points']) ? absint($data['points']) : 0;
     2813            $label = isset($data['label']) ? wp_kses_post($data['label']) : '';
     2814           
     2815            if ($discount <= 0 || $points < 1) {
     2816                return '';
     2817            }
     2818           
     2819            // Check if discount is already applied
     2820            $is_applied = isset($data['is_applied']) ? $data['is_applied'] : false;
     2821           
     2822            if ($is_applied) {
     2823                // Applied state: Return empty HTML (like traditional implementation)
     2824                $html = '';
     2825            } else {
     2826                // Build label with replacements (reuse existing logic from lines 2151-2172)
     2827                $redeem_point_lable = appsmav_str_replace('{points}', $points, $label);
     2828                $redeem_point_lable = appsmav_str_replace('{points_value}', wc_price($discount), $redeem_point_lable);
     2829               
     2830                $point_lable = ($points > 1)
     2831                    ? wp_kses_post(WC()->session->get('gr_points_lable', ''))
     2832                    : wp_kses_post(WC()->session->get('gr_point_lable', ''));
     2833               
     2834                $html = '<p class="grPointsRedeem" id="gr_checkout_lable_top">'
     2835                    . appsmav_str_replace('{points_label}', $point_lable, $redeem_point_lable);
     2836            }
     2837           
     2838            // Extra points logic (lines 2157-2170) - only for unapplied state
     2839            if (!$is_applied) {
     2840                $extra_pay_points = isset($data['extra_pay_points']) ? absint($data['extra_pay_points']) : 0;
     2841                $extra_pay_apply = '';
     2842                $extra_pay_confirm = 'display:none;';
     2843               
     2844                if (!empty($extra_pay_points)) {
     2845                    $extra_pay_apply = 'display:none;';
     2846                    $extra_pay_confirm = '';
     2847                    $extra_point_lable = ($extra_pay_points > 1)
     2848                        ? WC()->session->get('gr_points_lable', '')
     2849                        : WC()->session->get('gr_point_lable', '');
     2850                   
     2851                    $extra_points_info = isset($data['extra_points_info']) ? $data['extra_points_info'] : '';
     2852                    $extra_points_info = appsmav_str_replace('{points}', $extra_pay_points, $extra_points_info);
     2853                    $extra_points_info = appsmav_str_replace('{points_label}', $extra_point_lable, $extra_points_info);
     2854                    $html .= '<span class="gr_redeem_extra_point_info"><br>' . esc_html($extra_points_info) . '</span>';
     2855                }               
     2856                $html .= '</p>';           
     2857           
     2858                // Add form/buttons if not applied (lines 2175-2186)
     2859                $btn_agree = WC()->session->get('gr_btn_redeem_confirm', 'Apply Discount');
     2860                $btn_text = WC()->session->get('gr_redeem_btn_text', 'Apply Points');
     2861               
     2862                // Ensure we have button text
     2863                if (empty($btn_text)) {
     2864                    $btn_text = 'Apply Points';
     2865                }
     2866                if (empty($btn_agree)) {
     2867                    $btn_agree = 'Apply Discount';
     2868                }               
     2869               
     2870                $is_checkout_attr = isset($data['is_checkout']) && $data['is_checkout'] ? '1' : '0';
     2871                // Set form action to current page (cart or checkout)
     2872                $form_action = $is_checkout_attr ? get_permalink(wc_get_page_id('checkout')) : get_permalink(wc_get_page_id('cart'));
     2873                $html .= '<form class="gr_apply_discount" action="' . esc_url($form_action) . '" method="post" data-is-checkout="' . esc_attr($is_checkout_attr) . '">';
     2874                $html .= '<input type="hidden" name="gr_rewards_apply_discount" value="1" />';
     2875                $html .= '<input type="submit" class="button gr_rewards_apply_discount_confirm" style="' . esc_attr($extra_pay_confirm) . '" value="' . esc_attr($btn_agree) . '" />';
     2876                // Use standard WooCommerce button class for consistent styling (white background) on both classic and block pages
     2877                $html .= '<input type="submit" class="button gr_rewards_apply_discount" style="' . esc_attr($extra_pay_apply) . '" value="' . esc_attr($btn_text) . '" />';
     2878                $html .= '</form>';               
     2879            }
     2880           
     2881            return $html;
    23092882        }
    23102883
     
    23712944                }
    23722945
    2373                 if(WC()->session->get('gr_discount_applied') != 1)
    2374                 {
    2375                     $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
    2376                     WC()->cart->add_discount($redeem_coupon);
     2946                // Check if coupon is already in cart
     2947                $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
     2948                $cart_has_coupon = WC()->cart->has_discount($redeem_coupon);
     2949
     2950                if(!$cart_has_coupon)
     2951                {
     2952                    // Apply coupon using WooCommerce's built-in system
     2953                    // This automatically triggers 'woocommerce_coupon_message' filter (see get_discount_applied_message() at line 535)
     2954                    // The filter returns our custom message, and WooCommerce adds it as a notice to session
     2955                    WC()->cart->apply_coupon($redeem_coupon);
     2956                   
     2957                    // Set session flags
    23772958                    WC()->session->set('gr_discount_applied', 1);
    2378                 }
     2959                    WC()->session->set('gr_user_applied_discount', 1);
     2960                   
     2961                    // Clear the manual removal flag since user is now applying discount
     2962                    WC()->session->set('gr_user_manually_removed_discount', 0);
     2963                }
     2964               
     2965                // Get success message for JSON response (used by block pages)
     2966                $success_message = WC()->session->get('gr_redeemed_status_msg', '');
     2967                if (empty($success_message)) {
     2968                    $success_message = __('Reward added Successfully', 'gratisfaction');
     2969                }
     2970               
     2971                // IMPORTANT: Do NOT call wc_add_notice() here!
     2972                // Classic pages: WooCommerce already added notice via 'woocommerce_coupon_message' filter when apply_coupon() was called
     2973                // Block pages: JavaScript manually displays the message from JSON response (success_message below)
     2974                // Adding wc_add_notice() here would create DUPLICATE messages on classic pages
     2975               
     2976                // Return success response
     2977                wp_send_json_success(array(
     2978                    'message' => 'Discount applied',
     2979                    'success_message' => $success_message, // For block pages - JavaScript will display this
     2980                    'nonces' => array(
     2981                        'apply_nonce' => wp_create_nonce('apply_gr_discount'),
     2982                        'remove_nonce' => wp_create_nonce('remove_gr_discount'),
     2983                        'cart_details_nonce' => wp_create_nonce('gr_nonce')
     2984                    )
     2985                ));
    23792986            }
    23802987            catch(Exception $e)
     
    23933000                $msg = self::_checkNonce('','',0);
    23943001                if (!empty($msg)) {
    2395                     wp_send_json_error($msg);
     3002                    echo appsmav_json_encode(array('success' => false, 'data' => $msg));
     3003                    wp_die();
    23963004                }
    23973005
     
    24023010                $discount = WC()->session->get('gr_user_max_discount', 0);
    24033011                $points = WC()->session->get('gr_user_deduct_points', 0);
    2404                 $redeem_point_lable = str_replace('{points}', $points, $redeem_point_lable);
    2405                 $redeem_point_lable = str_replace('{points_value}', wc_price($discount), $redeem_point_lable);
     3012                $redeem_point_lable = appsmav_str_replace('{points}', $points, $redeem_point_lable);
     3013                $redeem_point_lable = appsmav_str_replace('{points_value}', wc_price($discount), $redeem_point_lable);
    24063014                $point_lable = ($points > 1) ? WC()->session->get('gr_points_lable', '') : WC()->session->get('gr_point_lable', '');
    2407                 $redeem_point_lable = str_replace('{points_label}', $point_lable, $redeem_point_lable);
     3015                $redeem_point_lable = appsmav_str_replace('{points_label}', $point_lable, $redeem_point_lable);
     3016               
     3017                $res = array();
    24083018                $res['msg'] = $redeem_point_lable;
    24093019
    2410                 die(json_encode($res));
     3020                echo appsmav_json_encode($res);
     3021                wp_die();
    24113022
    24123023            } catch (Exception $ex) {
     
    24193030            try
    24203031            {
     3032                // Early exit for unsigned users - loyalty points only available for logged-in users
     3033                if (!is_user_logged_in()) {
     3034                    return;
     3035                }
     3036               
    24213037                if (empty(WC()->session) || WC()->session->get('redeem_point_enabled') == 0)
    24223038                    return;
     
    24253041                $msg =  self::_checkNonce('','',0);
    24263042                if (!empty($msg)) {
    2427                     wp_send_json_error($msg);
    2428                 }
    2429 
     3043                    echo appsmav_json_encode(array('success' => false, 'data' => $msg));
     3044                    wp_die();
     3045                }
     3046
     3047                // This ensures fresh calculation after cart changes, coupon removal, or idle time
    24303048                self::gr_calc_point_value();
     3049               
     3050                // Check if coupon is actually in cart
     3051                $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
     3052                $cart_has_coupon = WC()->cart->has_discount($redeem_coupon);
    24313053
    24323054                $res = array(
    24333055                    'discounted_amount'   => WC()->session->get('gr_user_max_discount', 0),
    24343056                    'points'              => WC()->session->get('gr_user_deduct_points', 0),
    2435                     'cart_count'          => (is_array(WC()->cart->get_cart()) || WC()->cart->get_cart() instanceof Countable) ? count(WC()->cart->get_cart()) : 0,
     3057                    'cart_count'          => (is_array(WC()->cart->get_cart()) || (WC()->cart->get_cart() instanceof Countable)) ? count(WC()->cart->get_cart()) : 0,
    24363058                    'is_discount_applied' => 0,
    2437                     'extra_pbp'           => WC()->session->get('gr_user_extra_pay_points', 0)
     3059                    'extra_pbp'           => WC()->session->get('gr_user_extra_pay_points', 0),
     3060                    'redeem_restrict_terms' => WC()->session->get('gr_pay_redeem_restrict_terms', ''),
     3061                    'label_redeem_restriction_apply' => WC()->session->get('gr_label_redeem_restriction_apply', '')
    24383062                );
    24393063
     
    24413065                    //while cart is empty this session should set to 0
    24423066                    WC()->session->set('gr_discount_applied', 0);
    2443                 }
    2444                 //To update the popup in checkut page
    2445                 if (sanitize_text_field($_POST['event_type']) == 'remove_coupon') {
    24463067                    $res['is_discount_applied'] = 0;
    2447                 } else if (!empty($res['discounted_amount'])) {
    2448                     $res['is_discount_applied'] =  WC()->session->get('gr_discount_applied', 0);
    2449                 }
    2450 
    2451                 die(json_encode($res));
     3068                    $res['discounted_amount'] = 0;
     3069                    $res['points'] = 0;
     3070                    $res['extra_pbp'] = 0;
     3071                } else {
     3072                    // CART IS SOURCE OF TRUTH: Check if coupon is actually in cart
     3073                    if ($cart_has_coupon && !empty($res['discounted_amount'])) {
     3074                        // Coupon is in cart and has discount - it's applied
     3075                        $res['is_discount_applied'] = 1;
     3076                    } else {
     3077                        // Coupon not in cart - discount is not applied
     3078                        $res['is_discount_applied'] = 0;
     3079                        // Keep discounted_amount and points values (from gr_user_max_discount and gr_user_deduct_points)
     3080                        // These represent the AVAILABLE discount that CAN be applied, not what IS applied
     3081                        // This allows popup to show "Use X points for a Y discount" correctly
     3082                        // Only reset extra_pbp to prevent "additional points" message when no discount is applied
     3083                        $res['extra_pbp'] = 0;
     3084                    }
     3085                }
     3086
     3087                // Add fresh nonces to response for next action (prevents "Sorry, you are not allowed!" after multiple actions)
     3088                $res['nonces'] = array(
     3089                    'apply_nonce' => wp_create_nonce('apply_gr_discount'),
     3090                    'remove_nonce' => wp_create_nonce('remove_gr_discount'),
     3091                    'cart_details_nonce' => wp_create_nonce('gr_nonce')
     3092                );
     3093
     3094                echo appsmav_json_encode($res);
     3095                wp_die();
    24523096            }
    24533097            catch (Exception $ex)
    24543098            { }
     3099        }
     3100
     3101        /**
     3102         * AJAX handler: Get loyalty points data for blocks
     3103         * Handles: wp_ajax_get_gr_loyalty_points_blocks
     3104         * Handles: wp_ajax_nopriv_get_gr_loyalty_points_blocks
     3105         */
     3106        public function gr_ajax_get_loyalty_points_blocks() {
     3107            try {
     3108                // 0) Early exit for unsigned users - loyalty points only available for logged-in users
     3109                if (!is_user_logged_in()) {
     3110                    wp_send_json_error(array('message' => 'User not logged in.'));
     3111                    return;
     3112                }
     3113               
     3114                // 1) Strict CSRF verification - check nonce exists first
     3115                if (!isset($_POST['nonce'])) {
     3116                    wp_send_json_error(array('message' => 'Missing security token.'));
     3117                    return;
     3118                }
     3119               
     3120                // 2) Verify nonce matches the action
     3121                $nonce_valid = check_ajax_referer('gr_blocks_loyalty_data', 'nonce', false);
     3122                if (!$nonce_valid) {
     3123                    wp_send_json_error(array('message' => 'Invalid or expired security token.'));
     3124                    return;
     3125                }
     3126               
     3127                // Check WooCommerce session and cart
     3128                if (empty(WC()->session) || empty(WC()->cart)) {
     3129                    wp_send_json_error(array('message' => 'Session or cart not available.'));
     3130                    return;
     3131                }
     3132               
     3133                // Check if campaign is active
     3134                $campaign_enabled = WC()->session->get('gr_loyalty_campaign_enabled', 0);
     3135                $redeem_enabled = WC()->session->get('redeem_point_enabled');
     3136                $is_custom_cart = isset($_POST['is_custom_cart']) ? (int)$_POST['is_custom_cart'] : 0;
     3137           
     3138                if ($campaign_enabled != 1 || $redeem_enabled == 0) {
     3139                    wp_send_json_error(array('message' => 'Campaign not active.'));
     3140                    return;
     3141                }
     3142           
     3143                // Check PBP mode
     3144                $pbp_mode = WC()->session->get('gr_pbp_mode', 'points');
     3145                if ($pbp_mode != 'points') {
     3146                    wp_send_json_error(array('message' => 'Points mode not active.'));
     3147                    return;
     3148                }
     3149               
     3150                // Calculate point values including extra points
     3151                // This ensures gr_user_extra_pay_points is calculated for minimum point restrictions
     3152                // Calculate point values following the same pattern as traditional pages
     3153                $this->gr_calc_point_value();
     3154               
     3155                // Fetch data from session (AFTER calculation)
     3156                $discount = WC()->session->get('gr_user_max_discount', 0);
     3157                $points = WC()->session->get('gr_user_deduct_points', 0);
     3158                $label = WC()->session->get('gr_redeem_point_per_dollar_lable', '');
     3159               
     3160                // Only return data if there are points to redeem
     3161                if ($discount <= 0 || $points < 1) {
     3162                    wp_send_json_error(array('message' => 'No points available.'));
     3163                    return;
     3164                }
     3165               
     3166                // Prepare render data
     3167                $is_checkout_page = is_checkout();
     3168               
     3169                // Check if discount is actually applied by checking if coupon exists in cart
     3170                $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
     3171               
     3172                // Cart is the source of truth for applied discount status
     3173                // Ensure cart totals are current and check discount status
     3174                WC()->cart->calculate_totals();
     3175                $is_applied = WC()->cart->has_discount($redeem_coupon);
     3176               
     3177                // Detect if this is a block page (for button styling)
     3178                $is_block_page = function_exists('gr_is_cart_or_checkout_block') && gr_is_cart_or_checkout_block();
     3179               
     3180                $render_data = array(
     3181                    'discount' => $discount,
     3182                    'points' => $points,
     3183                    'label' => $label,
     3184                    'extra_pay_points' => WC()->session->get('gr_user_extra_pay_points', 0),
     3185                    'extra_points_info' => WC()->session->get('gr_redeem_extra_point_info', ''),
     3186                    'is_applied' => $is_applied,
     3187                    'is_checkout' => $is_checkout_page,
     3188                    'is_block_page' => $is_block_page
     3189                );
     3190           
     3191                // Generate HTML using shared method
     3192                $html = $this->gr_render_loyalty_banner_html($render_data);
     3193               
     3194                // HTML sanitized via gr_render_loyalty_banner_html()
     3195               
     3196                // Wrap in div (styles are in css/grconnect.css for FSE compatibility)
     3197                $final_html = '<div id="gr_checkout_redeem_lable">' . $html . '</div>';
     3198               
     3199                // Return JSON response with ALL data needed for both banner AND widget config
     3200                // This allows JavaScript to use just 1 AJAX call instead of 2
     3201                $response_data = array(
     3202                    // Banner data
     3203                    'discount' => $discount,
     3204                    'points' => $points,
     3205                    'html' => $final_html,
     3206                    'is_checkout' => $is_checkout_page ? '1' : '0',
     3207                   
     3208                    // Widget config data (same as gr_get_cart_details)
     3209                    'is_discount_applied' => $is_applied ? 1 : 0,
     3210                    'cart_count' => (is_array(WC()->cart->get_cart()) || (WC()->cart->get_cart() instanceof Countable)) ? count(WC()->cart->get_cart()) : 0,
     3211                    'extra_pbp' => WC()->session->get('gr_user_extra_pay_points', 0),
     3212                    'redeem_restrict_terms' => WC()->session->get('gr_pay_redeem_restrict_terms', ''),
     3213                    'label_redeem_restriction_apply' => WC()->session->get('gr_label_redeem_restriction_apply', ''),
     3214                   
     3215                    // Return fresh nonces to prevent expiration after 12-24 hours
     3216                    'new_nonce' => wp_create_nonce('gr_blocks_loyalty_data'), // For banner updates (block pages only)
     3217                    'nonces' => array(
     3218                        'apply_nonce' => wp_create_nonce('apply_gr_discount'),
     3219                        'remove_nonce' => wp_create_nonce('remove_gr_discount'),
     3220                        'cart_details_nonce' => wp_create_nonce('gr_nonce')
     3221                    )
     3222                );
     3223                wp_send_json_success($response_data);
     3224               
     3225            } catch (Exception $e) {
     3226                wp_send_json_error(array('message' => 'Server error occurred.'));
     3227            }
    24553228        }
    24563229
     
    25543327                    }
    25553328
    2556                     if ( (is_array($list_products) || $list_products instanceof Countable) && count($list_products)>0 )
     3329                    if ( (is_array($list_products) || ($list_products instanceof Countable)) && count($list_products)>0 )
    25573330                    {
    25583331                        $product_price_range = array();
     
    26183391
    26193392                $redeem_point_lable = WC()->session->get('gr_redeem_point_product_per_dollar_lable');
    2620                 $redeem_point_lable = str_replace('{points}', $discount, $redeem_point_lable);
     3393                $redeem_point_lable = appsmav_str_replace('{points}', $discount, $redeem_point_lable);
    26213394                $point_lable = ($max_points > 1) ? WC()->session->get('gr_points_lable', '') : WC()->session->get('gr_point_lable', '');
    2622                 $redeem_point_lable = str_replace('{points_label}', $point_lable, $redeem_point_lable);
     3395                $redeem_point_lable = appsmav_str_replace('{points_label}', $point_lable, $redeem_point_lable);
    26233396
    26243397                $redeem_restrict_terms = WC()->session->get('gr_pay_redeem_restrict_terms', '');
     
    26283401                    $redeem_point_lable .= ' <span style="position:relative;"><span class="gr_restriction_apply_title" style="cursor: pointer;border-bottom: 1px dashed;color: initial;display: inline-block;"><b>'.$label_redeem_restriction_apply.'</b></span>';
    26293402                    $redeem_point_lable .= '<span style="display:none;" class="gr_restriction_apply_message"><span>'.$redeem_restrict_terms.'</span><span class="gr_restriction_apply_close">&times;</span></span></span>';
    2630 
    2631                     wc_enqueue_js("
    2632                         jQuery('body').on('click', '.gr_restriction_apply_title', function(e) {
    2633                             e.preventDefault();
    2634                             jQuery('.gr_restriction_apply_message').toggle();
    2635                             return false;
    2636                         });
    2637 
    2638                         jQuery('body').on('click', '.gr_restriction_apply_close', function(e) {
    2639                             e.preventDefault();
    2640                             jQuery('.gr_restriction_apply_message').hide();
    2641                             return false;
    2642                         });
    2643                     ");
    26443403                }
    26453404
     
    26743433                if(!empty(WC()->session->get('gr_earn_exclude_products', ''))){
    26753434                    $earn_exclude_products = appsmav_explode(',', appsmav_trim(WC()->session->get('gr_earn_exclude_products', '')));
    2676                     $earn_exclude_products = array_map('trim', $earn_exclude_products);
     3435                    $earn_exclude_products = appsmav_array_map('trim', $earn_exclude_products);
     3436                    // Ensure result is still an array after array_map
     3437                    if (!is_array($earn_exclude_products)) {
     3438                        $earn_exclude_products = array();
     3439                    }
    26773440                }
    26783441
     
    26833446                    WC()->session->get('gr_purchase_theme_status', 0) == 0 ||
    26843447                    self::_isActiveCampaign() === false ||
    2685                     in_array($product->get_id(), $earn_exclude_products)
     3448                    (is_array($earn_exclude_products) && appsmav_in_array($product->get_id(), $earn_exclude_products))
    26863449                )
    26873450                    return;
     
    27193482                    }
    27203483
    2721                     if ( (is_array($list_products) || $list_products instanceof Countable) && count($list_products)>0 )
     3484                    if ( (is_array($list_products) || ($list_products instanceof Countable)) && count($list_products)>0 )
    27223485                    {
    27233486                        $product_price_range = array();
     
    27283491                            if (wc_tax_enabled())
    27293492                            {
    2730                                 if (wc_prices_include_tax() && in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
     3493                                if (wc_prices_include_tax() && appsmav_in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
    27313494                                    $p_price = wc_get_price_excluding_tax($product, array('qty' => 1, 'price' => $p_price));
    2732                                 else if (!wc_prices_include_tax() && !in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
     3495                                else if (!wc_prices_include_tax() && !appsmav_in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
    27333496                                    $p_price = wc_get_price_including_tax($product, array('qty' => 1, 'price' => $p_price));
    27343497                            }
     
    27643527                        $p_tax = $tax_inc - $tax_exc;
    27653528
    2766                         if (wc_prices_include_tax() && in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
     3529                        if (wc_prices_include_tax() && appsmav_in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
    27673530                            $p_price = $p_price - $p_tax;
    2768                         else if (!wc_prices_include_tax() && !in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
     3531                        else if (!wc_prices_include_tax() && !appsmav_in_array($reward_type, array('TOTAL_EXCLUDE_TAX','SUBTOTAL')))
    27693532                            $p_price = $p_price + $p_tax;
    27703533                    }
     
    27823545
    27833546                $earn_point_lable = WC()->session->get('gr_earn_point_per_dollar_lable', '');
    2784                 $earn_point_lable = str_replace('{points}', $point_earn, $earn_point_lable);
    2785                 $earn_point_lable = str_replace('{points_label}', $point_lable, $earn_point_lable);
     3547                $earn_point_lable = appsmav_str_replace('{points}', $point_earn, $earn_point_lable);
     3548                $earn_point_lable = appsmav_str_replace('{points_label}', $point_lable, $earn_point_lable);
    27863549
    27873550                $label_color = WC()->session->get('gr_label_color', '#4bb543');
     
    27953558        }
    27963559
    2797         private function gr_calc_point_value()
     3560        public function gr_calc_point_value()
    27983561        {
    27993562            try
     
    28153578                WC()->session->set('gr_user_extra_pay_points', 0);
    28163579
    2817                 $redeem_point = WC()->session->get('gr_redeem_point_per_dollar', 0)/$ratio;
     3580                // Prevent division by zero in currency conversion
     3581                $redeem_point = (!empty($ratio) && $ratio != 0)
     3582                    ? WC()->session->get('gr_redeem_point_per_dollar', 0) / $ratio
     3583                    : 0;
    28183584                $cart_total = WC()->cart->subtotal;
    2819                 $discount = floor(WC()->session->get('gr_user_points', 0) / $redeem_point);
     3585                // Prevent division by zero if redeem_point is 0
     3586                $discount = (!empty($redeem_point) && $redeem_point != 0)
     3587                    ? floor(WC()->session->get('gr_user_points', 0) / $redeem_point)
     3588                    : 0;
    28203589
    28213590                $applied_coupons = WC()->cart->applied_coupons;
     
    28703639                    if (!empty($max_point) )
    28713640                    {
    2872                         $discount_max_point = floor($max_point / $redeem_point);
     3641                        // Prevent division by zero if redeem_point is 0
     3642                        $discount_max_point = (!empty($redeem_point) && $redeem_point != 0)
     3643                            ? floor($max_point / $redeem_point)
     3644                            : 0;
    28733645                        if ($discount > $discount_max_point)
    28743646                        {
     
    29423714                    return;
    29433715
    2944                 WC()->session->set('gr_api_sess', empty($app_config['date_updated']) ? 0 : $app_config['date_updated']);
    2945                 WC()->session->set('gr_loyalty_campaign_enabled', empty($app_config['points']['loyalty_campaign_enabled']) ? 0 : $app_config['points']['loyalty_campaign_enabled']);
    2946                 WC()->session->set('earn_point_enabled', empty($app_config['points']['earn_point_enabled']) ? 0 : $app_config['points']['earn_point_enabled']);
    2947                 WC()->session->set('gr_purchase_theme_status', empty($app_config['points']['purchase_theme_status']) ? 0 : $app_config['points']['purchase_theme_status']);
    2948                 WC()->session->set('redeem_point_enabled', empty($app_config['points']['redeem_purchase_status']) ? 0 : $app_config['points']['redeem_purchase_status']);
    2949                 WC()->session->set('gr_roundoff_type', empty($app_config['points']['gr_roundoff_type']) ? 'ROUND' : $app_config['points']['gr_roundoff_type']);
    2950                 WC()->session->set('gr_redeem_point_per_dollar', empty($app_config['points']['redeem_point_per_dollar']) ? 0 : $app_config['points']['redeem_point_per_dollar']);
     3716                WC()->session->set('gr_api_sess', isset($app_config['date_updated']) ? $app_config['date_updated'] : 0);
     3717                // Safe nested array access - check if parent keys exist before accessing nested values
     3718                $points_config = isset($app_config['points']) && is_array($app_config['points']) ? $app_config['points'] : array();
     3719                $lang_config = isset($app_config['lang']) && is_array($app_config['lang']) ? $app_config['lang'] : array();
     3720               
     3721                WC()->session->set('gr_loyalty_campaign_enabled', isset($points_config['loyalty_campaign_enabled']) ? $points_config['loyalty_campaign_enabled'] : 0);
     3722                WC()->session->set('earn_point_enabled', isset($points_config['earn_point_enabled']) ? $points_config['earn_point_enabled'] : 0);
     3723                WC()->session->set('gr_purchase_theme_status', isset($points_config['purchase_theme_status']) ? $points_config['purchase_theme_status'] : 0);
     3724                WC()->session->set('redeem_point_enabled', isset($points_config['redeem_purchase_status']) ? $points_config['redeem_purchase_status'] : 0);
     3725                WC()->session->set('gr_roundoff_type', isset($points_config['gr_roundoff_type']) ? $points_config['gr_roundoff_type'] : 'ROUND');
     3726                WC()->session->set('gr_redeem_point_per_dollar', isset($points_config['redeem_point_per_dollar']) ? $points_config['redeem_point_per_dollar'] : 0);
    29513727                WC()->session->set('gr_redeem_theme_status', empty($app_config['points']['redeem_theme_status']) ? 0 : $app_config['points']['redeem_theme_status']);
    2952                 WC()->session->set('minimum_order_value', empty($app_config['points']['minimum_order_value']) ? 0 : $app_config['points']['minimum_order_value']);
    2953                 WC()->session->set('gr_redeem_point_per_dollar_lable', empty($app_config['lang']['redeem_point_per_dollar_lable']) ? '' : appsmav_stripslashes($app_config['lang']['redeem_point_per_dollar_lable']));
    2954                 WC()->session->set('gr_redeem_point_product_per_dollar_lable', empty($app_config['lang']['redeem_point_product_per_dollar_lable']) ? '' : appsmav_stripslashes($app_config['lang']['redeem_point_product_per_dollar_lable']));
    2955                 WC()->session->set('gr_earn_point_per_dollar', empty($app_config['points']['earn_point_per_dollar']) ? 0 : $app_config['points']['earn_point_per_dollar']);
    2956                 WC()->session->set('gr_earn_point_per_dollar_lable', empty($app_config['lang']['earn_point_per_dollar_lable']) ? '' : appsmav_stripslashes($app_config['lang']['earn_point_per_dollar_lable']));
    2957                 WC()->session->set('gr_point_lable', empty($app_config['lang']['point_lable']) ? '' : $app_config['lang']['point_lable']);
    2958                 WC()->session->set('gr_points_lable', empty($app_config['lang']['points_lable']) ? '' : $app_config['lang']['points_lable']);
    2959                 WC()->session->set('gr_redeem_btn_text', empty($app_config['lang']['redeem_btn_text']) ? '' : $app_config['lang']['redeem_btn_text']);
    2960                 WC()->session->set('gr_redeemed_btn_text', empty($app_config['lang']['redeemed_btn_text']) ? '' : $app_config['lang']['redeemed_btn_text']);
    2961                 WC()->session->set('gr_redeemed_status_msg', empty($app_config['lang']['redeemed_status_msg']) ? '' : $app_config['lang']['redeemed_status_msg']);
    2962                 WC()->session->set('label_redeemed_points', empty($app_config['lang']['label_redeemed_points']) ? '' : $app_config['lang']['label_redeemed_points']);
    2963                 WC()->session->set('label_life_time_points', empty($app_config['lang']['label_life_time_points']) ? '' : $app_config['lang']['label_life_time_points']);
    2964                 WC()->session->set('label_available_points', empty($app_config['lang']['label_available_points']) ? '' : $app_config['lang']['label_available_points']);
    2965                 WC()->session->set('error_more_points_required', empty($app_config['lang']['error_more_points_required']) ? '' : $app_config['lang']['error_more_points_required']);
    2966                 WC()->session->set('label_exclusion_points', empty($app_config['lang']['label_exclusion_points']) ? '' : $app_config['lang']['label_exclusion_points']);
    2967                 WC()->session->set('label_total_points', empty($app_config['lang']['label_total_points']) ? '' : $app_config['lang']['label_total_points']);
    2968                 WC()->session->set('no_records_found', empty($app_config['lang']['no_records_found']) ? '' : $app_config['lang']['no_records_found']);
    2969                 WC()->session->set('gr_loyalty_menu_name', appsmav_stripslashes($app_config['lang']['loyalty_menu_name']));
    2970                 WC()->session->set('gr_loyalty_date_start', empty($app_config['loyalty']['date_start']) ? 0 : $app_config['loyalty']['date_start']);
    2971                 WC()->session->set('gr_loyalty_date_end', empty($app_config['loyalty']['date_end']) ? 0 : $app_config['loyalty']['date_end']);
    2972                 WC()->session->set('gr_loyalty_timezone', empty($app_config['loyalty']['timezone']) ? 'America/Chicago' : $app_config['loyalty']['timezone']);
    2973                 WC()->session->set('gr_loyalty_is_open', empty($app_config['loyalty']['is_open']) ? 0 : $app_config['loyalty']['is_open']);
    2974                 WC()->session->set('gr_global_review_enabled', empty($app_config['reviews']['global_review_enabled']) ? 0 : $app_config['reviews']['global_review_enabled']);
    2975                 WC()->session->set('gr_global_review_points', empty($app_config['reviews']['global_review_points']) ? 0 : $app_config['reviews']['global_review_points']);
    2976                 WC()->session->set('gr_global_review_lable_enabled', empty($app_config['reviews']['global_review_lable_enabled']) ? 0 : $app_config['reviews']['global_review_lable_enabled']);
    2977                 WC()->session->set('gr_global_review_lable', empty($app_config['reviews']['global_review_lable']) ? '' : $app_config['reviews']['global_review_lable']);
    2978                 WC()->session->set('gr_restricted_user_roles', empty($app_config['points']['gr_restricted_user_roles'])?array():$app_config['points']['gr_restricted_user_roles'] );
    2979                 WC()->session->set('gr_roles_restrict_type', empty($app_config['points']['gr_roles_restrict_type'])?'restrict':$app_config['points']['gr_roles_restrict_type'] );
    2980                 WC()->session->set('gr_disable_non_loggedin', empty($app_config['points']['gr_disable_non_loggedin'])?0:$app_config['points']['gr_disable_non_loggedin'] );
    2981                 WC()->session->set('gr_reward_points_type', empty($app_config['points']['reward_points_type']) ? 'SUBTOTAL' : $app_config['points']['reward_points_type']);
     3728                WC()->session->set('minimum_order_value', isset($points_config['minimum_order_value']) ? $points_config['minimum_order_value'] : 0);
     3729                WC()->session->set('gr_redeem_point_per_dollar_lable', isset($lang_config['redeem_point_per_dollar_lable']) ? appsmav_stripslashes($lang_config['redeem_point_per_dollar_lable']) : '');
     3730                WC()->session->set('gr_redeem_point_product_per_dollar_lable', isset($lang_config['redeem_point_product_per_dollar_lable']) ? appsmav_stripslashes($lang_config['redeem_point_product_per_dollar_lable']) : '');
     3731                WC()->session->set('gr_earn_point_per_dollar', isset($points_config['earn_point_per_dollar']) ? $points_config['earn_point_per_dollar'] : 0);
     3732                WC()->session->set('gr_earn_point_per_dollar_lable', isset($lang_config['earn_point_per_dollar_lable']) ? appsmav_stripslashes($lang_config['earn_point_per_dollar_lable']) : '');
     3733                WC()->session->set('gr_point_lable', isset($lang_config['point_lable']) ? $lang_config['point_lable'] : '');
     3734                WC()->session->set('gr_points_lable', isset($lang_config['points_lable']) ? $lang_config['points_lable'] : '');
     3735                WC()->session->set('gr_redeem_btn_text', isset($lang_config['redeem_btn_text']) ? $lang_config['redeem_btn_text'] : '');
     3736                WC()->session->set('gr_redeemed_btn_text', isset($lang_config['redeemed_btn_text']) ? $lang_config['redeemed_btn_text'] : '');
     3737                WC()->session->set('gr_redeemed_status_msg', isset($lang_config['redeemed_status_msg']) ? $lang_config['redeemed_status_msg'] : '');
     3738                WC()->session->set('label_redeemed_points', isset($lang_config['label_redeemed_points']) ? $lang_config['label_redeemed_points'] : '');
     3739                WC()->session->set('label_life_time_points', isset($lang_config['label_life_time_points']) ? $lang_config['label_life_time_points'] : '');
     3740                WC()->session->set('label_available_points', isset($lang_config['label_available_points']) ? $lang_config['label_available_points'] : '');
     3741                WC()->session->set('error_more_points_required', isset($lang_config['error_more_points_required']) ? $lang_config['error_more_points_required'] : '');
     3742                WC()->session->set('label_exclusion_points', isset($lang_config['label_exclusion_points']) ? $lang_config['label_exclusion_points'] : '');
     3743                WC()->session->set('label_total_points', isset($lang_config['label_total_points']) ? $lang_config['label_total_points'] : '');
     3744                WC()->session->set('no_records_found', isset($lang_config['no_records_found']) ? $lang_config['no_records_found'] : '');
     3745                WC()->session->set('gr_loyalty_menu_name', appsmav_stripslashes(appsmav_get_nested_array_value($app_config, ['lang', 'loyalty_menu_name'], '')));
     3746                WC()->session->set('gr_loyalty_date_start', appsmav_get_nested_array_value($app_config, ['loyalty', 'date_start'], 0));
     3747                WC()->session->set('gr_loyalty_date_end', appsmav_get_nested_array_value($app_config, ['loyalty', 'date_end'], 0));
     3748                WC()->session->set('gr_loyalty_timezone', appsmav_get_nested_array_value($app_config, ['loyalty', 'timezone'], 'America/Chicago'));
     3749                WC()->session->set('gr_loyalty_is_open', appsmav_get_nested_array_value($app_config, ['loyalty', 'is_open'], 0));
     3750                WC()->session->set('gr_global_review_enabled', appsmav_get_nested_array_value($app_config, ['reviews', 'global_review_enabled'], 0));
     3751                WC()->session->set('gr_global_review_points', appsmav_get_nested_array_value($app_config, ['reviews', 'global_review_points'], 0));
     3752                WC()->session->set('gr_global_review_lable_enabled', appsmav_get_nested_array_value($app_config, ['reviews', 'global_review_lable_enabled'], 0));
     3753                WC()->session->set('gr_global_review_lable', appsmav_get_nested_array_value($app_config, ['reviews', 'global_review_lable'], ''));
     3754                WC()->session->set('gr_restricted_user_roles', appsmav_get_nested_array_value($app_config, ['points', 'gr_restricted_user_roles'], array()));
     3755                WC()->session->set('gr_roles_restrict_type', appsmav_get_nested_array_value($app_config, ['points', 'gr_roles_restrict_type'], 'restrict'));
     3756                WC()->session->set('gr_disable_non_loggedin', appsmav_get_nested_array_value($app_config, ['points', 'gr_disable_non_loggedin'], 0));
     3757                WC()->session->set('gr_reward_points_type', appsmav_get_nested_array_value($app_config, ['points', 'reward_points_type'], 'SUBTOTAL'));
    29823758                WC()->session->set('gr_redeem_restrict_status', isset($app_config['points']['gr_redeem_restrict_status']) ? $app_config['points']['gr_redeem_restrict_status'] : 0);
    29833759                WC()->session->set('gr_redeem_min_point', isset($app_config['points']['gr_redeem_min_point']) ? $app_config['points']['gr_redeem_min_point'] : 0);
     
    29853761                WC()->session->set('gr_redeem_cart_percent', isset($app_config['points']['gr_redeem_cart_percent']) ? $app_config['points']['gr_redeem_cart_percent'] : '');
    29863762                WC()->session->set('gr_label_color', isset($app_config['points']['gr_label_color']) ? $app_config['points']['gr_label_color'] : '#4bb543');
    2987                 WC()->session->set('gr_is_redeem_individual', empty($app_config['points']['gr_is_redeem_individual'])?0:$app_config['points']['gr_is_redeem_individual'] );
    2988                 WC()->session->set('gr_pay_exclude_products', empty($app_config['points']['gr_pay_exclude_products'])?array():$app_config['points']['gr_pay_exclude_products'] );
    2989                 WC()->session->set('gr_pay_exclude_categories', empty($app_config['points']['gr_pay_exclude_categories'])?array():$app_config['points']['gr_pay_exclude_categories'] );
    2990                 WC()->session->set('gr_pay_redeem_restrict_terms', empty($app_config['points']['gr_pay_redeem_restrict_terms'])?'':$app_config['points']['gr_pay_redeem_restrict_terms'] );
    2991                 WC()->session->set('gr_label_redeem_restriction_apply', empty($app_config['lang']['gr_label_redeem_restriction_apply'])?'':appsmav_stripslashes($app_config['lang']['gr_label_redeem_restriction_apply']) );
    2992                 WC()->session->set('gr_btn_redeem_confirm', empty($app_config['lang']['gr_btn_redeem_confirm'])?'':appsmav_stripslashes($app_config['lang']['gr_btn_redeem_confirm']) );
    2993                 WC()->session->set('gr_redeem_extra_point_info', empty($app_config['lang']['gr_redeem_extra_point_info'])?'':appsmav_stripslashes($app_config['lang']['gr_redeem_extra_point_info']) );
    2994                 WC()->session->set('gr_pbp_auto_apply', empty($app_config['points']['gr_pbp_auto_apply'])?0:$app_config['points']['gr_pbp_auto_apply'] );
    2995                 WC()->session->set('gr_earn_exclude_products', empty($app_config['points']['gr_earn_exclude_products'])?'':$app_config['points']['gr_earn_exclude_products'] );
    2996                 WC()->session->set('gr_pbp_mode', empty($app_config['points']['gr_pbp_mode'])?'points':$app_config['points']['gr_pbp_mode'] );
     3763                WC()->session->set('gr_is_redeem_individual', appsmav_get_nested_array_value($app_config, ['points', 'gr_is_redeem_individual'], 0));
     3764                WC()->session->set('gr_pay_exclude_products', appsmav_get_nested_array_value($app_config, ['points', 'gr_pay_exclude_products'], array()));
     3765                WC()->session->set('gr_pay_exclude_categories', appsmav_get_nested_array_value($app_config, ['points', 'gr_pay_exclude_categories'], array()));
     3766                WC()->session->set('gr_pay_redeem_restrict_terms', appsmav_get_nested_array_value($app_config, ['points', 'gr_pay_redeem_restrict_terms'], ''));
     3767                WC()->session->set('gr_label_redeem_restriction_apply', appsmav_stripslashes(appsmav_get_nested_array_value($app_config, ['lang', 'gr_label_redeem_restriction_apply'], '')));
     3768                WC()->session->set('gr_btn_redeem_confirm', appsmav_stripslashes(appsmav_get_nested_array_value($app_config, ['lang', 'gr_btn_redeem_confirm'], '')));
     3769                WC()->session->set('gr_redeem_extra_point_info', appsmav_stripslashes(appsmav_get_nested_array_value($app_config, ['lang', 'gr_redeem_extra_point_info'], '')));
     3770                WC()->session->set('gr_pbp_auto_apply', appsmav_get_nested_array_value($app_config, ['points', 'gr_pbp_auto_apply'], 0));
     3771                WC()->session->set('gr_earn_exclude_products', appsmav_get_nested_array_value($app_config, ['points', 'gr_earn_exclude_products'], ''));
     3772                WC()->session->set('gr_pbp_mode', appsmav_get_nested_array_value($app_config, ['points', 'gr_pbp_mode'], 'points'));
    29973773
    29983774                $redeem_coupon = WC()->session->get('gr_paybypoints_coupon', self::REDEEM_COUPON);
    2999                 if (!empty($redeem_coupon) && !empty($app_config['points']['gr_paybypoints_coupon']) &&
    3000                     $redeem_coupon != $app_config['points']['gr_paybypoints_coupon'])
     3775                $paybypoints_coupon = isset($app_config['points']['gr_paybypoints_coupon']) ? $app_config['points']['gr_paybypoints_coupon'] : '';
     3776                if (!empty($redeem_coupon) && !empty($paybypoints_coupon) &&
     3777                    $redeem_coupon != $paybypoints_coupon)
    30013778                {
    30023779                    WC()->session->set('gr_paybypoints_coupon_old', $redeem_coupon);
     
    30683845
    30693846                        if(is_array($response) && !empty($response['body']))
    3070                             $ret = json_decode($response['body'], true);
     3847                            $ret = appsmav_json_decode($response['body'], true);
    30713848                        else
    3072                             $ret['error'] = 1;
     3849                            $ret = array('error' => 1);
     3850
     3851                        // Ensure $ret is always an array to prevent "Cannot access offset of type string on string" error
     3852                        if(!is_array($ret))
     3853                            $ret = array('error' => 1);
    30733854
    30743855                        if(isset($ret['error']) && $ret['error'] != 1)
     
    31283909                                            'gr_label_redeem_restriction_apply' => $ret['gr_label_redeem_restriction_apply'],
    31293910                                            'gr_btn_redeem_confirm' => $ret['gr_btn_redeem_confirm'],
     3911                                            'gr_redeem_extra_point_info' => isset($ret['gr_redeem_extra_point_info']) ? $ret['gr_redeem_extra_point_info'] : '',
    31303912                                            'loyalty_menu_name' =>  empty($ret['loyalty_menu_name']) ? 'GR Loyalty' : appsmav_stripslashes($ret['loyalty_menu_name'])
    31313913                                        ),
     
    32224004                    $date_start = WC()->session->get('gr_loyalty_date_start', 0);
    32234005                    $date_end = WC()->session->get('gr_loyalty_date_end', 0);
    3224                     $timezone = WC()->session->get('gr_loyalty_timezone', 'America/Chicago');
     4006                    $timezone_raw = WC()->session->get('gr_loyalty_timezone', 'America/Chicago');
    32254007                    $is_open = WC()->session->get('gr_loyalty_is_open', 0);
    32264008                } else {
     
    32284010                    $date_start = !empty($app_config['loyalty']['date_start']) ? $app_config['loyalty']['date_start'] : 0;
    32294011                    $date_end = !empty($app_config['loyalty']['date_end']) ? $app_config['loyalty']['date_end'] : 0;
    3230                     $timezone = !empty($app_config['loyalty']['timezone']) ? $app_config['loyalty']['timezone'] : 'America/Chicago';
     4012                    $timezone_raw = !empty($app_config['loyalty']['timezone']) ? $app_config['loyalty']['timezone'] : 'America/Chicago';
    32314013                    $is_open = !empty($app_config['loyalty']['is_open']) ? $app_config['loyalty']['is_open'] : 0;
    32324014                }
    32334015
    3234                 if (!empty($date_start)) {
    3235                     $currentDate = new \DateTime("now");
    3236                     $currentDate->setTimezone(new DateTimeZone($timezone));
    3237 
    3238                     $startDate = DateTime::createFromFormat('U', $date_start);
    3239                     $startDate->setTimezone(new DateTimeZone($timezone));
    3240 
    3241                     $endDate = 0;
    3242                     if (empty($is_open)) {
    3243                         $endDate = DateTime::createFromFormat('U', $date_end);
    3244                         $endDate->setTimezone(new DateTimeZone($timezone));
    3245                     }
    3246 
    3247                     if ($startDate > $currentDate || (!empty($endDate) && $currentDate > $endDate)) {
    3248                         return false;
    3249                     }
    3250                 }
     4016            if (!empty($date_start)) {
     4017                // Ensure timezone is a valid string to prevent fatal error
     4018                // Validate immediately after retrieval to prevent any non-string values from propagating
     4019                if (!is_string($timezone_raw) || empty($timezone_raw)) {
     4020                    $timezone = 'America/Chicago';
     4021                } else {
     4022                    $timezone = $timezone_raw;
     4023                }
     4024               
     4025                // Validate timezone is in valid format
     4026                try {
     4027                    $timezoneObj = new DateTimeZone($timezone);
     4028                } catch (Exception $tz_exception) {
     4029                    // Invalid timezone string, use default
     4030                    $timezone = 'America/Chicago';
     4031                    $timezoneObj = new DateTimeZone($timezone);
     4032                }
     4033               
     4034                $currentDate = new DateTime("now");
     4035                $currentDate->setTimezone($timezoneObj);
     4036
     4037                $startDate = DateTime::createFromFormat('U', $date_start);
     4038                // Validate DateTime creation succeeded
     4039                if ($startDate !== false) {
     4040                    $startDate->setTimezone($timezoneObj);
     4041                } else {
     4042                    // Invalid date format, allow campaign
     4043                    return true;
     4044                }
     4045
     4046                $endDate = 0;
     4047                if (empty($is_open)) {
     4048                    $endDate = DateTime::createFromFormat('U', $date_end);
     4049                    // Validate DateTime creation succeeded
     4050                    if ($endDate !== false) {
     4051                        $endDate->setTimezone($timezoneObj);
     4052                    } else {
     4053                        // Invalid end date, treat as open-ended
     4054                        $endDate = 0;
     4055                    }
     4056                }
     4057
     4058                if ($startDate > $currentDate || (!empty($endDate) && $currentDate > $endDate)) {
     4059                    return false;
     4060                }
     4061            }
    32514062            }
    32524063            catch(Exception $e)
     
    32604071            try
    32614072            {
    3262                 $ip = !empty($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';
     4073                $ip = !empty($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(appsmav_unslash($_SERVER['REMOTE_ADDR'])) : '';
    32634074                $ip_details = array('ip' => $ip, 'city' => '', 'region_name' => '', 'country_code' => 'US');
    32644075
     
    32754086
    32764087                if (!empty($res)) {
    3277                     $ipLocArr = json_decode($res, true);
    3278 
    3279                     if (!empty($ipLocArr['geoplugin_request']) && $ipLocArr['geoplugin_request'] == $ip && in_array($ipLocArr['geoplugin_status'], array(200, 206))) {
     4088                    $ipLocArr = appsmav_json_decode($res, true);
     4089                   
     4090                    // Check if valid array and status matches
     4091                    if (is_array($ipLocArr) && !empty($ipLocArr['geoplugin_request']) &&
     4092                        isset($ipLocArr['geoplugin_status']) &&
     4093                        $ipLocArr['geoplugin_request'] == $ip &&
     4094                        appsmav_in_array($ipLocArr['geoplugin_status'], array(200, 206))) {
    32804095                        $ip_details['ip'] = empty($ipLocArr['geoplugin_request']) ? $ip : $ipLocArr['geoplugin_request'];
    32814096                        $ip_details['city'] = empty($ipLocArr['geoplugin_city']) ? null : $ipLocArr['geoplugin_city'];
     
    32994114                {
    33004115                    $resArr = array('gr_reg' => 4, 'message' => 'Enter valid email address');
    3301                     die(json_encode($resArr));
     4116                    echo appsmav_json_encode($resArr);
     4117                    wp_die();
    33024118                }
    33034119
     
    33314147                $params['date_format'] = 'd/m/Y'; //Dummy$p['grappsmav_reg_date_format'];
    33324148                $params['exclusion_period'] = 0; //$p['grconnect_reg_exclusion_period'];
    3333                 $params["app_lang"] = str_replace('-', '_', get_bloginfo('language'));
     4149                $params["app_lang"] = appsmav_str_replace('-', '_', get_bloginfo('language'));
    33344150                $myaccount_page_id = get_option('woocommerce_myaccount_page_id');
    33354151                $myaccount_page_url = get_permalink($myaccount_page_id);
     
    33424158                $res = $httpObj->getResponse();
    33434159
     4160                $resArr = array();
    33444161                if(!empty($res))
    3345                     $resArr = json_decode($res, true);
     4162                    $resArr = appsmav_json_decode($res, true);
     4163
     4164                // Ensure $resArr is always an array to prevent "Cannot access offset of type string on string" error
     4165                if(!is_array($resArr))
     4166                    $resArr = array();
    33464167
    33474168                if(isset($resArr['error']) && $resArr['error'] == 0)
     
    33704191                }
    33714192
    3372                 die(json_encode($resArr));
     4193                die(appsmav_json_encode($resArr));
    33734194            } catch (Exception $ex) {
    33744195
     
    34004221                $params['last_name'] = empty($param['last_name']) ? '' : $param['last_name'];
    34014222                $params['comment'] = isset($param['comment']) ? $param['comment'] : '';
    3402                 $params["app_lang"] = str_replace('-', '_', get_bloginfo('language'));
     4223                $params["app_lang"] = appsmav_str_replace('-', '_', get_bloginfo('language'));
    34034224                $allparam = implode('#WP#', $paramSalt);
    34044225                $params['salt'] = md5($allparam);
     
    34424263
    34434264                    if(!empty($res))
    3444                         $res = json_decode($res, true);
     4265                        $res = appsmav_json_decode($res, true);
     4266
     4267                    // Ensure $res is always an array to prevent "Cannot access offset of type string on string" error
     4268                    if(!is_array($res))
     4269                        $res = array();
    34454270
    34464271                    if(!empty($res['error']))
     
    34834308                // Data for product review
    34844309                $params['app'] = 'WP';
    3485                 $params["app_lang"] = str_replace('-', '_', get_bloginfo('language'));
     4310                $params["app_lang"] = appsmav_str_replace('-', '_', get_bloginfo('language'));
    34864311                $params['id_shop'] = $grShopId;
    34874312                $params['currency'] = get_option('woocommerce_currency', 'USD');
     
    34944319
    34954320                if(!empty($res))
    3496                     $res = json_decode($res, true);
     4321                    $res = appsmav_json_decode($res, true);
     4322
     4323                // Ensure $res is always an array to prevent "Cannot access offset of type string on string" error
     4324                if(!is_array($res))
     4325                    $res = array();
    34974326
    34984327                if(!empty($res['error']))
     
    35284357                    $coupon_code = sanitize_text_field($_REQUEST['gr_coupon_code']);
    35294358                    if(!empty(WC()->cart) && !empty($coupon_code)) {
    3530                         WC()->cart->add_discount($coupon_code);
     4359                        if (method_exists(WC()->cart, 'apply_coupon')) {
     4360                            WC()->cart->apply_coupon($coupon_code);
     4361                        } else {
     4362                            WC()->cart->add_discount($coupon_code);
     4363                        }
    35314364                    }
    35324365                }
     
    35454378                if (!empty($roles) && !empty($app_config['points']['gr_restricted_user_roles']))
    35464379                {
    3547                     $blocked_roles = array_intersect ($roles, $app_config['points']['gr_restricted_user_roles']);
    3548                     if ($restrict_type == 'restrict' && !empty($blocked_roles) && (is_array($blocked_roles) || $blocked_roles instanceof Countable) && count($blocked_roles) > 0)
     4380                    // Ensure both parameters are arrays before using array_intersect
     4381                    $roles_array = is_array($roles) ? $roles : array();
     4382                    $restricted_roles = (isset($app_config['points']['gr_restricted_user_roles']) && is_array($app_config['points']['gr_restricted_user_roles']))
     4383                        ? $app_config['points']['gr_restricted_user_roles']
     4384                        : array();
     4385                    $blocked_roles = appsmav_array_intersect($roles_array, $restricted_roles);
     4386                    if ($restrict_type == 'restrict' && !empty($blocked_roles) && (is_array($blocked_roles) || ($blocked_roles instanceof Countable)) && count($blocked_roles) > 0)
    35494387                        $is_restricted = true;
    3550                     else if ($restrict_type == 'allow' && empty($blocked_roles) && (is_array($blocked_roles) || $blocked_roles instanceof Countable) && count($blocked_roles) == 0)
     4388                    else if ($restrict_type == 'allow' && empty($blocked_roles) && (is_array($blocked_roles) || ($blocked_roles instanceof Countable)) && count($blocked_roles) == 0)
    35514389                        $is_restricted = true;
    35524390                }
     
    35774415                    $ex_products = empty($app_config['points']['gr_pay_exclude_products']) ? array() : appsmav_explode(',', $app_config['points']['gr_pay_exclude_products']);
    35784416                    $ex_category = empty($app_config['points']['gr_pay_exclude_categories']) ? array() : appsmav_explode(',', $app_config['points']['gr_pay_exclude_categories']);
     4417                   
     4418                    // Ensure both are arrays
     4419                    if (!is_array($ex_products)) {
     4420                        $ex_products = array();
     4421                    }
     4422                    if (!is_array($ex_category)) {
     4423                        $ex_category = array();
     4424                    }
    35794425
    35804426                    // Check this product is restricted
    3581                     if (!empty($ex_products) && in_array($id_product, $ex_products)) {
     4427                    if (!empty($ex_products) && is_array($ex_products) && appsmav_in_array($id_product, $ex_products)) {
    35824428                        throw new Exception('Restricted by product');
    35834429                    }
     
    35854431
    35864432                    // Check category is restricted
    3587                     if (!empty($ex_category))
     4433                    if (!empty($ex_category) && is_array($ex_category))
    35884434                    {
    35894435                        $list_category = get_the_terms($id_product, 'product_cat');
    3590                         foreach ( $list_category as $category ) {
    3591                             if (in_array($category->term_id, $ex_category)) {
    3592                                 throw new Exception('Restricted by category');
     4436                        // get_the_terms can return false or WP_Error, ensure it's an array
     4437                        if (is_array($list_category) && !empty($list_category)) {
     4438                            foreach ( $list_category as $category ) {
     4439                                if (isset($category->term_id) && appsmav_in_array($category->term_id, $ex_category)) {
     4440                                    throw new Exception('Restricted by category');
     4441                                }
    35934442                            }
    35944443                        }
     
    36394488                $review_details['comment_approved'] = !empty($comment->comment_approved) ? $comment->comment_approved : 0;
    36404489                $review_details['comment_status'] = $comment_status;
    3641                 $review_details['rating'] = !empty($commentDetails['rating'][0]) ? $commentDetails['rating'][0] : '';
     4490                // Safe array access - check if rating exists and is array before accessing index
     4491                $rating_meta = isset($commentDetails['rating']) && is_array($commentDetails['rating']) ? $commentDetails['rating'] : array();
     4492                $review_details['rating'] = isset($rating_meta[0]) ? $rating_meta[0] : '';
    36424493                $review_details['product_key'] = !empty($product_key) ? $product_key : '';
    36434494                $review_details['product_url'] = !empty($product_url) ? $product_url : '';
     
    36594510                $comment = get_comment( $comment_ID );
    36604511
     4512                // Guard: get_comment() can return null if comment doesn't exist
     4513                if (empty($comment) || !is_object($comment)) {
     4514                    return;
     4515                }
     4516
    36614517                if ( !empty($comment->comment_type) && $comment->comment_type == 'review')
    36624518                {
     
    36774533                // Product Review
    36784534                $comment = get_comment( $comment_ID );
     4535               
     4536                // Guard: get_comment() can return null if comment doesn't exist
     4537                if (empty($comment) || !is_object($comment)) {
     4538                    return;
     4539                }
     4540               
    36794541                if ( !empty($comment->comment_type) && $comment->comment_type == 'review')
    36804542                {
     
    37394601                    if (is_array($response) && !empty($response['body']))
    37404602                    {
    3741                         $ret = json_decode($response['body'], true);
     4603                        $ret = appsmav_json_decode($response['body'], true);
     4604                        // Ensure $ret is always an array to prevent "Cannot access offset of type string on string" error
     4605                        if(!is_array($ret))
     4606                            $ret = array();
    37424607                        if (isset($ret['error']) && $ret['error'] == 0 && !empty(WC()->session) && !empty($ret['gr_loyalty_referral_coupon']))
    37434608                            WC()->session->set('gr_loyalty_referral_coupon', $ret['gr_loyalty_referral_coupon']);
     
    37924657
    37934658                        $_product = wc_get_product($id_product);
     4659                        // Prevent fatal error if product not found
     4660                        if (empty($_product) || !is_object($_product)) {
     4661                            continue;
     4662                        }
    37944663                        $current_price = $_product->get_price();
    37954664
     
    37974666                        $base_price = current($prices);
    37984667
    3799                         if(!empty($base_price) && !empty($current_price))
     4668                        if(!empty($base_price) && $base_price != 0 && !empty($current_price))
    38004669                        {
    38014670                            $ratio = $current_price / $base_price;
     
    38244693                {
    38254694                    // Product amount from order data - this will come with order currency
     4695                    // Prevent division by zero if quantity is 0
     4696                    if (empty($prod['quantity']) || $prod['quantity'] == 0) {
     4697                        continue;
     4698                    }
    38264699                    $subtotal = $prod['subtotal'] / $prod['quantity'];
    38274700
     
    38414714                        $p_price = wc_get_price_excluding_tax($product, array('qty' => 1, 'price' => $p_price));
    38424715
    3843                     $ratio = $subtotal / $p_price;
     4716                    // Prevent division by zero if price is 0
     4717                    if (!empty($p_price) && $p_price != 0) {
     4718                        $ratio = $subtotal / $p_price;
     4719                    }
    38444720
    38454721                    break;
     
    38524728        }
    38534729
    3854         public function validate_apply_coupon( $true )
     4730        public function validate_apply_coupon( $is_true )
    38554731        {
    38564732            try
     
    38644740                // Get coupon description to validation GR created
    38654741                $description = $this->get_coupon_description( $coupon_code );
    3866                 if (!empty($description) && strpos($description, 'Gratisfaction Referral Invite') !== FALSE)
     4742                if (!empty($description) && appsmav_strpos($description, 'Gratisfaction Referral Invite') !== FALSE)
    38674743                {
    38684744                    if (!empty(WC()->session) && empty(WC()->session->get('gr_loyalty_referral_coupon', ''))) {
     
    38954771            { }
    38964772
    3897             return $true;
     4773            return $is_true;
    38984774        }
    38994775
     
    39114787                    // Get coupon description to validation GR created
    39124788                    $description = $this->get_coupon_description($coupon_code);
    3913                     if (!empty($description) && strpos($description, 'Gratisfaction Referral Invite') !== FALSE) {
     4789                    if (!empty($description) && appsmav_strpos($description, 'Gratisfaction Referral Invite') !== FALSE) {
    39144790                        if (!empty(WC()->session) && empty(WC()->session->get('gr_loyalty_referral_coupon', ''))) {
    39154791                            // Get the User's GR referral code
     
    39494825                        $this->get_settings_api(1);
    39504826
    3951 
    39524827                    if (WC()->session->get('gr_user_points', 0) < WC()->session->get('gr_user_deduct_points', 0))
    39534828                    {
     
    39854860                    ) );
    39864861
    3987                     if ((is_array($customer_orders) || $customer_orders instanceof Countable) && count($customer_orders) > 0)  {
     4862                    if ((is_array($customer_orders) || ($customer_orders instanceof Countable)) && count($customer_orders) > 0)  {
    39884863                        return TRUE;
    39894864                    }
     
    40004875                        'numberposts' => -1
    40014876                    ));
    4002                     if ((is_array($customer_orders) || $customer_orders instanceof Countable) && count($customer_orders) > 0)  {
     4877                    if ((is_array($customer_orders) || ($customer_orders instanceof Countable)) && count($customer_orders) > 0)  {
    40034878                        return TRUE;
    40044879                    }
     
    40184893                include(sprintf("%s/includes/grwoo-http-request-handler.php", GR_PLUGIN_BASE_PATH));
    40194894                include(sprintf("%s/includes/grwoo-functions.php", GR_PLUGIN_BASE_PATH));
    4020                 include(sprintf("%s/includes/grwoo-api.php", GR_PLUGIN_BASE_PATH));
     4895                // Only include REST API class if WP_REST_Controller exists (WordPress 4.4+)
     4896                if (class_exists('WP_REST_Controller')) {
     4897                    include(sprintf("%s/includes/grwoo-api.php", GR_PLUGIN_BASE_PATH));
     4898                }
     4899               
    40214900            } catch (Exception $ex) {
    40224901
     
    40934972 */
    40944973
    4095  if (!function_exists('appsmav_stripslashes')) {
    4096     /**
    4097      * Wrapper for stripslashes() that handles null values
    4098      * @param mixed $value The value to strip slashes from
    4099      * @return string The value with slashes stripped, or empty string if null
    4100      */
     4974 /**
     4975 * Wrapper for stripslashes() that handles null values
     4976 * @param mixed $value The value to strip slashes from
     4977 * @return string The value with slashes stripped, or empty string if null
     4978 */
     4979 if (!function_exists('appsmav_stripslashes')) {   
    41014980    function appsmav_stripslashes($value) {
    41024981        if (is_null($value) || !is_string($value)) {
     
    41074986}
    41084987
     4988/**
     4989 * Wrapper for strtolower() that handles null values
     4990 * @param mixed $value The value to convert to lowercase
     4991 * @return string The lowercase string, or empty string if null
     4992 */
    41094993if (!function_exists('appsmav_strtolower')) {
    4110     /**
    4111      * Wrapper for strtolower() that handles null values
    4112      * @param mixed $value The value to convert to lowercase
    4113      * @return string The lowercase string, or empty string if null
    4114      */
    41154994    function appsmav_strtolower($value) {
    41164995        if (is_null($value) || !is_string($value)) {
     
    41215000}
    41225001
     5002/**
     5003 * Wrapper for strtoupper() that handles null values
     5004 * @param mixed $value The value to convert to uppercase
     5005 * @return string The uppercase string, or empty string if null
     5006 */
    41235007if (!function_exists('appsmav_strtoupper')) {
    4124     /**
    4125      * Wrapper for strtoupper() that handles null values
    4126      * @param mixed $value The value to convert to uppercase
    4127      * @return string The uppercase string, or empty string if null
    4128      */
    41295008    function appsmav_strtoupper($value) {
    41305009        if (is_null($value) || !is_string($value)) {
     
    41355014}
    41365015
     5016/**
     5017 * Wrapper for strlen() that handles null values
     5018 * @param mixed $value The value to get length of
     5019 * @return int The string length, or 0 if null
     5020 */
    41375021if (!function_exists('appsmav_strlen')) {
    4138     /**
    4139      * Wrapper for strlen() that handles null values
    4140      * @param mixed $value The value to get length of
    4141      * @return int The string length, or 0 if null
    4142      */
    41435022    function appsmav_strlen($value) {
    41445023        if (is_null($value) || !is_string($value)) {
     
    41495028}
    41505029
     5030/**
     5031 * Wrapper for trim() that handles null values
     5032 * @param mixed $value The value to trim
     5033 * @return string The trimmed string, or empty string if null
     5034 */
    41515035if (!function_exists('appsmav_trim')) {
    4152     /**
    4153      * Wrapper for trim() that handles null values
    4154      * @param mixed $value The value to trim
    4155      * @return string The trimmed string, or empty string if null
    4156      */
    41575036    function appsmav_trim($value) {
    41585037        if (is_null($value) || !is_string($value)) {
     
    41635042}
    41645043
     5044/**
     5045 * Wrapper for explode() that handles null/empty values
     5046 * @param string $separator The boundary string
     5047 * @param mixed $string The input string
     5048 * @param int $limit Maximum number of elements (optional)
     5049 * @return array Array of strings, or empty array if input is null/empty
     5050 */
    41655051if (!function_exists('appsmav_explode')) {
    4166     /**
    4167      * Wrapper for explode() that handles null/empty values
    4168      * @param string $separator The boundary string
    4169      * @param mixed $string The input string
    4170      * @param int $limit Maximum number of elements (optional)
    4171      * @return array Array of strings, or empty array if input is null/empty
    4172      */
    41735052    function appsmav_explode($separator, $string, $limit = PHP_INT_MAX) {
    41745053        if (is_null($string) || !is_string($string) || $string === '') {
     
    41825061}
    41835062
     5063/**
     5064 * Wrapper for strstr() that handles null values
     5065 * @param mixed $haystack The input string
     5066 * @param mixed $needle The string to search for
     5067 * @param bool $before_needle Return part before needle (optional)
     5068 * @return string|false The portion of string, or false if needle is not found
     5069 */
    41845070if (!function_exists('appsmav_strstr')) {
    4185     /**
    4186      * Wrapper for strstr() that handles null values
    4187      * @param mixed $haystack The input string
    4188      * @param mixed $needle The string to search for
    4189      * @param bool $before_needle Return part before needle (optional)
    4190      * @return string|false The portion of string, or false if needle is not found
    4191      */
    41925071    function appsmav_strstr($haystack, $needle, $before_needle = false) {
    41935072        if (is_null($haystack) || !is_string($haystack)) {
     
    41995078        return strstr($haystack, $needle, $before_needle);
    42005079    }
    4201 
    4202     /**
    4203      * Wrapper for substr() that handles null values
    4204      * @param mixed $string The input string
    4205      * @param int $offset The starting position
    4206      * @param int|null $length The maximum length
    4207      * @return string The substring or empty string if null
    4208      */
     5080}
     5081
     5082/**
     5083 * Wrapper for substr() that handles null values
     5084 * @param mixed $string The input string
     5085 * @param int $offset The starting position
     5086 * @param int|null $length The maximum length
     5087 * @return string The substring or empty string if null
     5088 */
     5089if (!function_exists('appsmav_substr')) {
    42095090    function appsmav_substr($string, $offset, $length = null) {
    42105091        if (is_null($string) || !is_string($string)) {
     
    42135094        return $length === null ? substr($string, $offset) : substr($string, $offset, $length);
    42145095    }
    4215 
    4216     /**
    4217      * Wrapper for str_replace() that handles null values
    4218      * @param mixed $search The value being searched for
    4219      * @param mixed $replace The replacement value
    4220      * @param mixed $subject The string or array being searched and replaced on
    4221      * @return mixed The result string or array
    4222      */
     5096}
     5097
     5098/**
     5099 * Wrapper for wp_unslash() with fallback for older WordPress versions
     5100 * Recursively strips slashes from strings/arrays
     5101 *
     5102 * @param mixed $value Value to unslash
     5103 * @return mixed
     5104 */
     5105if (!function_exists('appsmav_unslash')) {
     5106    function appsmav_unslash($value) {
     5107        if (function_exists('wp_unslash')) {
     5108            return wp_unslash($value);
     5109        }
     5110
     5111        if (is_array($value)) {
     5112            return array_map('appsmav_unslash', $value);
     5113        }
     5114
     5115        return is_string($value) ? stripslashes($value) : $value;
     5116    }
     5117}
     5118
     5119/**
     5120 * Wrapper for str_replace() that handles null values
     5121 * @param mixed $search The value being searched for
     5122 * @param mixed $replace The replacement value
     5123 * @param mixed $subject The string or array being searched and replaced on
     5124 * @return mixed The result string or array
     5125 */
     5126if (!function_exists('appsmav_str_replace')) {
    42235127    function appsmav_str_replace($search, $replace, $subject) {
    42245128        if (is_null($subject)) {
     
    42275131        return str_replace($search, $replace, $subject);
    42285132    }
    4229 
    4230     /**
    4231      * Wrapper for preg_match() that handles null values
    4232      * @param string $pattern The pattern to search for
    4233      * @param mixed $subject The input string
    4234      * @param array|null $matches Array to store matches
    4235      * @return bool|int Returns 1 if pattern matches, 0 if not, false on error
    4236      */
     5133}
     5134
     5135/**
     5136 * Wrapper for preg_match() that handles null values
     5137 * @param string $pattern The pattern to search for
     5138 * @param mixed $subject The input string
     5139 * @param array|null $matches Array to store matches
     5140 * @return bool|int Returns 1 if pattern matches, 0 if not, false on error
     5141 */
     5142if (!function_exists('appsmav_preg_match')) {
    42375143    function appsmav_preg_match($pattern, $subject, &$matches = null) {
    42385144        if (is_null($subject) || !is_string($subject)) {
     
    42435149}
    42445150
     5151/**
     5152 * Wrapper for strpos() that handles null values (PHP 8+ compatible)
     5153 * @param mixed $haystack The input string
     5154 * @param mixed $needle The string to search for
     5155 * @param int $offset The search offset (optional)
     5156 * @return int|false The position or false if not found or null input
     5157 */
     5158if (!function_exists('appsmav_strpos')) {
     5159    function appsmav_strpos($haystack, $needle, $offset = 0) {
     5160        if (is_null($haystack) || !is_string($haystack)) {
     5161            return false;
     5162        }
     5163        if (is_null($needle) || !is_string($needle)) {
     5164            return false;
     5165        }
     5166        return strpos($haystack, $needle, $offset);
     5167    }
     5168}
     5169
     5170/**
     5171 * Detect if current page uses WooCommerce Cart or Checkout blocks
     5172 * Compatible with WordPress 3.0.1+ (fallback for pre-5.0 versions)
     5173 *
     5174 * @return bool True if blocks are detected
     5175 */
     5176if (!function_exists('gr_is_cart_or_checkout_block')) {
     5177    function gr_is_cart_or_checkout_block() {
     5178        // Requires WordPress 5.0+ for has_block function
     5179        if (!function_exists('has_block')) {
     5180            // Fallback for WordPress 3.0.1 - 4.9.x
     5181            // Check post content for block markers
     5182            global $post;
     5183            if (empty($post) || empty($post->post_content)) {
     5184                return false;
     5185            }
     5186           
     5187            return (
     5188                appsmav_strpos($post->post_content, '<!-- wp:woocommerce/cart') !== false ||
     5189                appsmav_strpos($post->post_content, '<!-- wp:woocommerce/checkout') !== false
     5190            );
     5191        }
     5192       
     5193        // Modern WordPress (5.0+)
     5194        return has_block('woocommerce/cart') || has_block('woocommerce/checkout');
     5195    }
     5196}
     5197
     5198/**
     5199 * Wrapper for in_array() that handles null/non-array haystack (PHP 8+ compatible)
     5200 * Prevents fatal error if haystack is not an array
     5201 *
     5202 * @param mixed $needle The value to search for
     5203 * @param mixed $haystack The array to search in
     5204 * @param bool $strict Use strict comparison (default: false)
     5205 * @return bool True if found, false otherwise
     5206 */
     5207if (!function_exists('appsmav_in_array')) {
     5208    function appsmav_in_array($needle, $haystack, $strict = false) {
     5209        if (!is_array($haystack)) {
     5210            return false;
     5211        }
     5212        return in_array($needle, $haystack, $strict);
     5213    }
     5214}
     5215
     5216/**
     5217 * Wrapper for array_intersect() that handles null/non-array parameters (PHP 8+ compatible)
     5218 * Prevents fatal error if parameters are not arrays
     5219 *
     5220 * @param array $array1 First array
     5221 * @param array $array2 Second array
     5222 * @return array Array containing all values from array1 that are present in array2
     5223 */
     5224if (!function_exists('appsmav_array_intersect')) {
     5225    function appsmav_array_intersect($array1, $array2) {
     5226        $arr1 = is_array($array1) ? $array1 : array();
     5227        $arr2 = is_array($array2) ? $array2 : array();
     5228        return array_intersect($arr1, $arr2);
     5229    }
     5230}
     5231
     5232/**
     5233 * Wrapper for json_encode() for WordPress 3.0.1+ compatibility
     5234 * WordPress added wp_json_encode() in version 4.1, so we need a fallback for older versions
     5235 *
     5236 * @param mixed $data The value being encoded
     5237 * @param int $options Optional. Options to be passed to json_encode(). Default 0
     5238 * @param int $depth Optional. Maximum depth. Must be greater than zero. Default 512
     5239 * @return string|false JSON encoded string on success, false on failure
     5240 */
     5241if (!function_exists('appsmav_json_encode')) {
     5242    function appsmav_json_encode($data, $options = 0, $depth = 512) {
     5243        // Use WordPress function if available (WordPress 4.1+)
     5244        if (function_exists('wp_json_encode')) {
     5245            return wp_json_encode($data, $options, $depth);
     5246        }
     5247       
     5248        // Fallback to native PHP json_encode for WordPress 3.0.1-4.0
     5249        if (version_compare(PHP_VERSION, '5.5.0', '>=')) {
     5250            // PHP 5.5+ supports depth parameter
     5251            return json_encode($data, $options, $depth);
     5252        } else {
     5253            // PHP 5.4 doesn't support depth parameter
     5254            return json_encode($data, $options);
     5255        }
     5256    }
     5257}
     5258
     5259/**
     5260 * Wrapper for json_decode() that handles null/empty strings (PHP 8+ compatible)
     5261 * Returns array instead of null on failure, prevents null access errors
     5262 *
     5263 * @param string $json JSON string to decode
     5264 * @param bool $assoc Return associative array (default: true)
     5265 * @return array|object Decoded data, or empty array/object on failure
     5266 */
     5267if (!function_exists('appsmav_json_decode')) {
     5268    function appsmav_json_decode($json, $assoc = true) {
     5269        if (empty($json) || !is_string($json)) {
     5270            return $assoc ? array() : new stdClass();
     5271        }
     5272       
     5273        $decoded = json_decode($json, $assoc);
     5274       
     5275        // Handle JSON decode errors
     5276        if (json_last_error() !== JSON_ERROR_NONE) {
     5277            return $assoc ? array() : new stdClass();
     5278        }
     5279       
     5280        // Ensure result is array/object (not null)
     5281        if ($assoc && !is_array($decoded)) {
     5282            return array();
     5283        }
     5284        if (!$assoc && !is_object($decoded)) {
     5285            return new stdClass();
     5286        }
     5287       
     5288        return $decoded;
     5289    }
     5290}
     5291
     5292/**
     5293 * Safely get array value with default fallback (PHP 8+ compatible)
     5294 * Prevents "Undefined array key" warnings
     5295 *
     5296 * @param array|ArrayAccess|null $array The array to access
     5297 * @param string|int $key The array key to retrieve
     5298 * @param mixed $default Default value if key doesn't exist (default: null)
     5299 * @return mixed The array value or default
     5300 */
     5301if (!function_exists('appsmav_array_get')) {
     5302    function appsmav_array_get($array, $key, $default = null) {
     5303        if (!is_array($array) && !($array instanceof ArrayAccess)) {
     5304            return $default;
     5305        }
     5306        return isset($array[$key]) ? $array[$key] : $default;
     5307    }
     5308}
     5309
     5310/**
     5311 * Safely get nested array value (e.g., $array['parent']['child']) (PHP 8+ compatible)
     5312 * Prevents "Undefined array key" warnings for nested access
     5313 *
     5314 * @param array|ArrayAccess|null $array The array to access
     5315 * @param array $keys Array of keys for nested access (e.g., ['billing', 'first_name'])
     5316 * @param mixed $default Default value if any key doesn't exist (default: null)
     5317 * @return mixed The nested array value or default
     5318 */
     5319if (!function_exists('appsmav_get_nested_array_value')) {
     5320    function appsmav_get_nested_array_value($array, $keys, $default = null) {
     5321        if (!is_array($keys) || empty($keys)) {
     5322            return $default;
     5323        }
     5324       
     5325        $current = $array;
     5326        foreach ($keys as $key) {
     5327            if (!is_array($current) && !($current instanceof ArrayAccess)) {
     5328                return $default;
     5329            }
     5330            if (!isset($current[$key])) {
     5331                return $default;
     5332            }
     5333            $current = $current[$key];
     5334        }
     5335       
     5336        return $current;
     5337    }
     5338}
     5339
     5340/**
     5341 * Wrapper for array_keys() that handles null/non-array parameters (PHP 8+ compatible)
     5342 * Prevents fatal error if parameter is not an array
     5343 *
     5344 * @param array|null $array The array to get keys from
     5345 * @param mixed $search_value Optional value to search for
     5346 * @param bool $strict Use strict comparison (optional)
     5347 * @return array Array of keys, or empty array if input is not an array
     5348 */
     5349if (!function_exists('appsmav_array_keys')) {
     5350    function appsmav_array_keys($array, $search_value = null, $strict = false) {
     5351        if (!is_array($array)) {
     5352            return array();
     5353        }
     5354        if ($search_value === null) {
     5355            return array_keys($array);
     5356        }
     5357        return array_keys($array, $search_value, $strict);
     5358    }
     5359}
     5360
     5361/**
     5362 * Wrapper for array_map() that handles null/non-array parameters (PHP 8+ compatible)
     5363 * Prevents fatal error if first parameter is not an array
     5364 * PHP 5.4 compatible - uses func_get_args() instead of variadic syntax
     5365 *
     5366 * @param callable $callback The callback function
     5367 * @param array|null $array The array to map over
     5368 * Additional arrays can be passed as extra arguments
     5369 * @return array Mapped array, or empty array if input is not an array
     5370 */
     5371if (!function_exists('appsmav_array_map')) {
     5372    function appsmav_array_map($callback, $array) {
     5373        if (!is_array($array)) {
     5374            return array();
     5375        }
     5376       
     5377        // Get all arguments passed to the function
     5378        $args = func_get_args();
     5379       
     5380        // If only callback and array were passed (most common case)
     5381        if (count($args) <= 2) {
     5382            return array_map($callback, $array);
     5383        }
     5384       
     5385        // Multiple arrays passed - use call_user_func_array
     5386        // $args contains [$callback, $array1, $array2, ...] which is exactly what array_map expects
     5387        return call_user_func_array('array_map', $args);
     5388    }
     5389}
     5390
     5391/**
     5392 * Backward compatible get_parent_id for WC < 3.0 and WC >= 3.0/
     5393 */
    42455394if (!function_exists('appsmav_get_parent_id')) {
    4246     /**
    4247      * Backward compatible get_parent_id for WC < 3.0 and WC >= 3.0
    4248      */
    42495395    function appsmav_get_parent_id($object) {
    42505396        try {
     
    42915437    add_shortcode('gr-points-balance', array('GR_Connect', 'gr_woo_points_balance'));
    42925438
    4293     $gr_connect->include_files();
    4294 
    42955439    global $pagenow;
    42965440
  • gratisfaction-all-in-one-loyalty-contests-referral-program-for-woocommerce/trunk/includes/grwoo-api.php

    r3398568 r3419727  
    33if( ! defined('ABSPATH'))
    44    exit;
     5
     6// Guard: WP_REST_Controller requires WordPress 4.4+
     7// Without this check, PHP will fatal error when parsing the class declaration
     8if (!class_exists('WP_REST_Controller')) {
     9    return;
     10}
    511
    612class Grwoo_API extends WP_REST_Controller
     
    230236        $msg = '';
    231237        try {
    232             if (strpos($request->get_header('user_agent'), 'Appsmav') === false) {
     238            $user_agent = $request->get_header('user_agent');
     239            if (appsmav_strpos($user_agent, 'Appsmav') === false) {
    233240                throw new Exception('Error: ');
    234241            }
     
    262269        $msg = '';
    263270        try {
    264             if (strpos($request->get_header('user_agent'), 'Appsmav') === false) {
     271            $user_agent = $request->get_header('user_agent');
     272            if (appsmav_strpos($user_agent, 'Appsmav') === false) {
    265273                throw new Exception('Error: ');
    266274            }
     
    303311            }
    304312
    305             $res = json_decode($res, true);
     313            $res = appsmav_json_decode($res, true);
    306314            if (empty($res) || !empty($res['error'])) {
    307315                throw new Exception('Error: ');
     
    332340            );
    333341            $categories = get_terms( $cat_args );
     342           
     343            // get_terms() can return WP_Error on failure
     344            if (is_wp_error($categories)) {
     345                $categories = array();
     346            }
    334347
    335348            $data = array(
    336349                'error' => 0,
    337                 'product_categories' => !empty($categories) ? $categories : array()
     350                'product_categories' => !empty($categories) && is_array($categories) ? $categories : array()
    338351            );
    339352        }
     
    440453            $order_id = sanitize_text_field($_POST['order_id']);
    441454            $order = new WC_Order($order_id);
    442             if (empty($order)) {
     455            if (empty($order) || !($order instanceof WC_Order) || empty($order->get_id())) {
    443456                throw new Exception("Order not found");
    444457            }
    445458
    446             $customer = new WC_Customer($order->get_customer_id());
     459            // Handle guest orders (customer_id = 0) - fallback to null
     460            $customer_id = $order->get_customer_id();
     461            $customer = !empty($customer_id) ? new WC_Customer($customer_id) : null;
    447462
    448463            if (version_compare( WC_VERSION, '3.7', '<' ))
     
    503518                    foreach ($prodArr as $prod) {
    504519                        // Product amount from order data - this will come with order currency
     520                        // Prevent division by zero if quantity is 0
     521                        if (empty($prod['quantity']) || $prod['quantity'] == 0) {
     522                            continue;
     523                        }
    505524                        $subtotalRatio = $prod['subtotal'] / $prod['quantity'];
    506525
     
    517536                            $p_price = wc_get_price_excluding_tax($product, array('qty' => 1, 'price' => $p_price));
    518537
    519                         $ratio = $subtotalRatio / $p_price;
     538                        // Prevent division by zero if price is 0
     539                        if (!empty($p_price) && $p_price != 0) {
     540                            $ratio = $subtotalRatio / $p_price;
     541                        }
    520542
    521543                        break;
     
    542564                'coupons' => $coupons,
    543565                'ratio' => $ratio,
    544                 'email' => $customer->get_email(),
    545                 'name' => $customer->get_first_name() . ' ' . $customer->get_last_name()
     566                'email' => !empty($customer) ? $customer->get_email() : $order->get_billing_email(),
     567                'name' => !empty($customer) ? $customer->get_first_name() . ' ' . $customer->get_last_name() : $order->get_billing_first_name() . ' ' . $order->get_billing_last_name()
    546568            );
    547569
     
    629651        {
    630652            $email = sanitize_email($_POST['email']);
    631             $user = get_user_by( 'email', $email);
     653            $user = get_user_by( 'email', $email );
     654
     655            if ( !($user instanceof WP_User) ) {
     656                // Optionally handle: user not found
     657                throw new Exception( 'User not found for email.' );
     658            }
    632659
    633660            // Get logged in user's order list
    634661            $customer_orders = get_posts( array(
    635662                'meta_key'    => '_customer_user',
    636                 'meta_value'  => $user->id,
     663                'meta_value'  => $user->ID,
    637664                'post_type'   => wc_get_order_types(),
    638                 'post_status' => array_keys( wc_get_order_statuses() ),
     665                'post_status' => appsmav_array_keys( wc_get_order_statuses() ),
    639666                'numberposts' => -1
    640             ));
     667            ) );
    641668
    642669            $args = array(
    643                 'limit' => 1000,
    644                 'customer' => $user->id
     670                'limit'    => 1000,
     671                'customer' => $user->ID
    645672            );
    646673
    647674            $customer_orders = wc_get_orders( $args );
     675            $orders = array(); // Initialize array before loop
    648676            foreach ( $customer_orders as $order ) {
    649677                $orders[] = array(
     
    708736            }
    709737
    710             $data = json_decode($response, true);
     738            $data = appsmav_json_decode($response, true);
    711739        } catch (Exception $e) {
    712740            $data['error'] = 1;
     
    748776            }
    749777
    750             $data = json_decode($response, true);
     778            $data = appsmav_json_decode($response, true);
    751779        } catch (Exception $e) {
    752780            $data['error'] = 1;
     
    790818            }
    791819
    792             $data = json_decode($response, true);
     820            $data = appsmav_json_decode($response, true);
    793821        } catch (Exception $e) {
    794822            $data['error'] = 1;
     
    842870            $app_config     =   gr_get_app_config();
    843871
    844             if(!empty($app_config) && is_array($app_config))
     872            if(!empty($app_config) && is_array($app_config) && is_array($config))
    845873                $config     =   array_merge($app_config, $config);
    846874
     
    11261154            {
    11271155                $data['msg'] = 'Yes';
    1128                 $data['coupon'] = json_decode($coupon, true);
     1156                // Get coupon data as array (WC_Coupon object, not JSON string)
     1157                $data['coupon'] = is_callable(array($coupon, 'get_data')) ? $coupon->get_data() : array();
    11291158            }
    11301159            else
     
    12721301                $order = new WC_Order( $order_id );
    12731302
    1274                 if (empty($order->get_id()))
     1303                // Validate order object before accessing methods
     1304                if (empty($order) || !($order instanceof WC_Order) || empty($order->get_id()))
    12751305                        continue;
    12761306
    1277                 $customer = new WC_Customer($order->get_customer_id());
     1307                // Handle guest orders (customer_id = 0) - fallback to null
     1308                $customer_id = $order->get_customer_id();
     1309                $customer = !empty($customer_id) ? new WC_Customer($customer_id) : null;
     1310               
     1311                // Safe date handling - prevent fatal if get_date_created() returns null
     1312                $date_created = $order->get_date_created();
     1313                $date_created_formatted = (is_object($date_created) && method_exists($date_created, 'format'))
     1314                    ? $date_created->format('c')
     1315                    : current_time('c');
     1316               
    12781317                $orderslist[] = array(
    12791318                    'id_order' => $order->get_id(),
     
    12841323                    'last_name' => $order->get_billing_last_name(),
    12851324                    'name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
    1286                     'email' => $customer->get_email(),
     1325                    'email' => !empty($customer) ? $customer->get_email() : $order->get_billing_email(),
    12871326                    'billing_email' => $order->get_billing_email(),
    12881327                    'currency' => $order->get_currency(),
    12891328                    'coupon' => version_compare( WC_VERSION, '3.7', '<' ) ? $order->get_used_coupons() : $order->get_coupon_codes(),
    1290                     'date_created' => $order->get_date_created()->format('c'),
     1329                    'date_created' => $date_created_formatted,
    12911330                    'status' => $order->get_status()
    12921331                );
     
    13401379
    13411380            foreach($orders as $order) {
    1342                 $customer = new WC_Customer($order->get_customer_id());
     1381                // Handle guest orders (customer_id = 0) - fallback to null
     1382                $customer_id = $order->get_customer_id();
     1383                $customer = !empty($customer_id) ? new WC_Customer($customer_id) : null;
     1384               
     1385                // Safe date handling - prevent fatal if get_date_created() returns null
     1386                $date_created = $order->get_date_created();
     1387                $date_created_formatted = (is_object($date_created) && method_exists($date_created, 'format'))
     1388                    ? $date_created->format('c')
     1389                    : current_time('c');
     1390               
    13431391                $orderslist[] = array(
    13441392                    'id_order' => $order->get_id(),
     
    13491397                    'last_name' => $order->get_billing_last_name(),
    13501398                    'name' => $order->get_billing_first_name() . ' ' . $order->get_billing_last_name(),
    1351                     'email' => $customer->get_email(),
     1399                    'email' => !empty($customer) ? $customer->get_email() : $order->get_billing_email(),
    13521400                    'billing_email' => $order->get_billing_email(),
    13531401                    'currency' => $order->get_currency(),
    13541402                    'coupon' => version_compare( WC_VERSION, '3.7', '<' ) ? $order->get_used_coupons() : $order->get_coupon_codes(),
    1355                     'date_created' => $order->get_date_created()->format('c'),
     1403                    'date_created' => $date_created_formatted,
    13561404                    'status' => $order->get_status()
    13571405                );
     
    13941442                throw new Exception('WooPluginNotFound');
    13951443
    1396             if(!in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins'))))
     1444            if(!appsmav_in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins'))))
    13971445                throw new Exception('PluginDeactivated');
    13981446
    1399             // Validate coupon types
    1400             if(!in_array(wc_clean($_POST['cpn_type']), array_keys(wc_get_coupon_types())))
    1401                 throw new WC_CLI_Exception('woocommerce_cli_invalid_coupon_type', sprintf(__('Invalid coupon type - the coupon type must be any of these: %s', 'woocommerce'), implode(', ', array_keys(wc_get_coupon_types()))));
     1447            $coupon_types = wc_get_coupon_types();
     1448            $coupon_type_keys = appsmav_array_keys($coupon_types);
     1449            if(!appsmav_in_array(wc_clean($_POST['cpn_type']), $coupon_type_keys))
     1450                throw new Exception(sprintf(__('Invalid coupon type - the coupon type must be any of these: %s', 'woocommerce'), implode(', ', $coupon_type_keys)));
    14021451
    14031452            $assoc_args = array(
     
    14211470            );
    14221471
    1423             // Safe JSON decode with error handling
    1424             $assoc_args['product_ids'] = $this->safe_json_decode(isset($assoc_args['product_ids']) ? $assoc_args['product_ids'] : '');
    1425             $assoc_args['exclude_product_ids'] = $this->safe_json_decode(isset($assoc_args['exclude_product_ids']) ? $assoc_args['exclude_product_ids'] : '');
    1426             $assoc_args['product_category_ids'] = $this->safe_json_decode(isset($assoc_args['product_category_ids']) ? $assoc_args['product_category_ids'] : '');
    1427             $assoc_args['exclude_product_category_ids'] = $this->safe_json_decode(isset($assoc_args['exclude_product_category_ids']) ? $assoc_args['exclude_product_category_ids'] : '');
    1428             $assoc_args['customer_emails'] = $this->safe_json_decode(appsmav_stripslashes(isset($assoc_args['customer_emails']) ? $assoc_args['customer_emails'] : ''));
     1472            // Safe JSON decode with error handling - using appsmav_json_decode from grconnect.php
     1473            $assoc_args['product_ids'] = appsmav_json_decode(isset($assoc_args['product_ids']) ? $assoc_args['product_ids'] : '', true);
     1474            $assoc_args['exclude_product_ids'] = appsmav_json_decode(isset($assoc_args['exclude_product_ids']) ? $assoc_args['exclude_product_ids'] : '', true);
     1475            $assoc_args['product_category_ids'] = appsmav_json_decode(isset($assoc_args['product_category_ids']) ? $assoc_args['product_category_ids'] : '', true);
     1476            $assoc_args['exclude_product_category_ids'] = appsmav_json_decode(isset($assoc_args['exclude_product_category_ids']) ? $assoc_args['exclude_product_category_ids'] : '', true);
     1477            $assoc_args['customer_emails'] = appsmav_json_decode(appsmav_stripslashes(isset($assoc_args['customer_emails']) ? $assoc_args['customer_emails'] : ''), true);
    14291478
    14301479            if(!empty($_POST['usage_limit_per_user']))
     
    14731522            $res = $httpObj->getResponse();
    14741523            if(!empty($res))
    1475                 $res = json_decode($res, true);
     1524                $res = appsmav_json_decode($res, true);
    14761525
    14771526            if(empty($res) || !empty($res['error']))
     
    15131562
    15141563            if(is_wp_error($id))
    1515                 throw new WC_CLI_Exception('woocommerce_cli_cannot_create_coupon', $id->get_error_message());
    1516 
    1517             // Set coupon meta
    1518             update_post_meta($id, 'discount_type', $coupon_data['type']);
    1519             update_post_meta($id, 'coupon_amount', wc_format_decimal($coupon_data['amount']));
     1564                throw new Exception('Cannot create coupon: ' . $id->get_error_message());
     1565
     1566            // Set coupon meta
     1567            update_post_meta($id, 'discount_type', isset($coupon_data['type']) ? $coupon_data['type'] : '');
     1568            update_post_meta($id, 'coupon_amount', wc_format_decimal(isset($coupon_data['amount']) ? $coupon_data['amount'] : 0));
    15201569            update_post_meta($id, 'individual_use', (!empty($coupon_data['individual_use']) ) ? 'yes' : 'no' );
    1521             update_post_meta($id, 'product_ids', implode(',', array_filter(array_map('intval', $coupon_data['product_ids']))));
    1522             update_post_meta($id, 'exclude_product_ids', implode(',', array_filter(array_map('intval', $coupon_data['exclude_product_ids']))));
    1523             update_post_meta($id, 'usage_limit', absint($coupon_data['usage_limit']));
    1524             update_post_meta($id, 'usage_limit_per_user', absint($coupon_data['usage_limit_per_user']));
    1525             update_post_meta($id, 'limit_usage_to_x_items', absint($coupon_data['limit_usage_to_x_items']));
    1526             update_post_meta($id, 'usage_count', absint($coupon_data['usage_count']));
    1527 
    1528             if('' !== wc_clean($coupon_data['expiry_date']))
     1570            // Use safe array_map wrapper - ensure array exists before mapping
     1571            $product_ids = isset($coupon_data['product_ids']) && is_array($coupon_data['product_ids']) ? $coupon_data['product_ids'] : array();
     1572            update_post_meta($id, 'product_ids', implode(',', array_filter(appsmav_array_map('intval', $product_ids))));
     1573            $exclude_product_ids = isset($coupon_data['exclude_product_ids']) && is_array($coupon_data['exclude_product_ids']) ? $coupon_data['exclude_product_ids'] : array();
     1574            update_post_meta($id, 'exclude_product_ids', implode(',', array_filter(appsmav_array_map('intval', $exclude_product_ids))));
     1575            update_post_meta($id, 'usage_limit', absint(isset($coupon_data['usage_limit']) ? $coupon_data['usage_limit'] : 0));
     1576            update_post_meta($id, 'usage_limit_per_user', absint(isset($coupon_data['usage_limit_per_user']) ? $coupon_data['usage_limit_per_user'] : 0));
     1577            update_post_meta($id, 'limit_usage_to_x_items', absint(isset($coupon_data['limit_usage_to_x_items']) ? $coupon_data['limit_usage_to_x_items'] : 0));
     1578            update_post_meta($id, 'usage_count', absint(isset($coupon_data['usage_count']) ? $coupon_data['usage_count'] : 0));
     1579
     1580            if(isset($coupon_data['expiry_date']) && '' !== wc_clean($coupon_data['expiry_date']))
    15291581                $coupon_data['expiry_date'] = date('Y-m-d', strtotime($coupon_data['expiry_date']));
    15301582
    1531             update_post_meta($id, 'expiry_date', wc_clean($coupon_data['expiry_date']));
     1583            update_post_meta($id, 'expiry_date', isset($coupon_data['expiry_date']) ? wc_clean($coupon_data['expiry_date']) : '');
    15321584            update_post_meta($id, 'free_shipping', (!empty($coupon_data['enable_free_shipping']) ) ? 'yes' : 'no' );
    1533             update_post_meta($id, 'product_categories', array_filter(array_map('intval', $coupon_data['product_category_ids'])));
    1534             update_post_meta($id, 'exclude_product_categories', array_filter(array_map('intval', $coupon_data['exclude_product_category_ids'])));
     1585            // Use safe array_map wrapper - ensure array exists before mapping
     1586            $product_category_ids = isset($coupon_data['product_category_ids']) && is_array($coupon_data['product_category_ids']) ? $coupon_data['product_category_ids'] : array();
     1587            update_post_meta($id, 'product_categories', array_filter(appsmav_array_map('intval', $product_category_ids)));
     1588            $exclude_product_category_ids = isset($coupon_data['exclude_product_category_ids']) && is_array($coupon_data['exclude_product_category_ids']) ? $coupon_data['exclude_product_category_ids'] : array();
     1589            update_post_meta($id, 'exclude_product_categories', array_filter(appsmav_array_map('intval', $exclude_product_category_ids)));
    15351590            update_post_meta($id, 'exclude_sale_items', (!empty($coupon_data['exclude_sale_items']) ) ? 'yes' : 'no' );
    1536             update_post_meta($id, 'minimum_amount', wc_format_decimal($coupon_data['minimum_amount']));
    1537             update_post_meta($id, 'maximum_amount', wc_format_decimal($coupon_data['maximum_amount']));
    1538             update_post_meta($id, 'customer_email', array_filter(array_map('sanitize_email', $coupon_data['customer_emails'])));
     1591            update_post_meta($id, 'minimum_amount', wc_format_decimal(isset($coupon_data['minimum_amount']) ? $coupon_data['minimum_amount'] : 0));
     1592            update_post_meta($id, 'maximum_amount', wc_format_decimal(isset($coupon_data['maximum_amount']) ? $coupon_data['maximum_amount'] : 0));
     1593            // Use safe array_map wrapper - ensure array exists before mapping
     1594            $customer_emails = isset($coupon_data['customer_emails']) && is_array($coupon_data['customer_emails']) ? $coupon_data['customer_emails'] : array();
     1595            update_post_meta($id, 'customer_email', array_filter(appsmav_array_map('sanitize_email', $customer_emails)));
    15391596
    15401597            if (!empty($_POST['custom_attributes']))
    15411598            {
    15421599                $custom_attributes = appsmav_stripslashes(sanitize_text_field($_POST['custom_attributes']));
    1543                 $custom_attributes = json_decode($custom_attributes, true);
     1600                $custom_attributes = appsmav_json_decode($custom_attributes, true);
    15441601                if (!empty($custom_attributes) && is_array($custom_attributes))
    15451602                {
     
    16321689            }
    16331690
    1634             // Get coupon by code
    1635             $coupon_id = wc_get_coupon_id_by_code($coupon_code);
     1691            // Get coupon by code - wc_get_coupon_id_by_code() requires WC 3.0+
     1692            if (function_exists('wc_get_coupon_id_by_code')) {
     1693                $coupon_id = wc_get_coupon_id_by_code($coupon_code);
     1694            } else {
     1695                // Fallback for WC < 3.0
     1696                $coupon_temp = new WC_Coupon($coupon_code);
     1697                $coupon_id = is_callable(array($coupon_temp, 'get_id')) ? $coupon_temp->get_id() : (isset($coupon_temp->id) ? $coupon_temp->id : 0);
     1698            }
    16361699
    16371700            if (!$coupon_id) {
     
    16781741        return $result;
    16791742    }
    1680 
    1681     /**
    1682      * Safe JSON decode with error handling
    1683      *
    1684      * @param string $json_string The JSON string to decode
    1685      * @return array Returns decoded array or empty array on error
    1686      */
    1687     private function safe_json_decode($json_string) {
    1688         if (empty($json_string)) {
    1689             return array();
    1690         }
    1691        
    1692         $decoded = json_decode($json_string, true);
    1693        
    1694         if (json_last_error() !== JSON_ERROR_NONE) {
    1695             return array();
    1696         }
    1697        
    1698         return is_array($decoded) ? $decoded : array();
    1699     }
    17001743}
  • gratisfaction-all-in-one-loyalty-contests-referral-program-for-woocommerce/trunk/includes/grwoo-functions.php

    r2295884 r3419727  
    3030        $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) );
    3131
    32     return in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommmerce.php', $active_plugins );
     32    return appsmav_in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins );
    3333}
    3434
     
    4444        $notice = '<strong>' . __( 'Gratisfaction is inactive.', 'gratisfaction' ) . '</strong> ' . __( 'WooCommerce is required for Gratisfaction to work.', 'gratisfaction' );
    4545
    46         printf( "<div class='error'><p>%s</p></div>", $notice );
     46        printf( "<div class='error'><p>%s</p></div>", wp_kses_post( $notice ) );
    4747    }
    4848}
     
    5555        $notice = '<strong>' . __( 'Woocommerce Coupon is disabled.', 'gratisfaction' ) . '</strong> ' . __( 'Enable it to work Gratisfaction coupon.', 'gratisfaction' );
    5656
    57         printf( "<div class='error'><p>%s</p></div>", $notice );
     57        printf( "<div class='error'><p>%s</p></div>", wp_kses_post( $notice ) );
    5858    }
    5959}
     
    7070               
    7171                if(!empty($config_json))
    72                     $config     =   json_decode($config_json, true);
     72                    $config     =   appsmav_json_decode($config_json, true);
    7373            }
    7474        } catch (Exception $e) {
     
    8080}
    8181
    82 if(!function_exists('gr_get_app_config')) {
     82if(!function_exists('gr_app_error_log')) {
    8383    function gr_app_error_log($msg) {
    8484        try {
     
    106106    function gr_set_app_config($config) {
    107107        try {
    108             $config_json    =   json_encode($config);
     108            $config_json    =   appsmav_json_encode($config);
    109109            $config_file    =   GR_PLUGIN_BASE_PATH.'/configs/app.json';
    110110
     
    113113
    114114            if(file_put_contents($config_file, $config_json) == FALSE) {
    115                 $data   =   json_encode(array(
     115                $data   =   appsmav_json_encode(array(
    116116                   'config'      => $config,
    117117                   'config_file' => $config_file,
  • gratisfaction-all-in-one-loyalty-contests-referral-program-for-woocommerce/trunk/includes/grwoo-http-request-handler.php

    r3386951 r3419727  
    9494        try
    9595        {
    96             $url = sanitize_url($url);
     96            // sanitize_url() requires WordPress 5.9+, fallback to esc_url_raw() for older versions
     97            $url = function_exists('sanitize_url') ? sanitize_url($url) : esc_url_raw($url);
    9798            if(!empty($url))
    9899                $this->_url = $url;
     
    112113            }
    113114            else {
    114                 $response = wp_remote_get( $url );
     115                $response = wp_remote_get($url, array('timeout' => 10));
    115116                if (is_wp_error( $response ) || !isset($response['body'])) {
    116117                    throw new Exception('Request failed');
  • gratisfaction-all-in-one-loyalty-contests-referral-program-for-woocommerce/trunk/js/grconnect.js

    r3269635 r3419727  
    5959    else
    6060    {
    61         //alert('Please clear errors while input.');
    6261        return false;
    6362    }
     
    201200
    202201    }else{
    203         //alert('Please clear errors while input.');
    204202        return false;
    205203    }
     
    270268                    $launchLink.attr('onclick', 'callAutoLogin()');
    271269                    $launchLink.removeAttr('href').removeAttr('target');
    272                 }, 2000); // 2 seconds delay
     270                }, 2000);
    273271            }, 'json'
    274272        );
     
    284282            $launchLink.attr('onclick', 'callAutoLogin()');
    285283            $launchLink.removeAttr('href').removeAttr('target');
    286         }, 2000); // 2 seconds delay
     284        }, 2000);
    287285    }
    288286}
  • gratisfaction-all-in-one-loyalty-contests-referral-program-for-woocommerce/trunk/readme.txt

    r3404633 r3419727  
    55Requires at least: 3.0.1
    66Tested up to: 6.8
    7 Stable tag: 4.5.5
     7Stable tag: 4.6.0
    88License: GPLv2 or later
    99License URI: http://www.gnu.org/licenses/gpl-2.0.html
     
    254254
    255255== Changelog ==
     256= 4.6.0 =
     257Improvements and bug fixes in block pages
     258
    256259= 4.5.5 =
    257260Improvements and bug fixes
     
    629632
    630633== Upgrade Notice ==
    631 = 4.5.5 =
    632 Improvements and bug fixes
     634= 4.6.0 =
     635Improvements and bug fixes in block pages
Note: See TracChangeset for help on using the changeset viewer.