PluginProbe
WiserReview Product Reviews for WooCommerce / 3.3
WiserReview Product Reviews for WooCommerce v3.3
3.7 3.6 3.5 trunk 1.0 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9 2.0 2.1 2.2 2.3 2.4 2.5 2.6 2.7 2.8 2.9 3.0 All 29 releases
wiser-review / wiser-review.php

wiser-review.php in WiserReview Product Reviews for WooCommerce 3.3, at wiser-review.php

2,768 lines 90.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Plugin Name: WiserReview Product Reviews for WooCommerce
4 * Plugin URI: https://wiserreview.com
5 * Description: Collect, manage, and display powerful product reviews and testimonials in WooCommerce using the WiserReview platform. Boost trust and conversion with automated review collection.
6 * Version: 3.3
7 * Author: Wiser Notify
8 * Author URI: https://wiserreview.com
9 * Requires Plugins: woocommerce
10 * Tested up to: 6.9
11 * Requires at least: 5.6
12 * Requires PHP: 7.4
13 * Text Domain: wiser-review
14 * License: GPLv2 or later
15 * License URI: https://www.gnu.org/licenses/gpl-2.0.html
16 *
17 * @package wiser-review
18 * */
19
20 if ( ! defined( 'ABSPATH' ) ) {
21 exit;
22 }
23 define( 'WISERRW_PLUGIN_FILE', __FILE__ );
24 define( 'WISERRW_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
25 define( 'WISERRW_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
26 define( 'WISERRW_PLUGIN_VERSION', '3.3' );
27 define( 'WISERRW_API_HOST', 'https://api.wiserreview.com/api/woocommerce/' );
28 define( 'WISERRW_RS_HOST', 'https://rs.wiserreview.com' );
29
30 if ( ! function_exists( 'wiserrw_scripts' ) ) {
31 /**
32 * Plugin styles and scripts.
33 */
34 function wiserrw_scripts( $hook ) {
35 $allowed_screens = array(
36 'toplevel_page_wiser-review'
37 );
38 $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null;
39 $screen_id = $screen ? $screen->id : $hook;
40 if ( ! in_array( $screen_id, $allowed_screens, true ) ) {
41 return;
42 }
43
44 wp_enqueue_style( 'wiserrw-admin-styles', plugins_url( '/assets/css/wiserrw-admin.css', WISERRW_PLUGIN_FILE ), array(), WISERRW_PLUGIN_VERSION );
45 wp_register_script( 'wiserrw-admin-script', plugins_url( '/assets/js/wiserw-js.js', WISERRW_PLUGIN_FILE ), array(), WISERRW_PLUGIN_VERSION );
46 wp_localize_script(
47 'wiserrw-admin-script',
48 'wiserrw_ajax_var',
49 array(
50 'url' => admin_url( 'admin-ajax.php' ),
51 'wiserrw_security' => wp_create_nonce( 'wiserrw_export_orders_nonce' ),
52 'action' => 'wiserrw_save_settings',
53 'nonce' => wp_create_nonce( 'wiserrw_save_settings' )
54 )
55 );
56 }
57 }
58 add_action( 'admin_enqueue_scripts', 'wiserrw_scripts' );
59
60 function wiserrw_ajax_save_settings() {
61 // Verify nonce for security
62 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'wiserrw_save_settings' ) ) {
63 wp_send_json_error( array(
64 'message' => 'Security check failed.',
65 ), 403 );
66 }
67
68 // Check user capabilities
69 if ( ! current_user_can( 'manage_options' ) ) {
70 wp_send_json_error( array(
71 'message' => 'Unauthorized access.',
72 ), 403 );
73 }
74
75 $wiserrw_data = isset( $_POST['wiserrw_data'] ) ? array_map( 'sanitize_text_field', wp_unslash( $_POST['wiserrw_data'] ) ) : array();
76
77 // Sanitize review form URL
78 if ( ! empty( $wiserrw_data['wiserrw_review_form_url'] ) ) {
79 $wiserrw_data['wiserrw_review_form_url'] = rtrim( strtok( trim( $wiserrw_data['wiserrw_review_form_url'] ), '?' ), '/' );
80 }
81 // Force review page toggle off if URL is invalid
82 $review_url = $wiserrw_data['wiserrw_review_form_url'] ?? '';
83 if ( empty( $review_url ) || strpos( $review_url, 'https://rating.wiserreview.com/' ) !== 0 ) {
84 unset( $wiserrw_data['wiserrw_review_page_enabled'] );
85 $wiserrw_data['wiserrw_review_form_url'] = '';
86 }
87
88 // Check if review page toggle changed BEFORE saving
89 $old_settings = get_option( 'wiserrw_api_settings', array() );
90 $old_review_page = $old_settings['wiserrw_review_page_enabled'] ?? '';
91 $new_review_page = $wiserrw_data['wiserrw_review_page_enabled'] ?? '';
92 $review_page_changed = ( $old_review_page !== $new_review_page );
93
94 update_option( 'wiserrw_api_settings', $wiserrw_data );
95
96 // Flush rewrite rules AFTER saving so the new setting is active
97 if ( $review_page_changed ) {
98 wiserrw_review_page_add_endpoint();
99 flush_rewrite_rules();
100 }
101
102 wp_send_json_success( array(
103 'message' => 'Settings saved.',
104 'saved' => $wiserrw_data,
105 ) );
106 }
107 add_action( 'wp_ajax_wiserrw_save_settings', 'wiserrw_ajax_save_settings' );
108
109 if ( ! function_exists( 'wiserrw_api_settings' ) ) {
110 /**
111 * Setup API settings page in admin panel.
112 */
113 function wiserrw_api_settings() {
114 add_menu_page(
115 __( 'Wiser Review', 'wiser-review' ),
116 __( 'Wiser Review', 'wiser-review' ),
117 'manage_options',
118 'wiser-review',
119 'wiserrw_api_settings_callback',
120 plugins_url( '/assets/images/icon.svg', WISERRW_PLUGIN_FILE ),
121 );
122 }
123 }
124 add_action( 'admin_menu', 'wiserrw_api_settings' );
125
126 /**
127 * Callback for the plugin settings.
128 */
129 function wiserrw_api_settings_callback() {
130 $wiserrw_api_settings = get_option('wiserrw_api_settings', array());
131 if (!wp_style_is('woocommerce_admin_styles', 'registered')) {
132 return;
133 }
134 wp_enqueue_style('woocommerce_admin_styles');
135 wp_enqueue_script('wiserrw-admin-script');
136 include_once WISERRW_PLUGIN_DIR . '/views/wiserw-plugin-settings.php';
137 }
138
139 /**
140 * Recursive sanitation for an array
141 *
142 * @param array $array to sanitize.
143 *
144 * @return mixed
145 */
146 function wiserrw_recursive_sanitize_text_field( $array ) {
147 foreach ( $array as $key => &$value ) {
148 if ( is_array( $value ) ) {
149 $value = wiserrw_recursive_sanitize_text_field( $value );
150 } else {
151 $value = sanitize_text_field( $value );
152 }
153 }
154 return $array;
155 }
156
157 /**
158 * Validate API Key
159 *
160 * @param string $api_key API Key to validate.
161 *
162 * @return bool
163 */
164
165 function wiserrw_validate_api( $api_key ) {
166 if ( empty( $api_key ) ) {
167 return (object) array( 'status' => 'error', 'msg' => 'API key is required' );
168 }
169
170 $url = WISERRW_API_HOST . 'verify?key=' . urlencode( $api_key ) .
171 '&wiserrw_hook_url=' . urlencode( get_site_url() . '/wp-json/wiserrw/v1/reviews' ) .
172 '&ver=' . urlencode( WISERRW_PLUGIN_VERSION );
173
174 $args = array(
175 'method' => 'GET',
176 'timeout' => 20,
177 'headers' => array(
178 'x-wiserrw-api-key' => $api_key,
179 ),
180 );
181
182 $response = wp_remote_get( $url, $args );
183
184 if ( is_wp_error( $response ) ) {
185 error_log( 'API Verify Error: ' . $response->get_error_message() );
186 return (object) array( 'status' => 'error', 'msg' => 'Connection failed. Please try again.' );
187 }
188
189 $response_code = wp_remote_retrieve_response_code( $response );
190 if ( $response_code !== 200 ) {
191 return (object) array( 'status' => 'error', 'msg' => 'Invalid response from server. Please check your API key.' );
192 }
193
194 $body = json_decode( wp_remote_retrieve_body( $response ) );
195
196 if ( ! is_object( $body ) ) {
197 return (object) array( 'status' => 'error', 'msg' => 'Invalid response format. Please try again.' );
198 }
199
200 if ( isset( $body->data ) && isset( $body->data->wsid ) ) {
201 update_option( 'wiserrw_api_data', $body->data );
202 update_option( 'wiserrw_wsid', $body->data->wsid );
203 update_option( 'wiserrw_automation_id', $body->data->automation_id ?? '' );
204 }
205
206 return $body;
207 }
208
209
210 /**
211 * Show Product Reviews
212 */
213 function wiserrw_show_product_review() {
214 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
215 if ( isset($wiserrw_api_settings['wiserrw_product_review_enabled']) && '1' === $wiserrw_api_settings['wiserrw_product_review_enabled'] ) {
216 $pid = wiserrw_get_current_product_id();
217 echo wiserrw_product_review( array( 'id' => $pid ) );
218 wiserrw_restore_product_globals();
219 }
220 }
221 add_action( 'woocommerce_after_single_product_summary', 'wiserrw_show_product_review', 14 );
222 // fix for the kadence theme
223 add_action( 'kadence_after_short_description_block', 'wiserrw_show_product_review' );
224
225
226 /**
227 * Show Product Reviews
228 */
229 function wiserrw_show_product_rating_on_collection() {
230 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
231 if ( isset($wiserrw_api_settings['wiserrw_product_card_enabled']) && '1' === $wiserrw_api_settings['wiserrw_product_card_enabled'] ) {
232 $pid = wiserrw_get_current_product_id();
233 echo wiserrw_rating_count( array( 'id' => $pid ) );
234 wiserrw_restore_product_globals();
235 }
236 }
237 add_action( 'woocommerce_after_shop_loop_item_title', 'wiserrw_show_product_rating_on_collection', 5 );
238
239 /**
240 * Add review JS script to head
241 */
242 function wiserr_review_script() {
243 $settings = (array) get_option( 'wiserrw_api_settings', array() );
244 if ( empty( $settings['wiserrw_live_mode_checkbox'] ) || $settings['wiserrw_live_mode_checkbox'] !== '1' ) {
245 return;
246 }
247
248 $api_data = get_option( 'wiserrw_api_data', array() );
249
250 // Support object or array; avoid bool access warnings
251 $header_pixel = '';
252 if ( is_object( $api_data ) && ! empty( $api_data->header_pixel ) ) {
253 $header_pixel = $api_data->header_pixel;
254 } elseif ( is_array( $api_data ) && ! empty( $api_data['header_pixel'] ) ) {
255 $header_pixel = $api_data['header_pixel'];
256 } else {
257 return; // nothing to print
258 }
259
260 $allowed_tags = array(
261 'script' => array(
262 'src' => true,
263 'type' => true,
264 'async' => true,
265 'defer' => true,
266 ),
267 );
268
269 echo wp_kses( $header_pixel, $allowed_tags );
270 }
271 add_action( 'wp_footer', 'wiserr_review_script' );
272
273
274 /**
275 * Show Product Rating Count.
276 */
277 function wiserrw_show_rating_count() {
278 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
279 if ( isset($wiserrw_api_settings['wiserrw_star_rating_enabled']) && '1' === $wiserrw_api_settings['wiserrw_star_rating_enabled'] ) {
280 $pid = wiserrw_get_current_product_id();
281 echo wiserrw_rating_count( array( 'id' => $pid ) );
282 wiserrw_restore_product_globals();
283 }
284 }
285 add_action( 'woocommerce_single_product_summary', 'wiserrw_show_rating_count', 9 );
286 // fix for the kadence theme
287 if ( wp_get_theme()->get( 'Name' ) === 'Kadence' ) {
288 add_action( 'woocommerce_after_add_to_cart_button', 'wiserrw_show_rating_count' );
289 }
290
291
292 /**
293 * Show Review Nudges Section.
294 */
295 function wiserrw_show_review_nudges_section() {
296 $wiserrw_api_settings = get_option('wiserrw_api_settings', array());
297 if (isset($wiserrw_api_settings['wiserrw_review_nudges_enabled'])
298 && '1' === $wiserrw_api_settings['wiserrw_review_nudges_enabled']) {
299 $pid = wiserrw_get_current_product_id();
300 echo wiserrw_product_nudges( array( 'id' => $pid ) );
301 wiserrw_restore_product_globals();
302 }
303 }
304 add_action( 'woocommerce_after_add_to_cart_form', 'wiserrw_show_review_nudges_section' );
305
306 /**
307 * Get current product ID reliably across all themes including Divi 5, Bricks, Elementor Pro.
308 * Uses WooCommerce's $GLOBALS['product'] first, falls back to get_the_ID().
309 */
310 function wiserrw_get_current_product_id() {
311 if ( isset( $GLOBALS['product'] ) && $GLOBALS['product'] instanceof WC_Product ) {
312 return $GLOBALS['product']->get_id();
313 }
314 return get_the_ID() ?: 0;
315 }
316
317 /**
318 * Restore WC product globals after shortcode processing.
319 * Prevents corrupted product data for downstream WooCommerce hooks.
320 */
321 function wiserrw_restore_product_globals() {
322 if ( isset( $GLOBALS['post'] ) && function_exists( 'wc_setup_product_data' ) ) {
323 wc_setup_product_data( $GLOBALS['post'] );
324 }
325 }
326
327 /**
328 * Shortcode for the Rating Count.
329 */
330 function wiserrw_rating_count( $atts ) {
331 $atts = shortcode_atts(
332 array(
333 'id' => wiserrw_get_current_product_id(),
334 ),
335 $atts,
336 'wiserrw_rating_count'
337 );
338
339 $product_id = intval( $atts['id'] );
340 if ( ! $product_id ) {
341 return '';
342 }
343
344 // Get SKU directly (faster than wc_get_product)
345 $sku = get_post_meta( $product_id, '_sku', true );
346
347 // Check settings
348 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
349 if ( ! $wiserrw_api_settings || '1' !== ( $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ?? '' ) ) {
350 return '';
351 }
352
353 // API HTML
354 $wiserrw_api_data = get_option( 'wiserrw_api_data' );
355 $product_rating_count = $wiserrw_api_data->product_rating_count ?? '';
356 $product_rating_count = preg_replace( '/data-pid=("|\')(.*?)\1/', 'data-pid="' . $product_id . '"', $product_rating_count );
357 $product_rating_count = preg_replace( '/data-skuid=("|\')(.*?)\1/', 'data-skuid="' . $sku . '"', $product_rating_count );
358
359 // --- Multilingual (cache per request) ---
360 static $lang_cache = array();
361 if ( isset( $lang_cache[$product_id] ) ) {
362 $opid_value = $lang_cache[$product_id]['opid'];
363 $current_lang = $lang_cache[$product_id]['lang'];
364 } else {
365 $original_pid = 0;
366 $current_lang = '';
367
368 // WPML
369 if ( has_filter( 'wpml_element_trid' ) ) {
370 $trid = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
371 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
372 $current_lang = apply_filters( 'wpml_current_language', null );
373
374 if ( $translations && is_array( $translations ) ) {
375 foreach ( $translations as $lang => $translation ) {
376 if ( ! empty( $translation->original ) ) {
377 $original_pid = (int) $translation->element_id;
378 }
379 }
380 }
381 }
382
383 // Polylang
384 if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
385 $current_lang = pll_current_language();
386 $default_lang = pll_default_language();
387 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
388 $original_pid = pll_get_post( $product_id, $default_lang );
389 }
390 }
391
392 // Fallback: current product if no original
393 $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
394
395 // Cache result
396 $lang_cache[$product_id] = array(
397 'opid' => $opid_value,
398 'lang' => $current_lang,
399 );
400 }
401
402 // Extract wrapper
403 $wiser_open_tag = '';
404 if ( preg_match( '/<div\b[^>]*>/i', $product_rating_count, $matches ) ) {
405 $wiser_open_tag = $matches[0];
406
407 // Ensure opid
408 if ( preg_match( '/data-opid=/', $wiser_open_tag ) ) {
409 $wiser_open_tag = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $wiser_open_tag );
410 } else {
411 $wiser_open_tag = str_replace( '<div', '<div data-opid="' . esc_attr( $opid_value ) . '"', $wiser_open_tag );
412 }
413
414 // Ensure lang
415 if ( ! empty( $current_lang ) ) {
416 if ( preg_match( '/data-lang=/', $wiser_open_tag ) ) {
417 $wiser_open_tag = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $wiser_open_tag );
418 } else {
419 $wiser_open_tag = str_replace( '<div', '<div data-lang="' . esc_attr( $current_lang ) . '"', $wiser_open_tag );
420 }
421 }
422 }
423
424 // Inner HTML: prefer post meta, patch if missing
425 $meta_html = get_post_meta( $product_id, 'wiserrw_star_rating_html', true );
426 // if ( $meta_html ) {
427 // Patch opid
428 if ( preg_match( '/data-opid=/', $meta_html ) ) {
429 $meta_html = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $meta_html );
430 } else {
431 $meta_html = preg_replace( '/<div\b/i', '<div data-opid="' . esc_attr( $opid_value ) . '"', $meta_html, 1 );
432 }
433
434 // Patch lang
435 if ( ! empty( $current_lang ) ) {
436 if ( preg_match( '/data-lang=/', $meta_html ) ) {
437 $meta_html = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $meta_html );
438 } else {
439 $meta_html = preg_replace( '/<div\b/i', '<div data-lang="' . esc_attr( $current_lang ) . '"', $meta_html, 1 );
440 }
441 }
442
443 $inner_html = $meta_html;
444
445 // Build output
446 $output = '';
447 if ( $wiser_open_tag ) {
448 $output .= $wiser_open_tag;
449 }
450 $output .= $inner_html;
451 $output .= '</div>';
452
453 return $output;
454 }
455 add_shortcode( 'wiserrw_rating_count', 'wiserrw_rating_count' );
456
457
458
459
460 /**
461 * Shortcode for the product.
462 */
463
464
465
466 function wiserrw_product_review( $atts ) {
467 $atts = shortcode_atts(
468 array(
469 'id' => wiserrw_get_current_product_id(),
470 ),
471 $atts,
472 'wiserrw_product_review'
473 );
474
475 $product_id = intval( $atts['id'] );
476 if ( ! $product_id ) {
477 return '';
478 }
479
480 $product = wc_get_product( $product_id );
481 if ( ! $product ) {
482 return '';
483 }
484
485 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
486 if ( ! $wiserrw_api_settings || '1' !== ( $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ?? '' ) ) {
487 return '';
488 }
489
490 $sku = $product->get_sku();
491 $data_login = is_user_logged_in() ? 'true' : 'false';
492 $wiserrw_api_data = get_option( 'wiserrw_api_data' );
493
494 // --- Multilingual (WPML / Polylang) ---
495 $original_pid = 0;
496 $current_lang = '';
497
498 // WPML
499 if ( has_filter( 'wpml_element_trid' ) ) {
500 $trid = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
501 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
502 $current_lang = apply_filters( 'wpml_current_language', null );
503
504 if ( $translations && is_array( $translations ) ) {
505 foreach ( $translations as $lang => $translation ) {
506 if ( ! empty( $translation->original ) ) {
507 $original_pid = (int) $translation->element_id;
508 }
509 }
510 }
511 }
512
513 // Polylang
514 if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
515 $current_lang = pll_current_language();
516 $default_lang = pll_default_language();
517 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
518 $original_pid = pll_get_post( $product_id, $default_lang );
519 }
520 }
521
522 // Fallback: use current product if no original found
523 $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
524
525 // --- Patch API HTML ---
526 $product_review_section = $wiserrw_api_data->product_review_section ?? '';
527
528 // --- Ensure data-lgin (NOT data-login — "lgin" is the correct attribute name used by the pixel JS) ---
529 if ( preg_match( '/data-lgin=/', $product_review_section ) ) {
530 $product_review_section = preg_replace( '/data-lgin=("|\')(.*?)\1/', 'data-lgin="' . $data_login . '"', $product_review_section );
531 } else {
532 $product_review_section = preg_replace( '/<div\b/i', '<div data-lgin="' . esc_attr( $data_login ) . '"', $product_review_section, 1 );
533 }
534
535 // --- Ensure pid ---
536 $product_review_section = preg_replace( '/data-pid=("|\')(.*?)\1/', 'data-pid="' . $product_id . '"', $product_review_section );
537
538 // --- Ensure skuid ---
539 $product_review_section = preg_replace( '/data-skuid=("|\')(.*?)\1/', 'data-skuid="' . $sku . '"', $product_review_section );
540
541 // --- Ensure opid ---
542 if ( preg_match( '/data-opid=/', $product_review_section ) ) {
543 $product_review_section = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $product_review_section );
544 } else {
545 $product_review_section = preg_replace( '/<div\b/i', '<div data-opid="' . esc_attr( $opid_value ) . '"', $product_review_section, 1 );
546 }
547
548 // --- Ensure lang ---
549 if ( ! empty( $current_lang ) ) {
550 if ( preg_match( '/data-lang=/', $product_review_section ) ) {
551 $product_review_section = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $product_review_section );
552 } else {
553 $product_review_section = preg_replace( '/<div\b/i', '<div data-lang="' . esc_attr( $current_lang ) . '"', $product_review_section, 1 );
554 }
555 }
556
557 // --- Extract the wrapper div tag ---
558 $wiser_open_tag = '';
559 if ( preg_match( '/<div\b[^>]*>/i', $product_review_section, $matches ) ) {
560 $wiser_open_tag = $matches[0];
561
562 // --- Ensure opid for wrapper ---
563 if ( preg_match( '/data-opid=/', $wiser_open_tag ) ) {
564 $wiser_open_tag = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $wiser_open_tag );
565 } else {
566 $wiser_open_tag = str_replace( '<div', '<div data-opid="' . esc_attr( $opid_value ) . '"', $wiser_open_tag );
567 }
568
569 // --- Ensure lang for wrapper ---
570 if ( ! empty( $current_lang ) ) {
571 if ( preg_match( '/data-lang=/', $wiser_open_tag ) ) {
572 $wiser_open_tag = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $wiser_open_tag );
573 } else {
574 $wiser_open_tag = str_replace( '<div', '<div data-lang="' . esc_attr( $current_lang ) . '"', $wiser_open_tag );
575 }
576 }
577 }
578
579 // --- Use post meta if available, fallback if not ---
580 $meta_html = get_post_meta( $product_id, 'wiserrw_product_html', true );
581 //if ( $meta_html ) {
582 // Patch opid for meta HTML
583 if ( preg_match( '/data-opid=/', $meta_html ) ) {
584 $meta_html = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $meta_html );
585 } else {
586 $meta_html = preg_replace( '/<div\b/i', '<div data-opid="' . esc_attr( $opid_value ) . '"', $meta_html, 1 );
587 }
588
589 // Patch lang for meta HTML
590 if ( ! empty( $current_lang ) ) {
591 if ( preg_match( '/data-lang=/', $meta_html ) ) {
592 $meta_html = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $meta_html );
593 } else {
594 $meta_html = preg_replace( '/<div\b/i', '<div data-lang="' . esc_attr( $current_lang ) . '"', $meta_html, 1 );
595 }
596 }
597
598 $inner_html = $meta_html;
599
600 // Build output
601 $output = '';
602 if ( $wiser_open_tag ) {
603 $output .= $wiser_open_tag;
604 }
605 $output .= $inner_html;
606 $output .= '</div>';
607
608 return $output;
609 }
610 add_shortcode( 'wiserrw_product_review', 'wiserrw_product_review' );
611
612 /**
613 * Shortcode for the Nudges.
614 */
615 function wiserrw_product_nudges( $atts = [] ) {
616 $atts = shortcode_atts(
617 array(
618 'id' => wiserrw_get_current_product_id(),
619 ),
620 $atts,
621 'wiserrw_product_nudges'
622 );
623
624 $product_id = intval( $atts['id'] );
625 if ( ! $product_id ) {
626 return '';
627 }
628
629 $product = wc_get_product( $product_id );
630 if ( ! $product ) {
631 return '';
632 }
633
634 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
635 if ( ! $wiserrw_api_settings || '1' !== ( $wiserrw_api_settings['wiserrw_live_mode_checkbox'] ?? '' ) ) {
636 return '';
637 }
638
639 $sku = $product->get_sku();
640 $wiserrw_api_data = get_option( 'wiserrw_api_data' );
641
642 // --- Multilingual (WPML / Polylang) ---
643 $original_pid = 0;
644 $current_lang = '';
645
646 // WPML
647 if ( has_filter( 'wpml_element_trid' ) ) {
648 $trid = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
649 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
650 $current_lang = apply_filters( 'wpml_current_language', null );
651
652 if ( $translations && is_array( $translations ) ) {
653 foreach ( $translations as $lang => $translation ) {
654 if ( ! empty( $translation->original ) ) {
655 $original_pid = (int) $translation->element_id;
656 }
657 }
658 }
659 }
660
661 // Polylang
662 if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
663 $current_lang = pll_current_language();
664 $default_lang = pll_default_language();
665 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
666 $original_pid = pll_get_post( $product_id, $default_lang );
667 }
668 }
669
670 // Fallback if no original found
671 $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
672
673 // --- Patch API HTML ---
674 $nudges_section = $wiserrw_api_data->nudges_section ?? '';
675
676 // Ensure pid
677 $nudges_section = preg_replace( '/data-pid=("|\')(.*?)\1/', 'data-pid="' . $product_id . '"', $nudges_section );
678
679 // Ensure skuid
680 $nudges_section = preg_replace( '/data-skuid=("|\')(.*?)\1/', 'data-skuid="' . $sku . '"', $nudges_section );
681
682 // Ensure opid
683 if ( preg_match( '/data-opid=/', $nudges_section ) ) {
684 $nudges_section = preg_replace( '/data-opid=("|\')(.*?)\1/', 'data-opid="' . $opid_value . '"', $nudges_section );
685 } else {
686 $nudges_section = preg_replace( '/<div\b/i', '<div data-opid="' . esc_attr( $opid_value ) . '"', $nudges_section, 1 );
687 }
688
689 // Ensure lang
690 if ( ! empty( $current_lang ) ) {
691 if ( preg_match( '/data-lang=/', $nudges_section ) ) {
692 $nudges_section = preg_replace( '/data-lang=("|\')(.*?)\1/', 'data-lang="' . $current_lang . '"', $nudges_section );
693 } else {
694 $nudges_section = preg_replace( '/<div\b/i', '<div data-lang="' . esc_attr( $current_lang ) . '"', $nudges_section, 1 );
695 }
696 }
697
698 return $nudges_section;
699 }
700 add_shortcode( 'wiserrw_product_nudges', 'wiserrw_product_nudges' );
701
702
703 // ─────────────────────────────────────────────────────────────────────────────
704 // LEAVE A REVIEW — My Account page + inline order links
705 // ─────────────────────────────────────────────────────────────────────────────
706
707 /**
708 * Build a review form URL for a given product.
709 *
710 * @param int $product_id WC product ID.
711 * @param int|null $order_id WC order ID (enables verified-buyer matching).
712 * @return string|false Full URL, or false if not configured.
713 */
714 function wiserrw_review_form_url( $product_id, $order_id = null ) {
715 $settings = get_option( 'wiserrw_api_settings', array() );
716
717 if ( empty( $settings['wiserrw_live_mode_checkbox'] ) || '1' !== $settings['wiserrw_live_mode_checkbox'] ) {
718 return false;
719 }
720
721 if ( empty( $settings['wiserrw_review_page_enabled'] ) || '1' !== $settings['wiserrw_review_page_enabled'] ) {
722 return false;
723 }
724
725 $base_url = $settings['wiserrw_review_form_url'] ?? '';
726 if ( ! $base_url || strpos( $base_url, 'https://rating.wiserreview.com/' ) !== 0 ) {
727 return false;
728 }
729
730 $params = array( 'pid' => (int) $product_id );
731 if ( $order_id ) {
732 $params['oid'] = (int) $order_id;
733 }
734
735 return $base_url . '?' . http_build_query( $params );
736 }
737
738 /**
739 * My Account endpoint: "leave-a-review"
740 */
741 define( 'WISERRW_REVIEW_ENDPOINT', 'leave-a-review' );
742
743 add_action( 'init', 'wiserrw_review_page_add_endpoint' );
744 function wiserrw_review_page_add_endpoint() {
745 $settings = get_option( 'wiserrw_api_settings', array() );
746 $enabled = ! empty( $settings['wiserrw_review_page_enabled'] ) && '1' === $settings['wiserrw_review_page_enabled'];
747
748 if ( $enabled ) {
749 add_rewrite_endpoint( WISERRW_REVIEW_ENDPOINT, EP_ROOT | EP_PAGES );
750 } else {
751 // Clean up stale rewrite rules if endpoint was previously registered
752 $rules = get_option( 'rewrite_rules', array() );
753 if ( is_array( $rules ) && isset( $rules[ WISERRW_REVIEW_ENDPOINT . '(/(.*))?/?$' ] ) ) {
754 flush_rewrite_rules();
755 }
756 }
757 }
758
759 add_filter( 'woocommerce_account_menu_items', 'wiserrw_review_page_menu_item' );
760 function wiserrw_review_page_menu_item( array $items ): array {
761 $settings = get_option( 'wiserrw_api_settings', array() );
762 if ( empty( $settings['wiserrw_review_page_enabled'] ) || '1' !== $settings['wiserrw_review_page_enabled'] ) {
763 return $items;
764 }
765
766 $new = array();
767 foreach ( $items as $key => $label ) {
768 if ( $key === 'customer-logout' ) {
769 $new[ WISERRW_REVIEW_ENDPOINT ] = __( 'Leave a Review', 'wiser-review' );
770 }
771 $new[ $key ] = $label;
772 }
773 return $new;
774 }
775
776 add_action( 'woocommerce_account_' . WISERRW_REVIEW_ENDPOINT . '_endpoint', 'wiserrw_review_page_content' );
777 function wiserrw_review_page_content() {
778 // Don't render if feature is disabled
779 $settings = get_option( 'wiserrw_api_settings', array() );
780 if ( empty( $settings['wiserrw_review_page_enabled'] ) || '1' !== $settings['wiserrw_review_page_enabled'] ) {
781 return;
782 }
783
784 $customer_id = get_current_user_id();
785 if ( ! $customer_id ) {
786 echo '<p>' . esc_html__( 'Please log in to see your purchased products.', 'wiser-review' ) . '</p>';
787 return;
788 }
789
790 $orders = wc_get_orders( array(
791 'customer_id' => $customer_id,
792 'status' => array( 'completed', 'processing' ),
793 'limit' => -1,
794 'orderby' => 'date',
795 'order' => 'DESC',
796 ) );
797
798 // Collect unique products, keeping most recent order ID per product
799 $products = array();
800 $order_ids = array();
801 foreach ( $orders as $order ) {
802 foreach ( $order->get_items() as $item ) {
803 $pid = $item->get_product_id();
804 if ( isset( $products[ $pid ] ) ) {
805 continue;
806 }
807 $product = $item->get_product();
808 if ( $product && $product->is_visible() ) {
809 $products[ $pid ] = $product;
810 $order_ids[ $pid ] = $order->get_id();
811 }
812 }
813 }
814
815 if ( empty( $products ) ) {
816 echo '<p>' . esc_html__( 'You have no completed orders with reviewable products yet.', 'wiser-review' ) . '</p>';
817 return;
818 }
819 ?>
820 <style>
821 .wiserrw-review-grid {
822 display: grid;
823 grid-template-columns: repeat( auto-fill, minmax( 200px, 1fr ) );
824 gap: 20px;
825 margin: 20px 0;
826 }
827 .wiserrw-review-card {
828 border: 1px solid #e5e5e5;
829 border-radius: 8px;
830 overflow: hidden;
831 display: flex;
832 flex-direction: column;
833 background: #fff;
834 transition: box-shadow .15s;
835 }
836 .wiserrw-review-card:hover { box-shadow: 0 4px 16px rgba(0,0,0,.1); }
837 .wiserrw-review-card-img {
838 width: 100%;
839 aspect-ratio: 1;
840 object-fit: cover;
841 background: #f7f7f7;
842 }
843 .wiserrw-review-card-img.placeholder {
844 display: flex;
845 align-items: center;
846 justify-content: center;
847 font-size: 40px;
848 color: #ccc;
849 }
850 .wiserrw-review-card-body {
851 padding: 12px;
852 flex: 1;
853 display: flex;
854 flex-direction: column;
855 gap: 10px;
856 }
857 .wiserrw-review-card-name {
858 font-size: 13px;
859 font-weight: 600;
860 line-height: 1.4;
861 color: #1d2327;
862 flex: 1;
863 }
864 .wiserrw-review-card-link {
865 display: block;
866 text-align: center;
867 padding: 8px 12px;
868 background: #2271b1;
869 color: #fff !important;
870 text-decoration: none !important;
871 border-radius: 5px;
872 font-size: 13px;
873 font-weight: 600;
874 transition: background .15s;
875 }
876 .wiserrw-review-card-link:hover { background: #135e96; }
877 </style>
878
879 <h3><?php esc_html_e( 'Leave a Review', 'wiser-review' ); ?></h3>
880 <p><?php esc_html_e( 'Thank you for shopping with us! Click any product below to leave a review.', 'wiser-review' ); ?></p>
881
882 <div class="wiserrw-review-grid">
883 <?php foreach ( $products as $pid => $product ) :
884 $review_url = wiserrw_review_form_url( $pid, $order_ids[ $pid ] ?? null );
885 $thumb_url = get_the_post_thumbnail_url( $product->get_id(), 'woocommerce_thumbnail' );
886 ?>
887 <div class="wiserrw-review-card">
888 <?php if ( $thumb_url ) : ?>
889 <img class="wiserrw-review-card-img"
890 src="<?php echo esc_url( $thumb_url ); ?>"
891 alt="<?php echo esc_attr( $product->get_name() ); ?>" loading="lazy" />
892 <?php else : ?>
893 <div class="wiserrw-review-card-img placeholder" aria-hidden="true">&#128230;</div>
894 <?php endif; ?>
895
896 <div class="wiserrw-review-card-body">
897 <div class="wiserrw-review-card-name">
898 <?php echo esc_html( $product->get_name() ); ?>
899 </div>
900
901 <?php if ( $review_url ) : ?>
902 <a href="<?php echo esc_url( $review_url ); ?>"
903 target="_blank"
904 rel="noopener noreferrer"
905 class="wiserrw-review-card-link">
906 &#11088; <?php esc_html_e( 'Leave a Review', 'wiser-review' ); ?>
907 </a>
908 <?php endif; ?>
909 </div>
910 </div>
911 <?php endforeach; ?>
912 </div>
913 <?php
914 }
915
916 /**
917 * Inline "Leave a Review" link per product on My Account → View Order page.
918 */
919 add_action( 'wp_enqueue_scripts', 'wiserrw_frontend_review_link_styles' );
920 function wiserrw_frontend_review_link_styles() {
921 if ( ! is_wc_endpoint_url() && ! is_account_page() ) {
922 return;
923 }
924 $settings = get_option( 'wiserrw_api_settings', array() );
925 if ( empty( $settings['wiserrw_review_page_enabled'] ) || '1' !== $settings['wiserrw_review_page_enabled'] ) {
926 return;
927 }
928 $css = '.wiserrw-inline-review-link {
929 display: inline-block; padding: 4px 10px; border: 1px solid currentColor;
930 border-radius: 4px; font-size: 12px; font-weight: 600;
931 text-decoration: none; opacity: 0.85; transition: opacity .15s; margin-top: 6px;
932 }
933 .wiserrw-inline-review-link:hover { opacity: 1; text-decoration: none; }
934 .woocommerce-MyAccount-navigation ul li.woocommerce-MyAccount-navigation-link--leave-a-review a::before {
935 content: "";
936 display: inline-block;
937 width: 16px; height: 16px;
938 margin-right: 6px;
939 vertical-align: middle;
940 background: currentColor;
941 -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'%3E%3Cpath d=\'M10 1.12l2.77 5.61 6.19.9-4.48 4.37 1.06 6.17L10 15.27l-5.54 2.9 1.06-6.17L1.04 7.63l6.19-.9L10 1.12z\'/%3E%3C/svg%3E") center/contain no-repeat;
942 mask: url("data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 20 20\'%3E%3Cpath d=\'M10 1.12l2.77 5.61 6.19.9-4.48 4.37 1.06 6.17L10 15.27l-5.54 2.9 1.06-6.17L1.04 7.63l6.19-.9L10 1.12z\'/%3E%3C/svg%3E") center/contain no-repeat;
943 }';
944 wp_register_style( 'wiserrw-review-link', false );
945 wp_enqueue_style( 'wiserrw-review-link' );
946 wp_add_inline_style( 'wiserrw-review-link', $css );
947 }
948
949 add_action( 'woocommerce_order_item_meta_end', 'wiserrw_inline_order_review_link', 10, 3 );
950 function wiserrw_inline_order_review_link( $item_id, $item, $order ) {
951 // Only show on My Account pages
952 if ( ! is_account_page() ) {
953 return;
954 }
955 if ( ! $item instanceof WC_Order_Item_Product ) {
956 return;
957 }
958 $product = $item->get_product();
959 if ( ! $product ) {
960 return;
961 }
962 $url = wiserrw_review_form_url( $product->get_id(), $order->get_id() );
963 if ( ! $url ) {
964 return;
965 }
966 echo '<div>'
967 . '<a href="' . esc_url( $url ) . '" '
968 . 'target="_blank" rel="noopener noreferrer" '
969 . 'class="wiserrw-inline-review-link">'
970 . '&#11088;&nbsp;' . esc_html__( 'Leave a Review', 'wiser-review' )
971 . '</a>'
972 . '</div>';
973 }
974
975 /**
976 * Flush rewrite rules when the review page feature is toggled.
977 */
978 register_activation_hook( WISERRW_PLUGIN_FILE, 'wiserrw_review_page_flush_on_activate' );
979 function wiserrw_review_page_flush_on_activate() {
980 wiserrw_review_page_add_endpoint();
981 flush_rewrite_rules();
982 }
983
984 register_deactivation_hook( WISERRW_PLUGIN_FILE, 'wiserrw_review_page_flush_on_deactivate' );
985 function wiserrw_review_page_flush_on_deactivate() {
986 // Remove the endpoint by not registering it, then flush to clean up rewrite rules
987 remove_action( 'init', 'wiserrw_review_page_add_endpoint' );
988 flush_rewrite_rules();
989 }
990
991
992 /**
993 * Send WooCommercer Order Data.
994 *
995 * @param int $order_id API Key to validate.
996 */
997 function wiserrw_woocommerce_order_status_completed( $order_id ) {
998 $wiserrw_api_settings = get_option('wiserrw_api_settings', array());
999 if (empty($wiserrw_api_settings) || empty($wiserrw_api_settings['wiserrw_api_key'])) {
1000 return; // Exit if no API settings
1001 }
1002
1003 $api_key = $wiserrw_api_settings['wiserrw_api_key'];
1004 $wsid = get_option( 'wiserrw_wsid', true );
1005 $automation_id = get_option( 'wiserrw_automation_id', true );
1006 if (!isset($wsid) || !isset($automation_id)) {
1007 return; // Exit if invalid API data
1008 }
1009
1010 $order = wc_get_order($order_id);
1011 if (!$order) {
1012 return; // Exit if invalid order
1013 }
1014
1015 $api_endpoint = WISERRW_API_HOST . 'webhook?wsid=' . $wsid . '&atmid=' . $automation_id.'&ver='.WISERRW_PLUGIN_VERSION;
1016
1017 // Initialize arrays
1018 $wiserrw_order_data = array();
1019 $customer_data = array();
1020
1021 /* Order Details */
1022 $wiserrw_order_data['oid'] = $order->get_id();
1023 $date_created = $order->get_date_created();
1024 $wiserrw_order_data['cdt'] = $date_created ? $date_created->format('Y-m-d H:i:s') : current_time('mysql');
1025 $wiserrw_order_data['orderAmount'] = $order->get_total();
1026
1027 /* Shipping Details */
1028 $country = $order->get_shipping_country();
1029 $state = $order->get_shipping_state();
1030 // Fallback to billing if shipping not available
1031 if ( empty( $country ) ) {
1032 $country = $order->get_billing_country();
1033 }
1034 if ( empty( $state ) ) {
1035 $state = $order->get_billing_state();
1036 }
1037
1038 /* Customer Details */
1039 $customer_data['email'] = $order->get_billing_email();
1040 $customer_data['phone'] = $order->get_billing_phone();
1041 $customer_data['first_name'] = $order->get_billing_first_name();
1042 $customer_data['last_name'] = $order->get_billing_last_name();
1043
1044 // Address
1045 $customer_data['cntry'] = $country;
1046 $customer_data['state'] = $state;
1047
1048 $wiserrw_order_data['customer'] = $customer_data;
1049
1050 /* Product Details */
1051 $wiserrw_order_data['line_items'] = array(); // Initialize line items array
1052 $items = $order->get_items();
1053
1054 foreach ( $items as $item ) {
1055 $product = $item->get_product();
1056 if (!$product || !$product instanceof WC_Product) {
1057 continue; // Skip if product doesn't exist anymore
1058 }
1059
1060 $product_id = $item->get_product_id();
1061 $product_price = $product->get_price();
1062 $url = get_permalink($product_id);
1063 $product_name = $item->get_name();
1064
1065 // --- Multilingual handling (Added for opid/lang) ---
1066 $original_pid = 0;
1067 $current_lang = '';
1068
1069 // WPML
1070 if ( has_filter( 'wpml_element_trid' ) ) {
1071 $trid = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
1072 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : [];
1073 $current_lang = apply_filters( 'wpml_current_language', null );
1074 if ( $translations && is_array( $translations ) ) {
1075 foreach ( $translations as $lang => $translation ) {
1076 if ( ! empty( $translation->original ) ) {
1077 $original_pid = (int) $translation->element_id;
1078 }
1079 }
1080 }
1081 }
1082
1083 // Polylang
1084 if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
1085 $current_lang = pll_current_language();
1086 $default_lang = pll_default_language();
1087 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
1088 $original_pid = pll_get_post( $product_id, $default_lang );
1089 }
1090 }
1091
1092 // Fallback (if no translation plugin, opid = current product id)
1093 $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
1094
1095 // Safely get product image
1096 $image = wp_get_attachment_image_src(get_post_thumbnail_id($product_id), 'single-post-thumbnail');
1097 $image_url = is_array($image) ? $image[0] : '';
1098
1099 // Create new product data array for each item
1100 $product_data = array(
1101 'id' => $product_id,
1102 'pn' => $product_name,
1103 'name' => $product_name,
1104 'prc' => $product_price,
1105 'pu' => $url,
1106 'piu' => $image_url,
1107 'opid' => $opid_value, // �
1108 Added
1109 'lang' => $current_lang ?: '' // �
1110 Added
1111 );
1112
1113 $wiserrw_order_data['line_items'][] = $product_data;
1114 }
1115
1116 // Send data to API
1117 $response = wp_remote_post(
1118 $api_endpoint,
1119 array(
1120 'method' => 'POST',
1121 'headers' => array(
1122 'Content-Type' => 'application/json',
1123 'x-wiserrw-api-key' => $api_key,
1124 ),
1125 'timeout' => 45,
1126 'redirection' => 5,
1127 'httpversion' => '1.0',
1128 'blocking' => true,
1129 'body' => wp_json_encode($wiserrw_order_data),
1130 )
1131 );
1132
1133 if (is_wp_error($response)) {
1134 $error_message = $response->get_error_message();
1135 error_log(sprintf(
1136 'WiserReview API Error (Order #%d): %s',
1137 $order_id,
1138 $error_message
1139 ));
1140 } else {
1141 $response_code = wp_remote_retrieve_response_code($response);
1142 if ($response_code !== 200) {
1143 $response_body = wp_remote_retrieve_body($response);
1144 error_log(sprintf(
1145 'WiserReview API Non-200 Response (Order #%d): Code %d - %s',
1146 $order_id,
1147 $response_code,
1148 $response_body
1149 ));
1150 }
1151 }
1152 }
1153
1154 add_action( 'woocommerce_order_status_completed', 'wiserrw_woocommerce_order_status_completed' );
1155
1156 /**
1157 * Export Orders.
1158 */
1159 function wiserrw_export_orders() {
1160 check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
1161 if ( ! current_user_can( 'manage_woocommerce' ) ) {
1162 wp_send_json_error( array( 'message' => 'You are not allowed to perform this action.' ), 403 );
1163 }
1164 $from_date = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
1165 $to_date = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';
1166 $duration = isset( $_POST['duration'] ) ? sanitize_text_field( wp_unslash( $_POST['duration'] ) ) : '';
1167 $offset = isset( $_POST['offset'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['offset'] ) ) ) : '';
1168 $limit = isset( $_POST['limit'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['limit'] ) ) ) : '';
1169
1170 if ( '' !== $duration ) {
1171 switch ( $duration ) {
1172 case '30_days':
1173 $date_from = strtotime( '-30 days' );
1174 break;
1175 case '3_months':
1176 $date_from = strtotime( '-3 months' );
1177 break;
1178 case '6_months':
1179 $date_from = strtotime( '-6 months' );
1180 break;
1181 case '1_year':
1182 $date_from = strtotime( '-1 year' );
1183 break;
1184 case '7_days':
1185 $date_from = strtotime( '-7 days' );
1186 break;
1187 default:
1188 $date_from = strtotime( '-7 days' );
1189 break;
1190 }
1191 // Get orders between date_from and now
1192 $date_to = current_time('timestamp');
1193 $all_orders = wc_get_orders(
1194 array(
1195 'status' => array( 'completed', 'processing' ),
1196 'date_created' => $date_from . '...' . $date_to, // Using WooCommerce's date range format
1197 'limit' => -1,
1198 )
1199 );
1200 } else {
1201 // For custom date range, ensure we get full days
1202 $start_date = strtotime($from_date . ' 00:00:00');
1203 $end_date = strtotime($to_date . ' 23:59:59');
1204
1205 if (!$start_date || !$end_date) {
1206 wp_send_json_error(array('message' => 'Invalid date range provided'));
1207 die();
1208 }
1209
1210 $all_orders = wc_get_orders(
1211 array(
1212 'status' => array( 'completed', 'processing' ),
1213 'date_created' => $start_date . '...' . $end_date, // Using WooCommerce's date range format
1214 'limit' => -1,
1215 )
1216 );
1217 }
1218 $total = count( $all_orders );
1219 $chunk_ids = array_slice( $all_orders, $offset, $limit );
1220 $upload_dir = wp_upload_dir();
1221 $filename = 'orders-export-progress.csv';
1222 $filepath = $upload_dir['basedir'] . '/' . $filename;
1223 $is_first_chunk = 0 === $offset;
1224
1225 $fp = fopen( $filepath, $is_first_chunk ? 'w' : 'a' );
1226 if ( $fp ) {
1227 if ( $is_first_chunk ) {
1228 fputcsv( $fp, array('Order Id', 'First Name', 'Last Name', 'Phone', 'Email', 'Country', 'State', 'Product Title', 'Product URL', 'Product Image URL', 'Product ID', 'Reviewer Image URL', 'Order Date' ) );
1229 }
1230
1231 foreach ( $chunk_ids as $order) {
1232 $order_id = $order->get_id();
1233 $user_id = $order->get_user_id();
1234
1235
1236 $country = $order->get_shipping_country();
1237 $state = $order->get_shipping_state();
1238
1239 // Fallback to billing if shipping not available
1240 if ( empty( $country ) ) {
1241 $country = $order->get_billing_country();
1242 }
1243 if ( empty( $state ) ) {
1244 $state = $order->get_billing_state();
1245 }
1246
1247 /* Product Details */
1248 $items = $order->get_items();
1249 foreach ( $items as $item ) {
1250 $product = $item->get_product();
1251 if (!$product) {
1252 continue;
1253 }
1254
1255 $product_id = $item->get_product_id();
1256 $product_price = $product->get_price();
1257 $url = get_permalink( $product_id );
1258 $product_name = $item->get_name();
1259 $image = wp_get_attachment_image_src( get_post_thumbnail_id( $product_id ), 'single-post-thumbnail' );
1260 $image_url = is_array($image) ? $image[0] : '';
1261 $reviewer_url = get_avatar_url( $user_id );
1262 $products_str[] = $product->get_name() . "|" . $image_url;
1263
1264 fputcsv(
1265 $fp,
1266 array(
1267 $order_id,
1268 $order->get_billing_first_name(),
1269 $order->get_billing_last_name(),
1270 $order->get_billing_phone(),
1271 $order->get_billing_email(),
1272 $country,
1273 $state,
1274 $product->get_name(),
1275 $url,
1276 $image_url,
1277 $order->get_id(),
1278 $reviewer_url,
1279 $order->get_date_created()->date( 'Y-m-d H:i:s' ),
1280 )
1281 );
1282 }
1283 }
1284
1285 fclose( $fp );
1286 }
1287
1288 $processed = $offset + count( $chunk_ids );
1289 $percent = round( ( $processed / $total ) * 100 );
1290
1291 wp_send_json_success(
1292 array(
1293 'percent' => $percent,
1294 'done' => $processed >= $total,
1295 'file_url' => $upload_dir['baseurl'] . '/' . $filename,
1296 )
1297 );
1298 }
1299
1300 add_action( 'wp_ajax_wiserrw_export_orders', 'wiserrw_export_orders' );
1301
1302 /**
1303 * Bulk Orders API.
1304 */
1305 function wiserrw_bulk_orders_send() {
1306
1307 check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
1308
1309 if ( ! current_user_can( 'manage_woocommerce' ) ) {
1310 wp_send_json_error(
1311 array( 'message' => __( 'You are not allowed to perform this action.', 'wiser-review' ) ),
1312 403
1313 );
1314 }
1315
1316 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
1317 $api_key = $wiserrw_api_settings['wiserrw_api_key'];
1318 $wsid = get_option( 'wiserrw_wsid', true );
1319 $automation_id = get_option( 'wiserrw_automation_id', true );
1320
1321 if ( !isset($wsid) || !isset($automation_id) ) {
1322 return; // Exit if invalid API data
1323 }
1324
1325 $wiserrw_order_data = array();
1326 $customer_data = array();
1327 $api_endpoint = WISERRW_API_HOST . 'webhook?wsid='
1328 . $wsid . '&atmid=' . $automation_id . '&ver=' . WISERRW_PLUGIN_VERSION;
1329
1330 $from_date = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
1331 $to_date = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';
1332 $duration = isset( $_POST['duration'] ) ? sanitize_text_field( wp_unslash( $_POST['duration'] ) ) : '';
1333 $offset = isset( $_POST['offset'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['offset'] ) ) ) : '';
1334 $limit = isset( $_POST['limit'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['limit'] ) ) ) : '';
1335
1336 if ( '' !== $duration ) {
1337 switch ( $duration ) {
1338 case '30_days': $date_from = strtotime( '-30 days' ); break;
1339 case '3_months': $date_from = strtotime( '-3 months' ); break;
1340 case '6_months': $date_from = strtotime( '-6 months' ); break;
1341 case '1_year': $date_from = strtotime( '-1 year' ); break;
1342 case '7_days': $date_from = strtotime( '-7 days' ); break;
1343 default: $date_from = strtotime( '-7 days' ); break;
1344 }
1345
1346 $date_to = current_time('timestamp');
1347 $all_orders = wc_get_orders(array(
1348 'status' => array( 'completed', 'processing' ),
1349 'date_created' => $date_from . '...' . $date_to,
1350 'limit' => -1,
1351 ));
1352
1353 } else {
1354 $start_date = new WC_DateTime($from_date);
1355 $end_date = new WC_DateTime($to_date);
1356 $all_orders = wc_get_orders(array(
1357 'status' => array( 'completed', 'processing' ),
1358 'date_created' => $start_date->getTimestamp() . '...' . $end_date->getTimestamp(),
1359 'limit' => -1,
1360 ));
1361 }
1362
1363 $total = count( $all_orders );
1364 if ( $total === 0 ) {
1365 wp_send_json_success(array(
1366 'percent' => 100,
1367 'done' => true,
1368 'message' => 'No orders found for selected range.',
1369 ));
1370 wp_die();
1371 }
1372
1373 if ( $total > 0 ) {
1374 $chunk_ids = array_slice( $all_orders, $offset, $limit );
1375 $api_errors = array();
1376
1377 foreach ( $chunk_ids as $order ) {
1378 if ( $order instanceof WC_Order_Refund ) {
1379 continue; // skip refunds
1380 }
1381
1382 $order_id = $order->get_id();
1383
1384 /* Order Details */
1385 $wiserrw_order_data['oid'] = $order_id;
1386 $wiserrw_order_data['cdt'] = $order->get_date_created()
1387 ? $order->get_date_created()->format('Y-m-d H:i:s') // �
1388 Clean date format
1389 : current_time('mysql');
1390 $wiserrw_order_data['orderAmount'] = $order->get_total();
1391
1392 /* Shipping Details */
1393 $country = $order->get_shipping_country();
1394 $state = $order->get_shipping_state();
1395
1396 // Fallback to billing if shipping not available
1397
1398 if ( empty( $country ) ) {
1399 $country = $order->get_billing_country();
1400 }
1401 if ( empty( $state ) ) {
1402 $state = $order->get_billing_state();
1403 }
1404
1405
1406
1407 /* Customer Details */
1408 $customer_data = array(
1409 'email' => $order->get_billing_email(),
1410 'phone' => $order->get_billing_phone(),
1411 'first_name' => $order->get_billing_first_name(),
1412 'last_name' => $order->get_billing_last_name(),
1413
1414 );
1415
1416 $wiserrw_order_data['cntry'] = $country;
1417 $wiserrw_order_data['state'] = $state;
1418 $wiserrw_order_data['customer'] = $customer_data;
1419
1420 /* Product Details */
1421 $wiserrw_order_data['line_items'] = array();
1422 foreach ( $order->get_items() as $item ) {
1423 $product = $item->get_product();
1424 if ( !$product || !$product instanceof WC_Product ) {
1425 continue;
1426 }
1427
1428 $product_id = $item->get_product_id();
1429 $product_name = $item->get_name();
1430 $product_price = $product->get_price();
1431 $url = get_permalink($product_id);
1432 $image = wp_get_attachment_image_src(get_post_thumbnail_id($product_id), 'single-post-thumbnail');
1433 $image_url = is_array($image) ? $image[0] : '';
1434
1435 // --- Multilingual handling ---
1436 $original_pid = 0;
1437 $current_lang = '';
1438
1439 // WPML
1440 if ( has_filter( 'wpml_element_trid' ) ) {
1441 $trid = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
1442 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : [];
1443 $current_lang = apply_filters( 'wpml_current_language', null );
1444 if ( $translations && is_array( $translations ) ) {
1445 foreach ( $translations as $lang => $translation ) {
1446 if ( ! empty( $translation->original ) ) {
1447 $original_pid = (int) $translation->element_id;
1448 }
1449 }
1450 }
1451 }
1452
1453 // Polylang
1454 if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
1455 $current_lang = pll_current_language();
1456 $default_lang = pll_default_language();
1457 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
1458 $original_pid = pll_get_post( $product_id, $default_lang );
1459 }
1460 }
1461
1462 // Fallback
1463 $opid_value = ( $original_pid && $original_pid !== $product_id ) ? $original_pid : $product_id;
1464
1465 $product_data = array(
1466 'id' => $product_id,
1467 'pn' => $product_name,
1468 'name' => $product_name,
1469 'prc' => $product_price,
1470 'pu' => $url,
1471 'piu' => $image_url,
1472 'opid' => $opid_value, // �
1473 Added
1474 'lang' => $current_lang ?: '' // �
1475 Added
1476 );
1477
1478 $wiserrw_order_data['line_items'][] = $product_data;
1479 }
1480
1481 // Send to API
1482 $response = wp_remote_post($api_endpoint, array(
1483 'method' => 'POST',
1484 'headers' => array(
1485 'Content-Type' => 'application/json',
1486 'x-wiserrw-api-key' => $api_key,
1487 ),
1488 'timeout' => 45,
1489 'blocking' => true,
1490 'body' => wp_json_encode($wiserrw_order_data),
1491 ));
1492
1493 if ( is_wp_error($response) ) {
1494 $api_errors[] = $response->get_error_message();
1495 } else {
1496 $code = wp_remote_retrieve_response_code($response);
1497 if ( $code !== 200 ) {
1498 $api_errors[] = wp_remote_retrieve_body($response);
1499 }
1500 }
1501 }
1502
1503 $processed = $offset + count( $chunk_ids );
1504 $percent = min(round( ( $processed / $total ) * 100 ), 100);
1505 $is_done = $processed >= $total;
1506
1507 wp_send_json_success(array(
1508 'percent' => $percent,
1509 'done' => $is_done,
1510 'total_processed' => $is_done ? $total : $processed,
1511 'total_orders' => $total,
1512 'processed_chunk' => count($chunk_ids),
1513 'api_error_messages' => $api_errors
1514 ));
1515 }
1516
1517 die();
1518 }
1519
1520 add_action( 'wp_ajax_wiserrw_bulk_orders_send', 'wiserrw_bulk_orders_send' );
1521
1522 /**
1523 * Bulk Orders count API.
1524 */
1525 function wiserrw_fetch_order_count() {
1526 check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
1527 if ( ! current_user_can( 'manage_woocommerce' ) ) {
1528 wp_send_json_error( array( 'message' => __('You are not allowed to perform this action.', 'wiser-review' ) ), 403 );
1529 }
1530 $duration = isset( $_POST['duration'] ) ? sanitize_text_field( wp_unslash( $_POST['duration'] ) ) : '';
1531 $from_date = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
1532 $to_date = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';
1533 $offset = isset( $_POST['offset'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['offset'] ) ) ) : '';
1534 $limit = isset( $_POST['limit'] ) ? intval( sanitize_text_field( wp_unslash( $_POST['limit'] ) ) ) : '';
1535 if ( '' !== $duration ) {
1536 switch ( $duration ) {
1537 case '30_days':
1538 $date_from = strtotime( '-30 days' );
1539 break;
1540 case '3_months':
1541 $date_from = strtotime( '-3 months' );
1542 break;
1543 case '6_months':
1544 $date_from = strtotime( '-6 months' );
1545 break;
1546 case '1_year':
1547 $date_from = strtotime( '-1 year' );
1548 break;
1549 case '7_days':
1550 $date_from = strtotime( '-7 days' );
1551 break;
1552 default:
1553 $date_from = strtotime( '-7 days' );
1554 break;
1555 }
1556 $all_orders = wc_get_orders(
1557 array(
1558 'status' => array( 'completed', 'processing' ),
1559 'date_created' => '>' . gmdate( 'Y-m-d H:i:s', $date_from ),
1560 'limit' => -1,
1561 )
1562 );
1563 } else {
1564 $start_date = new WC_DateTime($from_date);
1565 $end_date = new WC_DateTime($to_date);
1566 $all_orders = wc_get_orders(
1567 array(
1568 'status' => array( 'completed', 'processing' ),
1569 'date_created' => gmdate( $start_date . '...' . $end_date ),
1570 'limit' => -1,
1571 )
1572 );
1573 }
1574
1575 $total = count( $all_orders );
1576 wp_send_json_success(
1577 array(
1578 'total_orders' => $total,
1579 )
1580 );
1581 }
1582 add_action( 'wp_ajax_wiserrw_fetch_order_count', 'wiserrw_fetch_order_count' );
1583
1584 function wiserrw_count_products_with_reviews() {
1585 global $wpdb;
1586
1587 return $wpdb->get_var("
1588 SELECT count(ID)
1589 FROM {$wpdb->posts}
1590 WHERE post_type = 'product'
1591 AND post_status IN ('publish', 'draft')");
1592 }
1593
1594 function wiserrw_get_products( $page, $per_page ) {
1595 global $wpdb;
1596 $offset = ( $page - 1 ) * $per_page;
1597
1598 return $wpdb->get_results( $wpdb->prepare( "
1599 SELECT ID,
1600 post_title as title,
1601 post_name as slug,
1602 post_status,
1603 comment_count
1604 FROM {$wpdb->posts}
1605 WHERE post_type = 'product'
1606 AND post_status IN ('publish', 'draft')
1607 LIMIT %d, %d
1608 ", $offset, $per_page ), ARRAY_A );
1609 }
1610
1611
1612 function wiserrw_sync_products() {
1613 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
1614 $api_host = WISERRW_API_HOST;
1615 $per_page = isset( $_REQUEST['per_page'] ) ? intval( $_REQUEST['per_page'] ) : 50;
1616 $api_key = $wiserrw_api_settings['wiserrw_api_key'];
1617 $wsid = get_option( 'wiserrw_wsid', true );
1618 $automation_id = get_option( 'wiserrw_automation_id', true );
1619
1620 if ( ! $wsid || ! $automation_id ) {
1621 wp_die();
1622 }
1623
1624 // Pagination setup
1625 if ( isset( $_REQUEST['total_pages'] ) ) {
1626 $total_pages = (int) sanitize_text_field( wp_unslash( $_REQUEST['total_pages'] ) );
1627 } else {
1628 $count = wiserrw_count_products_with_reviews();
1629 $total_pages = (int) ceil( $count / max( 1, $per_page ) );
1630 }
1631
1632 $page = isset( $_REQUEST['page'] ) ? (int) sanitize_text_field( wp_unslash( $_REQUEST['page'] ) ) : 1;
1633 $finished = 0;
1634
1635 if ( $page <= $total_pages ) {
1636 $message = "Synced $page / $total_pages pages";
1637 $products = wiserrw_get_products( $page, $per_page );
1638 $products_json = [];
1639
1640 foreach ( $products as $prod ) {
1641 $p_id = (int) $prod['ID'];
1642 $registered = get_post_meta( $p_id, '_wiserrw_product_registered', true );
1643 if ( $registered ) {
1644 continue;
1645 }
1646
1647 $pr = wc_get_product( $p_id );
1648 if ( ! $pr ) {
1649 continue;
1650 }
1651
1652 // Collect SKUs
1653 $arrsku = [];
1654 if ( $pr->is_type( 'variable' ) ) {
1655 $parent_sku = (string) $pr->get_sku();
1656 if ( $parent_sku !== '' ) {
1657 $arrsku[] = $parent_sku;
1658 }
1659 $prod_variation = new WC_Product_Variable( $p_id );
1660 foreach ( $prod_variation->get_children() as $vid ) {
1661 $vsku = (string) get_post_meta( $vid, '_sku', true );
1662 if ( $vsku !== '' ) {
1663 $arrsku[] = $vsku;
1664 }
1665 }
1666 } else {
1667 $pr_sku = (string) $pr->get_sku();
1668 if ( $pr_sku !== '' ) {
1669 $arrsku[] = $pr_sku;
1670 }
1671 }
1672
1673 // Representative barcode
1674 $gtin = get_post_meta( $p_id, 'hwp_product_gtin', true )
1675 ?: $pr->get_attribute( 'GTIN' )
1676 ?: $pr->get_attribute( 'EAN' )
1677 ?: $pr->get_attribute( 'ISBN' )
1678 ?: get_post_meta( $p_id, '_global_unique_id', true );
1679
1680 // Title and URL
1681 $title = $prod['title'];
1682 if ( empty( $title ) ) {
1683 $cats = get_the_terms( $p_id, 'product_cat' );
1684 $title = ( ! empty( $cats ) && ! is_wp_error( $cats ) ) ? $cats[0]->name : get_bloginfo( 'name' );
1685 }
1686
1687 $product_url = get_permalink( $p_id );
1688 if ( ! $product_url || $prod['post_status'] === 'draft' ) {
1689 if ( $prod['post_status'] === 'draft' ) {
1690 $product_url = home_url( '/?post_type=product&p=' . $p_id );
1691 } else {
1692 $shop_page_url = get_permalink( wc_get_page_id( 'shop' ) );
1693 $product_url = $shop_page_url ? $shop_page_url : home_url( '/product/' );
1694 $product_url = trailingslashit( $product_url ) . sanitize_title( $title );
1695 }
1696 }
1697
1698 // Image URL
1699 $img_src = wp_get_attachment_image_src( get_post_thumbnail_id( $p_id ), 'shop_single' );
1700 $img_url = ( is_array( $img_src ) && isset( $img_src[0] ) ) ? $img_src[0] : wc_placeholder_img_src( 'shop_single' );
1701
1702 // --- Multilingual handling (product-specific) ---
1703 $original_pid = 0;
1704 $current_lang = '';
1705
1706 // WPML
1707 if ( has_filter( 'wpml_element_trid' ) ) {
1708 $trid = apply_filters( 'wpml_element_trid', null, $p_id, 'post_product' );
1709 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : [];
1710 if ( $translations && is_array( $translations ) ) {
1711 foreach ( $translations as $lang => $translation ) {
1712 if ( ! empty( $translation->original ) ) {
1713 $original_pid = (int) $translation->element_id;
1714 }
1715 if ( (int) $translation->element_id === $p_id ) {
1716 $current_lang = $lang; // �
1717 product-specific language
1718 }
1719 }
1720 }
1721 }
1722
1723 // Polylang
1724 if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_get_post_language' ) ) {
1725 $current_lang = pll_get_post_language( $p_id ); // �
1726 product-specific language
1727 $default_lang = function_exists( 'pll_default_language' ) ? pll_default_language() : '';
1728 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
1729 $original_pid = pll_get_post( $p_id, $default_lang );
1730 }
1731 }
1732
1733 // Fallback
1734 $opid_value = ( $original_pid && $original_pid !== $p_id ) ? $original_pid : $p_id;
1735
1736 // Build product payload
1737 $product_json = [
1738 'pid' => $p_id,
1739 'arrsku' => $arrsku,
1740 'shp' => get_site_url(),
1741 'wsid' => $wsid,
1742 'pn' => $title,
1743 'piu' => esc_url( $img_url ),
1744 'pu' => esc_url( $product_url ),
1745 'barcode' => (string) $gtin,
1746 'opid' => $opid_value, // �
1747 Added
1748 'lang' => $current_lang ?: '' // �
1749 Fixed: per-product lang
1750 ];
1751
1752 $products_json[] = $product_json;
1753
1754 // Mark as registered
1755 update_post_meta( $p_id, '_wiserrw_product_registered', '1' );
1756 }
1757
1758 if ( ! empty( $products_json ) ) {
1759 $data = [
1760 'method' => 'POST',
1761 'blocking' => true,
1762 'headers' => [
1763 'Content-Type' => 'application/json',
1764 'x-wiserrw-api-key' => $api_key,
1765 ],
1766 'body' => wp_json_encode( [
1767 'products' => $products_json,
1768 'ver' => WISERRW_PLUGIN_VERSION // �
1769 Added once at root level
1770 ] ),
1771 ];
1772 $url = $api_host . 'productWebhook?wsid=' . $wsid . '&key=' . $api_key;
1773 $response = wp_safe_remote_post( $url, $data );
1774 }
1775 } else {
1776 $message = 'Finished';
1777 $finished = 1;
1778 }
1779
1780 if ( $page == $total_pages ) {
1781 update_option( 'wiserrw_all_products_synced', 1 );
1782 }
1783
1784 $page++;
1785 $result = [
1786 'next_page' => $page,
1787 'total_pages' => $total_pages,
1788 'message' => $message,
1789 'finished' => $finished,
1790 ];
1791
1792 echo wp_json_encode( $result );
1793 wp_die();
1794 }
1795
1796
1797 add_action( 'wp_ajax_wiserrw_sync_products', 'wiserrw_sync_products' );
1798 add_action( 'wp_ajax_nopriv_wiserrw_sync_products', 'wiserrw_sync_products' );
1799
1800
1801 function wiserrw_reset_products() {
1802 global $wpdb;
1803
1804 check_ajax_referer( 'wiserrw_export_orders_nonce', 'wiserrw_security' );
1805 if ( ! current_user_can( 'manage_woocommerce' ) ) {
1806 wp_send_json_error( array( 'message' => __('You are not allowed to perform this action.','wiser-review' ) ), 403 );
1807 }
1808 // Clear only the "registered" flag for all products
1809 $wpdb->query(
1810 $wpdb->prepare(
1811 "DELETE FROM {$wpdb->postmeta} WHERE meta_key = %s",
1812 '_wiserrw_product_registered'
1813 )
1814 );
1815
1816 // JSON response (unchanged style)
1817 $result = array(
1818 'message' => __( 'Sync status cleared. Click Sync Products to re-sync your product catalog with WiserReview.', 'wiser-review' ),
1819 );
1820 echo wp_json_encode( $result );
1821 wp_die();
1822 }
1823
1824 // Register the AJAX action
1825 add_action( 'wp_ajax_wiserrw_reset_products', 'wiserrw_reset_products' );
1826 add_action( 'wp_ajax_nopriv_wiserrw_reset_products', 'wiserrw_reset_products' );
1827
1828
1829
1830 function wiserrw_product_delete_hook( $p_id ) {
1831 if ( get_post_type( $p_id ) === 'product' ) {
1832 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
1833 $api_host = WISERRW_API_HOST;
1834 $api_key = $wiserrw_api_settings['wiserrw_api_key'];
1835 $wsid = get_option( 'wiserrw_wsid', true );
1836 $automation_id = get_option( 'wiserrw_automation_id', true );
1837 if (!isset($wsid) || !isset($automation_id)) {
1838 return; // Exit if invalid API data
1839 }
1840 $url = $api_host . 'productWebhook/'.$p_id.'?wsid='.$wsid.'&key='.$api_key;
1841 $data = array(
1842 'method' => 'DELETE',
1843 'headers' => array(
1844 'x-wiserrw-api-key' => $api_key,
1845 ),
1846 );
1847 $response = wp_remote_request( $url, $data );
1848 }
1849
1850 }
1851 add_action( 'deleted_post', 'wiserrw_product_delete_hook' );
1852 add_action( 'trashed_post', 'wiserrw_product_delete_hook' );
1853
1854 /* -------------------- Product Update Hook -------------------- */
1855
1856 // Only send when name / slug / image / SKU / barcode changed.
1857
1858
1859 add_action( 'woocommerce_after_product_object_save', 'wiserrw_product_update_hook', 99, 1 );
1860
1861 function wiserrw_product_update_hook( $product ) {
1862 if ( ! $product instanceof WC_Product ) {
1863 return;
1864 }
1865
1866 $p_id = $product->get_id();
1867
1868 // Skip autosaves / revisions
1869 if ( wp_is_post_autosave( $p_id ) || wp_is_post_revision( $p_id ) ) {
1870 return;
1871 }
1872
1873 $is_variation = $product instanceof WC_Product_Variation;
1874 $parent_id = $is_variation ? $product->get_parent_id() : $p_id;
1875
1876 // --- Current values we watch ---
1877 $name = (string) $product->get_name();
1878 $permalink = (string) get_permalink( $is_variation ? $parent_id : $p_id );
1879
1880 // Effective image id (variation image, else parent/simple image)
1881 $effective_image_id = (int) $product->get_image_id();
1882 if ( ! $effective_image_id ) {
1883 $effective_image_id = (int) get_post_thumbnail_id( $is_variation ? $parent_id : $p_id );
1884 }
1885 $image_id = (string) $effective_image_id;
1886
1887 // SKU fingerprint
1888 if ( $product->is_type( 'variable' ) && ! $is_variation ) {
1889 $child_skus = array();
1890 foreach ( $product->get_children() as $vid ) {
1891 $vsku = (string) get_post_meta( $vid, '_sku', true );
1892 if ( $vsku !== '' ) {
1893 $child_skus[] = $vsku;
1894 }
1895 }
1896 sort( $child_skus, SORT_NATURAL | SORT_FLAG_CASE );
1897 $sku_fingerprint = implode( ',', $child_skus );
1898 } else {
1899 $sku_fingerprint = (string) $product->get_sku();
1900 }
1901
1902 // Barcode
1903 if ( $is_variation ) {
1904 $barcode = get_post_meta( $p_id, 'hwp_var_gtin', true )
1905 ?: get_post_meta( $parent_id, 'hwp_product_gtin', true )
1906 ?: $product->get_attribute( 'GTIN' )
1907 ?: $product->get_attribute( 'EAN' )
1908 ?: $product->get_attribute( 'ISBN' )
1909 ?: get_post_meta( $p_id, '_global_unique_id', true );
1910 } else {
1911 $barcode = get_post_meta( $p_id, 'hwp_product_gtin', true )
1912 ?: $product->get_attribute( 'GTIN' )
1913 ?: $product->get_attribute( 'EAN' )
1914 ?: $product->get_attribute( 'ISBN' )
1915 ?: get_post_meta( $p_id, '_global_unique_id', true );
1916 }
1917 $barcode = (string) $barcode;
1918
1919 // Watch hash
1920 $new_hash = md5( $name . '|' . $permalink . '|' . $image_id . '|' . $sku_fingerprint . '|' . $barcode );
1921 $old_hash = (string) get_post_meta( $p_id, '_wiserrw_watch_hash', true );
1922 if ( $new_hash === $old_hash ) {
1923 return;
1924 }
1925
1926 // --- API settings ---
1927 $wiserrw_api_settings = get_option( 'wiserrw_api_settings' );
1928 if ( empty( $wiserrw_api_settings['wiserrw_api_key'] ) ) {
1929 return;
1930 }
1931 $api_host = WISERRW_API_HOST;
1932 $api_key = $wiserrw_api_settings['wiserrw_api_key'];
1933 $wsid = get_option( 'wiserrw_wsid', true );
1934 $automation_id = get_option( 'wiserrw_automation_id', true );
1935 if ( ! $wsid || ! $automation_id ) {
1936 return;
1937 }
1938
1939 // arrsku
1940 $arrsku = array();
1941 if ( $product->is_type( 'variable' ) && ! $is_variation ) {
1942 $parent_sku = (string) $product->get_sku();
1943 if ( $parent_sku !== '' ) {
1944 $arrsku[] = $parent_sku;
1945 }
1946 foreach ( $product->get_children() as $vid ) {
1947 $vsku = (string) get_post_meta( $vid, '_sku', true );
1948 if ( $vsku !== '' ) {
1949 $arrsku[] = $vsku;
1950 }
1951 }
1952 } else {
1953 if ( $sku_fingerprint !== '' ) {
1954 $arrsku[] = $sku_fingerprint;
1955 }
1956 }
1957
1958 // Image URL
1959 $thumb_id = $effective_image_id;
1960 $img_src = $thumb_id ? wp_get_attachment_image_src( $thumb_id, 'shop_single' ) : false;
1961 $img_url = ( is_array( $img_src ) && isset( $img_src[0] ) ) ? $img_src[0] : wc_placeholder_img_src( 'shop_single' );
1962
1963 // --- Multilingual handling (Added) ---
1964 $original_pid = 0;
1965 $current_lang = '';
1966
1967 // WPML
1968 if ( has_filter( 'wpml_element_trid' ) ) {
1969 $trid = apply_filters( 'wpml_element_trid', null, $p_id, 'post_product' );
1970 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
1971 if ( $translations && is_array( $translations ) ) {
1972 foreach ( $translations as $lang => $translation ) {
1973 if ( ! empty( $translation->original ) ) {
1974 $original_pid = (int) $translation->element_id;
1975 }
1976 if ( (int) $translation->element_id === $p_id ) {
1977 $current_lang = $lang;
1978 }
1979 }
1980 }
1981 }
1982
1983 // Polylang
1984 if ( function_exists( 'pll_get_post_language' ) ) {
1985 $current_lang = pll_get_post_language( $p_id );
1986 if ( function_exists( 'pll_default_language' ) ) {
1987 $default_lang = pll_default_language();
1988 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
1989 $original_pid = pll_get_post( $p_id, $default_lang );
1990 }
1991 }
1992 }
1993
1994 // Fallbacks
1995 $opid_value = ( $original_pid && $original_pid !== $p_id ) ? $original_pid : $p_id;
1996
1997 // --- Build product payload ---
1998 $product_json = array(
1999 'pid' => (string) $p_id,
2000 'arrsku' => $arrsku,
2001 'shp' => get_site_url(),
2002 'wsid' => $wsid,
2003 'pn' => $name ?: get_the_title( $parent_id ),
2004 'piu' => $img_url,
2005 'pu' => $permalink,
2006 'barcode' => $barcode,
2007 'opid' => $opid_value, // �
2008 Added
2009 'lang' => $current_lang ?: '' // �
2010 Added
2011 );
2012
2013 $args = array(
2014 'method' => 'POST',
2015 'blocking' => true,
2016 'headers' => array(
2017 'Content-Type' => 'application/json',
2018 'x-wiserrw-api-key' => $api_key,
2019 ),
2020 'body' => wp_json_encode( array(
2021 'products' => array( $product_json ),
2022 'ver' => WISERRW_PLUGIN_VERSION
2023 ) ),
2024 );
2025
2026 // Clear flag before send
2027 delete_post_meta( $p_id, '_wiserrw_product_registered' );
2028
2029 $response = wp_safe_remote_post( $api_host . 'productWebhook?wsid=' . $wsid . '&key=' . $api_key, $args );
2030 if ( is_wp_error( $response ) ) {
2031 return;
2032 }
2033
2034 if ( (int) wp_remote_retrieve_response_code( $response ) === 200 ) {
2035 update_post_meta( $p_id, '_wiserrw_product_registered', '1' );
2036 update_post_meta( $p_id, '_wiserrw_watch_hash', $new_hash );
2037 }
2038 }
2039
2040
2041
2042 function wiserrw_custom_endpoints() {
2043 register_rest_route(
2044 'wiserrw/v1',
2045 '/reviews',
2046 array(
2047 array(
2048 'methods' => 'POST',
2049 'permission_callback' => 'wiserrw_api_auth',
2050 'callback' => 'wiserrw_api_callback',
2051 'args' => array(),
2052 )
2053 )
2054 );
2055 }
2056 add_action( 'rest_api_init', 'wiserrw_custom_endpoints' );
2057
2058
2059 function wiserrw_api_auth( WP_REST_Request $request ) {
2060 $provided = $request->get_header( 'wiserrw-wsid' );
2061 if ( ! $provided ) {
2062 $provided = $request->get_param( 'wiserrw_wsid' );
2063 }
2064
2065 if ( ! is_string( $provided ) || $provided === '' ) {
2066 return new WP_Error(
2067 'wiserrw_auth_missing',
2068 'Missing API key. Send it in the API Key header or api_key.',
2069 array( 'status' => 401 )
2070 );
2071 }
2072
2073 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2074 $wiserrw_wsid = get_option( 'wiserrw_wsid', true );
2075 if ($wiserrw_wsid == '') {
2076 return new WP_Error(
2077 'wiserrw_auth_config',
2078 'API key not configured.',
2079 array( 'status' => 500 )
2080 );
2081 }
2082
2083 $api_key = $wiserrw_wsid;
2084 $valid_keys = array($api_key);
2085
2086 foreach ( $valid_keys as $valid ) {
2087 if ( hash_equals( $valid, $provided ) ) {
2088 return true;
2089 }
2090 } return new WP_Error(
2091 'wiserrw_auth_missing',
2092 'Invalid API key.',
2093 array( 'status' => 401 )
2094 );
2095 }
2096
2097
2098
2099 // === MAIN API ENDPOINT (returns instantly) ===
2100 function wiserrw_api_callback( WP_REST_Request $request ) {
2101
2102 $payload = array(
2103 'status' => 'ok',
2104 'method' => $request->get_method(),
2105 'time' => current_time( 'mysql', true ),
2106 'data' => $request->get_json_params() ?: $request->get_params(),
2107 );
2108
2109 // Fire background call to self (non-blocking)
2110 wp_remote_post(
2111 rest_url( 'wiserreview/v1/async' ),
2112 array(
2113 'blocking' => false,
2114 'timeout' => 3,
2115 'body' => array(
2116 'json_data' => wp_json_encode( $payload ),
2117 ),
2118 )
2119 );
2120
2121 // Return immediately
2122 return new WP_REST_Response( $payload, 200 );
2123 }
2124 add_action( 'rest_api_init', function() {
2125 register_rest_route( 'wiserreview/v1', '/api', array(
2126 'methods' => 'POST',
2127 'callback' => 'wiserrw_api_callback',
2128 'permission_callback' => '__return_true',
2129 ));
2130 });
2131
2132 // === ASYNC BACKGROUND HANDLER ===
2133 add_action( 'rest_api_init', function() {
2134 register_rest_route( 'wiserreview/v1', '/async', array(
2135 'methods' => 'POST',
2136 'callback' => 'wiserrw_async_handler',
2137 'permission_callback' => '__return_true',
2138 ));
2139 });
2140
2141 function wiserrw_async_handler( WP_REST_Request $request ) {
2142
2143 $json_data = $request->get_param( 'json_data' );
2144 if ( empty( $json_data ) ) {
2145 return new WP_REST_Response( array( 'error' => 'No data received' ), 400 );
2146 }
2147
2148 $payload = json_decode( $json_data, true );
2149 if ( empty( $payload['data'] ) || ! is_array( $payload['data'] ) ) {
2150 return new WP_REST_Response( array( 'error' => 'Invalid payload' ), 400 );
2151 }
2152
2153 // Build a fake REST request to reuse your existing logic
2154 $fake_request = new WP_REST_Request( 'POST', '/wiserreview/v1/api' );
2155
2156 foreach ( $payload['data'] as $key => $value ) {
2157 $fake_request->set_param( $key, $value );
2158 }
2159
2160 // Force JSON body so get_json_params() works inside callback
2161 $fake_request->set_body( wp_json_encode( $payload['data'] ) );
2162 $fake_request->set_header( 'Content-Type', 'application/json' );
2163
2164 // Run your main process in background
2165 do_action( 'wiserrw_endpoint_called', $fake_request );
2166
2167 return new WP_REST_Response( array( 'status' => 'background task started' ), 200 );
2168 }
2169
2170 // === MAIN LOGIC HANDLER ===
2171 function wiserrw_api_response_callback( $request ) {
2172
2173 // --- Safely parse JSON and normal params ---
2174 $raw = $request->get_body();
2175 $data = json_decode( $raw, true );
2176
2177 // Fallback if JSON body not parsed
2178 if ( empty( $data ) || ! is_array( $data ) ) {
2179 $data = $request->get_json_params() ?: $request->get_params();
2180 }
2181
2182 if ( empty( $data ) || ! is_array( $data ) ) {
2183 return;
2184 }
2185 // �
2186 CHANGED: read everything from body, not from query
2187 $wsid_param = $data['wsid'] ?? '';
2188 $verify_api = $data['verifyApi'] ?? '';
2189 $updtyp = $data['updtyp'] ?? '';
2190 $widget_id = $data['widgetId'] ?? '';
2191
2192 // Current workspace ID saved in site
2193 $wsid = get_option( 'wiserrw_wsid', '' );
2194
2195 switch ( $updtyp ) {
2196
2197 // --- CSS update ---
2198 case 'update_css':
2199 // �
2200 CHANGED: verifyApi now read from body (string "true")
2201 $is_verified = filter_var( $verify_api, FILTER_VALIDATE_BOOLEAN );
2202
2203 if ( $is_verified && $wsid !== '' && $wsid === $wsid_param ) {
2204 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2205 $api_key = $wiserrw_api_settings['wiserrw_api_key'] ?? '';
2206
2207 // Validate API and update CSS
2208 wiserrw_validate_api( $api_key );
2209 wiserrw_update_css( $wsid );
2210 }
2211 break;
2212
2213 // --- HTML update (clear widget cache) ---
2214 case 'update_html':
2215 if ( ! empty( $widget_id ) ) {
2216 $option_key = 'wiserreview_widget_' . $widget_id;
2217 delete_option( $option_key );
2218 wiserrw_update_html( $wsid, $widget_id );
2219 }
2220 break;
2221
2222 // --- Schema generation and async remote update ---
2223 case 'update_schema':
2224 if ( ! empty( $data['arrPid'] ) && is_array( $data['arrPid'] ) ) {
2225 $wsid_to_use = $data['wsid'] ?? $wsid;
2226 $async_api_settings = get_option( 'wiserrw_api_settings', array() );
2227 $async_api_key = $async_api_settings['wiserrw_api_key'] ?? '';
2228
2229 foreach ( $data['arrPid'] as $p_id ) {
2230 if ( ! empty( $p_id ) && is_numeric( $p_id ) ) {
2231
2232 // Run schema generation now (needs WP context)
2233 wiserrw_generate_schema( $p_id );
2234
2235 // Send async schema update (non-blocking)
2236 wp_remote_post(
2237 WISERRW_RS_HOST . '/api/wp/updSchema',
2238 array(
2239 'method' => 'POST',
2240 'headers' => array(
2241 'Content-Type' => 'application/json',
2242 'x-wiserrw-api-key' => $async_api_key,
2243 ),
2244 'body' => wp_json_encode( array(
2245 'wsid' => $wsid_to_use,
2246 'pid' => $p_id,
2247 'updtyp' => 'update_schema',
2248 'ver' => WISERRW_PLUGIN_VERSION,
2249 )),
2250 'blocking' => false,
2251 'timeout' => 3,
2252 )
2253 );
2254 }
2255 }
2256 }
2257 break;
2258
2259 default:
2260 // No action for unknown updtyp
2261 break;
2262 }
2263 }
2264 add_action( 'wiserrw_endpoint_called', 'wiserrw_api_response_callback' );
2265
2266 function wiserrw_update_css( $wsid ) {
2267 $url = WISERRW_RS_HOST . '/api/wp/updSchema';
2268 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2269 $api_key = $wiserrw_api_settings['wiserrw_api_key'] ?? '';
2270 $body = array(
2271 'wsid' => $wsid,
2272 'updtyp' => 'update_css',
2273 'ver' => WISERRW_PLUGIN_VERSION
2274 );
2275 $response = wp_remote_post(
2276 $url,
2277 array(
2278 'method' => 'POST',
2279 'headers' => array(
2280 'Content-Type' => 'application/json',
2281 'x-wiserrw-api-key' => $api_key,
2282 ),
2283 'body' => wp_json_encode( $body ),
2284 'timeout' => 30,
2285 )
2286 );
2287 }
2288
2289 function wiserrw_update_schema( $wsid, $pid ) {
2290 $url = WISERRW_RS_HOST . '/api/wp/updSchema';
2291 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2292 $api_key = $wiserrw_api_settings['wiserrw_api_key'] ?? '';
2293 $body = array(
2294 'wsid' => $wsid,
2295 'pid' => $pid,
2296 'updtyp' => 'update_schema',
2297 'ver' => WISERRW_PLUGIN_VERSION
2298 );
2299 $response = wp_remote_post(
2300 $url,
2301 array(
2302 'method' => 'POST',
2303 'headers' => array(
2304 'Content-Type' => 'application/json',
2305 'x-wiserrw-api-key' => $api_key,
2306 ),
2307 'body' => wp_json_encode( $body ),
2308 'timeout' => 30,
2309 )
2310 );
2311 }
2312
2313 function wiserrw_update_html( $wsid, $widget_id ) {
2314 $url = WISERRW_RS_HOST . '/api/wp/updSchema';
2315 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2316 $api_key = $wiserrw_api_settings['wiserrw_api_key'] ?? '';
2317 $body = array(
2318 'wsid' => $wsid,
2319 'widgetId' => $widget_id,
2320 'updtyp' => 'update_html',
2321 'ver' => WISERRW_PLUGIN_VERSION
2322 );
2323 $response = wp_remote_post(
2324 $url,
2325 array(
2326 'method' => 'POST',
2327 'headers' => array(
2328 'Content-Type' => 'application/json',
2329 'x-wiserrw-api-key' => $api_key,
2330 ),
2331 'body' => wp_json_encode( $body ),
2332 'timeout' => 30,
2333 )
2334 );
2335 }
2336
2337
2338 function wiserrw_get_lang_context( $product_id ) {
2339 $ctx = array(
2340 'original_pid' => 0,
2341 'current_pid' => (int) $product_id,
2342 'lang' => '',
2343 );
2344
2345 if ( function_exists( 'pll_get_post' ) ) {
2346 $ctx['original_pid'] = (int) pll_get_post( $product_id, pll_default_language() );
2347 $ctx['lang'] = (string) pll_get_post_language( $product_id );
2348 return $ctx;
2349 }
2350
2351 if ( has_filter( 'wpml_object_id' ) ) {
2352 $trid = apply_filters( 'wpml_element_trid', null, $product_id, 'post_product' );
2353 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
2354 if ( is_array( $translations ) ) {
2355 foreach ( $translations as $lang => $t ) {
2356 $eid = is_object($t) ? (int)$t->element_id : (int)($t['element_id'] ?? 0);
2357 if ( ! empty( $t->original ) || ( is_array($t) && ! empty($t['original']) ) ) {
2358 $ctx['original_pid'] = $eid;
2359 }
2360 if ( $eid === (int) $product_id ) {
2361 $ctx['lang'] = $lang;
2362 }
2363 }
2364 }
2365 }
2366
2367 return $ctx;
2368 }
2369
2370 function wiserrw_generate_schema( $p_id ) {
2371 if ( empty( $p_id ) || ! is_numeric( $p_id ) ) {
2372 return;
2373 }
2374
2375 // --- Multilingual handling (WPML + Polylang safe) ---
2376 $original_pid = 0;
2377 $current_lang = '';
2378
2379 // WPML
2380 if ( has_filter( 'wpml_element_trid' ) ) {
2381 $trid = apply_filters( 'wpml_element_trid', null, $p_id, 'post_product' );
2382 $translations = $trid ? apply_filters( 'wpml_get_element_translations', null, $trid, 'post_product' ) : array();
2383 $current_lang = apply_filters( 'wpml_current_language', null );
2384 if ( $translations && is_array( $translations ) ) {
2385 foreach ( $translations as $lang => $translation ) {
2386 if ( ! empty( $translation->original ) ) {
2387 $original_pid = (int) $translation->element_id;
2388 }
2389 }
2390 }
2391 }
2392
2393 // Polylang
2394 if ( function_exists( 'pll_get_post' ) && function_exists( 'pll_current_language' ) ) {
2395 $current_lang = pll_current_language();
2396 $default_lang = pll_default_language();
2397 if ( $current_lang && $default_lang && $current_lang !== $default_lang ) {
2398 $original_pid = pll_get_post( $p_id, $default_lang );
2399 }
2400 }
2401
2402 $opid_value = ( $original_pid && $original_pid !== $p_id ) ? $original_pid : $p_id;
2403 $lang_value = $current_lang ?: '';
2404
2405 // --- API settings ---
2406 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2407 if ( ! is_array( $wiserrw_api_settings ) || empty( $wiserrw_api_settings['wiserrw_api_key'] ) ) {
2408 return;
2409 }
2410
2411 $wsid = get_option( 'wiserrw_wsid', true );
2412 $automation_id = get_option( 'wiserrw_automation_id', true );
2413 if ( ! $wsid || ! $automation_id ) {
2414 return;
2415 }
2416
2417 $api_key = $wiserrw_api_settings['wiserrw_api_key'];
2418 $url = WISERRW_RS_HOST . '/api/getData';
2419 $body = array(
2420 'arrType' => array( 'main', 'star_rating' ),
2421 'pid' => (string) $p_id,
2422 'opid' => (string) $opid_value,
2423 'lang' => $lang_value,
2424 'arrPid' => array( (string) $p_id ),
2425 'isFrom' => 'woocommerce',
2426 'wsid' => $wsid,
2427 'updtyp' => 'update_schema',
2428 'ver' => WISERRW_PLUGIN_VERSION
2429 );
2430
2431 $response = wp_remote_post(
2432 $url,
2433 array(
2434 'method' => 'POST',
2435 'headers' => array(
2436 'Content-Type' => 'application/json',
2437 'x-wiserrw-api-key' => $api_key,
2438 ),
2439 'body' => wp_json_encode( $body ),
2440 'timeout' => 30,
2441 )
2442 );
2443
2444 if ( is_wp_error( $response ) ) {
2445 return;
2446 }
2447
2448 $json = wp_remote_retrieve_body( $response );
2449 $data = json_decode( $json, true );
2450 if ( empty( $data['data'] ) ) {
2451 return;
2452 }
2453
2454 $schema_array = array();
2455 $reviewCount = 0;
2456
2457 foreach ( $data['data'] as $row ) {
2458 $wdtyp = $row['wdtyp'];
2459
2460 if ( $wdtyp == 'main' ) {
2461 $reviewCount = intval( $row['dataCount']['prtng'] );
2462
2463
2464 $schema_array['@context'] = "https://schema.org";
2465 $schema_array['@type'] = "Product";
2466 $schema_array['url'] = get_the_permalink( $p_id );
2467 $schema_array['name'] = get_the_title( $p_id );
2468 $schema_array['aggregateRating'] = array(
2469 '@type' => 'AggregateRating',
2470 'ratingValue' => $row['dataCount']['avgrtng'],
2471 'reviewCount' => $reviewCount,
2472 'bestRating' => '5',
2473 'worstRating' => '1',
2474 );
2475 }
2476
2477 if ( $wdtyp == 'star_rating' ) {// && !empty( $row['dataRating'][0]['html'] )
2478 update_post_meta( $p_id, 'wiserrw_star_rating_html', $row['dataRating'][0]['html'] );
2479 }
2480
2481 if ( $wdtyp == 'main' && !empty( $row['htmlForSEO'] ) ) {
2482 update_post_meta( $p_id, 'wiserrw_product_html', $row['htmlForSEO']);
2483 }
2484
2485 if ( ! empty( $row['data'] ) && is_array( $row['data'] ) ) {
2486 foreach ( $row['data'] as $review_row ) {
2487 $schema_array['review'][] = array(
2488 "@type" => "Review",
2489 'author' => array(
2490 '@type' => "Person",
2491 'name' => $review_row['un']
2492 ),
2493 'reviewBody' => $review_row['orgnlrtxt'],
2494 'reviewRating' => array(
2495 '@type' => 'Rating',
2496 'ratingValue' => $review_row['rtng'],
2497 'bestRating' => '5',
2498 'worstRating' => '1',
2499 ),
2500 'publisher' => array(
2501 "@type" => "Organization",
2502 "name" => "WiserReview"
2503 )
2504 );
2505 }
2506 }
2507 }
2508
2509 // �
2510 Save or delete only once, after loop
2511 if ( $reviewCount > 0 ) {
2512 update_post_meta( $p_id, 'wiserrw_schema_json', json_encode( $schema_array, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ) );
2513 } else {
2514 delete_post_meta( $p_id, 'wiserrw_schema_json' );
2515 // if($HideZeroStar){
2516 // delete_post_meta( $p_id, 'wiserrw_star_rating_html' );
2517 // }
2518 delete_post_meta( $p_id, 'wiserrw_product_html' );
2519 }
2520
2521 wiserrw_send_product_update_on_save_post( $p_id );
2522 }
2523
2524
2525 // Add schema only on WooCommerce single product pages
2526 add_action( 'wp_head', 'wiserrw_product_schema' );
2527 function wiserrw_product_schema() {
2528 if ( ! is_product() ) {
2529 return;
2530 }
2531
2532 // �
2533 Check if schema generation is enabled in settings
2534 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2535
2536 // Check if live mode is enabled (required for schema)
2537 $live_mode_enabled = isset( $wiserrw_api_settings['wiserrw_live_mode_checkbox'] );
2538
2539 if ( ! $live_mode_enabled ) {
2540 return; // Plugin not in live mode
2541 }
2542
2543 // Check if schema toggle is enabled
2544 $schema_enabled = isset( $wiserrw_api_settings['wiserrw_schema_enabled'] );
2545
2546 if ( ! $schema_enabled ) {
2547 return; // Schema generation disabled by user
2548 }
2549
2550 $p_id = get_queried_object_id();
2551 if ( ! $p_id ) {
2552 return;
2553 }
2554
2555 $wiserrw_schema_json = get_post_meta( $p_id, 'wiserrw_schema_json', true );
2556
2557 if ( $wiserrw_schema_json ) {
2558 echo '<script id="WiserReviewSchemaJson" type="application/ld+json">';
2559 echo $wiserrw_schema_json;
2560 echo '</script>';
2561 }
2562 }
2563
2564 function wiserrw_check_rest_api() {
2565 add_filter( 'https_ssl_verify', '__return_false' );
2566 $response = wp_remote_get( home_url( '/wp-json/' ) );
2567 remove_filter( 'https_ssl_verify', '__return_false' );
2568
2569 if ( is_wp_error( $response ) ) {
2570 echo 'WooCommerce REST API is not enabled. Enable it in settings and click Save again here..';
2571 return;
2572 }
2573
2574 $body = wp_remote_retrieve_body( $response );
2575 $data = json_decode( $body, true );
2576
2577 if ( !isset( $data['routes']['/wiserrw/v1/reviews'] ) ) {
2578 echo 'Please click Save again to apply the latest changes.';
2579 }
2580 }
2581
2582 function wiserrw_clear_wp_cache() {
2583 if( isset( $_GET['wiser_clear_cache'] ) && ( current_user_can( 'administrator' ) || current_user_can( 'editor' ) ) ) {
2584
2585 global $wp_fastest_cache;
2586
2587 // WP Rocket
2588 if ( function_exists( 'rocket_clean_domain' ) ) {
2589 rocket_clean_domain();
2590 }
2591
2592 if ( function_exists( 'wp_cache_flush' ) ) {
2593 wp_cache_flush();
2594 }
2595
2596 // WP Super Cache
2597 if ( function_exists( 'wp_cache_clear_cache' ) ) {
2598 wp_cache_clear_cache();
2599 }
2600
2601 // W3 Total Cache
2602 if ( function_exists( 'w3tc_flush_posts' ) ) {
2603 w3tc_flush_posts();
2604 }
2605
2606 // Cache Enabler
2607 if ( has_action( 'ce_clear_cache' ) ) {
2608 do_action( 'ce_clear_cache' );
2609 }
2610
2611 // Breeze
2612 if ( class_exists( 'Breeze_PurgeCache' ) ) {
2613 if ( method_exists( 'Breeze_PurgeCache', 'breeze_cache_flush' ) ) {
2614 Breeze_PurgeCache::breeze_cache_flush();
2615 }
2616 }
2617
2618 if ( class_exists( 'Swift_Performance_Cache' ) ) {
2619 if ( method_exists( 'Swift_Performance_Cache', 'clear_all_cache' ) ) {
2620 Swift_Performance_Cache::clear_all_cache();
2621 }
2622 }
2623
2624 // WP Fastest Cache
2625 if ( method_exists( 'WpFastestCache', 'deleteCache' ) && ! empty( $wp_fastest_cache ) ) {
2626 $wp_fastest_cache->deleteCache();
2627 }
2628
2629 // Autoptimize
2630 if ( class_exists( 'WpeCommon' ) ) {
2631 if ( method_exists( 'WpeCommon', 'purge_memcached' ) ) {
2632 WpeCommon::purge_memcached();
2633 }
2634 if ( method_exists( 'WpeCommon', 'clear_maxcdn_cache' ) ) {
2635 WpeCommon::clear_maxcdn_cache();
2636 }
2637 if ( method_exists( 'WpeCommon', 'purge_varnish_cache' ) ) {
2638 WpeCommon::purge_varnish_cache();
2639 }
2640 }
2641
2642 // SGOptimzer
2643 if ( function_exists('sg_cachepress_purge_cache') ) {
2644 sg_cachepress_purge_cache();
2645 }
2646 }
2647 }
2648 add_action( 'admin_init', 'wiserrw_clear_wp_cache' );
2649
2650 function wiserrw_send_product_update_on_save_post( $post_ID ) {
2651
2652 // WooCommerce must be available
2653 if ( ! class_exists( 'WooCommerce' ) ) return;
2654
2655 $product = wc_get_product( $post_ID );
2656 if ( ! $product ) return;
2657
2658 $wsid = get_option('wiserrw_wsid',true);
2659 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2660 $api_key = $wiserrw_api_settings['wiserrw_api_key'] ?? '';
2661 $data = array(
2662 'wsid' => $wsid,
2663 'pid' => $post_ID
2664 );
2665 // Make the request
2666 $response = wp_remote_post(
2667 WISERRW_RS_HOST . '/api/wp/updSchema',
2668 array(
2669 'method' => 'POST',
2670 'headers' => array(
2671 'Content-Type' => 'application/json',
2672 'x-wiserrw-api-key' => $api_key,
2673 ),
2674 'timeout' => 45,
2675 'redirection' => 5,
2676 'httpversion' => '1.0',
2677 'blocking' => true,
2678 'body' => wp_json_encode( $data ),
2679 'ver' => WISERRW_PLUGIN_VERSION
2680 )
2681 );
2682
2683 update_option( 'wiserrw__update_hook',wp_remote_retrieve_body($response) );
2684
2685 }
2686
2687 function wiserrw_show_product_rating_on_collection_woo_events() {
2688 $wiserrw_api_settings = (array) get_option( 'wiserrw_api_settings', [] );
2689
2690 if ( ! empty( $wiserrw_api_settings['wiserrw_product_card_enabled'] ) && $wiserrw_api_settings['wiserrw_product_card_enabled'] === '1' ) {
2691 $pid = wiserrw_get_current_product_id();
2692 echo wiserrw_rating_count( array( 'id' => $pid ) );
2693 wiserrw_restore_product_globals();
2694 }
2695 }
2696
2697 add_action( 'we_after_grid_content_html', 'wiserrw_show_product_rating_on_collection_woo_events', 5 );
2698
2699 // WiserReview Shortcode: [wiserreview_widget id="WidgetID value" type="carousel"]
2700 function wiserreview_widget_shortcode( $atts ) {
2701 $wsid = get_option('wiserrw_wsid',true);
2702 $atts = shortcode_atts( array(
2703 'id' => '',
2704 'type' => '',
2705 ), $atts, 'carousel_widget' );
2706
2707 if ( empty( $atts['id'] ) ) {
2708 return 'No widget ID provided';
2709 }
2710
2711 if ( empty( $atts['type'] ) ) {
2712 return 'No widget Type provided';
2713 }
2714
2715 $widget_id = sanitize_text_field( $atts['id'] );
2716 $type = sanitize_text_field( $atts['type'] );
2717 $option_key = 'wiserreview_widget_' . $widget_id;
2718
2719 // 1. If cache exists → serve instantly
2720 $cached_html = get_option( $option_key );
2721 if ( ! empty( $cached_html ) ) {
2722 return $cached_html;
2723 }
2724
2725 // 2. Render fallback immediately
2726 $fallback = wiserreview_shortcode_fallback( $widget_id, $type );
2727
2728 // 3. Try API fetch in same request (non-blocking for rendering)
2729 // The user sees fallback, but next time cache will be ready
2730 $api_url = add_query_arg(
2731 array(
2732 'widgetId' => $widget_id,
2733 'type' => $type,
2734 'ver' => WISERRW_PLUGIN_VERSION
2735 ),
2736 WISERRW_RS_HOST . '/api/getWdgtHTML'
2737
2738 );
2739
2740 $wiserrw_api_settings = get_option( 'wiserrw_api_settings', array() );
2741 $api_key = $wiserrw_api_settings['wiserrw_api_key'] ?? '';
2742 $response = wp_remote_get( $api_url, array(
2743 'timeout' => 20,
2744 'headers' => array(
2745 'x-wiserrw-api-key' => $api_key,
2746 ),
2747 ) );
2748
2749 if ( ! is_wp_error( $response ) ) {
2750 $json = json_decode( wp_remote_retrieve_body( $response ), true );
2751 if ( isset( $json['html'] ) && ! empty( $json['html'] ) ) {
2752 update_option( $option_key, $json['html'] );
2753 wiserrw_update_html( $wsid, $widget_id );
2754 }
2755 }
2756
2757 return $fallback;
2758 }
2759 add_shortcode( 'wiserreview_widget', 'wiserreview_widget_shortcode' );
2760
2761 function wiserreview_shortcode_fallback( $widget_id, $type ) {
2762 $widget_id = esc_attr( $widget_id );
2763 $type = esc_attr( $type );
2764
2765 return '<script src="https://embed.wiserreview.com/embed/' . $widget_id . '/widget.js" defer></script>'
2766 . '<div data-type="' . $type . '" class="wiser_review_' . $type . '" data-id="' . $widget_id . '"></div>';
2767 }
2768