Plugin Directory

Changeset 3374276


Ignore:
Timestamp:
10/07/2025 10:22:48 AM (5 months ago)
Author:
wisernotify
Message:

Updating trunk to version 2.5

Location:
wiser-review/trunk
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • wiser-review/trunk/readme.txt

    r3367093 r3374276  
    66Tested up to: 6.8
    77Requires PHP: 7.4
    8 Stable tag: 2.4
     8Stable tag: 2.5
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    133133= 2.4 =
    134134Google Rich review schema-if zero review, Don't generate the schema, Other improvement
     135
     136= 2.5 =
     137WPML & polylang support added. Fixed warning & optimized rich google snippt code
  • wiser-review/trunk/views/wiserw-plugin-settings.php

    r3359694 r3374276  
    4646                    <th class="name">Feature</th>
    4747                    <th class="status">Enabled</th>
    48                     <th class="description">Description</th>
     48                    <th class="description">Description & shortcode</th>
    4949                </tr>
    5050            </thead>
     
    6363                    </td>
    6464                    <td class="description">
    65                         Enable Reviews
     65                        Enable WiserReview
    6666                    </td>
    6767                </tr>
     
    8787                        </div>                 
    8888                    </td>
    89                     <td class="description">
    90                         Enter your WiserReview API key to activate product reviews, star ratings, and automated review requests.
    91                         If you don’t have an account with us, Create an account from <a href="https://app.wiserreview.com/signup" target="_new">here </a>. <br/><br/>
    92                         <a href="https://app.wiserreview.com/login" target="_new">Login in to WiserReview here </a> to get your API key from setting & manage reviews, customize widgets, and track review requests.
    93                     </td>
     89                <td class="description">
     90  Enter your WiserReview API key to activate product reviews, star ratings, and automated review requests.
     91  If you don’t have an account, create one <a href="https://app.wiserreview.com/signup" target="_blank">here</a>.<br><br>
     92  <a href="https://app.wiserreview.com/login" target="_blank">Login to WiserReview</a> and go to 
     93  <a href="https://app.wiserreview.com/setting" target="_blank"><strong>Workspace &gt; Settings</strong></a> to find your API key. 
     94   Also, you can  <a href="https://app.wiserreview.com/reviewmoderate" target="_blank">manage reviews</a>, <a href="https://app.wiserreview.com/reviewWidget" target="_blank">customize widgets</a>, and <a href="https://app.wiserreview.com/sequence" target="_blank">send review requests</a>.
     95</td>
     96
    9497                </tr>
    9598                <tr class="wiserrw_hide_row">
    9699                    <td>                       
    97100                        <a href="#" class="wc-payment-gateway-method-title">Star Rating Count at PDP</a>
    98                         <p class="wiserrw_info">Show Product Star Rating count on product page</p>
     101                        <p class="wiserrw_info">Turn on to show the rating count below the product title (default position). Keep off if you’re using the shortcode.</p>
    99102                    </td>
    100103                    <td  class="wisserrw_input_td">
     
    113116                        </div>
    114117                        <p class="wiserrw_info">Use the given shortcode if you are using a cutomized theme or you want to change the position of the widget on the product page.</p>
     118                        <p class="wiserrw_info">To display on a custom product page, use <code>[wiserrw_rating_count id="123"]</code>. (replace 123 with the product ID).</p>
    115119                    </td>
    116120                </tr>
     
    118122                    <td>
    119123                        <a href="#" class="wc-payment-gateway-method-title">Product Card Count At PLP</a>
    120                         <p class="wiserrw_info">Show Product Star Rating count on collection page</p>
     124                        <p class="wiserrw_info">Turn on to show the rating count below the product title on collection or listing pages (default position). Keep off if you’re using the shortcode.</p>
    121125                    </td>
    122126                    <td  class="wisserrw_input_td">
     
    140144                    <td>
    141145                        <a href="#" class="wc-payment-gateway-method-title">Product Review Section</a>
    142                         <p class="wiserrw_info">Show Product Review section</p>
     146                        <p class="wiserrw_info">Turn on to show the reviews block below the product summary (default position). Keep off if you’re using the shortcode.</p>
    143147                    </td>
    144148                    <td  class="wisserrw_input_td">
     
    157161                        </div>
    158162                        <p class="wiserrw_info">Use the given shortcode if you are using a cutomized theme or you want to change the position of the widget on the product page.</p>
     163                        <p class="wiserrw_info">To display on a custom product page, use <code>[wiserrw_product_review id="123"]</code>. (replace 123 with the product ID).</p>
    159164                    </td>
    160165                </tr>
     
    162167                    <td>
    163168                        <a href="#" class="wc-payment-gateway-method-title">Review nudges</a>
    164                         <p class="wiserrw_info">Show review nudges (Show selected featured carousel after call to action button ) </p>
     169                        <p class="wiserrw_info">Turn on to show review nudges below the main call-to-action button (default position). Keep off if you’re using the shortcode.</p>
    165170                    </td>
    166171                    <td  class="wisserrw_input_td">
     
    179184                        </div>
    180185                        <p class="wiserrw_info">Use the given shortcode if you are using a cutomized theme or you want to change the position of the widget on the product page.</p>
     186                        <p class="wiserrw_info">To display on a custom product page, use <code>[wiserrw_product_nudges id="123"]</code>. (replace 123 with the product ID).</p>
    181187                    </td>
    182188                </tr>
  • wiser-review/trunk/wiser-review.php

    r3366418 r3374276  
    44 * Plugin URI: https://wiserreview.com
    55 * Description: Wiser Review module helps you collect and display product reviews, star ratings, and nudges. It also automates review requests via email to boost custom engagement and conversions.
    6  * Version: 2.4
     6 * Version: 2.5
    77 * Author: Wiser Notify
    88 * Requires Plugins: woocommerce
     
    2222define( 'WISERRW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
    2323define( 'WISERRW_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
    24 define( 'WISERRW_PLUGIN_VERSION', 2.4 );
     24define( 'WISERRW_PLUGIN_VERSION', 2.5 );
    2525define( 'WISERRW_API_HOST', 'https://api.wiserreview.com/api/woocommerce/' );
     26
     27if ( ! function_exists( 'wiserrw_load_textdomain' ) ) {
     28    function wiserrw_load_textdomain() {
     29        load_plugin_textdomain( 'wiser-review', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' );
     30    }
     31}
     32add_action( 'plugins_loaded', 'wiserrw_load_textdomain' );
    2633
    2734if ( ! function_exists( 'wiserrw_scripts' ) ) {
     
    5966    function wiserrw_api_settings() {
    6067        add_menu_page(
    61             'Wiser Review',
    62             'Wiser Review',
     68            __( 'Wiser Review', 'wiser-review' ),
     69            __( 'Wiser Review', 'wiser-review' ),
    6370            'manage_options',
    6471            'wiser-review',
     
    7481 */
    7582function wiserrw_api_settings_callback() {
    76     wp_enqueue_style( 'woocommerce_admin_styles' );
    77     wp_enqueue_script( 'wiserrw-admin-script' );
     83    $wiserrw_api_settings = get_option('wiserrw_api_settings', array());
     84    if (!wp_style_is('woocommerce_admin_styles', 'registered')) {
     85        return;
     86    }
     87    wp_enqueue_style('woocommerce_admin_styles');
     88    wp_enqueue_script('wiserrw-admin-script');
    7889    include_once WISERRW_PLUGIN_DIR . '/views/wiserw-plugin-settings.php';
    7990}
     
    106117
    107118function wiserrw_validate_api( $api_key ) {
    108     $wiserrw_api_data = get_option( 'wiserrw_api_data', false );
    109     $url  = 'https://api.wiserreview.com/api/woocommerce/verify?key=' . $api_key.'&wiserrw_hook_url='.get_site_url().'/wp-json/wiserrw/v1/reviews';
    110     $args = array(
    111         'method'    => 'GET',
    112         'timeout'   => 20
    113     );
    114     $request  = wp_remote_post( $url, $args );
    115     $response = json_decode( wp_remote_retrieve_body( $request ) );
    116     if (is_object($response) && isset($response->data) && isset($response->data->wsid)) {
    117         update_option( 'wiserrw_wsid', $response->data->wsid  );
    118         update_option( 'wiserrw_automation_id', $response->data->automation_id  );
    119     }
    120     return $response;
    121 }
     119    if ( empty( $api_key ) ) {
     120        return false;
     121    }
     122
     123    $url = 'https://api.wiserreview.com/api/woocommerce/verify?key=' . urlencode( $api_key ) .
     124           '&wiserrw_hook_url=' . urlencode( get_site_url() . '/wp-json/wiserrw/v1/reviews' ) .
     125           '&ver=' . urlencode( WISERRW_PLUGIN_VERSION );
     126
     127    $args = array(
     128        'method'  => 'GET', 
     129        'timeout' => 20,
     130    );
     131
     132    $response = wp_remote_get( $url, $args );
     133
     134    if ( is_wp_error( $response ) ) {
     135        error_log( 'API Verify Error: ' . $response->get_error_message() );
     136        return false;
     137    }
     138
     139    $body = json_decode( wp_remote_retrieve_body( $response ) );
     140
     141 
     142
     143    if ( is_object( $body ) && isset( $body->data ) && isset( $body->data->wsid ) ) {
     144        update_option( 'wiserrw_api_data', $body->data );
     145        update_option( 'wiserrw_wsid', $body->data->wsid );
     146        update_option( 'wiserrw_automation_id', $body->data->automation_id ?? '' );
     147    }
     148
     149    return $body;
     150}
     151
    122152
    123153/**
     
    191221 */
    192222function wiserrw_show_review_nudges_section() {
    193     $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
    194     if ( '1' === $wiserrw_api_settings['wiserrw_review_nudges_enabled'] ) {
    195         echo do_shortcode( '[wiserrw_product_nudges]' );
    196     }
     223   
     224   $wiserrw_api_settings = get_option('wiserrw_api_settings', array());
     225   
     226    // Check if the key exists and equals '1'
     227    if (isset($wiserrw_api_settings['wiserrw_review_nudges_enabled'])
     228        && '1' === $wiserrw_api_settings['wiserrw_review_nudges_enabled']) {
     229        echo do_shortcode('[wiserrw_product_nudges]');
     230    }
    197231}
    198232add_action( 'woocommerce_after_add_to_cart_form', 'wiserrw_show_review_nudges_section' );
     
    201235 * Shortcode for the Rating Count.
    202236 */
    203 function wiserrw_rating_count() {
     237function wiserrw_rating_count( $atts ) {
    204238    ob_start();
    205     $product_id = get_the_ID();
     239
     240    $atts = shortcode_atts(
     241        array(
     242            'id' => get_the_ID(),
     243        ),
     244        $atts,
     245        'wiserrw_rating_count'
     246    );
     247
     248    $product_id = intval( $atts['id'] );
     249    if ( ! $product_id ) {
     250        return ob_get_clean();
     251    }
     252
     253    // Get SKU directly (faster than wc_get_product)
     254    $sku = get_post_meta( $product_id, '_sku', true );
     255
     256    // Check settings
     257    $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
     258    if ( ! $wiserrw_api_settings || '1' !== ( $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ?? '' ) ) {
     259        return ob_get_clean();
     260    }
     261
     262    // API HTML
     263    $wiserrw_api_data = get_option( 'wiserrw_api_data' );
     264    $product_rating_count = $wiserrw_api_data->product_rating_count ?? '';
     265    $product_rating_count = preg_replace( '/data-pid=("|\')(.*?)\1/', 'data-pid="' . $product_id . '"', $product_rating_count );
     266    $product_rating_count = preg_replace( '/data-skuid=("|\')(.*?)\1/', 'data-skuid="' . $sku . '"', $product_rating_count );
     267
     268    // --- Multilingual (cache per request) ---
     269    static $lang_cache = array();
     270    if ( isset( $lang_cache[$product_id] ) ) {
     271        $opid_value   = $lang_cache[$product_id]['opid'];
     272        $current_lang = $lang_cache[$product_id]['lang'];
     273    } else {
     274        $original_pid = 0;
     275        $current_lang = '';
     276
     277        // WPML
     278        if ( has_filter( 'wpml_element_trid' ) ) {
     279            $trid         = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
     280            $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
     281            $current_lang = apply_filters( 'wpml_current_language', null );
     282
     283            if ( $translations && is_array( $translations ) ) {
     284                foreach ( $translations as $lang => $translation ) {
     285                    if ( ! empty( $translation->original ) ) {
     286                        $original_pid = (int) $translation->element_id;
     287                    }
     288                }
     289            }
     290        }
     291
     292        // Polylang
     293        if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
     294            $current_lang = pll_current_language();
     295            $default_lang = pll_default_language();
     296            if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     297                $original_pid = pll_get_post( $product_id, $default_lang );
     298            }
     299        }
     300
     301        // Fallback: current product if no original
     302        $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
     303
     304        // Cache result
     305        $lang_cache[$product_id] = array(
     306            'opid' => $opid_value,
     307            'lang' => $current_lang,
     308        );
     309    }
     310
     311    // Extract wrapper
     312    $wiser_open_tag = '';
     313    if ( preg_match( '/<div\b[^>]*>/i', $product_rating_count, $matches ) ) {
     314        $wiser_open_tag = $matches[0];
     315
     316        // Ensure opid
     317        if ( preg_match( '/data-opid=/', $wiser_open_tag ) ) {
     318            $wiser_open_tag = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $wiser_open_tag );
     319        } else {
     320            $wiser_open_tag = str_replace( '<div', '<div data-opid="' . esc_attr( $opid_value ) . '"', $wiser_open_tag );
     321        }
     322
     323        // Ensure lang
     324        if ( ! empty( $current_lang ) ) {
     325            if ( preg_match( '/data-lang=/', $wiser_open_tag ) ) {
     326                $wiser_open_tag = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $wiser_open_tag );
     327            } else {
     328                $wiser_open_tag = str_replace( '<div', '<div data-lang="' . esc_attr( $current_lang ) . '"', $wiser_open_tag );
     329            }
     330        }
     331    }
     332
     333    // Inner HTML: prefer post meta, patch if missing
     334    $meta_html = get_post_meta( $product_id, 'wiserrw_star_rating_html', true );
     335    if ( $meta_html ) {
     336        // Patch opid
     337        if ( preg_match( '/data-opid=/', $meta_html ) ) {
     338            $meta_html = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $meta_html );
     339        } else {
     340            $meta_html = preg_replace( '/<div\b/i', '<div data-opid="' . esc_attr( $opid_value ) . '"', $meta_html, 1 );
     341        }
     342
     343        // Patch lang
     344        if ( ! empty( $current_lang ) ) {
     345            if ( preg_match( '/data-lang=/', $meta_html ) ) {
     346                $meta_html = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $meta_html );
     347            } else {
     348                $meta_html = preg_replace( '/<div\b/i', '<div data-lang="' . esc_attr( $current_lang ) . '"', $meta_html, 1 );
     349            }
     350        }
     351
     352        $inner_html = "<!-- Loaded from post meta (patched) -->" . $meta_html;
     353    } else {
     354        $inner_html = "<!-- Loaded from API fallback -->" . $product_rating_count;
     355    }
     356
     357    // Output
     358    if ( $wiser_open_tag ) {
     359        echo $wiser_open_tag;
     360    }
     361    echo $inner_html;
     362    echo "</div>";
     363
     364    return ob_get_clean();
     365}
     366add_shortcode( 'wiserrw_rating_count', 'wiserrw_rating_count' );
     367
     368
     369
     370
     371/**
     372 * Shortcode for the product.
     373 */
     374
     375
     376
     377function wiserrw_product_review( $atts ) {
     378    ob_start();
     379
     380    $atts = shortcode_atts(
     381        array(
     382            'id' => get_the_ID(),
     383        ),
     384        $atts,
     385        'wiserrw_product_review'
     386    );
     387
     388    $product_id = intval( $atts['id'] );
     389    if ( ! $product_id ) {
     390        return ob_get_clean();
     391    }
     392
    206393    $product = wc_get_product( $product_id );
    207394    if ( ! $product ) {
    208         return;
    209     }
    210     $sku = $product->get_sku();
     395        return ob_get_clean();
     396    }
    211397
    212398    $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
    213     if ( $wiserrw_api_settings && '1' === $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ) {
    214        
    215         $wiserrw_api_data = get_option( 'wiserrw_api_data' );
    216         $product_rating_count_pid = preg_replace( "/data-pid='(.*?)'/", "data-pid='$product_id'", $wiserrw_api_data->product_rating_count );
    217         $product_rating_count = preg_replace( "/data-skuid='(.*?)'/", "data-skuid='$sku'", $product_rating_count_pid );
    218         $allowed_tags = array(
    219             'div' => array(
    220                 'data-type' => true,
    221                 'class' => true,
    222                 'data-platform' => true,
    223                 'data-pid' => true,
    224                 'data-skuid' => true,
    225             ),
    226         );
    227        
    228         $parent_html = '';
    229         if ( preg_match( '/<div\b[^>]*>/i', $product_rating_count, $matches ) ) {
    230             $wiser_open_tag = $matches[0];
    231         }
    232         $product_rating_count = get_post_meta( $product_id, 'wiserrw_star_rating_html', true );
    233 
    234         echo $wiser_open_tag;
    235         if( $product_rating_count ) {
    236            
    237             echo $product_rating_count;
    238            
    239         }
    240          echo "</div>";
    241         $output = ob_get_contents();
     399    if ( ! $wiserrw_api_settings || '1' !== ( $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ?? '' ) ) {
    242400        return ob_get_clean();
    243401    }
    244 }
    245 add_shortcode( 'wiserrw_rating_count', 'wiserrw_rating_count' );
    246 
    247 /**
    248  * Shortcode for the Rating Count.
    249  */
    250 function wiserrw_product_review() {
    251     ob_start();
    252     $product_id = get_the_ID();
    253     $product = wc_get_product( $product_id );
    254     if ( ! $product ) {
    255         return;
    256     }
    257     $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
    258     if ( $wiserrw_api_settings && '1' === $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ) {
    259        
    260         $sku = $product->get_sku();
    261         $wiserrw_api_data = get_option( 'wiserrw_api_data' );
    262         if ( is_user_logged_in() ) {
    263             $data_in_value = 'true';
     402
     403    $sku           = $product->get_sku();
     404    $data_login    = is_user_logged_in() ? 'true' : 'false';
     405    $wiserrw_api_data = get_option( 'wiserrw_api_data' );
     406
     407    // --- Multilingual (WPML / Polylang) ---
     408    $original_pid = 0;
     409    $current_lang = '';
     410
     411    // WPML
     412    if ( has_filter( 'wpml_element_trid' ) ) {
     413        $trid         = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
     414        $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
     415        $current_lang = apply_filters( 'wpml_current_language', null );
     416
     417        if ( $translations && is_array( $translations ) ) {
     418            foreach ( $translations as $lang => $translation ) {
     419                if ( ! empty( $translation->original ) ) {
     420                    $original_pid = (int) $translation->element_id;
     421                }
     422            }
     423        }
     424    }
     425
     426    // Polylang
     427    if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
     428        $current_lang = pll_current_language();
     429        $default_lang = pll_default_language();
     430        if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     431            $original_pid = pll_get_post( $product_id, $default_lang );
     432        }
     433    }
     434
     435    // Fallback: use current product if no original found
     436    $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
     437
     438    // --- Patch API HTML ---
     439    $product_review_section = $wiserrw_api_data->product_review_section ?? '';
     440
     441    // Ensure data-login
     442    if ( preg_match( '/data-login=/', $product_review_section ) ) {
     443        $product_review_section = preg_replace( '/data-lgin=("|\')(.*?)\1/', 'data-login="' . $data_login . '"', $product_review_section );
     444    } else {
     445        $product_review_section = preg_replace( '/<div\b/i', '<div data-lgin="' . esc_attr( $data_login ) . '"', $product_review_section, 1 );
     446    }
     447
     448    // Ensure pid
     449    $product_review_section = preg_replace( '/data-pid=("|\')(.*?)\1/', 'data-pid="' . $product_id . '"', $product_review_section );
     450
     451    // Ensure skuid
     452    $product_review_section = preg_replace( '/data-skuid=("|\')(.*?)\1/', 'data-skuid="' . $sku . '"', $product_review_section );
     453
     454    // Ensure opid
     455    if ( preg_match( '/data-opid=/', $product_review_section ) ) {
     456        $product_review_section = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $product_review_section );
     457    } else {
     458        $product_review_section = preg_replace( '/<div\b/i', '<div data-opid="' . esc_attr( $opid_value ) . '"', $product_review_section, 1 );
     459    }
     460
     461    // Ensure lang
     462    if ( ! empty( $current_lang ) ) {
     463        if ( preg_match( '/data-lang=/', $product_review_section ) ) {
     464            $product_review_section = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $product_review_section );
    264465        } else {
    265             $data_in_value = 'false';
    266         }
    267 
    268         // If data-login exists, replace it; otherwise, add it to the first tag
    269         if ( preg_match( "/data-lgin=['\"](.*?)['\"]/", $wiserrw_api_data->product_review_section ) ) {
    270             $product_review_section_loggedin = preg_replace(
    271                 "/data-lgin=['\"](.*?)['\"]/",
    272                 "data-lgin='{$data_in_value}'",
    273                 $wiserrw_api_data->product_review_section
    274             );
    275         } else {
    276             $product_review_section_loggedin = preg_replace(
    277                 '/<(\w+)/',
    278                 "<$1 data-lgin='{$data_in_value}'",
    279                 $wiserrw_api_data->product_review_section,
    280                 1 // only add to the first tag
    281             );
    282         }
    283         $product_review_section_pid = preg_replace( "/data-pid='(.*?)'/", "data-pid='$product_id'", $product_review_section_loggedin );
    284 
    285         $product_review_section = preg_replace( "/data-skuid='(.*?)'/", "data-skuid='$sku'", $product_review_section_pid );
    286        
    287         $product_rating_count = preg_replace( "/data-pid='(.*?)'/", "data-pid='$product_id'", $wiserrw_api_data->product_rating_count );
    288         $product_rating_count = preg_replace( "/data-skuid='(.*?)'/", "data-skuid='$sku'", $wiserrw_api_data->product_rating_count );
    289        
    290         $nudges_section = preg_replace( "/data-pid='(.*?)'/", "data-pid='$product_id'", $wiserrw_api_data->nudges_section );
    291         $nudges_section = preg_replace( "/data-skuid='(.*?)'/", "data-skuid='$sku'", $wiserrw_api_data->nudges_section );
    292        
    293         $allowed_tags = array(
    294             'div' => array(
    295                 'data-type' => true,
    296                 'class' => true,
    297                 'data-platform' => true,
    298                 'data-pid' => true,
    299                 'data-skuid' => true,
    300             ),
    301         );
    302         echo $product_review_section;
    303     }
    304     $output = ob_get_contents();
     466            $product_review_section = preg_replace( '/<div\b/i', '<div data-lang="' . esc_attr( $current_lang ) . '"', $product_review_section, 1 );
     467        }
     468    }
     469
     470    // Output
     471    echo $product_review_section;
     472
    305473    return ob_get_clean();
    306474}
     
    310478 * Shortcode for the Nudges.
    311479 */
    312 function wiserrw_product_nudges() {
     480function wiserrw_product_nudges( $atts = [] ) {
    313481    ob_start();
     482
    314483    $product_id = get_the_ID();
    315     $product = wc_get_product( $product_id );
     484    $product    = wc_get_product( $product_id );
    316485    if ( ! $product ) {
    317         return;
    318     }
     486        return ob_get_clean();
     487    }
     488
    319489    $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
    320     if ( $wiserrw_api_settings && '1' === $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ) {
    321         $wiserrw_api_data = get_option( 'wiserrw_api_data' );
    322         $sku = $product->get_sku();
    323        
    324         $wiserrw_api_data = get_option( 'wiserrw_api_data' );
    325     $nudges_section_pid = preg_replace( "/data-pid='(.*?)'/", "data-pid='$product_id'", $wiserrw_api_data->nudges_section );
    326     $nudges_section = preg_replace( "/data-skuid='(.*?)'/", "data-skuid='$sku'", $nudges_section_pid );
    327         $allowed_tags = array(
    328             'div' => array(
    329                 'data-type' => true,
    330                 'class' => true,
    331                 'data-platform' => true,
    332                 'data-pid' => true,
    333                 'data-skuid' => true,
    334             ),
    335         );
    336         echo $nudges_section;
    337     }
    338     $output = ob_get_contents();
     490    if ( ! $wiserrw_api_settings || '1' !== ( $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ?? '' ) ) {
     491        return ob_get_clean();
     492    }
     493
     494    $sku = $product->get_sku();
     495    $wiserrw_api_data = get_option( 'wiserrw_api_data' );
     496
     497    // --- Multilingual (WPML / Polylang) ---
     498    $original_pid = 0;
     499    $current_lang = '';
     500
     501    // WPML
     502    if ( has_filter( 'wpml_element_trid' ) ) {
     503        $trid         = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
     504        $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
     505        $current_lang = apply_filters( 'wpml_current_language', null );
     506
     507        if ( $translations && is_array( $translations ) ) {
     508            foreach ( $translations as $lang => $translation ) {
     509                if ( ! empty( $translation->original ) ) {
     510                    $original_pid = (int) $translation->element_id;
     511                }
     512            }
     513        }
     514    }
     515
     516    // Polylang
     517    if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
     518        $current_lang = pll_current_language();
     519        $default_lang = pll_default_language();
     520        if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     521            $original_pid = pll_get_post( $product_id, $default_lang );
     522        }
     523    }
     524
     525    // Fallback if no original found
     526    $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
     527
     528    // --- Patch API HTML ---
     529    $nudges_section = $wiserrw_api_data->nudges_section ?? '';
     530
     531    // Ensure pid
     532    $nudges_section = preg_replace( '/data-pid=("|\')(.*?)\1/', 'data-pid="' . $product_id . '"', $nudges_section );
     533
     534    // Ensure skuid
     535    $nudges_section = preg_replace( '/data-skuid=("|\')(.*?)\1/', 'data-skuid="' . $sku . '"', $nudges_section );
     536
     537    // Ensure opid
     538    if ( preg_match( '/data-opid=/', $nudges_section ) ) {
     539        $nudges_section = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $nudges_section );
     540    } else {
     541        $nudges_section = preg_replace( '/<div\b/i', '<div data-opid="' . esc_attr( $opid_value ) . '"', $nudges_section, 1 );
     542    }
     543
     544    // Ensure lang
     545    if ( ! empty( $current_lang ) ) {
     546        if ( preg_match( '/data-lang=/', $nudges_section ) ) {
     547            $nudges_section = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $nudges_section );
     548        } else {
     549            $nudges_section = preg_replace( '/<div\b/i', '<div data-lang="' . esc_attr( $current_lang ) . '"', $nudges_section, 1 );
     550        }
     551    }
     552
     553    // Output
     554    echo $nudges_section;
     555
    339556    return ob_get_clean();
    340557}
     
    365582    }
    366583
    367     $api_endpoint = 'https://api.wiserreview.com/api/woocommerce/webhook?wsid=' . $wsid . '&atmid=' . $automation_id;
     584    $api_endpoint = 'https://api.wiserreview.com/api/woocommerce/webhook?wsid=' . $wsid . '&atmid=' . $automation_id.'&ver='.WISERRW_PLUGIN_VERSION;
    368585
    369586    // Initialize arrays
     
    393610        }
    394611       
    395         $product_id = $item->get_product_id();
     612        $product_id    = $item->get_product_id();
    396613        $product_price = $product->get_price();
    397         $url = get_permalink($product_id);
    398         $product_name = $item->get_name();
    399        
     614        $url           = get_permalink($product_id);
     615        $product_name  = $item->get_name();
     616
     617        // --- Multilingual handling (Added for opid/lang) ---
     618        $original_pid = 0;
     619        $current_lang = '';
     620
     621        // WPML
     622        if ( has_filter( 'wpml_element_trid' ) ) {
     623            $trid         = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
     624            $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : [];
     625            $current_lang = apply_filters( 'wpml_current_language', null );
     626            if ( $translations && is_array( $translations ) ) {
     627                foreach ( $translations as $lang => $translation ) {
     628                    if ( ! empty( $translation->original ) ) {
     629                        $original_pid = (int) $translation->element_id;
     630                    }
     631                }
     632            }
     633        }
     634
     635        // Polylang
     636        if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
     637            $current_lang = pll_current_language();
     638            $default_lang = pll_default_language();
     639            if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     640                $original_pid = pll_get_post( $product_id, $default_lang );
     641            }
     642        }
     643
     644        // Fallback (if no translation plugin, opid = current product id)
     645        $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
     646
    400647        // Safely get product image
    401648        $image = wp_get_attachment_image_src(get_post_thumbnail_id($product_id), 'single-post-thumbnail');
     
    404651        // Create new product data array for each item
    405652        $product_data = array(
    406             'id' => $product_id,
    407             'pn' => $product_name,
    408             'name' => $product_name,
    409             'prc' => $product_price,
    410             'pu' => $url,
    411             'piu' => $image_url
     653            'id'    => $product_id,
     654            'pn'    => $product_name,
     655            'name'  => $product_name,
     656            'prc'   => $product_price,
     657            'pu'    => $url,
     658            'piu'   => $image_url,
     659            'opid'  => $opid_value,          // ✅ Added
     660            'lang'  => $current_lang ?: ''   // ✅ Added
    412661        );
    413662       
     
    427676            'httpversion' => '1.0',
    428677            'blocking' => true,
    429             'body' => wp_json_encode($wiserrw_order_data), // Use wp_json_encode for better error handling
     678            'body' => wp_json_encode($wiserrw_order_data),
    430679        )
    431680    );
     
    450699        }
    451700    }
    452 
    453 }
     701}
     702
    454703add_action( 'woocommerce_order_status_completed', 'wiserrw_woocommerce_order_status_completed' );
    455704
     
    583832    );
    584833}
     834
    585835add_action( 'wp_ajax_wiserrw_export_orders', 'wiserrw_export_orders' );
    586836
     
    589839 */
    590840function wiserrw_bulk_orders_send() {
    591     check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
     841
     842    check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
    592843
    593844    if ( ! current_user_can( 'manage_woocommerce' ) ) {
    594         wp_send_json_error( array( 'message' => 'You are not allowed to perform this action.' ), 403 );
    595     }
    596     $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
    597     $api_key = $wiserrw_api_settings['wiserrw_api_key'];
    598     $wsid = get_option( 'wiserrw_wsid', true );
     845        wp_send_json_error(
     846            array( 'message' => __( 'You are not allowed to perform this action.', 'wiser-review' ) ),
     847            403
     848        );
     849    }
     850
     851    $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
     852    $api_key       = $wiserrw_api_settings['wiserrw_api_key'];
     853    $wsid          = get_option( 'wiserrw_wsid', true );
    599854    $automation_id = get_option( 'wiserrw_automation_id', true );
    600     if (!isset($wsid) || !isset($automation_id)) {
    601         return; // Exit if invalid API data
    602     }
    603     $wiserrw_order_data = array();
    604     $wiserrw_line_items = array();
    605     $customer_data = array();
    606     $api_endpoint = 'https://api.wiserreview.com/api/woocommerce/webhook?wsid=' . $wsid . '&atmid=' . $automation_id;
    607 
    608     $from_date  = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
    609     $to_date    = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';
    610     $duration    = isset( $_POST['duration'] ) ? sanitize_text_field( wp_unslash( $_POST['duration'] ) ) : '';
    611     $offset     = isset( $_POST['offset'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['offset'] ) ) ) : '';
    612     $limit      = isset( $_POST['limit'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['limit'] ) ) ) : '';
    613 
    614     if ( '' !== $duration ) {
    615         switch ( $duration ) {
    616             case '30_days':
    617                 $date_from = strtotime( '-30 days' );
    618                 break;
    619             case '3_months':
    620                 $date_from = strtotime( '-3 months' );
    621                 break;
    622             case '6_months':
    623                 $date_from = strtotime( '-6 months' );
    624                 break;
    625             case '1_year':
    626                 $date_from = strtotime( '-1 year' );
    627                 break;
    628             case '7_days':
    629                 $date_from = strtotime( '-7 days' ); // Fixed: Now correctly gets 7 days of orders
    630                 break;
    631             default:
    632                 $date_from = strtotime( '-7 days' );
    633                 break;
    634         }
    635        
    636         // Get orders between date_from and now
    637         $date_to = current_time('timestamp');
    638         $all_orders = wc_get_orders(
    639             array(
    640                 'status'        => array( 'completed', 'processing' ),
    641                 'date_created'  => $date_from . '...' . $date_to,  // Using WooCommerce's date range format
    642                 'limit'         => -1,
    643             )
    644         );
    645     } else {
    646         $start_date = new WC_DateTime($from_date);
     855
     856    if ( !isset($wsid) || !isset($automation_id) ) {
     857        return; // Exit if invalid API data
     858    }
     859
     860    $wiserrw_order_data = array();
     861    $customer_data      = array();
     862    $api_endpoint       = 'https://api.wiserreview.com/api/woocommerce/webhook?wsid='
     863                           . $wsid . '&atmid=' . $automation_id . '&ver=' . WISERRW_PLUGIN_VERSION;
     864
     865    $from_date  = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
     866    $to_date    = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';
     867    $duration   = isset( $_POST['duration'] ) ? sanitize_text_field( wp_unslash( $_POST['duration'] ) ) : '';
     868    $offset     = isset( $_POST['offset'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['offset'] ) ) ) : '';
     869    $limit      = isset( $_POST['limit'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['limit'] ) ) ) : '';
     870
     871    if ( '' !== $duration ) {
     872        switch ( $duration ) {
     873            case '30_days':  $date_from = strtotime( '-30 days' ); break;
     874            case '3_months': $date_from = strtotime( '-3 months' ); break;
     875            case '6_months': $date_from = strtotime( '-6 months' ); break;
     876            case '1_year':   $date_from = strtotime( '-1 year' ); break;
     877            case '7_days':   $date_from = strtotime( '-7 days' ); break;
     878            default:         $date_from = strtotime( '-7 days' ); break;
     879        }
     880
     881        $date_to = current_time('timestamp');
     882        $all_orders = wc_get_orders(array(
     883            'status'       => array( 'completed', 'processing' ),
     884            'date_created' => $date_from . '...' . $date_to,
     885            'limit'        => -1,
     886        ));
     887
     888    } else {
     889        $start_date = new WC_DateTime($from_date);
    647890        $end_date   = new WC_DateTime($to_date);
    648         $all_orders = wc_get_orders(
    649             array(
    650                 'status'        => array( 'completed', 'processing' ),
    651                 'date_created'  => gmdate( $start_date . '...' . $end_date ),
    652                 'limit'         => -1,
    653             )
    654         );
    655     }
    656 
    657     $total = count( $all_orders );
    658     if ( $total === 0 ) {
    659         wp_send_json_success([
    660             'percent'  => 100,
    661             'done'     => true,
    662             'message'  => 'No orders found for selected range.',
    663         ]);
     891        $all_orders = wc_get_orders(array(
     892            'status'       => array( 'completed', 'processing' ),
     893            'date_created' => $start_date->getTimestamp() . '...' . $end_date->getTimestamp(),
     894            'limit'        => -1,
     895        ));
     896    }
     897
     898    $total = count( $all_orders );
     899    if ( $total === 0 ) {
     900        wp_send_json_success(array(
     901            'percent' => 100,
     902            'done'    => true,
     903            'message' => 'No orders found for selected range.',
     904        ));
    664905        wp_die();
    665906    }
    666907
    667     if ( $total > 0 ) {
    668         $chunk_ids = array_slice( $all_orders, $offset, $limit );
    669         $upload_dir = wp_upload_dir();
    670         $filename = 'orders-export-progress.csv';
    671         $filepath = $upload_dir['basedir'] . '/' . $filename;
    672         $is_first_chunk = 0 === $offset;
    673 
    674         foreach ( $chunk_ids as $order ) {
    675             // Skip if this is a refund
    676             if ($order instanceof WC_Order_Refund) {
    677                 continue;
    678             }
    679             $order_id = $order->get_id();
    680 
    681                      
    682             /* Order Details */
    683             $wiserrw_order_data['oid'] = $order->get_id();
    684             $wiserrw_order_data['cdt'] = $order->get_date_created();
    685             $wiserrw_order_data['cdt'] = $order->get_date_created();
    686 
    687             /* Customer Details */
    688             $customer_data['email'] = $order->get_billing_email();
    689             $customer_data['phone'] = $order->get_billing_phone();
    690             $customer_data['first_name'] = $order->get_billing_first_name();
    691             $customer_data['last_name'] = $order->get_billing_last_name();
    692             $wiserrw_order_data['customer'] = $customer_data;
    693 
    694             /* Product Details */
    695             $wiserrw_order_data['line_items'] = array(); // Clear line items for new order
    696             $items = $order->get_items();
    697             foreach ( $items as $item ) {
    698                 $product = $item->get_product();
    699                 if (!$product || !$product instanceof WC_Product) {
    700                     continue; // Skip if product doesn't exist anymore
    701                 }
    702                 $product_id = $item->get_product_id();
    703                 $product_price = $product->get_price();
    704                 $url = get_permalink($product_id);
    705                 $product_name = $item->get_name();
    706                 $image = wp_get_attachment_image_src(get_post_thumbnail_id($product_id), 'single-post-thumbnail');
    707                 $image_url = is_array($image) ? $image[0] : '';
    708                
    709                 $product_data = array( // Initialize new array for each product
    710                     'id' => $product_id,
    711                     'pn' => $product_name,
    712                     'name' => $product_name,
    713                     'prc' => $product_price,
    714                     'pu' => $url,
    715                     'piu' => $image_url
    716                 );
    717                 $wiserrw_order_data['line_items'][] = $product_data;
    718             }
    719 
    720             $response = wp_remote_post(
    721                 $api_endpoint,
    722                 array(
    723                     'method' => 'POST',
    724                     'headers'   => array(
    725                         'Content-Type' => 'application/json',
    726                     ),
    727                     'timeout' => 45,
    728                     'redirection' => 5,
    729                     'httpversion' => '1.0',
    730                     'blocking' => true,
    731                     'body' => json_encode( $wiserrw_order_data ),
    732                 )
    733             );
    734             if ( is_wp_error( $response ) ) {
    735                 $error_message = $response->get_error_message();
    736                 error_log(sprintf(
    737                     'WiserReview Bulk Send API Error (Order #%d): %s',
    738                     $order_id,
    739                     $error_message
    740                 ));
    741                 $api_error_messages[] = $error_message;
    742             } else {
    743                 $response_code = wp_remote_retrieve_response_code($response);
    744                 if ($response_code !== 200) {
    745                     $error_message = wp_remote_retrieve_body($response);
    746                     error_log(sprintf(
    747                         'WiserReview Bulk Send API Non-200 Response (Order #%d): Code %d - %s',
    748                         $order_id,
    749                         $response_code,
    750                         $error_message
    751                     ));
    752                     $api_error_messages[] = $error_message;
    753                 }
    754             }
    755         }
    756 
    757         $processed = $offset + count( $chunk_ids );
    758         $percent = min(round( ( $processed / $total ) * 100 ), 100); // Ensure we don't exceed 100%
    759         $is_done = $processed >= $total;
    760 
    761         $response_data = array(
    762             'percent'  => $percent,
    763             'done'     => $is_done,
    764             'total_processed' => $is_done ? $total : $processed,
    765             'total_orders' => $total,
    766             'processed_chunk' => count($chunk_ids),
    767             'api_error_messages' => $api_error_messages
    768         );
    769 
    770         wp_send_json_success($response_data);
    771     }
    772     die();
    773 }
     908    if ( $total > 0 ) {
     909        $chunk_ids    = array_slice( $all_orders, $offset, $limit );
     910        $api_errors   = array();
     911
     912        foreach ( $chunk_ids as $order ) {
     913            if ( $order instanceof WC_Order_Refund ) {
     914                continue; // skip refunds
     915            }
     916
     917            $order_id = $order->get_id();
     918
     919            /* Order Details */
     920            $wiserrw_order_data['oid'] = $order_id;
     921            $wiserrw_order_data['cdt'] = $order->get_date_created()
     922                ? $order->get_date_created()->format('Y-m-d H:i:s') // ✅ Clean date format
     923                : current_time('mysql');
     924
     925            /* Customer Details */
     926            $customer_data = array(
     927                'email'      => $order->get_billing_email(),
     928                'phone'      => $order->get_billing_phone(),
     929                'first_name' => $order->get_billing_first_name(),
     930                'last_name'  => $order->get_billing_last_name(),
     931            );
     932            $wiserrw_order_data['customer'] = $customer_data;
     933
     934            /* Product Details */
     935            $wiserrw_order_data['line_items'] = array();
     936            foreach ( $order->get_items() as $item ) {
     937                $product = $item->get_product();
     938                if ( !$product || !$product instanceof WC_Product ) {
     939                    continue;
     940                }
     941
     942                $product_id    = $item->get_product_id();
     943                $product_name  = $item->get_name();
     944                $product_price = $product->get_price();
     945                $url           = get_permalink($product_id);
     946                $image         = wp_get_attachment_image_src(get_post_thumbnail_id($product_id), 'single-post-thumbnail');
     947                $image_url     = is_array($image) ? $image[0] : '';
     948
     949                // --- Multilingual handling ---
     950                $original_pid = 0;
     951                $current_lang = '';
     952
     953                // WPML
     954                if ( has_filter( 'wpml_element_trid' ) ) {
     955                    $trid         = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
     956                    $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : [];
     957                    $current_lang = apply_filters( 'wpml_current_language', null );
     958                    if ( $translations && is_array($translations) ) {
     959                        foreach ( $translations as $lang => $translation ) {
     960                            if ( ! empty( $translation->original ) ) {
     961                                $original_pid = (int) $translation->element_id;
     962                            }
     963                        }
     964                    }
     965                }
     966
     967                // Polylang
     968                if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
     969                    $current_lang = pll_current_language();
     970                    $default_lang = pll_default_language();
     971                    if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     972                        $original_pid = pll_get_post( $product_id, $default_lang );
     973                    }
     974                }
     975
     976                // Fallback
     977                $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
     978
     979                $product_data = array(
     980                    'id'   => $product_id,
     981                    'pn'   => $product_name,
     982                    'name' => $product_name,
     983                    'prc'  => $product_price,
     984                    'pu'   => $url,
     985                    'piu'  => $image_url,
     986                    'opid' => $opid_value,        // ✅ Added
     987                    'lang' => $current_lang ?: '' // ✅ Added
     988                );
     989
     990                $wiserrw_order_data['line_items'][] = $product_data;
     991            }
     992
     993            // Send to API
     994            $response = wp_remote_post($api_endpoint, array(
     995                'method'      => 'POST',
     996                'headers'     => array('Content-Type' => 'application/json'),
     997                'timeout'     => 45,
     998                'blocking'    => true,
     999                'body'        => wp_json_encode($wiserrw_order_data),
     1000            ));
     1001
     1002            if ( is_wp_error($response) ) {
     1003                $api_errors[] = $response->get_error_message();
     1004            } else {
     1005                $code = wp_remote_retrieve_response_code($response);
     1006                if ( $code !== 200 ) {
     1007                    $api_errors[] = wp_remote_retrieve_body($response);
     1008                }
     1009            }
     1010        }
     1011
     1012        $processed = $offset + count( $chunk_ids );
     1013        $percent   = min(round( ( $processed / $total ) * 100 ), 100);
     1014        $is_done   = $processed >= $total;
     1015
     1016        wp_send_json_success(array(
     1017            'percent'          => $percent,
     1018            'done'             => $is_done,
     1019            'total_processed'  => $is_done ? $total : $processed,
     1020            'total_orders'     => $total,
     1021            'processed_chunk'  => count($chunk_ids),
     1022            'api_error_messages' => $api_errors
     1023        ));
     1024    }
     1025
     1026    die();
     1027}
     1028
    7741029add_action( 'wp_ajax_wiserrw_bulk_orders_send', 'wiserrw_bulk_orders_send' );
    7751030
     
    7801035    check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
    7811036    if ( ! current_user_can( 'manage_woocommerce' ) ) {
    782         wp_send_json_error( array( 'message' => 'You are not allowed to perform this action.' ), 403 );
     1037        wp_send_json_error( array( 'message' => __('You are not allowed to perform this action.', 'wiser-review' ) ), 403 );
    7831038    }
    7841039    $duration  = isset( $_POST['duration'] ) ? sanitize_text_field( wp_unslash( $_POST['duration'] ) ) : '';
     
    8661121function wiserrw_sync_products() {
    8671122    $wiserrw_api_settings   = get_option( 'wiserrw_api_settings' );
    868     $full_product_synced    = get_option( 'wiserrw_all_products_synced' );
    8691123    $api_host               = constant( 'WISERRW_API_HOST' );
    8701124    $per_page               = isset( $_REQUEST['per_page'] ) ? intval( $_REQUEST['per_page'] ) : 50;
     
    8771131    }
    8781132
     1133    // Pagination setup
    8791134    if ( isset( $_REQUEST['total_pages'] ) ) {
    8801135        $total_pages = (int) sanitize_text_field( wp_unslash( $_REQUEST['total_pages'] ) );
     
    8901145        $message       = "Synced $page / $total_pages pages";
    8911146        $products      = wiserrw_get_products( $page, $per_page );
    892         $products_json = array();
     1147        $products_json = [];
    8931148
    8941149        foreach ( $products as $prod ) {
     
    8991154            }
    9001155
    901             $arrsku = array();
    902             $pr     = wc_get_product( $p_id );
     1156            $pr = wc_get_product( $p_id );
    9031157            if ( ! $pr ) {
    9041158                continue;
    9051159            }
    9061160
    907             // Collect SKUs similar to your original
     1161            // Collect SKUs
     1162            $arrsku = [];
    9081163            if ( $pr->is_type( 'variable' ) ) {
    9091164                $prod_variation = new WC_Product_Variable( $p_id );
     
    9211176            }
    9221177
    923             // Representative barcode (same idea you used)
     1178            // Representative barcode
    9241179            $gtin = get_post_meta( $p_id, 'hwp_product_gtin', true )
    9251180                ?: $pr->get_attribute( 'GTIN' )
     
    9281183                ?: get_post_meta( $p_id, '_global_unique_id', true );
    9291184
    930             // Title and URL fallbacks
     1185            // Title and URL
    9311186            $title = $prod['title'];
    9321187            if ( empty( $title ) ) {
     
    9461201            }
    9471202
    948             // Image URL with placeholder fallback
     1203            // Image URL
    9491204            $img_src = wp_get_attachment_image_src( get_post_thumbnail_id( $p_id ), 'shop_single' );
    9501205            $img_url = ( is_array( $img_src ) && isset( $img_src[0] ) ) ? $img_src[0] : wc_placeholder_img_src( 'shop_single' );
    9511206
    952             $product_json = array(
    953                 'pid'      => $p_id,
    954                 'arrsku'   => $arrsku,
    955                 'shp'      => get_site_url(),
    956                 'wsid'     => $wsid,
    957                 'pn'       => $title,
    958                 'piu'      => esc_url( $img_url ),
    959                 'pu'       => esc_url( $product_url ),
    960                 'barcode'  => (string) $gtin,
    961             );
     1207            // --- Multilingual handling (product-specific) ---
     1208            $original_pid = 0;
     1209            $current_lang = '';
     1210
     1211            // WPML
     1212            if ( has_filter( 'wpml_element_trid' ) ) {
     1213                $trid         = apply_filters( 'wpml_element_trid', null, $p_id, 'post_product' );
     1214                $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : [];
     1215                if ( $translations && is_array( $translations ) ) {
     1216                    foreach ( $translations as $lang => $translation ) {
     1217                        if ( ! empty( $translation->original ) ) {
     1218                            $original_pid = (int) $translation->element_id;
     1219                        }
     1220                        if ( (int) $translation->element_id === $p_id ) {
     1221                            $current_lang = $lang; // ✅ product-specific language
     1222                        }
     1223                    }
     1224                }
     1225            }
     1226
     1227            // Polylang
     1228            if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_get_post_language' ) ) {
     1229                $current_lang = pll_get_post_language( $p_id ); // ✅ product-specific language
     1230                $default_lang = function_exists( 'pll_default_language' ) ? pll_default_language() : '';
     1231                if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     1232                    $original_pid = pll_get_post( $p_id, $default_lang );
     1233                }
     1234            }
     1235
     1236            // Fallback
     1237            $opid_value = ( $original_pid && $original_pid !== $p_id ) ? $original_pid : $p_id;
     1238
     1239            // Build product payload
     1240            $product_json = [
     1241                'pid'     => $p_id,
     1242                'arrsku'  => $arrsku,
     1243                'shp'     => get_site_url(),
     1244                'wsid'    => $wsid,
     1245                'pn'      => $title,
     1246                'piu'     => esc_url( $img_url ),
     1247                'pu'      => esc_url( $product_url ),
     1248                'barcode' => (string) $gtin,
     1249                'opid'    => $opid_value,        // ✅ Added
     1250                'lang'    => $current_lang ?: '' // ✅ Fixed: per-product lang
     1251            ];
    9621252
    9631253            $products_json[] = $product_json;
    9641254
    965             // Mark as registered (like your original)
     1255            // Mark as registered
    9661256            update_post_meta( $p_id, '_wiserrw_product_registered', '1' );
    9671257        }
    9681258
    9691259        if ( ! empty( $products_json ) ) {
    970             $data = array(
     1260            $data = [
    9711261                'method'   => 'POST',
    9721262                'blocking' => true,
    973                 'headers'  => array( 'Content-Type' => 'application/json' ),
    974                 'body'     => wp_json_encode( array( 'products' => $products_json ) ),
    975             );
     1263                'headers'  => [ 'Content-Type' => 'application/json' ],
     1264                'body'     => wp_json_encode( [
     1265                    'products' => $products_json,
     1266                    'ver'      => WISERRW_PLUGIN_VERSION // ✅ Added once at root level
     1267                ] ),
     1268            ];
    9761269            $url      = $api_host . 'productWebhook?wsid=' . $wsid . '&key=' . $api_key;
    9771270            $response = wp_safe_remote_post( $url, $data );
    978 
    979             // (Silent on errors, like your original; you can log if you want)
    9801271        }
    9811272    } else {
     
    9891280
    9901281    $page++;
    991     $result = array(
     1282    $result = [
    9921283        'next_page'   => $page,
    9931284        'total_pages' => $total_pages,
    9941285        'message'     => $message,
    9951286        'finished'    => $finished,
    996     );
     1287    ];
    9971288
    9981289    echo wp_json_encode( $result );
     
    10001291}
    10011292
     1293
    10021294add_action( 'wp_ajax_wiserrw_sync_products', 'wiserrw_sync_products' );
    10031295add_action( 'wp_ajax_nopriv_wiserrw_sync_products', 'wiserrw_sync_products' );
     
    10091301    check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
    10101302    if ( ! current_user_can( 'manage_woocommerce' ) ) {
    1011         wp_send_json_error( array( 'message' => 'You are not allowed to perform this action.' ), 403 );
     1303        wp_send_json_error( array( 'message' => __('You are not allowed to perform this action.','wiser-review' ) ), 403 );
    10121304    }
    10131305    // Clear only the "registered" flag for all products
     
    10211313    // JSON response (unchanged style)
    10221314    $result = array(
    1023         'message' => 'All Products Sync Status Cleared',
     1315        'message' => __( 'All Products Sync Status Cleared', 'wiser-review' ),
    10241316    );
    10251317    echo wp_json_encode( $result );
     
    10611353add_action( 'woocommerce_after_product_object_save', 'wiserrw_product_update_hook', 99, 1 );
    10621354
    1063 
    10641355function wiserrw_product_update_hook( $product ) {
    1065     if ( ! $product instanceof WC_Product ) {
    1066         return;
    1067     }
    1068 
    1069     $p_id = $product->get_id();
    1070 
    1071     // Skip autosaves / revisions
    1072     if ( wp_is_post_autosave( $p_id ) || wp_is_post_revision( $p_id ) ) {
    1073         return;
    1074     }
    1075 
    1076     // Skip background cron or ajax jobs (vendor imports often run this way)
    1077     if ( ( defined( 'DOING_CRON' ) && DOING_CRON ) || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) ) {
    1078         return;
    1079     }
    1080 
    1081     $is_variation = $product instanceof WC_Product_Variation;
    1082     $parent_id    = $is_variation ? $product->get_parent_id() : $p_id;
    1083 
    1084     // --- Watched fields ---
    1085 
    1086     // Name
    1087     $name = (string) $product->get_name();
    1088 
    1089     // Permalink (normalize: strip trailing slash)
    1090     $permalink = untrailingslashit( (string) get_permalink( $is_variation ? $parent_id : $p_id ) );
    1091 
    1092     // Image ID (normalize to string "0" if none)
    1093     $effective_image_id = (int) $product->get_image_id();
    1094     if ( ! $effective_image_id ) {
    1095         $effective_image_id = (int) get_post_thumbnail_id( $is_variation ? $parent_id : $p_id );
    1096     }
    1097     $image_id = (string) ( $effective_image_id ?: 0 );
    1098 
    1099     // SKU fingerprint
    1100     if ( $product->is_type( 'variable' ) && ! $is_variation ) {
    1101         $child_skus = array();
    1102         foreach ( $product->get_children() as $vid ) {
    1103             $vsku = strtolower( trim( (string) get_post_meta( $vid, '_sku', true ) ) );
    1104             if ( $vsku !== '' ) {
    1105                 $child_skus[] = $vsku;
    1106             }
    1107         }
    1108         sort( $child_skus, SORT_NATURAL | SORT_FLAG_CASE );
    1109         $sku_fingerprint = implode( ',', $child_skus );
    1110     } else {
    1111         $sku_fingerprint = strtolower( trim( (string) $product->get_sku() ) );
    1112     }
    1113 
    1114     // Barcode / GTIN (normalize)
    1115     if ( $is_variation ) {
    1116         $barcode = get_post_meta( $p_id, 'hwp_var_gtin', true )
    1117             ?: get_post_meta( $parent_id, 'hwp_product_gtin', true )
    1118             ?: $product->get_attribute( 'GTIN' )
    1119             ?: $product->get_attribute( 'EAN' )
    1120             ?: $product->get_attribute( 'ISBN' )
    1121             ?: get_post_meta( $p_id, '_global_unique_id', true );
    1122     } else {
    1123         $barcode = get_post_meta( $p_id, 'hwp_product_gtin', true )
    1124             ?: $product->get_attribute( 'GTIN' )
    1125             ?: $product->get_attribute( 'EAN' )
    1126             ?: $product->get_attribute( 'ISBN' )
    1127             ?: get_post_meta( $p_id, '_global_unique_id', true );
    1128     }
    1129     $barcode = strtolower( trim( (string) $barcode ) );
    1130 
    1131     // Build watch hash
    1132     $new_hash = md5( $name . '|' . $permalink . '|' . $image_id . '|' . $sku_fingerprint . '|' . $barcode );
    1133     $old_hash = (string) get_post_meta( $p_id, '_wiserrw_watch_hash', true );
    1134 
    1135     // If nothing changed, exit
    1136     if ( $new_hash === $old_hash ) {
    1137         return;
    1138     }
    1139 
    1140     // --- Prepare payload ---
    1141 
    1142     $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
    1143     if ( empty( $wiserrw_api_settings['wiserrw_api_key'] ) ) {
    1144         return;
    1145     }
    1146     $api_host      = constant( 'WISERRW_API_HOST' );
    1147     $api_key       = $wiserrw_api_settings['wiserrw_api_key'];
    1148     $wsid          = get_option( 'wiserrw_wsid', true );
    1149     $automation_id = get_option( 'wiserrw_automation_id', true );
    1150     if ( ! $wsid || ! $automation_id ) {
    1151         return;
    1152     }
    1153 
    1154     // Build arrsku
    1155     $arrsku = array();
    1156     if ( $product->is_type( 'variable' ) && ! $is_variation ) {
    1157         foreach ( $product->get_children() as $vid ) {
    1158             $vsku = strtolower( trim( (string) get_post_meta( $vid, '_sku', true ) ) );
    1159             if ( $vsku !== '' ) {
    1160                 $arrsku[] = $vsku;
    1161             }
    1162         }
    1163     } else {
    1164         if ( $sku_fingerprint !== '' ) {
    1165             $arrsku[] = $sku_fingerprint;
    1166         }
    1167     }
    1168 
    1169     // Image URL (fallback to placeholder)
    1170     $thumb_id = $effective_image_id;
    1171     $img_src  = $thumb_id ? wp_get_attachment_image_src( $thumb_id, 'shop_single' ) : false;
    1172     $img_url  = ( is_array( $img_src ) && isset( $img_src[0] ) ) ? $img_src[0] : wc_placeholder_img_src( 'shop_single' );
    1173 
    1174     $product_json = array(
    1175         'pid'     => (string) $p_id,
    1176         'arrsku'  => $arrsku,
    1177         'shp'     => get_site_url(),
    1178         'wsid'    => $wsid,
    1179         'pn'      => $name ?: get_the_title( $parent_id ),
    1180         'piu'     => $img_url,
    1181         'pu'      => $permalink,
    1182         'barcode' => $barcode,
    1183     );
    1184 
    1185     $args = array(
    1186         'method'   => 'POST',
    1187         'blocking' => true,
    1188         'headers'  => array( 'Content-Type' => 'application/json' ),
    1189         'body'     => wp_json_encode( array( 'products' => array( $product_json ) ) ),
    1190     );
    1191 
    1192     // Clear flag before sending
    1193     delete_post_meta( $p_id, '_wiserrw_product_registered' );
    1194 
    1195     $response = wp_safe_remote_post( $api_host . 'productWebhook?wsid=' . $wsid . '&key=' . $api_key, $args );
    1196     if ( is_wp_error( $response ) ) {
    1197         return;
    1198     }
    1199 
    1200     if ( (int) wp_remote_retrieve_response_code( $response ) === 200 ) {
    1201         update_post_meta( $p_id, '_wiserrw_product_registered', '1' );
    1202         update_post_meta( $p_id, '_wiserrw_watch_hash', $new_hash );
    1203     }
    1204 }
    1205 
    1206 
    1207 // -------------------- Custom REST API Endpoints -------------------- //
     1356    if ( ! $product instanceof WC_Product ) {
     1357        return;
     1358    }
     1359
     1360    $p_id = $product->get_id();
     1361
     1362    // Skip autosaves / revisions
     1363    if ( wp_is_post_autosave( $p_id ) || wp_is_post_revision( $p_id ) ) {
     1364        return;
     1365    }
     1366
     1367    $is_variation = $product instanceof WC_Product_Variation;
     1368    $parent_id    = $is_variation ? $product->get_parent_id() : $p_id;
     1369
     1370    // --- Current values we watch ---
     1371    $name      = (string) $product->get_name();
     1372    $permalink = (string) get_permalink( $is_variation ? $parent_id : $p_id );
     1373
     1374    // Effective image id (variation image, else parent/simple image)
     1375    $effective_image_id = (int) $product->get_image_id();
     1376    if ( ! $effective_image_id ) {
     1377        $effective_image_id = (int) get_post_thumbnail_id( $is_variation ? $parent_id : $p_id );
     1378    }
     1379    $image_id = (string) $effective_image_id;
     1380
     1381    // SKU fingerprint
     1382    if ( $product->is_type( 'variable' ) && ! $is_variation ) {
     1383        $child_skus = array();
     1384        foreach ( $product->get_children() as $vid ) {
     1385            $vsku = (string) get_post_meta( $vid, '_sku', true );
     1386            if ( $vsku !== '' ) {
     1387                $child_skus[] = $vsku;
     1388            }
     1389        }
     1390        sort( $child_skus, SORT_NATURAL | SORT_FLAG_CASE );
     1391        $sku_fingerprint = implode( ',', $child_skus );
     1392    } else {
     1393        $sku_fingerprint = (string) $product->get_sku();
     1394    }
     1395
     1396    // Barcode
     1397    if ( $is_variation ) {
     1398        $barcode = get_post_meta( $p_id, 'hwp_var_gtin', true )
     1399            ?: get_post_meta( $parent_id, 'hwp_product_gtin', true )
     1400            ?: $product->get_attribute( 'GTIN' )
     1401            ?: $product->get_attribute( 'EAN' )
     1402            ?: $product->get_attribute( 'ISBN' )
     1403            ?: get_post_meta( $p_id, '_global_unique_id', true );
     1404    } else {
     1405        $barcode = get_post_meta( $p_id, 'hwp_product_gtin', true )
     1406            ?: $product->get_attribute( 'GTIN' )
     1407            ?: $product->get_attribute( 'EAN' )
     1408            ?: $product->get_attribute( 'ISBN' )
     1409            ?: get_post_meta( $p_id, '_global_unique_id', true );
     1410    }
     1411    $barcode = (string) $barcode;
     1412
     1413    // Watch hash
     1414    $new_hash = md5( $name . '|' . $permalink . '|' . $image_id . '|' . $sku_fingerprint . '|' . $barcode );
     1415    $old_hash = (string) get_post_meta( $p_id, '_wiserrw_watch_hash', true );
     1416    if ( $new_hash === $old_hash ) {
     1417        return;
     1418    }
     1419
     1420    // --- API settings ---
     1421    $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
     1422    if ( empty( $wiserrw_api_settings['wiserrw_api_key'] ) ) {
     1423        return;
     1424    }
     1425    $api_host      = constant( 'WISERRW_API_HOST' );
     1426    $api_key       = $wiserrw_api_settings['wiserrw_api_key'];
     1427    $wsid          = get_option( 'wiserrw_wsid', true );
     1428    $automation_id = get_option( 'wiserrw_automation_id', true );
     1429    if ( ! $wsid || ! $automation_id ) {
     1430        return;
     1431    }
     1432
     1433    // arrsku
     1434    $arrsku = array();
     1435    if ( $product->is_type( 'variable' ) && ! $is_variation ) {
     1436        foreach ( $product->get_children() as $vid ) {
     1437            $vsku = (string) get_post_meta( $vid, '_sku', true );
     1438            if ( $vsku !== '' ) {
     1439                $arrsku[] = $vsku;
     1440            }
     1441        }
     1442    } else {
     1443        if ( $sku_fingerprint !== '' ) {
     1444            $arrsku[] = $sku_fingerprint;
     1445        }
     1446    }
     1447
     1448    // Image URL
     1449    $thumb_id  = $effective_image_id;
     1450    $img_src   = $thumb_id ? wp_get_attachment_image_src( $thumb_id, 'shop_single' ) : false;
     1451    $img_url   = ( is_array( $img_src ) && isset( $img_src[0] ) ) ? $img_src[0] : wc_placeholder_img_src( 'shop_single' );
     1452
     1453    // --- Multilingual handling (Added) ---
     1454    $original_pid = 0;
     1455    $current_lang = '';
     1456
     1457    // WPML
     1458    if ( has_filter( 'wpml_element_trid' ) ) {
     1459        $trid         = apply_filters( 'wpml_element_trid', null, $p_id, 'post_product' );
     1460        $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
     1461        if ( $translations && is_array( $translations ) ) {
     1462            foreach ( $translations as $lang => $translation ) {
     1463                if ( ! empty( $translation->original ) ) {
     1464                    $original_pid = (int) $translation->element_id;
     1465                }
     1466                if ( (int) $translation->element_id === $p_id ) {
     1467                    $current_lang = $lang;
     1468                }
     1469            }
     1470        }
     1471    }
     1472
     1473    // Polylang
     1474    if ( function_exists( 'pll_get_post_language' ) ) {
     1475        $current_lang = pll_get_post_language( $p_id );
     1476        if ( function_exists( 'pll_default_language' ) ) {
     1477            $default_lang = pll_default_language();
     1478            if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     1479                $original_pid = pll_get_post( $p_id, $default_lang );
     1480            }
     1481        }
     1482    }
     1483
     1484    // Fallbacks
     1485    $opid_value = ( $original_pid && $original_pid !== $p_id ) ? $original_pid : $p_id;
     1486
     1487    // --- Build product payload ---
     1488    $product_json = array(
     1489        'pid'     => (string) $p_id,
     1490        'arrsku'  => $arrsku,
     1491        'shp'     => get_site_url(),
     1492        'wsid'    => $wsid,
     1493        'pn'      => $name ?: get_the_title( $parent_id ),
     1494        'piu'     => $img_url,
     1495        'pu'      => $permalink,
     1496        'barcode' => $barcode,
     1497        'opid'    => $opid_value,        // ✅ Added
     1498        'lang'    => $current_lang ?: '' // ✅ Added
     1499    );
     1500
     1501    $args = array(
     1502        'method'   => 'POST',
     1503        'blocking' => true,
     1504        'headers'  => array( 'Content-Type' => 'application/json' ),
     1505        'body'     => wp_json_encode( array(
     1506             'products' => array( $product_json ),
     1507             'ver'      => WISERRW_PLUGIN_VERSION
     1508             ) ),
     1509    );
     1510
     1511    // Clear flag before send
     1512    delete_post_meta( $p_id, '_wiserrw_product_registered' );
     1513
     1514    $response = wp_safe_remote_post( $api_host . 'productWebhook?wsid=' . $wsid . '&key=' . $api_key, $args );
     1515    if ( is_wp_error( $response ) ) {
     1516        return;
     1517    }
     1518
     1519    if ( (int) wp_remote_retrieve_response_code( $response ) === 200 ) {
     1520        update_post_meta( $p_id, '_wiserrw_product_registered', '1' );
     1521        update_post_meta( $p_id, '_wiserrw_watch_hash', $new_hash );
     1522    }
     1523}
     1524
     1525
    12081526
    12091527function wiserrw_custom_endpoints() {
     
    12641582
    12651583
     1584// === MAIN API ENDPOINT (returns instantly) ===
    12661585function wiserrw_api_callback( WP_REST_Request $request ) {
    12671586
    12681587    $payload = array(
    1269         'status'  => 'ok',
    1270         'method'  => $request->get_method(),
    1271         'time'    => current_time( 'mysql', true ),
    1272         'data'    => $request->get_json_params() ?: $request->get_params(),
    1273     );
    1274     do_action( 'wiserrw_endpoint_called', $request );
    1275     return new WP_REST_Response( $payload, 200 );
    1276 }
    1277 
    1278 
     1588        'status'  => 'ok',
     1589        'method'  => $request->get_method(),
     1590        'time'    => current_time( 'mysql', true ),
     1591        'data'    => $request->get_json_params() ?: $request->get_params(),
     1592    );
     1593
     1594    // Fire background call to self (non-blocking)
     1595    wp_remote_post(
     1596        rest_url( 'wiserreview/v1/async' ),
     1597        array(
     1598            'blocking' => false,
     1599            'timeout'  => 3,
     1600            'body'     => array(
     1601                'json_data' => wp_json_encode( $payload ),
     1602            ),
     1603        )
     1604    );
     1605
     1606    // Return immediately
     1607    return new WP_REST_Response( $payload, 200 );
     1608}
     1609add_action( 'rest_api_init', function() {
     1610    register_rest_route( 'wiserreview/v1', '/api', array(
     1611        'methods'             => 'POST',
     1612        'callback'            => 'wiserrw_api_callback',
     1613        'permission_callback' => '__return_true',
     1614    ));
     1615});
     1616
     1617// === ASYNC BACKGROUND HANDLER ===
     1618add_action( 'rest_api_init', function() {
     1619    register_rest_route( 'wiserreview/v1', '/async', array(
     1620        'methods'             => 'POST',
     1621        'callback'            => 'wiserrw_async_handler',
     1622        'permission_callback' => '__return_true',
     1623    ));
     1624});
     1625
     1626function wiserrw_async_handler( WP_REST_Request $request ) {
     1627
     1628    $json_data = $request->get_param( 'json_data' );
     1629    if ( empty( $json_data ) ) {
     1630        return new WP_REST_Response( array( 'error' => 'No data received' ), 400 );
     1631    }
     1632
     1633    $payload = json_decode( $json_data, true );
     1634    if ( empty( $payload['data'] ) || ! is_array( $payload['data'] ) ) {
     1635        return new WP_REST_Response( array( 'error' => 'Invalid payload' ), 400 );
     1636    }
     1637
     1638    // Build a fake REST request to reuse your existing logic
     1639    $fake_request = new WP_REST_Request( 'POST', '/wiserreview/v1/api' );
     1640
     1641    foreach ( $payload['data'] as $key => $value ) {
     1642        $fake_request->set_param( $key, $value );
     1643    }
     1644
     1645    // Force JSON body so get_json_params() works inside callback
     1646    $fake_request->set_body( wp_json_encode( $payload['data'] ) );
     1647    $fake_request->set_header( 'Content-Type', 'application/json' );
     1648
     1649    // Run your main process in background
     1650    do_action( 'wiserrw_endpoint_called', $fake_request );
     1651
     1652    return new WP_REST_Response( array( 'status' => 'background task started' ), 200 );
     1653}
     1654
     1655
     1656// === MAIN LOGIC HANDLER ===
    12791657function wiserrw_api_response_callback( $request ) {
    1280     $data = $request->get_json_params() ?: $request->get_params();
    1281 
    1282      $query_params = $request->get_params();
    1283     if (empty($data) || !is_array($data)) {
     1658
     1659    // --- Safely parse JSON and normal params ---
     1660    $raw  = $request->get_body();
     1661    $data = json_decode( $raw, true );
     1662
     1663    // Fallback if JSON body not parsed
     1664    if ( empty( $data ) || ! is_array( $data ) ) {
     1665        $data = $request->get_json_params() ?: $request->get_params();
     1666    }
     1667
     1668    if ( empty( $data ) || ! is_array( $data ) ) {
    12841669        return;
    12851670    }
    1286 $verify     = isset($query_params['verify_api']) ? $query_params['verify_api'] : '';
    1287 $wsid_param = isset($query_params['wiserrw_wsid']) ? $query_params['wiserrw_wsid'] : '';
     1671 // ✅ CHANGED: read everything from body, not from query
     1672    $wsid_param = $data['wsid']      ?? '';
     1673    $verify_api = $data['verifyApi'] ?? '';
     1674    $updtyp     = $data['updtyp']    ?? '';
     1675    $widget_id  = $data['widgetId']  ?? '';
     1676
     1677    // Current workspace ID saved in site
    12881678    $wsid = get_option( 'wiserrw_wsid', '' );
    1289      // Verify API key if 'verify_api' is true and 'wiserrw_wsid' matches
    1290     if ( $verify && $wsid !== '' && $wsid == $wsid_param ) {
    1291         $wiserrw_api_settings = get_option('wiserrw_api_settings', array());
    1292         $api_key = $wiserrw_api_settings['wiserrw_api_key'];
    1293         wiserrw_validate_api( $api_key );       
    1294     }
    1295 
    1296 
    1297     if (empty($data) || !is_array($data)) {
    1298         return;
    1299     }
    1300    
    1301     update_option( 'wiserrw_response', $data );
    1302    
    1303     if (!isset($data['arrPid']) || !is_array($data['arrPid'])) {
    1304         return;
    1305     }
    1306 
    1307     foreach( $data['arrPid'] as $p_id ) {
    1308         if (!empty($p_id) && is_numeric($p_id)) {
    1309             wiserrw_generate_schema( $p_id );
    1310         }
     1679
     1680    switch ( $updtyp ) {
     1681
     1682        // --- CSS update ---
     1683        case 'update_css':
     1684            // ✅ CHANGED: verifyApi now read from body (string "true")
     1685            $is_verified = filter_var( $verify_api, FILTER_VALIDATE_BOOLEAN );
     1686
     1687            if ( $is_verified && $wsid !== '' && $wsid === $wsid_param ) {
     1688                $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
     1689                $api_key = $wiserrw_api_settings['wiserrw_api_key'] ?? '';
     1690
     1691                // Validate API and update CSS
     1692                wiserrw_validate_api( $api_key );
     1693                wiserrw_update_css( $wsid );
     1694            }
     1695            break;
     1696           
     1697        // --- HTML update (clear widget cache) ---
     1698        case 'update_html':
     1699            if ( ! empty( $widget_id ) ) {
     1700                $option_key = 'wiserreview_widget_' . $widget_id;
     1701                delete_option( $option_key );
     1702            }
     1703            break;
     1704
     1705        // --- Schema generation and async remote update ---
     1706        case 'update_schema':
     1707            if ( ! empty( $data['arrPid'] ) && is_array( $data['arrPid'] ) ) {
     1708                $wsid_to_use = $data['wsid'] ?? $wsid;
     1709
     1710                foreach ( $data['arrPid'] as $p_id ) {
     1711                    if ( ! empty( $p_id ) && is_numeric( $p_id ) ) {
     1712
     1713                        // Run schema generation now (needs WP context)
     1714                        wiserrw_generate_schema( $p_id );
     1715
     1716                        // Send async schema update (non-blocking)
     1717                        wp_remote_post(
     1718                            'https://rs.wiserreview.com/api/wp/updSchema',
     1719                            array(
     1720                                'method'   => 'POST',
     1721                                'headers'  => array( 'Content-Type' => 'application/json' ),
     1722                                'body'     => wp_json_encode( array(
     1723                                    'wsid'   => $wsid_to_use,
     1724                                    'pid'    => $p_id,
     1725                                    'updtyp' => 'update_schema',
     1726                                    'ver'    => WISERRW_PLUGIN_VERSION,
     1727                                )),
     1728                                'blocking' => false,
     1729                                'timeout'  => 3,
     1730                            )
     1731                        );
     1732                    }
     1733                }
     1734            }
     1735            break;
     1736
     1737        default:
     1738            // No action for unknown updtyp
     1739            break;
    13111740    }
    13121741}
    13131742add_action( 'wiserrw_endpoint_called', 'wiserrw_api_response_callback' );
    13141743
    1315 function wiserrw_generate_schema( $p_id ) {
    1316     if (empty($p_id) || !is_numeric($p_id)) {
    1317         return;
    1318     }
    1319 
    1320     $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
    1321     if (!is_array($wiserrw_api_settings) || empty($wiserrw_api_settings['wiserrw_api_key'])) {
    1322         return;
    1323     }
    1324 
    1325     $api_key = $wiserrw_api_settings['wiserrw_api_key'];
    1326     $wsid = get_option( 'wiserrw_wsid', true );
    1327     $automation_id = get_option( 'wiserrw_automation_id', true );
    1328     if (!isset($wsid) || !isset($automation_id)) {
    1329         return; // Exit if invalid API data
    1330     }
    1331 
    1332     $p_id_arr = array($p_id); // Initialize array properly
    1333     $url = 'https://rs.wiserreview.com/api/getData';
    1334 
    1335    
    1336     for( $i=0; $i<count($p_id_arr); $i++ ){
    1337         $product = get_post($p_id_arr[$i]);
    1338         if( !$product ) {
    1339             continue;
    1340         }
    1341         // ✅ Reset on every loop
    1342     $arrPid = array( (string) $p_id_arr[$i] );
    1343         $schema_array = array();
    1344         $body = array(
    1345             'arrType' => array('main','star_rating'),
    1346             'pid'     => (string)$p_id_arr[$i],
    1347             'arrPid' => $arrPid,
    1348             'isFrom' => 'woocommerce',
    1349             'wsid'    => $wsid,
    1350         );
    1351    
    1352         $response = wp_remote_post(
    1353             $url,
    1354             array(
    1355                 'method'      => 'POST',
    1356                 'headers'     => array(
    1357                     'Content-Type' => 'application/json',
    1358                 ),
    1359                 'body'        => wp_json_encode( $body ),
    1360                 'timeout'     => 30,
    1361             )
    1362         );
    1363         if ( !is_wp_error( $response ) ) {
    1364             $json = wp_remote_retrieve_body( $response );
    1365             $data = json_decode( $json, true );
    1366             foreach( $data['data'] as $row ) {
    1367                 $wdtyp = $row['wdtyp'];
    1368                 if( $wdtyp == 'main' ){
    1369 
    1370                     $schema_array['@context'] = "https://schema.org";
    1371                     $schema_array['@type'] = "Product";
    1372                     $schema_array['url'] = get_the_permalink($p_id_arr[$i]);
    1373                     $schema_array['name'] = get_the_title($p_id_arr[$i]);
    1374                     $schema_array['aggregateRating'] = array(
    1375                         '@type' => 'AggregateRating',
    1376                         'ratingValue' => $row['dataCount']['avgrtng'],
    1377                         'reviewCount' => $row['dataCount']['prtng'],
    1378                         'bestRating' => '5',
    1379                         'worstRating' => '1',
    1380                     );
    1381                 }
    1382                 if( $wdtyp == 'star_rating' ) {
    1383                     $star_rating_html = $row['dataRating'][0]['html'];
    1384                    
    1385                     update_post_meta( $p_id_arr[$i], 'wiserrw_star_rating_html', $star_rating_html );
     1744function wiserrw_update_css( $wsid ) {
     1745    $url = 'https://rs.wiserreview.com/api/wp/updSchema';
     1746     $body = array(
     1747        'wsid' => $wsid,
     1748        'updtyp' => 'update_css',
     1749        'ver' => WISERRW_PLUGIN_VERSION
     1750    );
     1751    $response = wp_remote_post(
     1752        $url,
     1753        array(
     1754            'method'      => 'POST',
     1755            'headers'     => array(
     1756                'Content-Type' => 'application/json',
     1757            ),
     1758            'body'        => wp_json_encode( $body ),
     1759            'timeout'     => 30,
     1760        )
     1761    );
     1762}
     1763
     1764function wiserrw_update_schema( $wsid, $pid ) {
     1765    $url = 'https://rs.wiserreview.com/api/wp/updSchema';
     1766    $body = array(
     1767        'wsid' => $wsid,
     1768        'pid' => $pid,
     1769        'updtyp' => 'update_schema',
     1770        'ver' => WISERRW_PLUGIN_VERSION
     1771    );
     1772    $response = wp_remote_post(
     1773        $url,
     1774        array(
     1775            'method'      => 'POST',
     1776            'headers'     => array(
     1777                'Content-Type' => 'application/json',
     1778            ),
     1779            'body'        => wp_json_encode( $body ),
     1780            'timeout'     => 30,
     1781        )
     1782    );
     1783}
     1784
     1785function wiserrw_update_html( $wsid, $widget_id ) {
     1786    $url = 'https://rs.wiserreview.com/api/wp/updSchema';
     1787    $body = array(
     1788        'wsid' => $wsid,
     1789        'widgetId' => $widget_id,
     1790        'updtyp' => 'update_html',
     1791        'ver' => WISERRW_PLUGIN_VERSION
     1792    );
     1793    $response = wp_remote_post(
     1794        $url,
     1795        array(
     1796            'method'      => 'POST',
     1797            'headers'     => array(
     1798                'Content-Type' => 'application/json',
     1799            ),
     1800            'body'        => wp_json_encode( $body ),
     1801            'timeout'     => 30,
     1802        )
     1803    );
     1804}
     1805
     1806
     1807function wiserrw_get_lang_context( $product_id ) {
     1808    $ctx = array(
     1809        'original_pid' => 0,
     1810        'current_pid'  => (int) $product_id,
     1811        'lang'         => '',
     1812    );
     1813
     1814    if ( function_exists( 'pll_get_post' ) ) {
     1815        $ctx['original_pid'] = (int) pll_get_post( $product_id, pll_default_language() );
     1816        $ctx['lang']         = (string) pll_get_post_language( $product_id );
     1817        return $ctx;
     1818    }
     1819
     1820    if ( has_filter( 'wpml_object_id' ) ) {
     1821        $trid = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
     1822        $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
     1823        if ( is_array( $translations ) ) {
     1824            foreach ( $translations as $lang => $t ) {
     1825                $eid = is_object($t) ? (int)$t->element_id : (int)($t['element_id'] ?? 0);
     1826                if ( ! empty( $t->original ) || ( is_array($t) && ! empty($t['original']) ) ) {
     1827                    $ctx['original_pid'] = $eid;
    13861828                }
    1387                 foreach( $row['data'] as $review_row ) {
    1388                    
    1389                    
    1390                    $review = array(
    1391                         "@type" => "Review",
    1392                         'author' => array(
    1393                             '@type' => "Person",
    1394                             'name' => $review_row['un']
    1395                         ),
    1396                         'reviewBody' => $review_row['orgnlrtxt'],
    1397                         'reviewRating' => array(
    1398                             '@type' => 'Rating',
    1399                             'ratingValue' => $review_row['rtng'],
    1400                             'bestRating' => '5',
    1401                             'worstRating' => '1',
    1402                         ),
    1403                         'publisher' => array(
    1404                             "@type" => "Organization",
    1405                             "name" => "WiserReview"
    1406                         )
    1407                     );
    1408                     $schema_array['review'][] = $review;
     1829                if ( $eid === (int) $product_id ) {
     1830                    $ctx['lang'] = $lang;
    14091831                }
    1410                 $json_schema = json_encode($schema_array,JSON_UNESCAPED_SLASHES);
    1411                
    1412                 if( $row['dataCount']['prtng'] > 0 ) {
    1413                     update_post_meta( $p_id_arr[$i], 'wiserrw_schema_json', $json_schema );
    1414                 } else {
    1415                     delete_post_meta( $p_id_arr[$i], 'wiserrw_schema_json' );
    1416                 }
    14171832            }
    14181833        }
    1419         wiserrw_send_product_update_on_save_post( $p_id_arr[$i] );
    1420     }
     1834    }
     1835
     1836    return $ctx;
     1837}
     1838
     1839function wiserrw_generate_schema( $p_id ) {
     1840    if ( empty( $p_id ) || ! is_numeric( $p_id ) ) {
     1841        return;
     1842    }
     1843
     1844    // --- Multilingual handling (WPML + Polylang safe) ---
     1845    $original_pid = 0;
     1846    $current_lang = '';
     1847
     1848    // WPML
     1849    if ( has_filter( 'wpml_element_trid' ) ) {
     1850        $trid         = apply_filters( 'wpml_element_trid', null, $p_id, 'post_product' );
     1851        $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
     1852        $current_lang = apply_filters( 'wpml_current_language', null );
     1853        if ( $translations && is_array( $translations ) ) {
     1854            foreach ( $translations as $lang => $translation ) {
     1855                if ( ! empty( $translation->original ) ) {
     1856                    $original_pid = (int) $translation->element_id;
     1857                }
     1858            }
     1859        }
     1860    }
     1861
     1862    // Polylang
     1863    if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
     1864        $current_lang = pll_current_language();
     1865        $default_lang = pll_default_language();
     1866        if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
     1867            $original_pid = pll_get_post( $p_id, $default_lang );
     1868        }
     1869    }
     1870
     1871    $opid_value = ( $original_pid && $original_pid !== $p_id ) ? $original_pid : $p_id;
     1872    $lang_value = $current_lang ?: '';
     1873
     1874    // --- API settings ---
     1875    $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
     1876    if ( ! is_array( $wiserrw_api_settings ) || empty( $wiserrw_api_settings['wiserrw_api_key'] ) ) {
     1877        return;
     1878    }
     1879
     1880    $wsid          = get_option( 'wiserrw_wsid', true );
     1881    $automation_id = get_option( 'wiserrw_automation_id', true );
     1882    if ( ! $wsid || ! $automation_id ) {
     1883        return;
     1884    }
     1885
     1886    $url  = 'https://rs.wiserreview.com/api/getData';
     1887    $body = array(
     1888        'arrType' => array( 'main', 'star_rating' ),
     1889        'pid'     => (string) $p_id,
     1890        'opid'    => (string) $opid_value,
     1891        'lang'    => $lang_value,
     1892        'arrPid'  => array( (string) $p_id ),
     1893        'isFrom'  => 'woocommerce',
     1894        'wsid'    => $wsid,
     1895        'updtyp'  => 'update_schema',
     1896        'ver'     => WISERRW_PLUGIN_VERSION
     1897    );
     1898
     1899    $response = wp_remote_post(
     1900        $url,
     1901        array(
     1902            'method'  => 'POST',
     1903            'headers' => array( 'Content-Type' => 'application/json' ),
     1904            'body'    => wp_json_encode( $body ),
     1905            'timeout' => 30,
     1906        )
     1907    );
     1908
     1909    if ( is_wp_error( $response ) ) {
     1910        return;
     1911    }
     1912
     1913    $json = wp_remote_retrieve_body( $response );
     1914    $data = json_decode( $json, true );
     1915    if ( empty( $data['data'] ) ) {
     1916        return;
     1917    }
     1918
     1919    $schema_array = array();
     1920    $reviewCount  = 0;
     1921
     1922    foreach ( $data['data'] as $row ) {
     1923        $wdtyp = $row['wdtyp'];
     1924
     1925        if ( $wdtyp == 'main' ) {
     1926            $reviewCount = intval( $row['dataCount']['prtng'] );
     1927
     1928            $schema_array['@context'] = "https://schema.org";
     1929            $schema_array['@type']    = "Product";
     1930            $schema_array['url']      = get_the_permalink( $p_id );
     1931            $schema_array['name']     = get_the_title( $p_id );
     1932            $schema_array['aggregateRating'] = array(
     1933                '@type'       => 'AggregateRating',
     1934                'ratingValue' => $row['dataCount']['avgrtng'],
     1935                'reviewCount' => $reviewCount,
     1936                'bestRating'  => '5',
     1937                'worstRating' => '1',
     1938            );
     1939        }
     1940
     1941        if ( $wdtyp == 'star_rating' && !empty( $row['dataRating'][0]['html'] ) ) {
     1942            update_post_meta( $p_id, 'wiserrw_star_rating_html', $row['dataRating'][0]['html'] );
     1943        }
     1944
     1945        if ( ! empty( $row['data'] ) && is_array( $row['data'] ) ) {
     1946            foreach ( $row['data'] as $review_row ) {
     1947                $schema_array['review'][] = array(
     1948                    "@type"        => "Review",
     1949                    'author'       => array(
     1950                        '@type' => "Person",
     1951                        'name'  => $review_row['un']
     1952                    ),
     1953                    'reviewBody'   => $review_row['orgnlrtxt'],
     1954                    'reviewRating' => array(
     1955                        '@type'       => 'Rating',
     1956                        'ratingValue' => $review_row['rtng'],
     1957                        'bestRating'  => '5',
     1958                        'worstRating' => '1',
     1959                    ),
     1960                    'publisher'    => array(
     1961                        "@type" => "Organization",
     1962                        "name"  => "WiserReview"
     1963                    )
     1964                );
     1965            }
     1966        }
     1967    }
     1968
     1969    // ✅ Save or delete only once, after loop
     1970    if ( $reviewCount > 0 ) {
     1971        update_post_meta( $p_id, 'wiserrw_schema_json', json_encode( $schema_array, JSON_UNESCAPED_SLASHES ) );
     1972    } else {
     1973        delete_post_meta( $p_id, 'wiserrw_schema_json' );
     1974    }
     1975
     1976    wiserrw_send_product_update_on_save_post( $p_id );
    14211977}
    14221978
     
    15532109            'blocking' => true,
    15542110            'body'    => wp_json_encode( $data ),
     2111            'ver' => WISERRW_PLUGIN_VERSION
    15552112        )
    15562113    );
     
    15592116
    15602117}
     2118
     2119function wiserrw_show_product_rating_on_collection_woo_events() {
     2120    $wiserrw_api_settings = (array) get_option( 'wiserrw_api_settings', [] );
     2121
     2122    if ( ! empty( $wiserrw_api_settings['wiserrw_product_card_enabled'] ) && $wiserrw_api_settings['wiserrw_product_card_enabled'] === '1' ) {
     2123        echo do_shortcode( '[wiserrw_rating_count]' );
     2124    }
     2125}
     2126
     2127add_action( 'we_after_grid_content_html', 'wiserrw_show_product_rating_on_collection_woo_events', 5 );
     2128
     2129// WiserReview Shortcode: [wiserreview_widget id="WidgetID value" type="carousel"]
     2130function wiserreview_widget_shortcode( $atts ) {
     2131    $wsid = get_option('wiserrw_wsid',true);
     2132    $atts = shortcode_atts( array(
     2133        'id' => '',
     2134        'type' => '',
     2135    ), $atts, 'carousel_widget' );
     2136
     2137    if ( empty( $atts['id'] ) ) {
     2138        return 'No widget ID provided';
     2139    }
     2140   
     2141    if ( empty( $atts['type'] ) ) {
     2142        return 'No widget Type provided';
     2143    }
     2144
     2145    $widget_id  = sanitize_text_field( $atts['id'] );
     2146    $type  = sanitize_text_field( $atts['type'] );
     2147    $option_key = 'wiserreview_widget_' . $widget_id;
     2148
     2149    // 1. If cache exists → serve instantly
     2150    $cached_html = get_option( $option_key );
     2151    if ( ! empty( $cached_html ) ) {
     2152        return $cached_html;
     2153    }
     2154
     2155    // 2. Render fallback immediately
     2156    $fallback = wiserreview_shortcode_fallback( $widget_id, $type );
     2157
     2158    // 3. Try API fetch in same request (non-blocking for rendering)
     2159    //    The user sees fallback, but next time cache will be ready
     2160    $api_url  = add_query_arg(
     2161        array(
     2162            'widgetId' => $widget_id,
     2163            'type'     => $type,
     2164            'ver' => WISERRW_PLUGIN_VERSION
     2165        ),
     2166        'https://rs.wiserreview.com/api/getWdgtHTML'
     2167       
     2168    );
     2169
     2170    $response = wp_remote_get( $api_url, array( 'timeout' => 20 ) );
     2171
     2172    if ( ! is_wp_error( $response ) ) {
     2173        $json = json_decode( wp_remote_retrieve_body( $response ), true );
     2174        if ( isset( $json['html'] ) && ! empty( $json['html'] ) ) {
     2175            update_option( $option_key, $json['html'] );
     2176            wiserrw_update_html( $wsid, $widget_id );
     2177        }
     2178    }
     2179
     2180    return $fallback;
     2181}
     2182add_shortcode( 'wiserreview_widget', 'wiserreview_widget_shortcode' );
     2183
     2184function wiserreview_shortcode_fallback( $widget_id, $type ) {
     2185    $widget_id = esc_attr( $widget_id );
     2186
     2187    return '<script src="https://embed.wiserreview.com/embed/' . $widget_id . '/widget.js" defer></script>'
     2188         . '<div data-type="'.$type.'" class="wiser_review_'.$type.'" data-id="' . $widget_id . '"></div>';
     2189}
Note: See TracChangeset for help on using the changeset viewer.