PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 3.0.1
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v3.0.1
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 / ZohoCRMAjax.php
commercebird / admin / includes / Actions / Ajax Last commit date
AcfAjax.php 3 weeks ago ExactOnlineAjax.php 1 month ago SettingsAjax.php 1 month ago ZohoCRMAjax.php 3 months ago ZohoInventoryAjax.php 1 month ago index.php 1 year ago
ZohoCRMAjax.php
979 lines
1 <?php
2
3 namespace CommerceBird\Admin\Actions\Ajax;
4
5 use CMBIRD_Auth_Zoho;
6 use CMBIRD_API_Handler_Zoho;
7 use CommerceBird\Admin\Actions\Sync\ZohoCRMSync;
8 use CommerceBird\Admin\Template;
9 use CommerceBird\Admin\Traits\AjaxRequest;
10 use CommerceBird\Admin\Traits\OptionStatus;
11 use CommerceBird\Admin\Traits\Singleton;
12 use CommerceBird\Admin\Traits\LogWriter;
13 use WC_Tax;
14 use Throwable;
15
16
17 if ( ! defined( 'ABSPATH' ) ) {
18 exit;
19 }
20 /**
21 * Initializes the Zoho CRM class.
22 *
23 * @since 1.0.0
24 * @return void
25 */
26 final class ZohoCRMAjax {
27
28 use Singleton;
29 use AjaxRequest;
30 use OptionStatus;
31 use LogWriter;
32
33
34
35 private const FORMS = array(
36 // 'connect' => array(.
37 // 'token',.
38 // ),.
39 'product' => array(
40 'importProducts',
41 ),
42 'order' => array( 'range' ),
43 'order_settings' => array(
44 'syncAsDeals',
45 ),
46 'order_field_mapping' => array(
47 'dealFields',
48 ),
49 'order_status_stage_mapping' => array(
50 'mapping',
51 ),
52 'customer' => array(
53 'importCustomers',
54 ),
55 'contact_settings' => array(
56 'syncCustomers',
57 'syncOnRegistration',
58 'syncOnUpdate',
59 ),
60 'contact_layout' => array(
61 'layoutId',
62 ),
63 'fields' => array(
64 'form',
65 'module',
66 ),
67 'tax' => array(
68 'selectedTaxRates',
69 ),
70 );
71 private const ACTIONS = array(
72 'get_zcrm_connect' => 'connection_get',
73 'save_zcrm_connect' => 'connection_set',
74 'reset_zcrm_connect' => 'connection_reset',
75 'is_zcrm_connected' => 'connection_done',
76 'import_zcrm_product' => 'product_import',
77 'map_zcrm_product' => 'product_map',
78 'map_zcrm_customer' => 'customer_map',
79 'map_zcrm_order' => 'order_map',
80 'export_zcrm_order' => 'order_export',
81 'get_zcrm_order_settings' => 'order_settings_get',
82 'save_zcrm_order_settings' => 'order_settings_save',
83 'get_zcrm_deal_fields' => 'deal_fields_get',
84 'get_zcrm_deal_field_mapping' => 'deal_field_mapping_get',
85 'save_zcrm_deal_field_mapping' => 'deal_field_mapping_save',
86 'get_wc_order_statuses' => 'wc_order_statuses_get',
87 'get_zcrm_deal_stages' => 'deal_stages_get',
88 'get_order_status_stage_mapping' => 'order_status_stage_mapping_get',
89 'save_order_status_stage_mapping' => 'order_status_stage_mapping_save',
90 'refresh_zcrm_fields' => 'refresh_zcrm_fields',
91 'zcrm_get_custom_fields' => 'zcrm_get_custom_fields',
92 'zcrm_save_custom_fields' => 'zcrm_save_custom_fields',
93 'zcrm_reset_custom_fields' => 'zcrm_reset_custom_fields',
94 'zcrm_fields' => 'get_zcrm_fields',
95 'zcrm_handle_code' => 'handle_code',
96 'get_zcrm_tax' => 'tax_get',
97 'save_zcrm_tax' => 'tax_set',
98 'reset_zcrm_tax' => 'tax_reset',
99 'get_wc_taxes' => 'wc_tax_collect',
100 'get_zcrm_taxes' => 'zoho_tax_rates_collect',
101 'get_zcrm_layouts' => 'layouts_get',
102 'save_zcrm_layout' => 'layout_save',
103 'get_zcrm_selected_layout' => 'layout_get_selected',
104 'export_zcrm_contact' => 'contact_export',
105 'get_zcrm_contact_layouts' => 'contact_layouts_get',
106 'save_zcrm_contact_layout' => 'contact_layout_save',
107 'get_zcrm_selected_contact_layout' => 'contact_layout_get_selected',
108 );
109 private const OPTIONS = array(
110 // 'connect' => array(.
111 // 'token' => 'commercebird-exact-online-token',.
112 // ),.
113 'zcrm_sales_orders_fields' => 'zcrm_sales_orders_fields',
114 'zcrm_contacts_fields' => 'zcrm_contacts_fields',
115 'zcrm_products_fields' => 'zcrm_products_fields',
116 'zcrm_sales_order_layout' => 'zcrm_sales_order_layout',
117 'zcrm_contact_layout' => 'zcrm_contact_layout',
118 );
119
120 public function __construct() {
121 $this->load_actions();
122 }
123
124 /**
125 * Sets the connection for the Zoho crm API.
126 */
127 public function connection_set(): void {
128 $this->verify(
129 array(
130 'account_domain',
131 'client_id',
132 'client_secret',
133 'redirect_uri',
134 ),
135 );
136 try {
137 $domain = CMBIRD_Auth_Zoho::normalize_domain( $this->data['account_domain'] );
138 $crm_url = sprintf( 'https://www.zohoapis.%s/', $domain );
139 $oauth_args = array(
140 'response_type' => 'code',
141 'client_id' => $this->data['client_id'],
142 'scope' => 'ZohoCRM.users.ALL,ZohoCRM.bulk.ALL,ZohoCRM.modules.ALL,ZohoCRM.settings.ALL,ZohoCRM.org.ALL,profile.userphoto.READ,ZohoFiles.files.CREATE',
143 'redirect_uri' => $this->data['redirect_uri'],
144 'prompt' => 'consent',
145 'access_type' => 'offline',
146 'state' => wp_create_nonce( Template::NAME ),
147 );
148
149 update_option( 'cmbird_zoho_crm_domain', $domain );
150 update_option( 'cmbird_zoho_crm_cid', $this->data['client_id'] );
151 update_option( 'cmbird_zoho_crm_cs', $this->data['client_secret'] );
152 update_option( 'cmbird_zoho_crm_url', $crm_url );
153 update_option( 'cmbird_authorization_redirect_uri', $this->data['redirect_uri'] );
154 $account_base = CMBIRD_Auth_Zoho::get_accounts_base_url( $domain );
155 $redirect = esc_url_raw( add_query_arg( $oauth_args, $account_base . '/oauth/v2/auth' ) );
156 $this->response = array(
157 'redirect' => $redirect,
158 'message' => 'We are redirecting you to zoho. please wait...',
159 );
160 } catch ( Throwable $throwable ) {
161 $this->errors = array( 'message' => $throwable->getMessage() );
162 }
163
164 $this->serve();
165 }
166
167 /**
168 * Retrieves the connection details.
169 *
170 * @return void
171 */
172 public function connection_get(): void {
173 $this->verify();
174 $this->response['account_domain'] = get_option( 'cmbird_zoho_crm_domain', '' );
175 $this->response['client_id'] = get_option( 'cmbird_zoho_crm_cid', '' );
176 $this->response['client_secret'] = get_option( 'cmbird_zoho_crm_cs', '' );
177 $this->response['crm_url'] = get_option( 'cmbird_zoho_crm_url', '' );
178 $this->response['redirect_uri'] = get_option( 'cmbird_authorization_redirect_uri', '' );
179 $this->serve();
180 }
181
182 /**
183 * Handles the oAuth code.
184 */
185 public function handle_code(): void {
186 $this->verify();
187 if ( array_key_exists( 'code', $this->request ) ) {
188 $class_functions = new CMBIRD_Auth_Zoho();
189 $code = $this->request['code'];
190 $state = $this->request['state'] ?? '';
191
192 if ( empty( $state ) || ! wp_verify_nonce( $state, Template::NAME ) ) {
193 $this->errors = array( 'message' => 'Invalid OAuth state. Please try connecting again.' );
194 $this->serve();
195 return;
196 }
197
198 try {
199 $access_token = $class_functions->get_zoho_access_token( $code, 'zoho_crm' );
200 if ( is_wp_error( $access_token ) ) {
201 $this->errors = array( 'message' => $access_token->get_error_message() );
202 } elseif ( is_array( $access_token ) && array_key_exists( 'access_token', $access_token ) ) {
203 update_option( 'cmbird_zoho_crm_auth_code', $code );
204 update_option( 'cmbird_zoho_crm_access_token', $access_token['access_token'] );
205 update_option( 'cmbird_zoho_crm_refresh_token', $access_token['refresh_token'] );
206 update_option( 'cmbird_zoho_crm_timestamp', strtotime( gmdate( 'Y-m-d H:i:s' ) ) + $access_token['expires_in'] );
207 $this->response = (array) $access_token;
208 } else {
209 $this->errors = array( 'message' => 'Unable to retrieve Zoho CRM access token.' );
210 }
211 } catch ( Throwable $throwable ) {
212 $this->errors = array( 'message' => $throwable->getMessage() );
213 }
214 }
215 $this->serve();
216 }
217
218 /**
219 * Resets the connection by deleting specific options from the database.
220 */
221 public function connection_reset(): void {
222 $this->verify();
223 try {
224 $options = array(
225 'cmbird_zoho_crm_domain',
226 'cmbird_zoho_crm_cid',
227 'cmbird_zoho_crm_cs',
228 'cmbird_zoho_crm_url',
229 'cmbird_zoho_crm_access_token',
230 );
231 foreach ( $options as $zi_option ) {
232 delete_option( $zi_option );
233 }
234 $this->response = array( 'message' => 'Reset successfully!' );
235 } catch ( Throwable $throwable ) {
236 $this->errors = array( 'message' => $throwable->getMessage() );
237 }
238
239 $this->serve();
240 }
241
242
243 public function connection_done() {
244 $this->verify();
245 $zoho_crm_url = get_option( 'cmbird_zoho_crm_url' );
246 $url = $zoho_crm_url . 'crm/v7/org';
247 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
248 $json = $execute_curl_call_handle->execute_curl_call_get( $url );
249 if ( is_wp_error( $json ) ) {
250 $this->errors = array( 'message' => $json->get_error_message() );
251 } elseif ( empty( $json ) ) {
252 $this->errors = array( 'message' => 'We lost connection with zoho. please refresh page.' );
253 } else {
254 $this->response = ! empty( $json->org ) ? (array) $json->org[0] : array();
255 // schedule a wp cron to refresh the token after one hour from now.
256 if ( ! wp_next_scheduled( 'zcrm_refresh_token' ) ) {
257 wp_schedule_event( time() + 3600, 'hourly', 'zcrm_refresh_token' );
258 } else {
259 // unschedule first and reschedule.
260 wp_unschedule_event( wp_next_scheduled( 'zcrm_refresh_token' ), 'zcrm_refresh_token' );
261 wp_schedule_event( time() + 3600, 'hourly', 'zcrm_refresh_token' );
262 }
263 }
264 $this->serve();
265 }
266
267 /**
268 * Get Zoho CRM custom fields.
269 *
270 * @since 1.0.0
271 * @return void
272 */
273 public function refresh_zcrm_fields() {
274 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin-only AJAX call, nonce verified in parent.
275 $module = isset( $_GET['module'] ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : '';
276 if ( empty( $module ) ) {
277 $this->errors['message'] = 'Module name is required.';
278 } else {
279 $fields = ( new ZohoCRMSync() )->get_custom_fields( $module );
280 if ( is_wp_error( $fields ) ) {
281 $this->errors['message'] = $fields->get_error_message();
282 } else {
283 $option_name = 'zcrm_' . strtolower( $module ) . '_fields';
284 update_option( self::OPTIONS[ $option_name ], $fields );
285 $this->response = array( 'message' => 'Refresh successfully!' );
286 $this->response['fields'] = $fields;
287 }
288 $this->serve();
289 }
290 }
291
292 /**
293 * Get Zoho CRM fields from WordPress database.
294 */
295 public function get_zcrm_fields() {
296 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin-only AJAX call, nonce verified in parent.
297 $module = isset( $_GET['module'] ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : '';
298 if ( empty( $module ) ) {
299 $this->errors['message'] = 'Module name is required.';
300 } else {
301 $fields = get_option( 'zcrm_' . strtolower( $module ) . '_fields', array() );
302 $this->response['fields'] = $fields;
303 $this->serve();
304 }
305 }
306
307 /**
308 * Retrieves the fields and serves the response.
309 *
310 * @return object
311 */
312 public function zcrm_get_custom_fields(): void {
313
314 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin-only AJAX call, nonce verified in parent.
315 $module = isset( $_GET['module'] ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : '';
316
317 if ( empty( $module ) ) {
318 $this->errors['message'] = 'Module name is required.';
319 } else {
320 $this->verify();
321 $option_name = 'zcrm_' . strtolower( $module ) . '_custom_fields';
322 $this->response['form'] = get_option( $option_name, array() );
323 $this->serve();
324 }
325 }
326
327 /**
328 * Sets the custom fields for the form.
329 *
330 * @return void
331 */
332 public function zcrm_save_custom_fields(): void {
333 $this->verify( self::FORMS['fields'] );
334 $module = isset( $this->data['module'] ) ? $this->data['module'] : '';
335 if ( empty( $module ) ) {
336 $this->errors['message'] = 'Module name is required.';
337 } else {
338 try {
339 $option_name = 'zcrm_' . strtolower( $module ) . '_custom_fields';
340 update_option( $option_name, $this->data['form'] );
341 $this->response = array( 'message' => 'saved' );
342 } catch ( Throwable $throwable ) {
343 $this->errors = array( 'message' => $throwable->getMessage() );
344 }
345 $this->serve();
346 }
347 }
348
349 /**
350 * Resets the fields.
351 *
352 * @return void
353 */
354 public function zcrm_reset_custom_fields(): void {
355 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin-only AJAX call, nonce verified in parent.
356 $module = isset( $_GET['module'] ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : '';
357 if ( empty( $module ) ) {
358 $this->errors['message'] = 'Module name is required.';
359 } else {
360 $this->verify();
361 $option_name = 'zcrm_' . strtolower( $module ) . '_custom_fields';
362 delete_option( $option_name );
363 $this->response = array( 'message' => 'Reset successfully!' );
364 $this->serve();
365 }
366 }
367
368 /**
369 * Export Orders to Zoho CRM.
370 *
371 * @param int $start_date_raw The start date.
372 * @param int $end_date_raw The end date.
373 */
374 public function export_order( $start_date_raw, $end_date_raw ) {
375 // $fd = fopen( __DIR__ . '/export_order.log', 'a+' );
376
377 $start_date = gmdate( 'Y-m-d H:i:s', $start_date_raw );
378 $end_date = gmdate( 'Y-m-d H:i:s', $end_date_raw );
379 // Define the order statuses to exclude.
380 $exclude_statuses = array( 'wc-failed', 'wc-cancelled', 'wc-checkout-draft' );
381 $posts_per_page = 20;
382 $paged = 1;
383 $max_execution_time = ini_get( 'max_execution_time' );
384
385 // Extend execution time for bulk operations.
386 if ( $max_execution_time > 0 && $max_execution_time < 300 ) {
387 set_time_limit( 300 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Required for bulk order sync.
388
389 }
390
391 do {
392 // Reset execution time for each batch to prevent timeout.
393 if ( function_exists( 'set_time_limit' ) ) {
394 set_time_limit( 60 ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Required for bulk order sync.
395 }
396
397 // Query to get orders.
398 $args = array(
399 'date_created' => $start_date . '...' . $end_date,
400 'status' => array_diff( array_keys( wc_get_order_statuses() ), $exclude_statuses ),
401 'limit' => $posts_per_page,
402 'paged' => $paged,
403 'orderby' => 'date',
404 'order' => 'ASC',
405 'return' => 'ids',
406 'meta_query' => array(
407 array(
408 'key' => 'zcrm_sales_order_id',
409 'compare' => 'NOT EXISTS',
410 ),
411 ),
412 );
413 $orders = wc_get_orders( $args );
414 // Loop through orders and schedule them for Zoho CRM sync.
415 foreach ( $orders as $order_id ) {
416 // Use WC Action Scheduler to sync the order to Zoho CRM.
417 $existing_schedule = as_has_scheduled_action( 'sync_zcrm_order', array( $order_id ) );
418 if ( ! $existing_schedule ) {
419 as_schedule_single_action( time(), 'sync_zcrm_order', array( $order_id ) );
420 }
421 }
422
423 // Check if we have more orders to process before cleanup.
424 $has_more_orders = ! empty( $orders );
425
426 // Memory management - clean up variables.
427 unset( $orders, $args );
428
429 // Optional: garbage collection to free memory.
430 if ( function_exists( 'gc_collect_cycles' ) ) {
431 gc_collect_cycles();
432 }
433
434 // Increment the offset for the next batch.
435 ++$paged;
436
437 // Safety check to prevent infinite loops (max 1000 pages = 20,000 orders).
438 if ( $paged > 1000 ) {
439 break;
440 }
441 } while ( $has_more_orders );
442 // fclose( $fd );
443 }
444
445 /**
446 * AJAX endpoint to export WooCommerce orders to Zoho CRM.
447 */
448 public function order_export() {
449 $this->verify( self::FORMS['order'] );
450 if ( empty( $this->data ) || empty( $this->data['range'] ) ) {
451 $this->response['success'] = false;
452 $this->response['message'] = __( 'Select dates', 'commercebird' );
453 $this->serve();
454 }
455 // Set the date range to last 30 days.
456 $start_date = strtotime( $this->data['range'][0] );
457 $end_date = strtotime( $this->data['range'][1] );
458 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
459 if ( empty( $zoho_crm_access_token ) ) {
460 $this->response['success'] = false;
461 $this->response['message'] = __( 'Zoho CRM is not Connected.', 'commercebird' );
462 $this->serve();
463 }
464 $this->export_order( $start_date, $end_date );
465 $this->response['success'] = true;
466 $this->response['message'] = __( 'Exported', 'commercebird' );
467 $this->serve();
468 }
469
470 /**
471 * Retrieves an array of all tax rates from WooCommerce.
472 *
473 * @return array An array of all tax rates.
474 */
475 public function wc_taxes(): array {
476 $wc_tax_array = array();
477 $tax_classes = WC_Tax::get_tax_classes(); // Retrieve all tax classes.
478 if ( ! in_array( '', $tax_classes ) ) { // Make sure "Standard rate" (empty class name) is present.
479 array_unshift( $tax_classes, '' );
480 }
481
482 foreach ( $tax_classes as $tax_class ) { // For each tax class, get all rates.
483 $taxes = WC_Tax::get_rates_for_tax_class( $tax_class );
484
485 foreach ( $taxes as $key => $tax ) {
486 $taxarray = (array) $tax;
487 $taxarray['id'] = $key;
488 $wc_tax_array[] = $taxarray;
489 }
490 }
491
492 return $wc_tax_array;
493 }
494 /**
495 * Executes the wc_tax_collect function.
496 *
497 * This function verifies the current state, retrieves the taxes using the
498 * wc_taxes method, and serves the response.
499 */
500 public function wc_tax_collect(): void {
501 $this->verify();
502 $this->response = $this->wc_taxes();
503 $this->serve();
504 }
505
506 /**
507 * Retrieves the tax information.
508 *
509 * @return void
510 */
511 public function tax_get(): void {
512 $this->verify();
513 $this->response = array();
514 foreach ( $this->wc_taxes() as $tax ) {
515 $this->response['selectedTaxRates'][] = $tax['id'] . '^^' . get_option( 'cmbird_zoho_crm_tax_rate_' . $tax['id'] );
516 }
517 $this->serve();
518 }
519
520
521 /**
522 * Saves tax rates to the database.
523 */
524 public function tax_set(): void {
525 $this->verify( self::FORMS['tax'] );
526 $rates = array_filter( $this->data['selectedTaxRates'] );
527 try {
528 foreach ( $rates as $value ) {
529 $valarray = explode( '^^', $value );
530 update_option( 'cmbird_zoho_crm_tax_rate_' . $valarray[0], $valarray[1] );
531 }
532 $this->response = array( 'message' => 'Saved' );
533 } catch ( Throwable $throwable ) {
534 $this->errors = array( 'message' => $throwable->getMessage() );
535 }
536
537 $this->serve();
538 }
539
540 /**
541 * Resets the tax settings.
542 *
543 * This function verifies the tax settings and then deletes the options
544 * related to the tax rates
545 *
546 * @return void
547 */
548 public function tax_reset(): void {
549 $this->verify();
550 foreach ( $this->wc_taxes() as $tax ) {
551 delete_option( 'cmbird_zoho_crm_tax_rate_' . $tax['id'] );
552 }
553 $this->response = array( 'message' => 'Reset successfully!' );
554 $this->serve();
555 }
556
557 /**
558 * Retrieves the Zoho tax rates for the organization.
559 *
560 * This function verifies the request, retrieves the organization ID and URL
561 * from the options, constructs the URL for the API request, and executes
562 * the cURL call to fetch the tax rates. If the response contains the
563 * 'taxes' key, it sets the response to the tax rates. Otherwise, it sets
564 * the errors to a default message.
565 */
566 public function zoho_tax_rates_collect(): void {
567 $this->verify();
568 $zoho_crm_url = get_option( 'cmbird_zoho_crm_url' );
569 $url = $zoho_crm_url . 'crm/v7/org/taxes';
570 try {
571 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
572 $json = (array) $execute_curl_call_handle->execute_curl_call_get( $url );
573
574 // error_log('Data: ' . print_r($response, true));
575 if (
576 is_array( $json ) &&
577 isset( $json['org_taxes'] ) &&
578 isset( $json['org_taxes']->taxes )
579 ) {
580 $this->response = $json['org_taxes']->taxes;
581 } else {
582 $this->errors = array( 'message' => 'Something went wrong!' );
583 }
584 } catch ( Throwable $throwable ) {
585 $this->errors = array( 'message' => $throwable->getMessage() );
586 }
587 $this->serve();
588 }
589
590 /**
591 * Get Zoho CRM Sales Order layouts.
592 */
593 public function layouts_get(): void {
594 $this->verify();
595 $zoho_crm_url = get_option( 'cmbird_zoho_crm_url' );
596 $url = $zoho_crm_url . 'crm/v7/settings/layouts?module=Sales_Orders';
597 try {
598 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
599 $json = (array) $execute_curl_call_handle->execute_curl_call_get( $url );
600
601 if (
602 is_array( $json ) &&
603 isset( $json['layouts'] ) &&
604 is_array( $json['layouts'] )
605 ) {
606 // Format the layouts for frontend dropdown.
607 $formatted_layouts = array();
608 foreach ( $json['layouts'] as $layout ) {
609 if ( isset( $layout->id ) && isset( $layout->name ) ) {
610 $formatted_layouts[] = array(
611 'id' => $layout->id,
612 'name' => $layout->name,
613 'api_name' => $layout->api_name ?? '',
614 'status' => $layout->status ?? '',
615 );
616 }
617 }
618 $this->response = $formatted_layouts;
619 } else {
620 $this->errors = array( 'message' => 'Unable to fetch layouts!' );
621 }
622 } catch ( Throwable $throwable ) {
623 $this->errors = array( 'message' => $throwable->getMessage() );
624 }
625 $this->serve();
626 }
627
628 /**
629 * Save selected Zoho CRM Sales Order layout.
630 */
631 public function layout_save(): void {
632 $this->verify( array( 'layout_id' ) );
633 if ( empty( $this->data['layout_id'] ) ) {
634 $this->errors = array( 'message' => 'Layout ID is required!' );
635 $this->serve();
636 return;
637 }
638
639 $updated = update_option( self::OPTIONS['zcrm_sales_order_layout'], sanitize_text_field( $this->data['layout_id'] ) );
640
641 if ( $updated ) {
642 $this->response = array( 'message' => 'Layout saved successfully!' );
643 } else {
644 $this->errors = array( 'message' => 'Failed to save layout!' );
645 }
646 $this->serve();
647 }
648
649 /**
650 * Get selected Zoho CRM Sales Order layout.
651 */
652 public function layout_get_selected(): void {
653 $this->verify();
654 $saved_layout = get_option( self::OPTIONS['zcrm_sales_order_layout'], '' );
655 $this->response = array( 'selected_layout' => $saved_layout );
656 $this->serve();
657 }
658
659 /**
660 * Get order settings for Zoho CRM sync.
661 */
662 public function order_settings_get(): void {
663 $this->verify();
664 $sync_as_deals = get_option( 'cmbird_zcrm_sync_as_deals', false );
665 $this->response = array( 'syncAsDeals' => (bool) $sync_as_deals );
666 $this->serve();
667 }
668
669 /**
670 * Save order settings for Zoho CRM sync.
671 */
672 public function order_settings_save(): void {
673 $this->verify( self::FORMS['order_settings'] );
674 try {
675 $sync_as_deals = isset( $this->data['syncAsDeals'] ) ? (bool) $this->data['syncAsDeals'] : false;
676 update_option( 'cmbird_zcrm_sync_as_deals', $sync_as_deals );
677 $this->response = array(
678 'success' => true,
679 'message' => __( 'Order settings saved successfully!', 'commercebird' ),
680 );
681 } catch ( Throwable $throwable ) {
682 $this->errors = array( 'message' => $throwable->getMessage() );
683 }
684 $this->serve();
685 }
686
687 /**
688 * Get Zoho CRM Deals fields.
689 */
690 public function deal_fields_get(): void {
691 $this->verify();
692 $refresh = isset( $this->data['refresh'] ) && $this->data['refresh'];
693
694 $fields = get_option( 'zcrm_deals_fields', array() );
695
696 // If refresh is requested or no fields exist, try to refresh them from Zoho.
697 if ( $refresh || empty( $fields ) ) {
698 $sync = new ZohoCRMSync();
699 $fresh_fields = $sync->get_all_fields( 'Deals' );
700 if ( ! is_wp_error( $fresh_fields ) ) {
701 // Convert to simple key-value format, excluding hardcoded fields.
702 $formatted_fields = array();
703 foreach ( $fresh_fields as $field_key => $field_label ) {
704 // Skip hardcoded fields (Deal_Name, Amount, Currency).
705 if ( in_array( $field_key, array( 'Deal_Name', 'Amount', 'Currency' ) ) ) {
706 continue;
707 }
708 $formatted_fields[ $field_key ] = $field_label;
709 }
710
711 // Add empty option at the beginning.
712 $formatted_fields = array_merge( array( '' => 'Select a field...' ), $formatted_fields );
713
714 update_option( 'zcrm_deals_fields', $formatted_fields );
715 $fields = $formatted_fields;
716 }
717 } else {
718 // Ensure empty option exists.
719 if ( ! isset( $fields[''] ) ) {
720 $fields = array_merge( array( '' => 'Select a field...' ), $fields );
721 }
722 }
723
724 $this->response = array( 'fields' => $fields );
725 $this->serve();
726 }
727
728 /**
729 * Get deal field mapping settings.
730 */
731 public function deal_field_mapping_get(): void {
732 $this->verify();
733 $mapping = get_option( 'cmbird_zcrm_deal_field_mapping', array() );
734 $this->response = array( 'dealFields' => $mapping );
735 $this->serve();
736 }
737
738 /**
739 * Save deal field mapping settings.
740 */
741 public function deal_field_mapping_save(): void {
742 $this->verify( self::FORMS['order_field_mapping'] );
743 try {
744 $deal_fields = isset( $this->data['dealFields'] ) ? $this->data['dealFields'] : array();
745 update_option( 'cmbird_zcrm_deal_field_mapping', $deal_fields );
746 $this->response = array(
747 'success' => true,
748 'message' => __( 'Deal field mapping saved successfully!', 'commercebird' ),
749 );
750 } catch ( Throwable $throwable ) {
751 $this->errors = array( 'message' => $throwable->getMessage() );
752 }
753 $this->serve();
754 }
755
756 /**
757 * Get WooCommerce order statuses.
758 */
759 public function wc_order_statuses_get(): void {
760 $this->verify();
761 $order_statuses = wc_get_order_statuses();
762 $formatted_statuses = array();
763
764 // Add empty option.
765 $formatted_statuses[''] = 'Select order status...';
766
767 foreach ( $order_statuses as $status_key => $status_label ) {
768 $formatted_statuses[ $status_key ] = $status_label;
769 }
770
771 $this->response = array( 'statuses' => $formatted_statuses );
772 $this->serve();
773 }
774
775 /**
776 * Get Zoho Deal stages.
777 */
778 public function deal_stages_get(): void {
779 $this->verify();
780 $cached_stages = get_option( 'zcrm_deal_stages', array() );
781
782 // Check if we have cached stages and they're not empty.
783 if ( ! empty( $cached_stages ) && is_array( $cached_stages ) && count( $cached_stages ) > 1 ) {
784 $this->response = array( 'stages' => $cached_stages );
785 $this->serve();
786 return;
787 }
788
789 try {
790 // Directly get the Stage field pick list values without calling get_all_fields.
791 $zoho_crm_url = get_option( 'cmbird_zoho_crm_url' );
792 $url = $zoho_crm_url . 'crm/v7/settings/fields?module=Deals';
793 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
794 $json = $execute_curl_call_handle->execute_curl_call_get( $url );
795
796 $stage_options = array( '' => 'Select stage...' );
797
798 if ( ! is_wp_error( $json ) && ! empty( $json->fields ) ) {
799 foreach ( $json->fields as $field ) {
800 if ( $field->api_name === 'Stage' && ! empty( $field->pick_list_values ) ) {
801 foreach ( $field->pick_list_values as $option ) {
802 $stage_options[ $option->actual_value ] = $option->display_value;
803 }
804 break;
805 }
806 }
807 }
808
809 // Cache the stages.
810 update_option( 'zcrm_deal_stages', $stage_options );
811 $this->response = array( 'stages' => $stage_options );
812 } catch ( \Exception $e ) {
813 $this->response = array( 'stages' => array( '' => 'Select stage...' ) );
814 }
815
816 $this->serve();
817 }
818
819 /**
820 * Get order status to stage mapping settings.
821 */
822 public function order_status_stage_mapping_get(): void {
823 $this->verify();
824 $mapping = get_option( 'cmbird_zcrm_order_status_stage_mapping', array() );
825 $this->response = array( 'mapping' => $mapping );
826 $this->serve();
827 }
828
829 /**
830 * Save order status to stage mapping settings.
831 */
832 public function order_status_stage_mapping_save(): void {
833 $this->verify( self::FORMS['order_status_stage_mapping'] );
834 try {
835 $mapping = isset( $this->data['mapping'] ) ? $this->data['mapping'] : array();
836 update_option( 'cmbird_zcrm_order_status_stage_mapping', $mapping );
837 $this->response = array(
838 'success' => true,
839 'message' => __( 'Order status to stage mapping saved successfully!', 'commercebird' ),
840 );
841 } catch ( Throwable $throwable ) {
842 $this->errors = array( 'message' => $throwable->getMessage() );
843 }
844 $this->serve();
845 }
846
847 /**
848 * Export all existing WooCommerce customers to Zoho CRM as contacts.
849 */
850 public function contact_export(): void {
851 $this->verify();
852
853 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
854 if ( empty( $zoho_crm_access_token ) ) {
855 $this->response['success'] = false;
856 $this->response['message'] = __( 'Zoho CRM is not Connected.', 'commercebird' );
857 $this->serve();
858 }
859
860 try {
861 // Get all customers in batches.
862 $posts_per_page = 20;
863 $paged = 1;
864 $total_exported = 0;
865
866 do {
867 $customers = get_users(
868 array(
869 'role' => 'customer',
870 'number' => $posts_per_page,
871 'paged' => $paged,
872 'fields' => 'ID',
873 'orderby' => 'registered',
874 'order' => 'ASC',
875 )
876 );
877
878 // Loop through customers and schedule them for Zoho CRM sync.
879 foreach ( $customers as $customer_id ) {
880 // Use WC Action Scheduler to sync the customer to Zoho CRM.
881 $existing_schedule = as_has_scheduled_action( 'sync_zcrm_contact', array( $customer_id ) );
882 if ( ! $existing_schedule ) {
883 as_schedule_single_action( time(), 'sync_zcrm_contact', array( $customer_id ) );
884 ++$total_exported;
885 }
886 }
887
888 // Increment the page for the next batch.
889 ++$paged;
890 } while ( ! empty( $customers ) );
891
892 $this->response = array(
893 'success' => true,
894 'message' => sprintf(
895 /* translators: %d: number of customers */
896 __( '%d customers scheduled for export to Zoho CRM.', 'commercebird' ),
897 $total_exported
898 ),
899 );
900
901 } catch ( Throwable $throwable ) {
902 $this->errors = array( 'message' => $throwable->getMessage() );
903 }
904
905 $this->serve();
906 }
907
908 /**
909 * Get Zoho CRM Contact layouts.
910 */
911 public function contact_layouts_get(): void {
912 $this->verify();
913 $zoho_crm_url = get_option( 'cmbird_zoho_crm_url' );
914 $url = $zoho_crm_url . 'crm/v7/settings/layouts?module=Contacts';
915
916 try {
917 $execute_curl_call_handle = new CMBIRD_API_Handler_Zoho();
918 $json = (array) $execute_curl_call_handle->execute_curl_call_get( $url );
919
920 if (
921 is_array( $json ) &&
922 isset( $json['layouts'] ) &&
923 is_array( $json['layouts'] )
924 ) {
925 // Format the layouts for frontend dropdown.
926 $formatted_layouts = array();
927 foreach ( $json['layouts'] as $layout ) {
928 if ( isset( $layout->id ) && isset( $layout->name ) ) {
929 $formatted_layouts[] = array(
930 'id' => $layout->id,
931 'name' => $layout->name,
932 'api_name' => $layout->api_name ?? '',
933 'status' => $layout->status ?? '',
934 );
935 }
936 }
937 $this->response = array( 'layouts' => $formatted_layouts );
938 } else {
939 $this->errors = array( 'message' => 'Unable to fetch contact layouts!' );
940 }
941 } catch ( Throwable $throwable ) {
942 $this->errors = array( 'message' => $throwable->getMessage() );
943 }
944 $this->serve();
945 }
946
947 /**
948 * Save selected Zoho CRM Contact layout.
949 */
950 public function contact_layout_save(): void {
951 $this->verify( self::FORMS['contact_layout'] );
952
953 if ( empty( $this->data['layoutId'] ) ) {
954 $this->errors = array( 'message' => 'Contact Layout ID is required!' );
955 $this->serve();
956 return;
957 }
958
959 $updated = update_option( self::OPTIONS['zcrm_contact_layout'], sanitize_text_field( $this->data['layoutId'] ) );
960
961 if ( false !== $updated ) {
962 $this->response = array( 'message' => 'Contact layout saved successfully!' );
963 } else {
964 $this->errors = array( 'message' => 'Failed to save contact layout!' );
965 }
966 $this->serve();
967 }
968
969 /**
970 * Get selected Zoho CRM Contact layout.
971 */
972 public function contact_layout_get_selected(): void {
973 $this->verify();
974 $saved_layout = get_option( self::OPTIONS['zcrm_contact_layout'], '' );
975 $this->response = array( 'selectedLayout' => $saved_layout );
976 $this->serve();
977 }
978 }
979