PluginProbe
AI Chatbot for WooCommerce & Live Chat – onWebChat / 3.7.2
AI Chatbot for WooCommerce & Live Chat – onWebChat v3.7.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.7.2, at admin/tabs/woocommerce.php

855 lines 38.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 = 9000;
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 $btn.prop('disabled', true).text('Syncing products...');
654
655 // Hide the "Last sync" info while syncing
656 $('#onwebchat_last_sync_info').hide();
657 $('#onwebchat_total_synced_info').hide();
658
659 // Show initial syncing message with progress bar
660 $('#onwebchat_sync_status_display').html(
661 '<p style="margin: 0 0 10px 0;"><strong>⏳ Sync in progress:</strong> <span id="sync_progress_text">0 / ? products synced</span></p>' +
662 '<div style="background: #fff; border: 1px solid #ddd; border-radius: 4px; height: 20px; overflow: hidden;">' +
663 '<div id="sync_progress_bar" style="background: #2271b1; height: 100%; width: 0%; transition: width 0.3s;"></div>' +
664 '</div>' +
665 '<p style="margin: 10px 0 0 0; color: #666;">Please wait, this may take several minutes.</p>'
666 );
667
668 // Start polling for progress updates
669 var progressInterval = setInterval(function() {
670 $.ajax({
671 url: ajaxurl,
672 type: 'POST',
673 data: {
674 action: 'onwebchat_wc_get_sync_status',
675 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
676 },
677 success: function(statusResponse) {
678 if (statusResponse.success && statusResponse.data.in_progress) {
679 var done = statusResponse.data.done || 0;
680 var total = statusResponse.data.total || 0;
681 var percentage = total > 0 ? Math.round((done / total) * 100) : 0;
682
683 $('#sync_progress_text').text(done + ' / ' + total + ' products synced');
684 $('#sync_progress_bar').css('width', percentage + '%');
685 }
686 }
687 });
688 }, 2000); // Poll every 2 seconds
689
690 $.ajax({
691 url: ajaxurl,
692 type: 'POST',
693 timeout: 120000, // 2 minute timeout for large syncs
694 data: {
695 action: 'onwebchat_wc_sync_now',
696 categories: owcSelected.join(','),
697 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
698 },
699 success: function(response) {
700 clearInterval(progressInterval); // Stop polling
701
702 if (response.success) {
703 var result = response.data.result;
704 var stats = result.stats;
705 var msg = 'Sync completed!\n\n' +
706 'Products processed: ' + result.done + '/' + response.data.total + '\n' +
707 'Created: ' + stats.created + '\n' +
708 'Updated: ' + stats.updated + '\n' +
709 'Unchanged: ' + stats.skipped + '\n' +
710 'Errors: ' + stats.errors;
711 alert(msg);
712 location.reload();
713 } else {
714 alert('Error: ' + response.data);
715 $btn.prop('disabled', false);
716 owcUpdateSyncButtonLabel();
717 }
718 },
719 error: function(xhr, status, error) {
720 clearInterval(progressInterval); // Stop polling
721 alert('An error occurred: ' + error + '. Please try again.');
722 $btn.prop('disabled', false);
723 owcUpdateSyncButtonLabel();
724 }
725 });
726 });
727
728 // Manual process batch button (for debugging stuck syncs)
729 $('#onwebchat_manual_process_batch').on('click', function() {
730 var $btn = $(this);
731 $btn.prop('disabled', true).text('Processing...');
732
733 $.ajax({
734 url: ajaxurl,
735 type: 'POST',
736 data: {
737 action: 'onwebchat_wc_manual_process_batch',
738 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
739 },
740 success: function(response) {
741 if (response.success) {
742 alert('Batch processed. Check console/logs for details.');
743 location.reload();
744 } else {
745 alert('Error: ' + response.data);
746 $btn.prop('disabled', false).text('Process Batch Manually');
747 }
748 },
749 error: function() {
750 alert('An error occurred. Please try again.');
751 $btn.prop('disabled', false).text('Process Batch Manually');
752 }
753 });
754 });
755
756 // Reset sync status button
757 $('#onwebchat_reset_sync_status').on('click', function() {
758 if (!confirm('Mark sync as complete? This will reset the progress indicator.')) {
759 return;
760 }
761
762 var $btn = $(this);
763 $btn.prop('disabled', true).text('Resetting...');
764
765 $.ajax({
766 url: ajaxurl,
767 type: 'POST',
768 data: {
769 action: 'onwebchat_wc_reset_sync_status',
770 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
771 },
772 success: function(response) {
773 if (response.success) {
774 location.reload();
775 } else {
776 alert('Error: ' + response.data);
777 $btn.prop('disabled', false).text('Mark Sync as Complete');
778 }
779 },
780 error: function() {
781 alert('An error occurred. Please try again.');
782 $btn.prop('disabled', false).text('Mark Sync as Complete');
783 }
784 });
785 });
786
787 // Regenerate secret button (now "Disconnect" button)
788 $('#onwebchat_regenerate_secret').on('click', function() {
789 if (!confirm('This will disconnect WooCommerce sync. You will need to re-authenticate with your credentials. Continue?')) {
790 return;
791 }
792
793 var $btn = $(this);
794 $btn.prop('disabled', true).text('Disconnecting...');
795
796 $.ajax({
797 url: ajaxurl,
798 type: 'POST',
799 data: {
800 action: 'onwebchat_wc_regenerate_secret',
801 nonce: '<?php echo wp_create_nonce('onwebchat_wc_sync_nonce'); ?>'
802 },
803 success: function(response) {
804 if (response.success) {
805 alert('WooCommerce sync disconnected. Please re-authenticate to continue syncing.');
806 location.reload();
807 } else {
808 alert('Error: ' + response.data);
809 $btn.prop('disabled', false).text('Disconnect');
810 }
811 },
812 error: function() {
813 alert('An error occurred. Please try again.');
814 $btn.prop('disabled', false).text('Disconnect');
815 }
816 });
817 });
818 });
819 </script>
820
821 <?php endif; // End if secret exists ?>
822
823 <?php
824 }
825
826 /**
827 * Handle form submissions for WooCommerce tab
828 */
829 function onwebchat_handle_woocommerce_actions() {
830
831 if (isset($_POST["action"]) && $_POST["action"] == "save_wc_sync") {
832
833 if (!isset($_POST['_wpnonce']) || !wp_verify_nonce($_POST['_wpnonce'], 'onwebchat_wc_sync_nonce')) {
834 wp_die('Sorry, your nonce did not verify.');
835 }
836
837 if (!current_user_can('manage_options')) {
838 wp_die('Insufficient permissions.');
839 }
840
841 // Save settings
842 $sync_enabled = isset($_POST["onwebchat_wc_sync_enabled"]) ? true : false;
843 $sync_mode = isset($_POST["onwebchat_wc_sync_mode"]) ? sanitize_text_field($_POST["onwebchat_wc_sync_mode"]) : 'short_fallback_full';
844
845 update_option('onwebchat_wc_sync_enabled', $sync_enabled);
846 update_option('onwebchat_wc_sync_mode', $sync_mode);
847
848 // Secret is now obtained via authenticated connection, not auto-generated
849
850 wp_redirect(admin_url('admin.php?page=onwebchat_settings&tab=woocommerce&wc_saved=1'));
851 exit;
852 }
853 }
854
855