PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.6.3
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.6.3
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / admin / includes / Actions / Ajax / ZohoInventoryAjax.php
commercebird / admin / includes / Actions / Ajax Last commit date
AcfAjax.php 8 months ago ExactOnlineAjax.php 8 months ago SettingsAjax.php 8 months ago ZohoCRMAjax.php 8 months ago ZohoInventoryAjax.php 8 months ago index.php 1 year ago
ZohoInventoryAjax.php
1148 lines
1 <?php
2
3 namespace CommerceBird\Admin\Actions\Ajax;
4
5 use CMBIRD_Auth_Zoho;
6 use CMBIRD_API_Handler_Zoho;
7 use CMBIRD_Pricelist_ZI;
8 use CMBIRD_Categories_ZI;
9 use CommerceBird\Admin\Template;
10 use CommerceBird\Admin\Traits\AjaxRequest;
11 use CommerceBird\Admin\Traits\OptionStatus;
12 use CommerceBird\Admin\Traits\Singleton;
13 use CommerceBird\Admin\Traits\LogWriter;
14 use Throwable;
15 use WC_Tax;
16 use function gettype;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit;
20 }
21 final class ZohoInventoryAjax {
22
23
24 use Singleton;
25 use AjaxRequest;
26 use OptionStatus;
27 use LogWriter;
28
29 private const FORMS = array(
30 'tax' => array(
31 'selectedTaxRates',
32 ),
33 'product' => array(
34 'item_from_zoho',
35 'disable_stock_sync',
36 'disable_product_sync',
37 'enable_accounting_stock',
38 ),
39 'cron' => array(
40 'form',
41 'categories',
42 ),
43 'order' => array(
44 'disable_sync',
45 'enable_auto_number',
46 'enable_order_status',
47 'order_prefix',
48 'location_id',
49 'enable_locationstock',
50 ),
51 'contact' => array(
52 'enable_cron',
53 ),
54 'price' => array(
55 'wp_user_role',
56 'zoho_inventory_pricelist',
57 'wcb2b',
58 ),
59 );
60
61 private const ACTIONS = array(
62 'get_zoho_connect' => 'connection_get',
63 'save_zoho_connect' => 'connection_set',
64 'reset_zoho_connect' => 'connection_reset',
65 'get_zoho_tax' => 'tax_get',
66 'save_zoho_tax' => 'tax_set',
67 'reset_zoho_tax' => 'tax_reset',
68 'get_zoho_product' => 'product_get',
69 'save_zoho_product' => 'product_set',
70 'reset_zoho_product' => 'product_reset',
71 'get_zoho_cron' => 'cron_get',
72 'save_zoho_cron' => 'cron_set',
73 'reset_zoho_cron' => 'cron_reset',
74 'get_zoho_order' => 'order_get',
75 'save_zoho_order' => 'order_set',
76 'reset_zoho_order' => 'order_reset',
77 'get_zoho_contact' => 'contact_get',
78 'save_zoho_contact' => 'contact_set',
79 'reset_zoho_contact' => 'contact_reset',
80 'get_zoho_price' => 'price_get',
81 'save_zoho_price' => 'price_set',
82 'reset_zoho_price' => 'price_reset',
83 'get_zoho_fields' => 'fields_get',
84 'save_zoho_fields' => 'fields_set',
85 'reset_zoho_fields' => 'fields_reset',
86 'is_connected' => 'connection_done',
87 'get_wc_taxes' => 'wc_tax_collect',
88 'get_zoho_taxes' => 'zoho_tax_rates_collect',
89 'get_zoho_categories' => 'zoho_categories_collect',
90 'get_zoho_locations' => 'zoho_locations_collect',
91 'get_zoho_prices' => 'zoho_prices_collect',
92 'get_all_custom_fields' => 'wc_custom_fields_collect',
93 'get_zoho_webhooks' => 'get_zoho_webhooks',
94 'handle_code' => 'handle_code',
95 'convert_guest' => 'convert_guest',
96 'zoho_ajax_call_variable_item_from_zoho' => 'sync_variable_items_from_zoho',
97 'zoho_ajax_call_item_from_zoho' => 'sync_simple_items_from_zoho',
98 'zoho_ajax_call_item' => 'sync_items_to_zoho',
99 'import_zoho_contacts' => 'sync_contacts_from_zoho',
100 'zoho_ajax_call_composite_item_from_zoho' => 'sync_composite_items_from_zoho',
101 'zoho_ajax_call_composite_item' => 'sync_composite_items_to_zoho',
102 );
103
104 private const SOURCE = 'zoho';
105
106 /**
107 * ZohoInventoryAjax constructor.
108 */
109 public function __construct() {
110 $this->load_actions();
111 }
112
113 /**
114 * Collects custom checkout fields for WooCommerce.
115 *
116 * This function verifies the request and collects custom fields from the
117 * THWCFD_Utils::get_checkout_fields() function if the THWCFD class exists.
118 *
119 * @return void
120 */
121 public function wc_custom_fields_collect(): void {
122 $this->verify();
123 $types = array( 'billing', 'shipping', 'additional' );
124 $all_fields = array();
125 $custom_fields = array();
126 // Get all the fields.
127 foreach ( $types as $type ) {
128 // Skip if an unsupported type.
129 if (
130 ! in_array(
131 $type,
132 array(
133 'billing',
134 'shipping',
135 'additional',
136 ),
137 true,
138 )
139 ) {
140 continue;
141 }
142
143 $temp_fields = get_option( 'wc_fields_' . $type );
144 if ( false !== $temp_fields ) {
145 $all_fields = array_merge( $all_fields, $temp_fields );
146 }
147 }
148 // Loop through each field to see if it is a custom field.
149 foreach ( $all_fields as $name => $options ) {
150 if ( isset( $options['custom'] ) && $options['custom'] ) {
151 $label = trim( $options['label'] );
152 $custom_fields[ $name ] = empty( $label ) ? __( 'Please set label', 'commercebird' ) : $label;
153 }
154 }
155
156 $this->response = $custom_fields;
157
158 $this->serve();
159 }
160
161 /**
162 * Retrieves the connection details.
163 *
164 * @return void
165 */
166 public function connection_get(): void {
167 $this->verify();
168 $this->response['account_domain'] = get_option( 'cmbird_zoho_inventory_domain' );
169 $this->response['organization_id'] = get_option( 'cmbird_zoho_inventory_oid' );
170 $this->response['client_id'] = get_option( 'cmbird_zoho_inventory_cid' );
171 $this->response['client_secret'] = get_option( 'cmbird_zoho_inventory_cs' );
172 $this->response['inventory_url'] = get_option( 'cmbird_zoho_inventory_url' );
173 $this->response['redirect_uri'] = get_option( 'cmbird_authorization_redirect_uri' );
174 $this->serve();
175 }
176
177 /**
178 * Retrieves the fields and serves the response.
179 *
180 * @return void
181 */
182 public function fields_get(): void {
183 $this->verify();
184 $this->response['form'] = get_option( 'cmbird_wootozoho_custom_fields', array() );
185 $this->serve();
186 }
187
188 /**
189 * Sets the custom fields for the form.
190 *
191 * @return void
192 */
193 public function fields_set(): void {
194 $this->verify( array( 'form' ) );
195 try {
196 update_option( 'cmbird_wootozoho_custom_fields', $this->data['form'] );
197 $this->response = array( 'message' => 'saved' );
198 } catch ( Throwable $throwable ) {
199 $this->errors = array( 'message' => $throwable->getMessage() );
200 }
201 $this->serve();
202 }
203
204 /**
205 * Resets the fields.
206 *
207 * @return void
208 */
209 public function fields_reset(): void {
210 $this->verify();
211 delete_option( 'cmbird_wootozoho_custom_fields' );
212 $this->response = array( 'message' => 'Reset successfully!' );
213 $this->serve();
214 }
215
216 /**
217 * Resets the price.
218 *
219 * @return void
220 */
221 public function price_reset(): void {
222 $this->verify();
223 delete_option( 'cmbird_zoho_pricelist_id' );
224 delete_option( 'cmbird_zoho_pricelist_role' );
225 delete_option( 'cmbird_zoho_pricelist_wcb2b_groups' );
226 $this->response = array( 'message' => 'Reset successfully!' );
227 $this->serve();
228 }
229
230 /**
231 * Retrieves the price information.
232 *
233 * @return void
234 */
235 public function price_get(): void {
236 $this->verify();
237 $this->response['zoho_inventory_pricelist'] = get_option( 'cmbird_zoho_pricelist_id' );
238 $this->response['wp_user_role'] = get_option( 'cmbird_zoho_pricelist_role' );
239 if ( class_exists( 'WooCommerceB2B' ) ) {
240 $this->response['wcb2b'] = get_option( 'cmbird_zoho_pricelist_wcb2b_groups' );
241 }
242 $this->serve();
243 }
244
245 /**
246 * Sets the price and saves the pricelist.
247 */
248 public function price_set(): void {
249 $this->verify( self::FORMS['price'] );
250 try {
251 $import_pricelist = new CMBIRD_Pricelist_ZI();
252 $success = $import_pricelist->save_price_list( $this->data );
253 if ( class_exists( 'Addify_B2B_Plugin' ) ) {
254 update_option( 'cmbird_zoho_pricelist_role', $this->data['wp_user_role'] );
255 } else {
256 update_option( 'cmbird_zoho_pricelist_wcb2b_groups', $this->data['wcb2b'] );
257 }
258 if ( $success ) {
259 $this->response = array( 'message' => 'Saved' );
260 if ( class_exists( 'WooCommerceB2B' ) ) {
261 $this->response['wcb2b'] = $this->data['wcb2b'];
262 }
263 } else {
264 $this->errors = array( 'message' => 'Failed to save' );
265 }
266 } catch ( Throwable $throwable ) {
267 $this->errors = array( 'message' => $throwable->getMessage() );
268 $this->write_log(
269 array(
270 'message' => $throwable->getMessage(),
271 'files' => wp_list_pluck( $throwable->getTrace(), 'file', 'line' ),
272 ),
273 'zoho-ajax-error'
274 );
275 }
276
277 $this->serve();
278 }
279
280 /**
281 * Collects Zoho prices.
282 *
283 * @return void
284 */
285 public function zoho_prices_collect(): void {
286 $this->verify();
287 $price_list_class = new CMBIRD_Pricelist_ZI();
288 $prices = $price_list_class->zi_get_all_pricelist();
289 $this->response = wp_list_pluck( $prices, 'name', 'pricebook_id' );
290 $this->serve();
291 }
292
293 /**
294 * Retrieves the contact details.
295 *
296 * @return void
297 */
298 public function contact_get() {
299 $this->verify();
300 $this->response = $this->option_status_get( self::FORMS['contact'], self::SOURCE );
301 $this->serve();
302 }
303 /**
304 * Sets the contact and updates the status.
305 *
306 * @return void
307 */
308 public function contact_set() {
309 $this->verify( self::FORMS['contact'] );
310 $this->option_status_update( $this->data, self::SOURCE );
311 if ( isset( $this->data['enable'] ) && ! empty( $this->data['enable'] ) ) {
312 if ( ! wp_next_scheduled( 'zoho_contact_sync' ) ) {
313 wp_schedule_event( time(), 'daily', 'zoho_contact_sync' );
314 }
315 } else {
316 wp_clear_scheduled_hook( 'zoho_contact_sync' );
317 }
318 $this->response = array( 'message' => 'Saved!' );
319 $this->serve();
320 }
321 /**
322 * Resets the contact.
323 *
324 * @return void
325 */
326 public function contact_reset() {
327 $this->verify();
328 $this->option_status_remove( self::FORMS['contact'], self::SOURCE );
329 $this->response = array( 'message' => 'Reset successfully!' );
330 $this->serve();
331 }
332
333 /**
334 * Sets the order and updates the status.
335 *
336 * @return void
337 */
338 public function order_set(): void {
339 $this->verify( self::FORMS['order'] );
340 $this->option_status_update( $this->data, self::SOURCE );
341 $this->response = array( 'message' => 'Saved!' );
342 $this->serve();
343 }
344
345 /**
346 * Retrieves the order.
347 *
348 * @return void
349 */
350 public function order_get(): void {
351 $this->verify();
352 $this->response = $this->option_status_get( self::FORMS['order'], self::SOURCE );
353 $this->serve();
354 }
355
356 /**
357 * Resets the order.
358 *
359 * @return void
360 */
361 public function order_reset(): void {
362 $this->verify();
363 $this->option_status_remove( self::FORMS['order'], self::SOURCE );
364 $this->response = array( 'message' => 'Reset successfully!' );
365 $this->serve();
366 }
367
368 /**
369 * Collects Zoho locations.
370 *
371 * This function verifies the request, retrieves the Zoho inventory
372 * organization ID and URL from the options, constructs the URL
373 * for the API call, and executes the cURL call to retrieve the
374 * locations. The response is then processed to extract the
375 * location names and IDs, and stored in the class property.
376 *
377 * @return void
378 */
379 public function zoho_locations_collect(): void {
380 // $fd = fopen( __DIR__ . '/zoho_locations.txt', 'w+' );
381 $this->verify();
382 $zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
383 $zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
384 $url = $zoho_inventory_url . 'inventory/v1/locations?organization_id=' . $zoho_inventory_oid;
385 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
386 $json = $execute_curl_call_handle->execute_curl_call_get( $url );
387 // save the first location id to the database of the first object of json.
388 if ( ! empty( $json->locations ) && is_array( $json->locations ) && array_key_exists( 0, $json->locations ) ) {
389 $location_id = $json->locations[0]->location_id;
390 update_option( 'cmbird_zoho_parent_location_id_status', $location_id );
391 }
392 $this->response = wp_list_pluck( $json->locations, 'location_name', 'location_id' );
393 $this->serve();
394 }
395
396 /**
397 * Sets the cron for the class.
398 */
399 public function cron_set(): void {
400 $this->verify( self::FORMS['cron'] );
401 if ( array_key_exists( 'form', $this->data ) ) {
402 $decode = json_decode( $this->data['form'], true );
403 update_option( 'zi_cron_interval', $decode['zi_cron_interval'] );
404 unset( $decode['zi_cron_interval'] );
405 $this->option_status_update( $decode, self::SOURCE );
406 }
407 if ( array_key_exists( 'categories', $this->data ) ) {
408 $decode = json_decode( $this->data['categories'] );
409 update_option( 'cmbird_zoho_item_category', serialize( $decode ) );
410 }
411 $this->response = array( 'message' => 'Saved' );
412 $this->serve();
413 }
414
415 /**
416 * Executes the cron_get function.
417 *
418 * This function verifies the form status and retrieves the form options
419 * for syncing name, price, image, and description. It also retrieves
420 * the cron interval and the Zoho item categories.
421 *
422 * @return void
423 */
424 public function cron_get(): void {
425 $this->verify();
426 $this->response = array();
427 $this->response['form'] = $this->option_status_get(
428 array(
429 'disable_name_sync',
430 'disable_price_sync',
431 'disable_image_sync',
432 'disable_description_sync',
433 ),
434 self::SOURCE,
435 );
436 $this->response['form']['zi_cron_interval'] = get_option( 'zi_cron_interval', 'none' );
437 $categories = get_option( 'cmbird_zoho_item_category', '' );
438 $this->response['categories'] = maybe_unserialize( $categories );
439 $this->serve();
440 }
441
442 /**
443 * Resets the cron job.
444 *
445 * This function verifies the cron job, removes the specified options,
446 * deletes the 'zoho_item_category' option, and serves the cron job.
447 *
448 * @return void
449 */
450 public function cron_reset(): void {
451 $this->verify();
452 $this->option_status_remove(
453 array(
454 'disable_name_sync',
455 'disable_price_sync',
456 'disable_image_sync',
457 'disable_description_sync',
458 ),
459 self::SOURCE,
460 );
461 delete_option( 'cmbird_zoho_item_category' );
462 // delete all options that contain 'zoho_id_for_term_id_'.
463 global $wpdb;
464
465 // First, count how many matching options exist.
466 $count_before = $wpdb->get_var(
467 $wpdb->prepare(
468 "SELECT COUNT(*) FROM {$wpdb->prefix}options WHERE option_name LIKE %s",
469 'cmbird_zoho_id_for_term_id_%'
470 )
471 );
472
473 // Now delete them.
474 $deleted_rows = $wpdb->query(
475 $wpdb->prepare(
476 "DELETE FROM {$wpdb->prefix}options WHERE option_name LIKE %s",
477 'cmbird_zoho_id_for_term_id_%'
478 )
479 );
480
481 $this->response = array( 'message' => "Reset successfully! Found {$count_before} category mapping options, deleted {$deleted_rows}." );
482 $this->serve();
483 }
484
485 /**
486 * Collects all categories from both Zoho Inventory and WooCommerce.
487 *
488 * This function retrieves categories from Zoho Inventory and merges them with
489 * WooCommerce categories to provide a complete list for the frontend display.
490 * This ensures all 66+ categories are visible, not just the ones in Zoho.
491 */
492 public function zoho_categories_collect(): void {
493 $this->verify();
494 $category_class = new CMBIRD_Categories_ZI();
495
496 // Only return Zoho categories since we're storing Zoho category IDs in options.
497 $zoho_categories = $category_class->cmbird_get_zoho_item_categories();
498 if ( gettype( $zoho_categories ) === 'array' ) {
499 $this->response = $zoho_categories;
500 } else {
501 $this->response = array();
502 }
503 $this->serve();
504 }
505
506 /**
507 * Executes the connection process and handles the response.
508 */
509 public function connection_done(): void {
510 $this->verify();
511 $zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
512 $zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
513 $url = $zoho_inventory_url . 'inventory/v1/apiusagereport/dashboard?version=all&organization_id=' . $zoho_inventory_oid;
514
515 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
516 $json = $execute_curl_call_handle->execute_curl_call_get( $url );
517
518 if ( is_wp_error( $json ) ) {
519 $this->errors = array( 'message' => $json->get_error_message() );
520 return;
521 }
522
523 if ( empty( $json ) || empty( $json->data ) || empty( $json->data->api_usage ) ) {
524 $this->errors = array( 'message' => 'We lost connection with Zoho. Please refresh the page.' );
525 return;
526 }
527 $api_usage = $json->data->api_usage;
528 // Debug JSON encoding.
529 $encoded_api_usage = wp_json_encode( $api_usage );
530 // Ensure it's not null.
531 $this->response = json_decode( $encoded_api_usage, true ) ?? array();
532 $this->serve();
533 }
534
535 /**
536 * Executes the product_set function.
537 */
538 public function product_set(): void {
539 $this->verify( self::FORMS['product'] );
540 if ( ! empty( $this->data ) ) {
541 $this->option_status_update( $this->data, self::SOURCE );
542 }
543 $this->response = array( 'message' => 'Saved!' );
544 $this->serve();
545 }
546
547 /**
548 * Retrieves the product data.
549 *
550 * @return void
551 */
552 public function product_get(): void {
553 $this->verify();
554 $this->response = $this->option_status_get( self::FORMS['product'], self::SOURCE );
555 $this->serve();
556 }
557
558 /**
559 * Resets the product.
560 *
561 * @return void
562 */
563 public function product_reset(): void {
564 $this->verify();
565 $this->option_status_remove( self::FORMS['product'], self::SOURCE );
566 $this->response = array( 'message' => 'Reset successfully!' );
567 $this->serve();
568 }
569
570 /**
571 * Retrieves the tax information.
572 *
573 * @return void
574 */
575 public function tax_get(): void {
576 $this->verify();
577 $this->response = array();
578 foreach ( $this->wc_taxes() as $tax ) {
579 $this->response['selectedTaxRates'][] = $tax['id'] . '^^' . get_option( 'cmbird_zoho_inventory_tax_rate_' . $tax['id'] );
580 }
581 $this->serve();
582 }
583
584 /**
585 * Retrieves an array of all tax rates from WooCommerce.
586 *
587 * @return array An array of all tax rates.
588 */
589 public function wc_taxes(): array {
590 $wc_tax_array = array();
591 $tax_classes = WC_Tax::get_tax_classes(); // Retrieve all tax classes.
592 if ( ! in_array( '', $tax_classes ) ) { // Make sure "Standard rate" (empty class name) is present.
593 array_unshift( $tax_classes, '' );
594 }
595
596 foreach ( $tax_classes as $tax_class ) { // For each tax class, get all rates.
597 $taxes = WC_Tax::get_rates_for_tax_class( $tax_class );
598
599 foreach ( $taxes as $key => $tax ) {
600 $taxarray = (array) $tax;
601 $taxarray['id'] = $key;
602 $wc_tax_array[] = $taxarray;
603 }
604 }
605
606 return $wc_tax_array;
607 }
608
609 /**
610 * Resets the tax settings.
611 *
612 * This function verifies the tax settings and then deletes the options
613 * related to the tax rates
614 *
615 * @return void
616 */
617 public function tax_reset(): void {
618 $this->verify();
619 foreach ( $this->wc_taxes() as $tax ) {
620 delete_option( 'cmbird_zoho_inventory_tax_rate_' . $tax['id'] );
621 }
622 $this->response = array( 'message' => 'Reset successfully!' );
623 $this->serve();
624 }
625
626 /**
627 * Saves tax rates to the database.
628 */
629 public function tax_set(): void {
630 $this->verify( self::FORMS['tax'] );
631 $rates = array_filter( $this->data['selectedTaxRates'] );
632 try {
633 foreach ( $rates as $value ) {
634 $valarray = explode( '^^', $value );
635 update_option( 'cmbird_zoho_inventory_tax_rate_' . $valarray[0], $valarray[1] );
636 }
637 $this->response = array( 'message' => 'Saved' );
638 } catch ( Throwable $throwable ) {
639 $this->errors = array( 'message' => $throwable->getMessage() );
640 }
641
642 $this->serve();
643 }
644
645 /**
646 * Sets the connection for the Zoho Inventory API.
647 */
648 public function connection_set(): void {
649 $this->verify(
650 array(
651 'account_domain',
652 'organization_id',
653 'client_id',
654 'client_secret',
655 'redirect_uri',
656 ),
657 );
658 try {
659 $inventory = sprintf( 'https://www.zohoapis.%s/', $this->data['account_domain'] );
660 update_option( 'cmbird_zoho_inventory_domain', $this->data['account_domain'] );
661 update_option( 'cmbird_zoho_inventory_oid', $this->data['organization_id'] );
662 update_option( 'cmbird_zoho_inventory_cid', $this->data['client_id'] );
663 update_option( 'cmbird_zoho_inventory_cs', $this->data['client_secret'] );
664 update_option( 'cmbird_zoho_inventory_url', $inventory );
665 update_option( 'cmbird_authorization_redirect_uri', $this->data['redirect_uri'] );
666 $redirect = esc_url_raw( 'https://accounts.zoho.' . $this->data['account_domain'] . '/oauth/v2/auth?response_type=code&client_id=' . $this->data['client_id'] . '&scope=ZohoInventory.FullAccess.all&redirect_uri=' . $this->data['redirect_uri'] . '&prompt=consent&access_type=offline&state=' . wp_create_nonce( Template::NAME ) );
667 $this->response = array(
668 'redirect' => $redirect,
669 'message' => 'We are redirecting you to zoho. please wait...',
670 );
671 } catch ( Throwable $throwable ) {
672 $this->errors = array( 'message' => $throwable->getMessage() );
673 }
674
675 $this->serve();
676 }
677
678 /**
679 * Resets the connection by deleting specific options from the database.
680 */
681 public function connection_reset(): void {
682 $this->verify();
683 try {
684 // Check if resetAll is true (sent from Vue).
685 $reset_all = isset( $this->data['reset'] ) && $this->data['reset'] == 1;
686 if ( ! $reset_all ) {
687 $options = array(
688 'cmbird_zoho_inventory_domain',
689 'cmbird_zoho_inventory_oid',
690 'cmbird_zoho_inventory_cid',
691 'cmbird_zoho_inventory_cs',
692 'cmbird_zoho_inventory_url',
693 'cmbird_zoho_inventory_access_token',
694 );
695 foreach ( $options as $zi_option ) {
696 delete_option( $zi_option );
697 }
698 $this->response = array( 'message' => 'Reset successfully!' );
699 } else {
700 global $wpdb;
701 // Delete all product and customer meta keys.
702 $wpdb->query( "DELETE FROM {$wpdb->prefix}postmeta WHERE meta_key IN ('zi_item_id', 'zi_category_id')" );
703 $wpdb->query( "DELETE FROM {$wpdb->prefix}usermeta WHERE meta_key IN ('zi_contact_id', 'zi_primary_contact_id', 'zi_billing_address_id', 'zi_shipping_address_id', 'zi_contact_persons_id')" );
704 // reset every option that starts with cmbird_.
705 $wpdb->query(
706 $wpdb->prepare(
707 "DELETE FROM {$wpdb->prefix}options WHERE option_name LIKE %s",
708 'cmbird_zoho_%'
709 )
710 );
711 $this->response['message'] = __( 'All settings are reset successfully', 'commercebird' );
712 }
713 } catch ( Throwable $throwable ) {
714 $this->errors = array( 'message' => $throwable->getMessage() );
715 }
716
717 $this->serve();
718 }
719
720 /**
721 * Retrieves the Zoho tax rates for the organization.
722 *
723 * This function verifies the request, retrieves the organization ID and URL
724 * from the options, constructs the URL for the API request, and executes
725 * the cURL call to fetch the tax rates. If the response contains the
726 * 'taxes' key, it sets the response to the tax rates. Otherwise, it sets
727 * the errors to a default message.
728 */
729 public function zoho_tax_rates_collect(): void {
730 $this->verify();
731 $zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
732 $zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
733 $url = $zoho_inventory_url . 'inventory/v1/settings/taxes?organization_id=' . $zoho_inventory_oid;
734 try {
735 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
736 $json = (array) $execute_curl_call_handle->execute_curl_call_get( $url );
737 if ( array_key_exists( 'taxes', $json ) ) {
738 $this->response = $json['taxes'];
739 } else {
740 $this->errors = array( 'message' => 'Something went wrong!' );
741 }
742 } catch ( Throwable $throwable ) {
743 $this->errors = array( 'message' => $throwable->getMessage() );
744 }
745
746 $this->serve();
747 }
748
749 /**
750 * Executes the wc_tax_collect function.
751 *
752 * This function verifies the current state, retrieves the taxes using the
753 * wc_taxes method, and serves the response.
754 */
755 public function wc_tax_collect(): void {
756 $this->verify();
757 $this->response = $this->wc_taxes();
758 $this->serve();
759 }
760
761 /**
762 * Handles the oAuth code.
763 */
764 public function handle_code(): void {
765 $this->verify();
766 if ( array_key_exists( 'code', $this->request ) ) {
767 $class_functions = new CMBIRD_Auth_Zoho();
768 $code = $this->request['code'];
769 try {
770 $access_token = $class_functions->get_zoho_access_token( $code, 'zoho_inventory' );
771 if ( array_key_exists( 'access_token', $access_token ) ) {
772 update_option( 'cmbird_zoho_inventory_auth_code', $code );
773 update_option( 'cmbird_zoho_inventory_access_token', $access_token['access_token'] );
774 update_option( 'cmbird_zoho_inventory_refresh_token', $access_token['refresh_token'] );
775 update_option( 'cmbird_zoho_inventory_timestamp', strtotime( gmdate( 'Y-m-d H:i:s' ) ) + $access_token['expires_in'] );
776 $this->response = (array) $access_token;
777 } else {
778 $this->errors = (array) $access_token;
779 }
780 } catch ( Throwable $throwable ) {
781 $this->errors = array( 'message' => $throwable->getMessage() );
782 }
783 }
784 $this->serve();
785 }
786
787 /**
788 * Converts all guest users to a registered users.
789 *
790 * This function verifies the request, retrieves all guest orders
791 *
792 * @return void
793 */
794 public function convert_guest(): void {
795 $this->verify();
796 $args = array(
797 'status' => array( 'wc-processing', 'wc-completed' ),
798 'return' => 'ids',
799 'customer' => 0, // Only guest orders.
800 'limit' => -1, // Retrieve all guest orders.
801 );
802
803 $orders = wc_get_orders( $args );
804 if ( empty( $orders ) ) {
805 $this->response = array( 'message' => __( 'No guest orders found.', 'commercebird' ) );
806 $this->serve();
807 return;
808 }
809 $count = count( $orders );
810 foreach ( $orders as $order_id ) {
811 $order = wc_get_order( $order_id );
812 $email = $order->get_billing_email();
813
814 if ( email_exists( $email ) === false ) {
815 $username = strstr( $email, '@', true );
816 $password = wp_generate_password();
817 $user_id = wc_create_new_customer( $email, $username, $password );
818
819 if ( ! is_wp_error( $user_id ) ) {
820 // use update_meta to set the user ID for the order.
821 $order->set_customer_id( $user_id );
822 $order->save();
823 }
824 } else {
825 // If the email already exists, we can still update the order to the existing user.
826 $user = get_user_by( 'email', $email );
827 if ( $user ) {
828 $order->set_customer_id( $user->ID );
829 $order->save();
830 }
831 }
832 }
833 /* translators: %d: Number of orders converted */
834 $this->response['message'] = sprintf( __( 'Successfully converted %d guest orders to registered users.', 'commercebird' ), $count );
835 $this->serve();
836 } /**
837 * Fetches webhooks from Zoho Inventory API.
838 *
839 * @return void
840 */
841 public function get_zoho_webhooks(): void {
842 $this->verify();
843 $zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
844 $zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
845 if ( ! $zoho_inventory_oid || ! $zoho_inventory_url ) {
846 $this->errors = array( 'message' => 'Missing Zoho organization ID or domain.' );
847 $this->serve();
848 return;
849 }
850 $url = $zoho_inventory_url . 'inventory/v1/settings/webhooks?page=1&per_page=50&usestate=false&organization_id=' . $zoho_inventory_oid;
851 try {
852 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
853 $json = $execute_curl_call_handle->execute_curl_call_get( $url );
854
855 // Check if the response is a WP_Error object.
856 if ( is_wp_error( $json ) ) {
857 $this->errors = array( 'message' => $json->get_error_message() );
858 } elseif ( isset( $json->webhooks ) && is_array( $json->webhooks ) ) {
859 $this->response = $json->webhooks;
860 } else {
861 $this->errors = array( 'message' => 'Failed to fetch webhooks or no webhooks found.' );
862 }
863 } catch ( Throwable $throwable ) {
864 $this->errors = array( 'message' => $throwable->getMessage() );
865 }
866 $this->serve();
867 }
868
869 /**
870 * Function to sync variable items from Zoho to WooCommerce.
871 */
872 public function sync_variable_items_from_zoho(): void {
873 $this->verify();
874
875 // Clear Orphan data.
876 $zi_common_class = new \CMBIRD_Common_Functions();
877 $zi_common_class->clear_orphan_data();
878
879 // check if a category is selected.
880 $selected_category = isset( $_GET['category'] ) ? sanitize_text_field( $_GET['category'] ) : null;
881
882 if ( $selected_category ) {
883 $categories = array( $selected_category ); // Only sync the selected category.
884 } else {
885 // get category to filter by category.
886 $opt_category = get_option( 'cmbird_zoho_item_category' );
887 if ( $opt_category ) {
888 // convert serialized string to array.
889 $categories = maybe_unserialize( $opt_category );
890 if ( ! is_array( $categories ) ) {
891 $categories = array();
892 }
893 } else {
894 $categories = array();
895 }
896 }
897
898 // Slice the category array to start from the last synced category index.
899 if ( ! empty( $categories ) ) {
900 foreach ( $categories as $category_index => $category_id ) {
901 $data = array(
902 'page' => 1,
903 'category' => $category_id,
904 );
905 $existing_schedule = as_has_scheduled_action( 'import_group_items_cron', $data );
906 // Schedule the action if it doesn't exist.
907 if ( ! $existing_schedule ) {
908 as_schedule_single_action( time(), 'import_group_items_cron', $data );
909 }
910 }
911 }
912
913 $this->response = array( 'message' => 'Items are being imported in background. You can visit other tabs :).' );
914 $this->serve();
915 }
916
917 /**
918 * Function to sync simple items from Zoho to WooCommerce.
919 */
920 public function sync_simple_items_from_zoho(): void {
921 $this->verify();
922
923 // Clear Orphan data.
924 $zi_common_class = new \CMBIRD_Common_Functions();
925 $zi_common_class->clear_orphan_data();
926
927 // Check if a category is selected.
928 // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Admin sync action, nonce verified in parent.
929 $selected_category = isset( $_GET['category'] ) ? sanitize_text_field( wp_unslash( $_GET['category'] ) ) : null;
930
931 if ( $selected_category ) {
932 $categories = array( $selected_category ); // Only sync the selected category.
933 } else {
934 $zoho_item_category = get_option( 'cmbird_zoho_item_category' );
935 if ( $zoho_item_category ) {
936 // convert serialized string to array.
937 $categories = maybe_unserialize( $zoho_item_category );
938 if ( ! is_array( $categories ) ) {
939 $categories = array();
940 }
941 } else {
942 $categories = array();
943 }
944 }
945
946 if ( empty( $categories ) ) {
947 $this->errors = array( 'message' => __( 'Please select at least one category from cron tab', 'commercebird' ) );
948 } else {
949 foreach ( $categories as $index => $category_id ) {
950 $data = array(
951 'page' => 1,
952 'category' => $category_id,
953 );
954 $existing_schedule = as_has_scheduled_action( 'import_simple_items_cron', $data );
955 if ( ! $existing_schedule ) {
956 as_schedule_single_action( time(), 'import_simple_items_cron', $data );
957 }
958 }
959 $this->response = array( 'message' => __( 'Items are being imported in background. You can visit other tabs :).', 'commercebird' ) );
960 }
961 $this->serve();
962 }
963
964 /**
965 * Function to sync items from WooCommerce to Zoho.
966 */
967 public function sync_items_to_zoho(): void {
968 $this->verify();
969
970 global $wpdb;
971
972 $meta_key = 'zi_item_id';
973 $post_ids = $wpdb->get_col(
974 $wpdb->prepare(
975 "SELECT p.ID
976 FROM {$wpdb->prefix}posts AS p
977 WHERE p.post_type = 'product'
978 AND p.post_status = 'publish'
979 AND NOT EXISTS (
980 SELECT 1
981 FROM {$wpdb->prefix}postmeta AS pm
982 WHERE p.ID = pm.post_id
983 AND pm.meta_key = %s
984 )",
985 $meta_key
986 )
987 );
988 // Create an array to hold the product IDs.
989 $product_ids = array();
990 // Adding all items in the queue.
991 foreach ( $post_ids as $post_id ) {
992 // Add post ID to the array.
993 $product_ids[] = $post_id;
994 // Check if the array contains 10 product IDs (batch size).
995 if ( count( $product_ids ) === 10 ) {
996 // Schedule the action with a delay increasing exponentially for each batch.
997 as_schedule_single_action( time(), 'sync_zi_product_cron', array( $product_ids ) );
998 // Clear the array for the next batch of product IDs.
999 $product_ids = array();
1000 }
1001 }
1002
1003 // Handle any remaining product IDs if the count is less than 10 in the last batch.
1004 if ( count( $product_ids ) > 0 ) {
1005 // Pass the remaining product IDs to the scheduler function.
1006 as_schedule_single_action( time(), 'sync_zi_product_cron', array( $product_ids ) );
1007 } else {
1008 $this->response = array( 'message' => 'No items to sync.' );
1009 $this->serve();
1010 return;
1011 }
1012
1013 $this->response = array( 'message' => 'Products are being synced in background.' );
1014 $this->serve();
1015 }
1016
1017 /**
1018 * Sync contacts from Zoho to WooCommerce.
1019 */
1020 public function sync_contacts_from_zoho(): void {
1021 $this->verify();
1022
1023 $page = 1;
1024 $data_arr = (object) array();
1025 $data_arr->page = $page;
1026 $existing_schedule = as_has_scheduled_action( 'sync_zi_import_contacts', array( $data_arr ) );
1027
1028 // Wrap this via Action Scheduler per page.
1029 if ( ! $existing_schedule ) {
1030 // Schedule the cron job.
1031 as_schedule_single_action( time(), 'sync_zi_import_contacts', array( $data_arr ) );
1032 }
1033
1034 $this->response = array( 'message' => 'Syncing in background. You can visit other tabs :).' );
1035 $this->serve();
1036 }
1037
1038 /**
1039 * Sync composite items from Zoho to WooCommerce.
1040 */
1041 public function sync_composite_items_from_zoho(): void {
1042 $this->verify();
1043
1044 // Clear Orphan data.
1045 $zi_common_class = new \CMBIRD_Common_Functions();
1046 $zi_common_class->clear_orphan_data();
1047
1048 // check if a category is selected.
1049 $selected_category = isset( $_GET['category'] ) ? sanitize_text_field( $_GET['category'] ) : null;
1050 if ( $selected_category ) {
1051 $categories = array( $selected_category ); // Only sync the selected category.
1052 } else {
1053 // get category to filter by category.
1054 $opt_category = get_option( 'cmbird_zoho_item_category' );
1055 if ( $opt_category ) {
1056 // convert serialized string to array.
1057 $categories = maybe_unserialize( $opt_category );
1058 if ( ! is_array( $categories ) ) {
1059 $categories = array();
1060 }
1061 } else {
1062 $categories = array();
1063 }
1064 }
1065
1066 $item_add_resp = array();
1067 foreach ( $categories as $category_id ) {
1068 $product_class = new \CMBIRD_Products_ZI();
1069 $response = $product_class->recursively_sync_composite_item_from_zoho( 1, $category_id );
1070 $item_add_resp = array_merge( $item_add_resp, $response );
1071 }
1072
1073 // Send log message to admin if there are responses.
1074 if ( ! empty( $item_add_resp ) ) {
1075 $this->send_log_message_to_admin( $item_add_resp, 'Log Message for manual sync', 'Composite item sync from zoho' );
1076 }
1077
1078 $this->response = $item_add_resp;
1079 $this->serve();
1080 }
1081
1082 /**
1083 * Sync composite items from WooCommerce to Zoho.
1084 */
1085 public function sync_composite_items_to_zoho(): void {
1086 $this->verify();
1087
1088 $irgs = array(
1089 'post_type' => array( 'product' ),
1090 'posts_per_page' => '-1',
1091 'post_status' => 'publish',
1092 'tax_query' => array(
1093 array(
1094 'taxonomy' => 'product_type',
1095 'field' => 'slug',
1096 'terms' => 'bundle',
1097 ),
1098 ),
1099 );
1100
1101 $my_query = new \WP_Query( $irgs );
1102 $bundled_product = $my_query->posts;
1103 if ( count( $bundled_product ) > 0 ) {
1104 foreach ( $bundled_product as $prod ) {
1105 $bundle_childs = \WC_PB_DB::query_bundled_items(
1106 array(
1107 'return' => 'id=>product_id', // 'objects'.
1108 'bundle_id' => array( $prod->ID ),
1109 )
1110 );
1111 if ( count( $bundle_childs ) > 0 ) {
1112 foreach ( $bundle_childs as $child_item_id ) {
1113 $zoho_item_id = get_post_meta( $child_item_id, 'zi_item_id', true );
1114 if ( empty( $zoho_item_id ) ) {
1115 break;
1116 }
1117 }
1118 }
1119 }
1120 }
1121
1122 $this->response = array(
1123 'message' => 'Sync Started',
1124 'code' => 200,
1125 );
1126 $this->serve();
1127 }
1128
1129 /**
1130 * Send log message to admin.
1131 *
1132 * @param array $sync_logs Log messages.
1133 * @param string $subject Email subject.
1134 * @param string $message Email message.
1135 */
1136 private function send_log_message_to_admin( $sync_logs, $subject, $message ): void {
1137 $table_root = "<h3>$message</h3>";
1138 $table_root .= '<table><thead><tr><th>Action</th><th> Log message</th></tr></thead><tbody>';
1139
1140 foreach ( $sync_logs as $logs ) {
1141 $table_root .= "<tr><td>$logs->resp_id</td><td>$logs->message</td></tr>";
1142 }
1143 $table_root .= '</tbody></table>';
1144 $common_class = new \CMBIRD_Common_Functions();
1145 $common_class->send_email( $subject, $table_root );
1146 }
1147 }
1148