PluginProbe
AI Chatbot for WooCommerce & Live Chat – onWebChat / 3.8.2
AI Chatbot for WooCommerce & Live Chat – onWebChat v3.8.2
3.10.0 3.9.2 3.9.3 3.9.1 3.9.0 3.8.4 3.8.2 3.8.1 3.8.0 3.7.2 3.7.1 3.7.0 3.6.0 3.5.5 trunk 1.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.2 1.0.3 1.0.4 1.0.5 All 49 releases
onwebchat / admin / tabs / woocommerce.php

woocommerce.php in AI Chatbot for WooCommerce & Live Chat – onWebChat 3.8.2, at admin/tabs/woocommerce.php

878 lines 39.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WooCommerce Tab - Product Sync Settings
4 */
5
6 if (!defined('ABSPATH')) {
7 exit;
8 }
9
10 function onwebchat_woocommerce_tab() {
11
12 // Handle form submissions
13 onwebchat_handle_woocommerce_actions();
14
15 // Get current settings
16 $sync_enabled = get_option('onwebchat_wc_sync_enabled', false);
17 $sync_mode = get_option('onwebchat_wc_sync_mode', 'short_fallback_full');
18 $secret = get_option('onwebchat_wc_sync_secret', '');
19 $order_lookup_enabled = get_option('onwebchat_wc_order_lookup_enabled', false);
20
21 // Get sync status
22 $sync_status = array(
23 'last_sync' => get_option('onwebchat_wc_last_bulk_sync', 0),
24 'in_progress' => get_option('onwebchat_wc_bulk_in_progress', false),
25 'done' => get_option('onwebchat_wc_bulk_done', 0),
26 'total' => get_option('onwebchat_wc_bulk_total', 0),
27 'last_sync_start' => get_option('onwebchat_wc_last_sync_start', 0),
28 );
29
30 // Calculate cooldown status
31 $cooldown_period = 5 * 60; // 5 minutes // 5min // 5 * 60 //also in the file woocommerce-sync.php
32 $time_since_last_sync = time() - $sync_status['last_sync_start'];
33 $is_in_cooldown = ($time_since_last_sync < $cooldown_period) && $sync_status['last_sync_start'] > 0;
34 $cooldown_remaining = $is_in_cooldown ? ceil(($cooldown_period - $time_since_last_sync) / 60) : 0;
35
36 ?>
37
38 <?php if (isset($_GET['wc_saved']) && $_GET['wc_saved'] == '1'): ?>
39 <div class="notice notice-success is-dismissible">
40 <p><strong>WooCommerce settings saved successfully!</strong></p>
41 </div>
42 <?php endif; ?>
43
44 <?php
45 // Check for authentication errors
46 $auth_error = get_transient('onwebchat_wc_auth_error');
47 if ($auth_error):
48 ?>
49 <div class="notice notice-error is-dismissible">
50 <p><strong>⚠️ WooCommerce Sync Authentication Error:</strong></p>
51 <p><?php echo esc_html($auth_error); ?></p>
52 <p>This usually happens when:</p>
53 <ul style="list-style: disc; margin-left: 20px;">
54 <li>You're trying to connect the plugin to a different onWebChat account</li>
55 </ul>
56 <p><strong>Solution:</strong> Please enter your onWebChat account credentials again in the "Connect WooCommerce Sync" section below and click "Connect WooCommerce Sync" button to reconnect.</p>
57 </div>
58 <?php endif; ?>
59
60 <h2>WooCommerce Product Sync</h2>
61 <p>
62 Let your AI chatbot understand your WooCommerce products.
63 When enabled, products are automatically synced and kept up to date, allowing the AI to answer product-related questions accurately.
64 </p>
65
66 <?php if (empty($secret)): ?>
67 <!-- Authentication required section -->
68 <div class="notice notice-warning" style="margin: 15px 0; padding: 15px;">
69 <h3 style="margin-top: 0;">🔐 Connect WooCommerce Sync</h3>
70 <p>
71 To enable WooCommerce sync, please enter your onWebChat account credentials below.
72 </p>
73 <p style="color: #666; font-size: 13px;">
74 <!-- <strong>💡 Tip:</strong> If you originally connected using email/password in the <a href="?page=onwebchat_settings&tab=general">General tab</a>,
75 try unlinking and re-linking your account there. WooCommerce sync will be connected automatically! -->
76 </p>
77
78 <table class="form-table" style="margin-bottom: 0;">
79 <tr>
80 <th scope="row"><label for="onwebchat_wc_email">onWebChat Email (username)</label></th>
81 <td>
82 <input type="email"
83 id="onwebchat_wc_email"
84 class="regular-text"
85 placeholder="Your registered email"
86 value="<?php echo esc_attr(get_option('onwebchat_plugin_option_user', '')); ?>">
87 </td>
88 </tr>
89 <tr>
90 <th scope="row"><label for="onwebchat_wc_password">onWebChat Password</label></th>
91 <td>
92 <input type="password"
93 id="onwebchat_wc_password"
94 class="regular-text"
95 placeholder="Your account password">
96 </td>
97 </tr>
98 </table>
99
100 <p style="margin-top: 15px;">
101 <button type="button" id="onwebchat_wc_connect" class="button button-primary">
102 Connect WooCommerce Sync
103 </button>
104 <span id="onwebchat_wc_connect_status" style="margin-left: 10px;"></span>
105 </p>
106 </div>
107
108 <script type="text/javascript">
109 jQuery(document).ready(function($) {
110 $('#onwebchat_wc_connect').on('click', function() {
111 var email = $('#onwebchat_wc_email').val();
112 var password = $('#onwebchat_wc_password').val();
113
114 if (!email || !password) {
115 alert('Please enter both email and password.');
116 return;
117 }
118
119 var $btn = $(this);
120 var $status = $('#onwebchat_wc_connect_status');
121
122 $btn.prop('disabled', true).text('Connecting...');
123 $status.html('');
124
125 $.ajax({
126 url: ajaxurl,
127 type: 'POST',
128 data: {
129 action: 'onwebchat_wc_connect',
130 email: email,
131 password: password,
132 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
133 },
134 success: function(response) {
135 if (response.success) {
136 $status.html('<span style="color: green;">✓ Connected successfully!</span>');
137 setTimeout(function() {
138 location.reload();
139 }, 1000);
140 } else {
141 // Render the server-supplied error as TEXT (never HTML) so a malicious/MITM'd
142 // API response can't inject markup into the admin page.
143 $status.empty().append(
144 $('<span/>').css('color', 'red').text('' + response.data)
145 );
146 $btn.prop('disabled', false).text('Connect WooCommerce Sync');
147 }
148 },
149 error: function() {
150 $status.html('<span style="color: red;">✗ Connection error. Please try again.</span>');
151 $btn.prop('disabled', false).text('Connect WooCommerce Sync');
152 }
153 });
154 });
155 });
156 </script>
157
158 <?php else: ?>
159 <!-- Connected - show settings -->
160
161 <form action="admin.php?page=onwebchat_settings&tab=woocommerce" method="post">
162 <input type="hidden" name="action" value="save_wc_sync">
163 <?php wp_nonce_field('onwebchat_wc_sync_nonce'); ?>
164
165 <table class="form-table">
166 <tr>
167 <th scope="row" style="line-height: 1 !important;">
168 <label for="onwebchat_wc_sync_enabled">Enable Product Sync</label>
169 </th>
170 <td>
171 <label for="onwebchat_wc_sync_enabled">
172 <input type="checkbox"
173 id="onwebchat_wc_sync_enabled"
174 name="onwebchat_wc_sync_enabled"
175 value="1"
176 <?php checked($sync_enabled, true); ?>>
177 Automatically sync products to onWebChat for AI training
178 </label>
179 <p id="onwebchat_wc_sync_status" style="margin: 8px 0 0 0; font-size: 13px; min-height: 20px; visibility: hidden; opacity: 0;">
180 </p>
181 </td>
182 </tr>
183
184 <tr style="display: none;">
185 <th scope="row">
186 <label for="onwebchat_wc_sync_mode">Description Mode</label>
187 </th>
188 <td>
189 <select id="onwebchat_wc_sync_mode" name="onwebchat_wc_sync_mode" style="width: 350px;">
190 <option value="short_only" <?php selected($sync_mode, 'short_only'); ?>>
191 Short description only
192 </option>
193 <option value="short_fallback_full" <?php selected($sync_mode, 'short_fallback_full'); ?>>
194 Short description (fallback to full)
195 </option>
196 </select>
197 <p class="description">
198 Choose how product descriptions are synced for AI training
199 </p>
200 </td>
201 </tr>
202
203 <?php if (!empty($secret)): ?>
204 <tr>
205 <th scope="row">Connection Status</th>
206 <td>
207 <span style="color: green; font-weight: bold; vertical-align: middle;"> Connected to onWebChat</span>
208 <button type="button"
209 id="onwebchat_regenerate_secret"
210 class="button button-small"
211 style="margin-left: 10px; vertical-align: middle;">
212 Disconnect
213 </button>
214 <p class="description">
215 WooCommerce sync is securely connected. Click "Disconnect" to re-authenticate.
216 </p>
217 </td>
218 </tr>
219 <?php endif; ?>
220 </table>
221 </form>
222
223 <hr>
224
225 <h2>AI Order Status Lookup</h2>
226 <p>
227 Let your AI chatbot answer "where is my order?" with live data. The store verifies identity using the
228 order number and email (signed-in users only need the order number) before sharing details.
229 </p>
230
231 <table class="form-table">
232 <tr>
233 <th scope="row" style="line-height: 1 !important;">
234 <label for="onwebchat_wc_order_lookup_enabled">Enable Order Status Lookup</label>
235 </th>
236 <td>
237 <label for="onwebchat_wc_order_lookup_enabled">
238 <input type="checkbox"
239 id="onwebchat_wc_order_lookup_enabled"
240 name="onwebchat_wc_order_lookup_enabled"
241 value="1"
242 <?php checked($order_lookup_enabled, true); ?>>
243 Allow the AI chatbot to look up live order status (with identity verification)
244 </label>
245 <p id="onwebchat_wc_order_lookup_status" style="margin: 8px 0 0 0; font-size: 13px; min-height: 20px; visibility: hidden; opacity: 0;">
246 </p>
247 </td>
248 </tr>
249 </table>
250
251 <script type="text/javascript">
252 jQuery(document).ready(function($) {
253 $('#onwebchat_wc_order_lookup_enabled').on('change', function() {
254 var $checkbox = $(this);
255 var $status = $('#onwebchat_wc_order_lookup_status');
256 var isEnabled = $checkbox.is(':checked');
257
258 $checkbox.prop('disabled', true);
259
260 $.ajax({
261 url: ajaxurl,
262 type: 'POST',
263 data: {
264 action: 'onwebchat_wc_save_order_lookup',
265 order_lookup_enabled: isEnabled ? '1' : '0',
266 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
267 },
268 success: function(response) {
269 if (response.success) {
270 var autohide = true;
271 if (response.data.warning) {
272 // Local testing mode: saved locally, but the onWebChat server was not notified.
273 $status.empty().append(
274 $('<span/>').css('color', '#996800').text('' + (response.data.message || 'Saved locally; onWebChat server not notified.'))
275 );
276 autohide = false;
277 } else if (response.data.enabled) {
278 $status.html('<span style="color: green;">�
279 AI order status lookup enabled</span>');
280 } else {
281 $status.html('<span style="color: #d63638;"> AI order status lookup disabled</span>');
282 }
283 $status.css('visibility', 'visible').css('opacity', 1);
284 if (autohide) {
285 setTimeout(function() {
286 $status.animate({opacity: 0}, 300, function() {
287 $status.css('visibility', 'hidden');
288 });
289 }, 1700);
290 }
291 } else {
292 // Revert checkbox on error
293 $checkbox.prop('checked', !isEnabled);
294 alert('Error: ' + (response.data || 'Failed to save setting'));
295 }
296 $checkbox.prop('disabled', false);
297 },
298 error: function() {
299 $checkbox.prop('checked', !isEnabled);
300 alert('An error occurred. Please try again.');
301 $checkbox.prop('disabled', false);
302 }
303 });
304 });
305 });
306 </script>
307
308 <hr>
309
310 <h2>Bulk Sync Status</h2>
311
312 <?php
313 // Large-catalogue category picker. Stores above the threshold can choose
314 // which categories to sync (staying within the hard cap); the choice is
315 // persisted as the ongoing auto-sync scope. Below the threshold the whole
316 // catalogue is synced as before and no picker is shown.
317 if (class_exists('OnWebChat_WooCommerce_Sync')) {
318 $owc_threshold = OnWebChat_WooCommerce_Sync::CATEGORY_SELECT_THRESHOLD;
319 $owc_max = OnWebChat_WooCommerce_Sync::MAX_SYNC_PRODUCTS;
320 } else {
321 $owc_threshold = 2000;
322 $owc_max = 15000;
323 }
324
325 $owc_counts = wp_count_posts('product');
326 $owc_published_count = (is_object($owc_counts) && isset($owc_counts->publish)) ? (int) $owc_counts->publish : 0;
327 $owc_needs_selection = $owc_published_count > $owc_threshold;
328 $owc_over_max = $owc_published_count > $owc_max;
329
330 // Saved sync scope (product_cat term IDs) to pre-tick in the picker.
331 $owc_saved_scope = array();
332 $owc_scope_raw = (string) get_option('onwebchat_wc_sync_categories', '');
333 if ($owc_scope_raw !== '') {
334 foreach (explode(',', $owc_scope_raw) as $owc_sid) {
335 $owc_sid = (int) trim($owc_sid);
336 if ($owc_sid > 0) {
337 $owc_saved_scope[] = $owc_sid;
338 }
339 }
340 }
341
342 $owc_categories = $owc_needs_selection ? get_terms(array(
343 'taxonomy' => 'product_cat',
344 'hide_empty' => false,
345 'orderby' => 'name',
346 'order' => 'ASC',
347 )) : array();
348 if (is_wp_error($owc_categories)) {
349 $owc_categories = array();
350 }
351
352 // Build the category tree. The picker only offers top-level categories, and
353 // selecting one covers its whole subtree, so each row shows a distinct product
354 // count spanning the category and all of its descendants.
355 $owc_children = array(); // parent_id => array of term objects
356 foreach ($owc_categories as $owc_term) {
357 $owc_children[(int) $owc_term->parent][] = $owc_term;
358 }
359
360 // Distinct published products in a category and its whole subtree. Counting
361 // distinct posts (WP_Query found_posts with include_children) avoids the
362 // over-count you get from summing per-term counts when a product is assigned
363 // to both a parent and a child category.
364 if (!function_exists('owc_subtree_product_count')) {
365 function owc_subtree_product_count($term_id) {
366 $query = new WP_Query(array(
367 'post_type' => 'product',
368 'post_status' => 'publish',
369 'posts_per_page' => 1,
370 'fields' => 'ids',
371 'no_found_rows' => false,
372 'tax_query' => array(array(
373 'taxonomy' => 'product_cat',
374 'field' => 'term_id',
375 'terms' => (int) $term_id,
376 'include_children' => true,
377 )),
378 ));
379 return (int) $query->found_posts;
380 }
381 }
382
383 // Renderable top-level categories (parent 0) that actually cover products.
384 $owc_top_rows = array();
385 $owc_top_level = isset($owc_children[0]) ? $owc_children[0] : array();
386 foreach ($owc_top_level as $owc_term) {
387 $owc_count = owc_subtree_product_count((int) $owc_term->term_id);
388 if ($owc_count > 0) {
389 $owc_top_rows[] = array('term' => $owc_term, 'count' => $owc_count);
390 }
391 }
392 ?>
393
394 <?php if ($owc_needs_selection && !empty($owc_top_rows)): ?>
395 <div id="owc-category-picker" style="background: #f9f9f9; padding: 20px; border-radius: 8px; max-width: 800px; margin-top: 15px;">
396 <p style="margin: 0 0 10px 0;"><strong>Choose what to sync</strong></p>
397 <p id="owc-category-intro" style="margin: 0 0 12px 0; color: #555;">
398 <?php if ($owc_over_max): ?>
399 <?php printf(
400 'Your store has %s products. onWebChat can sync up to %s, so please tick the categories you want to sync. Selecting a category includes all of its subcategories.',
401 esc_html(number_format_i18n($owc_published_count)),
402 esc_html(number_format_i18n($owc_max))
403 ); ?>
404 <?php else: ?>
405 <?php printf(
406 'Your store has %s products. Leave every category unticked to sync the whole catalogue, including products without a category, or tick specific categories to sync only those. Selecting a category includes all of its subcategories.',
407 esc_html(number_format_i18n($owc_published_count))
408 ); ?>
409 <?php endif; ?>
410 </p>
411 <p style="margin: 0 0 10px 0;">
412 <input type="text" id="owc-category-search" placeholder="Search categories..." class="regular-text" style="max-width: 360px;">
413 </p>
414 <p style="margin: 0 0 8px 0;">
415 <label><input type="checkbox" id="owc-category-all"> <strong>Select all</strong></label>
416 <span id="owc-category-selected" style="margin-left: 12px; color: #666;"></span>
417 </p>
418 <div id="owc-category-list" style="max-height: 280px; overflow: auto; border: 1px solid #ddd; border-radius: 4px; background: #fff; padding: 8px 12px;">
419 <?php foreach ($owc_top_rows as $owc_row): $owc_term = $owc_row['term']; ?>
420 <label class="owc-category-row" data-name="<?php echo esc_attr(strtolower($owc_term->name)); ?>" style="display: block; padding: 3px 0;">
421 <input type="checkbox" class="owc-category-cb" value="<?php echo (int) $owc_term->term_id; ?>" <?php echo in_array((int) $owc_term->term_id, $owc_saved_scope, true) ? 'checked' : ''; ?>>
422 <?php echo esc_html($owc_term->name); ?>
423 <span style="color: #999;">(<?php echo (int) $owc_row['count']; ?>)</span>
424 </label>
425 <?php endforeach; ?>
426 </div>
427 </div>
428 <?php endif; ?>
429
430 <div id="onwebchat_sync_status_display" style="background: #f9f9f9; padding: 20px; border-radius: 8px; max-width: 800px; margin-top: 15px;">
431 <?php if ($sync_status['in_progress']): ?>
432 <p style="margin: 0 0 10px 0;">
433 <strong>⏳ Sync in progress:</strong>
434 <?php echo esc_html($sync_status['done']); ?> / <?php echo esc_html($sync_status['total']); ?> products synced
435 </p>
436 <div style="background: #fff; border: 1px solid #ddd; border-radius: 4px; height: 20px; overflow: hidden;">
437 <div style="background: #2271b1; height: 100%; width: <?php echo $sync_status['total'] > 0 ? ($sync_status['done'] / $sync_status['total'] * 100) : 0; ?>%; transition: width 0.3s;"></div>
438 </div>
439 <?php elseif ($sync_status['last_sync'] > 0): ?>
440 <p id="onwebchat_last_sync_info" style="margin: 0;">
441 <strong>✓ Last bulk sync:</strong>
442 <?php echo esc_html(human_time_diff($sync_status['last_sync'], current_time('timestamp')) . ' ago'); ?>
443 </p>
444 <p id="onwebchat_total_synced_info" style="margin: 10px 0 0 0; color: #666;">
445 Total products synced: <?php echo esc_html($sync_status['total']); ?>
446 </p>
447 <?php else: ?>
448 <p style="margin: 0; color: #666;">
449 No bulk sync has been performed yet.
450 </p>
451 <?php endif; ?>
452
453 <?php if (!$is_in_cooldown): ?>
454 <p style="margin-top: 15px;">
455 <button type="button"
456 id="onwebchat_sync_now"
457 class="button button-primary"
458 <?php echo $sync_status['in_progress'] ? 'disabled' : ''; ?>>
459 <?php echo $sync_status['in_progress'] ? 'Sync in Progress...' : 'Sync All Products Now'; ?>
460 </button>
461 </p>
462 <?php endif; ?>
463
464 <p class="description onwebchat-sync-description" style="margin-top: 10px;">
465 <?php if ($is_in_cooldown): ?>
466 <?php if ($sync_enabled): ?>
467 Bulk sync was completed recently. Your products are already up to date, and any new or updated products will be synced automatically.
468 <?php else: ?>
469 Bulk sync was completed recently. Your products are already up to date. Please enable "Automatically sync products to onWebChat for AI training" above to keep all your products synced automatically.
470 <?php endif; ?>
471 <?php else: ?>
472 Click to sync all existing products. This process runs in the background and may take several minutes depending on your product count.
473 <?php endif; ?>
474 </p>
475
476 <?php
477 // Show reset button if sync appears stuck (done >= total OR in progress for more than 3 minutes)
478 $stuck_sync = $sync_status['in_progress'] && (
479 $sync_status['done'] >= $sync_status['total'] ||
480 (time() - $sync_status['last_sync_start'] > 180)
481 );
482 if ($stuck_sync):
483 ?>
484 <p style="margin-top: 10px;">
485 <button type="button"
486 id="onwebchat_manual_process_batch"
487 class="button button-secondary"
488 style="margin-right: 10px;">
489 Process Batch Manually
490 </button>
491 <button type="button"
492 id="onwebchat_reset_sync_status"
493 class="button">
494 Mark Sync as Complete
495 </button>
496 <span class="description" style="margin-left: 10px;">
497 If sync appears stuck, try processing manually or reset status.
498 </span>
499 </p>
500 <?php endif; ?>
501 </div>
502
503 <script type="text/javascript">
504 jQuery(document).ready(function($) {
505 // Cooldown timer - reload page when cooldown expires
506 <?php if ($is_in_cooldown): ?>
507 var cooldownSeconds = <?php echo ($cooldown_period - $time_since_last_sync); ?>;
508 setTimeout(function() {
509 location.reload();
510 }, cooldownSeconds * 1000);
511 <?php endif; ?>
512
513 // ---- Large-catalogue category picker ----
514 var owcOverMax = <?php echo (!empty($owc_over_max)) ? 'true' : 'false'; ?>;
515 var owcHasPicker = $('#owc-category-picker').length > 0;
516
517 function owcSelectedCategoryIds() {
518 var ids = [];
519 $('.owc-category-cb:checked').each(function() {
520 ids.push($(this).val());
521 });
522 return ids;
523 }
524
525 function owcUpdateSyncButtonLabel() {
526 var $btn = $('#onwebchat_sync_now');
527 if (!$btn.length || $btn.prop('disabled')) {
528 return;
529 }
530 var label = (owcOverMax || owcSelectedCategoryIds().length > 0)
531 ? 'Sync selected categories'
532 : 'Sync All Products Now';
533 $btn.text(label);
534 }
535
536 function owcUpdateSelected() {
537 var selected = owcSelectedCategoryIds().length;
538 var total = $('.owc-category-cb').length;
539 $('#owc-category-selected').text(selected > 0 ? (selected + ' of ' + total + ' categories selected') : '');
540 $('#owc-category-all').prop('checked', total > 0 && selected === total);
541 owcUpdateSyncButtonLabel();
542 }
543
544 if (owcHasPicker) {
545 $('.owc-category-cb').on('change', owcUpdateSelected);
546
547 // Select all toggles only the rows currently visible (respects search).
548 $('#owc-category-all').on('change', function() {
549 var checked = $(this).is(':checked');
550 $('.owc-category-row:visible').find('.owc-category-cb').prop('checked', checked);
551 owcUpdateSelected();
552 });
553
554 $('#owc-category-search').on('input', function() {
555 var q = $(this).val().toLowerCase();
556 $('.owc-category-row').each(function() {
557 var name = String($(this).data('name'));
558 $(this).toggle(name.indexOf(q) !== -1);
559 });
560 });
561
562 // Reflect the saved scope (pre-ticked categories) and set the label.
563 owcUpdateSelected();
564 }
565
566 // Sync enabled checkbox - save immediately via AJAX
567 $('#onwebchat_wc_sync_enabled').on('change', function() {
568 var $checkbox = $(this);
569 var $status = $('#onwebchat_wc_sync_status');
570 var $syncDescription = $('.onwebchat-sync-description');
571 var isEnabled = $checkbox.is(':checked');
572
573 // Disable checkbox while saving
574 $checkbox.prop('disabled', true);
575
576 $.ajax({
577 url: ajaxurl,
578 type: 'POST',
579 data: {
580 action: 'onwebchat_wc_save_sync_enabled',
581 sync_enabled: isEnabled ? '1' : '0',
582 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
583 },
584 success: function(response) {
585 if (response.success) {
586 if (response.data.enabled) {
587 $status.html('<span style="color: green;">�
588 WooCommerce product sync enabled</span>');
589 // Update the sync description text if in cooldown
590 if ($syncDescription.length) {
591 var currentText = $syncDescription.text().trim();
592 if (currentText.includes('Bulk sync was completed recently')) {
593 $syncDescription.text('Bulk sync was completed recently. Your products are already up to date, and any new or updated products will be synced automatically.');
594 }
595 }
596 } else {
597 $status.html('<span style="color: #d63638;"> WooCommerce product sync disabled</span>');
598 // Update the sync description text if in cooldown
599 if ($syncDescription.length) {
600 var currentText = $syncDescription.text().trim();
601 if (currentText.includes('Bulk sync was completed recently')) {
602 $syncDescription.text('Bulk sync was completed recently. Your products are already up to date. Please enable "Automatically sync products to onWebChat for AI training" above to keep all your products synced automatically.');
603 }
604 }
605 }
606 // Ensure status is visible and fade in smoothly
607 $status.css('visibility', 'visible').css('opacity', 1);
608 // Hide it after 2.5 seconds with fade out
609 setTimeout(function() {
610 $status.animate({opacity: 0}, 300, function() {
611 $status.css('visibility', 'hidden');
612 });
613 }, 1700);
614 } else {
615 // Revert checkbox on error
616 $checkbox.prop('checked', !isEnabled);
617 alert('Error: ' + (response.data || 'Failed to save setting'));
618 }
619 $checkbox.prop('disabled', false);
620 },
621 error: function() {
622 // Revert checkbox on error
623 $checkbox.prop('checked', !isEnabled);
624 alert('An error occurred. Please try again.');
625 $checkbox.prop('disabled', false);
626 }
627 });
628 });
629
630 // Sync now button
631 $('#onwebchat_sync_now').on('click', function() {
632 if ($(this).prop('disabled')) {
633 return;
634 }
635
636 // Determine the sync scope from the category picker (if shown).
637 var owcSelected = owcHasPicker ? owcSelectedCategoryIds() : [];
638
639 // Above the hard cap a category selection is required.
640 if (owcHasPicker && owcOverMax && owcSelected.length === 0) {
641 alert('Your store has too many products to sync all at once. Please tick at least one category to sync.');
642 return;
643 }
644
645 var confirmMsg = (owcSelected.length > 0)
646 ? 'Sync products in the selected categories with onWebChat to train your AI chatbot?'
647 : 'Sync all published products with onWebChat to train your AI chatbot?';
648 if (!confirm(confirmMsg)) {
649 return;
650 }
651
652 var $btn = $(this);
653 var nonce = '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>';
654 $btn.prop('disabled', true).text('Syncing products...');
655
656 // Hide the "Last sync" info while syncing
657 $('#onwebchat_last_sync_info').hide();
658 $('#onwebchat_total_synced_info').hide();
659
660 // Show initial syncing message with progress bar
661 $('#onwebchat_sync_status_display').html(
662 '<p style="margin: 0 0 10px 0;"><strong>⏳ Sync in progress:</strong> <span id="sync_progress_text">Starting…</span></p>' +
663 '<div style="background: #fff; border: 1px solid #ddd; border-radius: 4px; height: 20px; overflow: hidden;">' +
664 '<div id="sync_progress_bar" style="background: #2271b1; height: 100%; width: 0%; transition: width 0.3s;"></div>' +
665 '</div>' +
666 '<p style="margin: 10px 0 0 0; color: #666;">Please keep this page open until the sync completes.</p>'
667 );
668
669 function owcSyncFail(msg) {
670 alert('Error: ' + msg);
671 $btn.prop('disabled', false);
672 owcUpdateSyncButtonLabel();
673 $('#onwebchat_sync_status_display').html('');
674 }
675
676 function owcSyncProgress(done, total) {
677 var pct = total > 0 ? Math.min(100, Math.round((done / total) * 100)) : 0;
678 $('#sync_progress_text').text(done + ' / ' + total + ' products synced');
679 $('#sync_progress_bar').css('width', pct + '%');
680 }
681
682 // Drive the sync one page per request until the run reports complete.
683 // A single long request would outrun the server timeout on large
684 // catalogues and report a false failure; this keeps every request
685 // short and only reports success once the whole run is done.
686 function owcRunBatch(total, attempt) {
687 $.ajax({
688 url: ajaxurl,
689 type: 'POST',
690 timeout: 120000,
691 data: { action: 'onwebchat_wc_sync_batch', nonce: nonce },
692 success: function(resp) {
693 if (!resp || !resp.success) { owcSyncFail((resp && resp.data) || 'Sync failed'); return; }
694 var d = resp.data || {};
695 total = d.total || total;
696 owcSyncProgress(d.done || 0, total);
697
698 if (d.in_progress) {
699 owcRunBatch(total, 0);
700 } else {
701 var s = d.stats || {};
702 alert('Sync completed!\n\n' +
703 'Created: ' + (s.created || 0) + '\n' +
704 'Updated: ' + (s.updated || 0) + '\n' +
705 'Unchanged: ' + (s.skipped || 0) + '\n' +
706 'Errors: ' + (s.errors || 0));
707 location.reload();
708 }
709 },
710 error: function() {
711 // One page timing out is recoverable (progress is saved
712 // server-side): retry a few times before giving up.
713 if (attempt < 3) {
714 setTimeout(function() { owcRunBatch(total, attempt + 1); }, 3000);
715 } else {
716 owcSyncFail('A network error occurred. Some products may not have synced; please try again.');
717 }
718 }
719 });
720 }
721
722 // Kick off the run, then let the browser drive the batches.
723 $.ajax({
724 url: ajaxurl,
725 type: 'POST',
726 timeout: 120000,
727 data: {
728 action: 'onwebchat_wc_sync_start',
729 categories: owcSelected.join(','),
730 nonce: nonce
731 },
732 success: function(response) {
733 if (!response || !response.success) { owcSyncFail((response && response.data) || 'Could not start sync'); return; }
734 var total = response.data.total || 0;
735 owcSyncProgress(0, total);
736 if (total === 0) {
737 alert('No products found to sync.');
738 $btn.prop('disabled', false);
739 owcUpdateSyncButtonLabel();
740 $('#onwebchat_sync_status_display').html('');
741 return;
742 }
743 owcRunBatch(total, 0);
744 },
745 error: function() {
746 owcSyncFail('Could not start sync. Please try again.');
747 }
748 });
749 });
750
751 // Manual process batch button (for debugging stuck syncs)
752 $('#onwebchat_manual_process_batch').on('click', function() {
753 var $btn = $(this);
754 $btn.prop('disabled', true).text('Processing...');
755
756 $.ajax({
757 url: ajaxurl,
758 type: 'POST',
759 data: {
760 action: 'onwebchat_wc_manual_process_batch',
761 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
762 },
763 success: function(response) {
764 if (response.success) {
765 alert('Batch processed. Check console/logs for details.');
766 location.reload();
767 } else {
768 alert('Error: ' + response.data);
769 $btn.prop('disabled', false).text('Process Batch Manually');
770 }
771 },
772 error: function() {
773 alert('An error occurred. Please try again.');
774 $btn.prop('disabled', false).text('Process Batch Manually');
775 }
776 });
777 });
778
779 // Reset sync status button
780 $('#onwebchat_reset_sync_status').on('click', function() {
781 if (!confirm('Mark sync as complete? This will reset the progress indicator.')) {
782 return;
783 }
784
785 var $btn = $(this);
786 $btn.prop('disabled', true).text('Resetting...');
787
788 $.ajax({
789 url: ajaxurl,
790 type: 'POST',
791 data: {
792 action: 'onwebchat_wc_reset_sync_status',
793 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
794 },
795 success: function(response) {
796 if (response.success) {
797 location.reload();
798 } else {
799 alert('Error: ' + response.data);
800 $btn.prop('disabled', false).text('Mark Sync as Complete');
801 }
802 },
803 error: function() {
804 alert('An error occurred. Please try again.');
805 $btn.prop('disabled', false).text('Mark Sync as Complete');
806 }
807 });
808 });
809
810 // Regenerate secret button (now "Disconnect" button)
811 $('#onwebchat_regenerate_secret').on('click', function() {
812 if (!confirm('This will disconnect WooCommerce sync. You will need to re-authenticate with your credentials. Continue?')) {
813 return;
814 }
815
816 var $btn = $(this);
817 $btn.prop('disabled', true).text('Disconnecting...');
818
819 $.ajax({
820 url: ajaxurl,
821 type: 'POST',
822 data: {
823 action: 'onwebchat_wc_regenerate_secret',
824 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
825 },
826 success: function(response) {
827 if (response.success) {
828 alert('WooCommerce sync disconnected. Please re-authenticate to continue syncing.');
829 location.reload();
830 } else {
831 alert('Error: ' + response.data);
832 $btn.prop('disabled', false).text('Disconnect');
833 }
834 },
835 error: function() {
836 alert('An error occurred. Please try again.');
837 $btn.prop('disabled', false).text('Disconnect');
838 }
839 });
840 });
841 });
842 </script>
843
844 <?php endif; // End if secret exists ?>
845
846 <?php
847 }
848
849 /**
850 * Handle form submissions for WooCommerce tab
851 */
852 function onwebchat_handle_woocommerce_actions() {
853
854 if (isset($_POST["action"]) && $_POST["action"] == "save_wc_sync") {
855
856 if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'onwebchat_wc_sync_nonce')) {
857 wp_die('Sorry, your nonce did not verify.');
858 }
859
860 if (!current_user_can('manage_options')) {
861 wp_die('Insufficient permissions.');
862 }
863
864 // Save settings
865 $sync_enabled = isset($_POST["onwebchat_wc_sync_enabled"]) ? true : false;
866 $sync_mode = isset($_POST["onwebchat_wc_sync_mode"]) ? sanitize_text_field($_POST["onwebchat_wc_sync_mode"]) : 'short_fallback_full';
867
868 update_option('onwebchat_wc_sync_enabled', $sync_enabled);
869 update_option('onwebchat_wc_sync_mode', $sync_mode);
870
871 // Secret is now obtained via authenticated connection, not auto-generated
872
873 wp_redirect(admin_url('admin.php?page=onwebchat_settings&tab=woocommerce&wc_saved=1'));
874 exit;
875 }
876 }
877
878