PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.8.0
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.8.0
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 / includes / classes / class-common.php
commercebird / includes / classes Last commit date
apis 3 months ago purchase-orders 3 months ago quotes 2 months ago zoho-crm 6 months ago zoho-inventory 3 months ago class-api-handler-zoho.php 6 months ago class-auth-zoho.php 3 months ago class-common.php 2 months ago class-plugin.php 3 months ago class-wc-api.php 5 months ago index.php 1 year ago
class-common.php
330 lines
1 <?php
2
3 if ( ! defined( 'ABSPATH' ) ) {
4 exit;
5 }
6
7 if ( ! class_exists( 'CMBIRD_Common_Functions' ) ) {
8 /**
9 * Common functions for CommerceBird plugin integration with Zoho.
10 */
11 class CMBIRD_Common_Functions {
12
13 /**
14 * Constructor.
15 */
16 public function __construct() {
17 add_action( 'woocommerce_thankyou', array( $this, 'cmbird_sync_frontend_order' ) );
18 add_action( 'woocommerce_rest_insert_shop_order_object', array( $this, 'cmbird_on_insert_rest_api' ), 20, 3 );
19 add_filter( 'wcs_renewal_order_created', array( $this, 'cmbird_zi_sync_renewal_order' ), 10, 2 );
20 add_action( 'wp_ajax_zoho_admin_order_sync', array( $this, 'cmbird_zoho_order_sync' ) );
21 }
22
23 /**
24 * Sync order when it's created via the checkout.
25 *
26 * @param int $order_id The order ID.
27 * @return void
28 */
29 public function cmbird_sync_frontend_order( $order_id ) {
30 // return if the order is not coming via thank you page.
31 if ( ! is_wc_endpoint_url( 'order-received' ) ) {
32 return;
33 }
34 // Check if the transient flag is set.
35 if ( get_transient( 'cmbird_thankyou_callback_executed_' . $order_id ) ) {
36 return;
37 }
38 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
39 // First sync the customer to Zoho Inventory if the access token is set.
40 if ( is_string( $zoho_inventory_access_token ) && trim( $zoho_inventory_access_token ) !== '' ) {
41 $zi_order_class = new CMBIRD_Order_Sync_ZI();
42 $zi_order_class->cmbird_zi_sync_customer_checkout( $order_id );
43 // Use WC Action Scheduler to sync the order to Zoho Inventory.
44 $existing_schedule = as_has_scheduled_action( 'sync_zi_order', array( $order_id ) );
45 if ( ! $existing_schedule ) {
46 as_schedule_single_action( time(), 'sync_zi_order', array( $order_id ) );
47 // Set the transient flag to prevent multiple executions.
48 set_transient( 'cmbird_thankyou_callback_executed_' . $order_id, true, 60 );
49 }
50 }
51 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
52 // If the access token is set, sync the order to Zoho CRM.
53 if ( is_string( $zoho_crm_access_token ) && trim( $zoho_crm_access_token ) !== '' ) {
54 // Use WC Action Scheduler to sync the order to Zoho CRM.
55 $existing_schedule = as_has_scheduled_action( 'sync_zcrm_order', array( $order_id ) );
56 if ( ! $existing_schedule ) {
57 as_schedule_single_action( time(), 'sync_zcrm_order', array( $order_id ) );
58 // Set the transient flag to prevent multiple executions.
59 set_transient( 'cmbird_thankyou_callback_executed_' . $order_id, true, 60 );
60 }
61 }
62 }
63
64 /**
65 * Sync order when its scheduled via the Action Scheduler.
66 *
67 * @return void
68 */
69 public function cmbird_orders_prepare_sync() {
70 $args = func_get_args();
71 $order_id = $args[0] ?? null;
72 if ( get_option( 'cmbird_zoho_inventory_access_token' ) && $order_id ) {
73 try {
74 $zi_order_class = new CMBIRD_Order_Sync_ZI();
75 $zi_order_class->zi_order_sync( $order_id );
76 } catch ( \Throwable $e ) {
77 error_log( 'Error in cmbird_orders_prepare_sync: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Required for security logging.
78 }
79 }
80 if ( get_option( 'cmbird_zoho_crm_access_token' ) && $order_id ) {
81 try {
82 $zcrm_order_class = new CMBIRD_ZCRM_SalesOrder();
83 $zcrm_order_class->cmbird_zcrm_order_sync( $order_id );
84 } catch ( \Throwable $e ) {
85 error_log( 'Error in cmbird_orders_prepare_sync: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Required for security logging.
86 }
87 }
88 }
89
90 /**
91 * Sync order when it's created via the WC API.
92 *
93 * @param WC_Data $object Inserted object.
94 * @param WP_REST_Request $request Request object.
95 * @param boolean $is_creating True when creating object, false when updating.
96 */
97 public function cmbird_on_insert_rest_api( $object, $request, $is_creating ) {
98 // $fd = fopen( __DIR__ . '/on_insert_rest_api.txt', 'w+' );
99 $request_body = $request->get_body();
100 $request_body_array = json_decode( $request_body, true );
101 // if the request body is empty or not an array, return early.
102 if ( empty( $request_body_array ) || ! is_array( $request_body_array ) ) {
103 return;
104 }
105 $order_status = $request_body_array['status'];
106 $order_id = $object->get_id();
107
108 if ( get_option( 'cmbird_zoho_inventory_access_token' ) ) {
109 $zi_order_class = new CMBIRD_Order_Sync_ZI();
110 // Check how many keys there are in the request body array. If there are only two keys then we don't need to do anything.
111 if ( count( $request_body_array ) === 2 && null !== $request_body_array ) {
112 if ( in_array( $order_status, array( 'cancelled', 'wc-merged' ) ) ) {
113 $zi_order_class->salesorder_void( $order_id );
114 }
115 } else {
116 $zi_order_class->zi_order_sync( $order_id );
117 }
118 }
119 // same for Zoho CRM.
120 if ( get_option( 'cmbird_zoho_crm_access_token' ) ) {
121 $zcrm_order_class = new CMBIRD_ZCRM_SalesOrder();
122 // Check how many keys there are in the request body array. If there are only two keys then we don't need to do anything.
123 if ( count( $request_body_array ) === 2 && null !== $request_body_array ) {
124 return;
125 } else {
126 $zcrm_order_class->cmbird_zcrm_order_sync( $order_id );
127 }
128 }
129
130 // fclose($fd);
131 }
132
133 /**
134 * Sync Renewal Order to Zoho once it's created.
135 *
136 * @param WC_Order $renewal_order The renewal order object.
137 * @param WC_Subscription $subscription The subscription object.
138 * @return WC_Order The renewal order object.
139 */
140 public function cmbird_zi_sync_renewal_order( $renewal_order, $subscription ) {
141 $order_id = $renewal_order->get_id();
142
143 // Sync the order to Zoho CRM.
144 if ( get_option( 'cmbird_zoho_crm_access_token' ) ) {
145 $zcrm_order_class = new CMBIRD_ZCRM_SalesOrder();
146 $zcrm_order_class->cmbird_zcrm_order_sync( $order_id );
147 }
148
149 // Sync the order to Zoho Inventory.
150 if ( get_option( 'cmbird_zoho_inventory_access_token' ) ) {
151 $zi_order_class = new CMBIRD_Order_Sync_ZI();
152 $zi_order_class->zi_order_sync( $order_id );
153 }
154
155 return $renewal_order;
156 }
157
158 /**
159 * Sync a WooCommerce order to Zoho CRM and Zoho Inventory.
160 *
161 * Called via AJAX from the Zoho Order Sync admin page.
162 *
163 * @param int $order_id The order ID to sync.
164 *
165 * @return void
166 */
167 public function cmbird_zoho_order_sync( $order_id ) {
168 if ( ! $order_id && isset( $_POST['nonce'], $_POST['arg_order_data'] ) ) {
169 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'zoho_admin_order_sync' ) ) {
170 wp_send_json_error( 'Nonce verification failed' );
171 }
172 $order_id = sanitize_text_field( wp_unslash( $_POST['arg_order_data'] ) );
173 }
174
175 if ( $order_id <= 0 ) {
176 wp_send_json_error( 'Invalid order ID.' );
177 }
178
179 // Sync the order to Zoho CRM.
180 if ( get_option( 'cmbird_zoho_crm_access_token' ) ) {
181 $zcrm_order_class = new CMBIRD_ZCRM_SalesOrder();
182 $zcrm_order_class->cmbird_zcrm_order_sync( $order_id );
183 }
184
185 // Sync the order to Zoho Inventory.
186 if ( get_option( 'cmbird_zoho_inventory_access_token' ) ) {
187 $zi_order_class = new CMBIRD_Order_Sync_ZI();
188 $zi_order_class->zi_order_sync( $order_id );
189 }
190
191 wp_send_json_success( 'Order synced successfully.' );
192 }
193
194 /**
195 * Function to clear all orphan data.
196 */
197 public function clear_orphan_data() {
198 global $wpdb;
199 // Delete orphaned product variations.
200 $deleted_variations = absint(
201 $wpdb->query(
202 "DELETE products
203 FROM {$wpdb->posts} products
204 LEFT JOIN {$wpdb->posts} wp ON wp.ID = products.post_parent
205 WHERE wp.ID IS NULL AND products.post_type = 'product_variation';"
206 )
207 );
208 // Delete orphaned postmeta.
209 $deleted_postmeta = absint(
210 $wpdb->query(
211 "DELETE pm
212 FROM {$wpdb->postmeta} pm
213 LEFT JOIN {$wpdb->posts} wp ON wp.ID = pm.post_id
214 WHERE wp.ID IS NULL;"
215 )
216 );
217 // Return the number of deleted entries (orphaned variations + orphaned postmeta).
218 return $deleted_variations + $deleted_postmeta;
219 }
220
221 /**
222 * Get the tax class based on the tax percentage.
223 *
224 * @param float $percentage The tax percentage.
225 * @return string|false The tax class if found, or standard if not found.
226 */
227 public function get_tax_class_by_percentage( $percentage ) {
228 // $fd = fopen( __DIR__ . '/get_tax_class_by_percentage.txt', 'a+' );
229
230 global $wpdb;
231 // Determine the number of decimal places in the provided percentage.
232 $decimal_places = strlen( substr( strrchr( $percentage, '.' ), 1 ) );
233
234 // Round the percentage to the determined number of decimal places.
235 $rounded_percentage = round( $percentage, $decimal_places );
236
237 // Try to get from cache first.
238 $cache_key = 'cmbird_tax_rate_' . md5( $rounded_percentage . '_' . $decimal_places );
239 $tax_rates = wp_cache_get( $cache_key, 'commercebird' );
240
241 if ( false === $tax_rates ) {
242 $tax_rates = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_tax_rates WHERE ROUND(tax_rate, %d) = %f", $decimal_places, $rounded_percentage ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached above.
243 wp_cache_set( $cache_key, $tax_rates, 'commercebird', HOUR_IN_SECONDS );
244 }
245
246 // If tax rates are found.
247 if ( $tax_rates ) {
248 // Get the tax class from the first matching tax rate.
249 $tax_class = $tax_rates[0]->tax_rate_class;
250 return $tax_class;
251 } else {
252 // Return null if no tax rates match the provided percentage.
253 return 'standard';
254 }
255 }
256
257 /**
258 * Get the WooCommerce tax class for a given Zoho tax ID using the configured mapping.
259 *
260 * @param string $zoho_tax_id The Zoho Inventory tax ID.
261 * @return string|null The WooCommerce tax_rate_class, or null if no mapping found.
262 */
263 public function get_tax_class_by_zoho_id( $zoho_tax_id ) {
264 if ( empty( $zoho_tax_id ) ) {
265 return null;
266 }
267
268 $cache_key = 'cmbird_tax_class_zoho_' . md5( $zoho_tax_id );
269 $cached = wp_cache_get( $cache_key, 'commercebird' );
270
271 if ( false !== $cached ) {
272 return '' !== $cached ? $cached : null;
273 }
274
275 global $wpdb;
276
277 // Find all configured Zoho→WC tax mappings.
278 $mappings = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached above.
279 $wpdb->prepare(
280 "SELECT option_name, option_value FROM {$wpdb->options} WHERE option_name LIKE %s",
281 'cmbird_zoho_inventory_tax_rate_%'
282 )
283 );
284
285 $wc_tax_rate_id = null;
286 foreach ( $mappings as $mapping ) {
287 $parts = explode( '##', $mapping->option_value );
288 if ( isset( $parts[0] ) && $parts[0] === $zoho_tax_id ) {
289 $wc_tax_rate_id = substr( $mapping->option_name, strlen( 'cmbird_zoho_inventory_tax_rate_' ) );
290 break;
291 }
292 }
293
294 if ( null === $wc_tax_rate_id ) {
295 wp_cache_set( $cache_key, '', 'commercebird', HOUR_IN_SECONDS );
296 return null;
297 }
298
299 $rate = $wpdb->get_row( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Cached above.
300 $wpdb->prepare(
301 "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d",
302 $wc_tax_rate_id
303 )
304 );
305
306 $tax_class = $rate ? $rate->tax_rate_class : null;
307 wp_cache_set( $cache_key, $tax_class ?? '', 'commercebird', HOUR_IN_SECONDS );
308
309 return $tax_class;
310 }
311
312 /**
313 * Send email to admin.
314 *
315 * @param string $subject The subject of the email.
316 * @param string $message The message body of the email.
317 *
318 * @return void
319 */
320 public function send_email( $subject, $message ) {
321 $admin_email = get_option( 'admin_email' );
322 $headers = array( 'Content-Type: text/html; charset=UTF-8' );
323
324 // Send the email.
325 wp_mail( $admin_email, $subject, $message, $headers );
326 }
327 }
328 }
329 $cmbird_common_functions = new CMBIRD_Common_Functions();
330