PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.6.0
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.6.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 / apis / class-api-for-product-webhook.php
commercebird / includes / classes / apis Last commit date
class-api-for-cmbird.php 9 months ago class-api-for-exact-webhooks.php 9 months ago class-api-for-product-webhook.php 7 months ago class-api-for-shipping-status.php 9 months ago class-api-for-woo-order.php 7 months ago class-api-for-zoho-inventory.php 9 months ago class-commercebird-list-items-api-controller.php 10 months ago class-commercebird-media-api-controller.php 10 months ago class-commercebird-metadata-controller.php 9 months ago index.php 1 year ago trait-api-permission.php 9 months ago
class-api-for-product-webhook.php
692 lines
1 <?php
2
3 namespace CommerceBird\API;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 use CMBIRD_Image_ZI;
10 use CMBIRD_Products_ZI;
11 use CMBIRD_Products_ZI_Export;
12 use WC_Data_Exception;
13 use WC_Product_Variation;
14 use WC_Product_Variable;
15 use WP_REST_Response;
16 use wpdb;
17 use CMBIRD_Common_Functions;
18 use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
19
20 /**
21 * Handles product webhook operations for CommerceBird API.
22 */
23 class ProductWebhook {
24
25
26 use Api;
27
28 /**
29 * REST API endpoint for Zoho product webhook.
30 *
31 * @var string
32 */
33 private static string $endpoint = 'zoho-product';
34
35 /**
36 * Indicates if WooCommerce taxes are enabled.
37 *
38 * @var bool
39 */
40 private $is_tax_enabled;
41
42 /**
43 * ProductWebhook constructor.
44 * Registers REST route, checks tax settings, and sets up hooks for queue processing and cleanup.
45 */
46 public function __construct() {
47 register_rest_route(
48 self::$namespace,
49 self::$endpoint,
50 array(
51 'methods' => 'POST',
52 'callback' => array( $this, 'handle' ),
53 'permission_callback' => '__return_true',
54 )
55 );
56 // Check if WooCommerce taxes are enabled and store the result.
57 $this->is_tax_enabled = 'yes' === get_option( 'woocommerce_calc_taxes' );
58
59 // Register queue processing hook.
60 add_action( 'cmbird_process_webhook_queue', array( $this, 'process_queued_requests' ) );
61
62 // Add cleanup for old rate limit data.
63 add_action( 'wp_scheduled_delete', array( $this, 'cleanup_rate_limit_data' ) );
64 }
65
66 /**
67 * Retrieves whether WooCommerce taxes are enabled.
68 *
69 * @return bool
70 */
71 public function is_tax_enabled(): bool {
72 return $this->is_tax_enabled;
73 }
74
75
76 /**
77 * Processes incoming product webhook data.
78 *
79 * @throws WC_Data_Exception If there is an error with WooCommerce product data.
80 */
81 private function process( array $data ): WP_REST_Response {
82 $response = new WP_REST_Response();
83 $response->set_data( $this->empty_response );
84 $response->set_status( 404 );
85 if ( ! array_key_exists( 'item', $data ) && ! array_key_exists( 'inventory_adjustment', $data ) ) {
86 return $response;
87 }
88
89 // Accounting stock mode check.
90 $accounting_stock = get_option( 'cmbird_zoho_enable_accounting_stock_status' );
91 $zi_enable_locationstock = get_option( 'cmbird_zoho_enable_locationstock_status' );
92 $location_id = get_option( 'cmbird_zoho_location_id_status' );
93
94 // variable item sync.
95 if ( array_key_exists( 'item', $data ) ) {
96 return $this->process_product_data( $data['item'], $zi_enable_locationstock, $location_id, $accounting_stock );
97 }
98 // inventory_adjustment.
99 if ( array_key_exists( 'inventory_adjustment', $data ) ) {
100 return $this->inventory_adjustment( $data['inventory_adjustment'] );
101 }
102
103 return $response;
104 }
105
106 /**
107 * Processes incoming product webhook data.
108 *
109 * @param array $item - incoming product webhook data.
110 * @param bool $zi_enable_locationstock - location stock enabled status.
111 * @param int $location_id - location id.
112 * @param bool $accounting_stock - accounting stock status.
113 *
114 * @return WP_REST_Response
115 * @throws WC_Data_Exception If there is an error with WooCommerce product data.
116 */
117 public function process_product_data( $item, $zi_enable_locationstock, $location_id, $accounting_stock ): WP_REST_Response {
118 // $fd = fopen( __DIR__ . '/process_product_data.txt', 'a+' );
119
120 // clean orphaned data from the database.
121 $common_class = new CMBIRD_Common_Functions();
122 $common_class->clear_orphan_data();
123
124 global $wpdb;
125 $item_id = $item['item_id'];
126 $item_name = $item['name'];
127 $item_price = $item['rate'];
128 $item_sku = $item['sku'];
129 $item_description = $item['description'];
130 $item_status = $item['status'] === 'active' ? 'publish' : 'draft';
131 $item_brand = $item['brand'];
132 $category_id = $item['category_id'];
133 $custom_fields = $item['custom_fields'];
134 $item_image = $item['image_name'];
135 // Stock mode check.
136 $locations = $item['locations'] ?? $item['warehouses'] ?? array();
137 if ( $zi_enable_locationstock ) {
138 foreach ( $locations as $location ) {
139 // Check both location_id and warehouse_id fields against the configured location_id.
140 $location_match = ( isset( $location['location_id'] ) && $location['location_id'] === $location_id ) ||
141 ( isset( $location['warehouse_id'] ) && $location['warehouse_id'] === $location_id );
142
143 if ( $location_match ) {
144 if ( $accounting_stock ) {
145 // Check for warehouse_ prefix if warehouse_id was used, otherwise use location_ prefix!
146 $item_stock = isset( $location['warehouse_id'] )
147 ? $location['warehouse_available_for_sale_stock']
148 : $location['location_available_for_sale_stock'];
149 } else {
150 // Check for warehouse_ prefix if warehouse_id was used, otherwise use location_ prefix!
151 $item_stock = isset( $location['warehouse_id'] )
152 ? $location['warehouse_actual_available_for_sale_stock']
153 : $location['location_actual_available_for_sale_stock'];
154 }
155 }
156 }
157 } elseif ( $accounting_stock && ! $zi_enable_locationstock ) {
158 $item_stock = $item['available_for_sale_stock'];
159 } else {
160 $item_stock = $item['actual_available_for_sale_stock'];
161 }
162 if ( isset( $item['group_name'] ) ) {
163 $group_name = $item['group_name'];
164 } else {
165 $group_name = '';
166 }
167 $item_category = $item['category_name'];
168 if ( isset( $item['group_id'] ) ) {
169 $groupid = $item['group_id'];
170 } else {
171 $groupid = '';
172 }
173
174 // Item package details.
175 $details = $item['package_details'];
176 $weight = floatval( $details['weight'] );
177 $length = floatval( $details['length'] );
178 $width = floatval( $details['width'] );
179 $height = floatval( $details['height'] );
180
181 // fwrite($fd, PHP_EOL . '$groupid : ' . $groupid);
182 if ( ! empty( $groupid ) ) {
183 // fwrite($fd, PHP_EOL . 'Inside grouped items');
184 // find parent variable product.
185 $row = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key='zi_item_id' AND meta_value=%s", $groupid ) );
186 $group_id = $row->post_id;
187
188 if ( ! empty( $group_id ) ) {
189 $existing_parent_product = wc_get_product( $group_id );
190 $zi_disable_itemdescription_sync = get_option( 'cmbird_zoho_disable_description_sync_status' );
191 if ( ! empty( $item_description ) && ! $zi_disable_itemdescription_sync ) {
192 // fwrite($fd, PHP_EOL . 'Item description update : ' . $item_description);
193 $existing_parent_product->set_short_description( $item_description );
194 }
195 // Update the name of the variable product if allowed.
196 $zi_disable_itemname_sync = get_option( 'cmbird_zoho_disable_name_sync_status' );
197 if ( ! $zi_disable_itemname_sync ) {
198 $existing_parent_product->set_name( $item['group_name'] );
199 $slug = sanitize_title( $item['group_name'] );
200 $existing_parent_product->set_slug( $slug );
201 }
202 // Brand update if taxonomy product_brand exists.
203 if ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
204 wp_set_object_terms( $groupid, $item_brand, 'product_brand' );
205 } elseif ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
206 wp_set_object_terms( $groupid, $item_brand, 'product_brand' );
207 }
208 // Update the custom fields if the custom fields are not empty.
209 $cmbird_product_zi = new CMBIRD_Products_ZI();
210 if ( ! empty( $custom_fields ) ) {
211 $cmbird_product_zi->sync_item_custom_fields( $custom_fields, $groupid );
212 }
213
214 // Create or Update the Attributes.
215 // turn $item into array.
216 $gp_arr = (array) $item;
217 $attr_created = $cmbird_product_zi->sync_attributes_of_group( $gp_arr, $group_id );
218 if ( ! empty( $group_id ) && $attr_created ) {
219 // Create the variations.
220 $cmbird_product_zi->import_variable_product_variations( $item, $group_id );
221 }
222
223 // set the product status of the variable parent product.
224 // $existing_parent_product->set_status( $item_status );
225 $existing_parent_product->save();
226 } else {
227 // Add in scheduler to create the Variable Product.
228 $last_synced_page = get_option( 'cmbird_group_item_sync_page_cat_id_' . $category_id );
229 if ( ! intval( $last_synced_page ) ) {
230 $last_synced_page = 1;
231 }
232 $data = array(
233 'page' => $last_synced_page,
234 'category' => $category_id,
235 );
236 $existing_schedule = as_has_scheduled_action( 'import_group_items_cron', $data );
237 // Schedule the action if it doesn't exist.
238 if ( ! $existing_schedule ) {
239 as_schedule_single_action( time(), 'import_group_items_cron', $data );
240 }
241 }
242
243 $row_item = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key='zi_item_id' AND meta_value=%s", $item_id ) );
244 $variation_id = $row_item->post_id;
245 if ( $variation_id ) {
246 // updating existing variations.
247 $variation = new WC_Product_Variation( $variation_id );
248 // Prices.
249 if ( ! empty( $item_price ) ) {
250 $variation->set_price( $item_price );
251 }
252 $variation->set_regular_price( $item_price );
253 // Stock.
254 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
255 if ( ! empty( $item_stock ) && ! $zi_disable_stock_sync ) {
256 // fwrite($fd, PHP_EOL . 'Stock is here:'. $item_stock );
257 $variation->set_stock_quantity( $item_stock );
258 $variation->set_manage_stock( true );
259 // $variation->set_stock_status('');
260 } else {
261 // fwrite($fd, PHP_EOL . 'Available Stock : false');
262 $variation->set_manage_stock( false );
263 }
264 // featured image.
265 $zi_disable_itemimage_sync = get_option( 'cmbird_zoho_disable_image_sync_status' );
266 if ( ! empty( $item_image ) && ! $zi_disable_itemimage_sync ) {
267 // fwrite($fd, PHP_EOL . 'Sync Image' );
268 $image_class = new CMBIRD_Image_ZI();
269 $image_class->cmbird_zi_get_image( $item_id, $item_name, $variation_id, $item_image );
270 }
271 // Disable or enable the variation based on the item_status.
272 $variation->set_status( $item_status );
273 // Update Purchase price.
274 $variation->update_meta_data( '_cost_price', $item['purchase_rate'] );
275
276 // Map taxes while syncing product from zoho.
277 if ( $item['tax_id'] && ! $this->is_tax_enabled() ) {
278 $zi_common_class = new CMBIRD_Common_Functions();
279 $woo_tax_class = $zi_common_class->get_tax_class_by_percentage( $item['tax_percentage'] );
280 $variation->set_tax_status( 'taxable' );
281 $variation->set_tax_class( $woo_tax_class );
282 }
283 // weight & dimensions.
284 $variation->set_weight( $weight );
285 $variation->set_length( $length );
286 $variation->set_width( $width );
287 $variation->set_height( $height );
288
289 $variation->save(); // Save the data.
290 } elseif ( 'publish' === $item_status ) {
291 $attribute_name11 = $item['attribute_option_name1'];
292 $attribute_name12 = $item['attribute_option_name2'];
293 $attribute_name13 = $item['attribute_option_name3'];
294 // Prepare the variation data.
295 $attribute_arr = array();
296 if ( ! empty( $attribute_name11 ) ) {
297 $sanitized_name1 = wc_sanitize_taxonomy_name( $item['attribute_name1'] );
298 $attribute_arr[ $sanitized_name1 ] = $attribute_name11;
299 }
300 if ( ! empty( $attribute_name12 ) ) {
301 $sanitized_name2 = wc_sanitize_taxonomy_name( $item['attribute_name2'] );
302 $attribute_arr[ $sanitized_name2 ] = $attribute_name12;
303 }
304 if ( ! empty( $attribute_name13 ) ) {
305 $sanitized_name3 = wc_sanitize_taxonomy_name( $item['attribute_name3'] );
306 $attribute_arr[ $sanitized_name3 ] = $attribute_name13;
307 }
308
309 // here actually create new variation because sku not found.
310 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
311 $variation = new WC_Product_Variation();
312 $variation->set_parent_id( $group_id );
313 $variation->set_status( 'publish' );
314 $variation->set_regular_price( $item_price );
315 $variation->set_sku( $item_sku );
316 $variation->set_weight( $weight );
317 $variation->set_length( $length );
318 $variation->set_width( $width );
319 $variation->set_height( $height );
320 if ( ! $zi_disable_stock_sync ) {
321 $variation->set_stock_quantity( $item_stock );
322 $variation->set_manage_stock( true );
323 $variation->set_stock_status( '' );
324 } else {
325 $variation->set_manage_stock( false );
326 }
327 // Map taxes while syncing product from zoho.
328 if ( $item['tax_id'] && ! $this->is_tax_enabled() ) {
329 $zi_common_class = new CMBIRD_Common_Functions();
330 $woo_tax_class = $zi_common_class->get_tax_class_by_percentage( $item['tax_percentage'] );
331 $variation->set_tax_status( 'taxable' );
332 $variation->set_tax_class( $woo_tax_class );
333 }
334 $variation->add_meta_data( 'zi_item_id', $item->item_id );
335 $variation_id = $variation->save();
336
337 // Get the variation attributes with correct attribute values.
338 foreach ( $attribute_arr as $attribute => $term_name ) {
339 $taxonomy = $attribute;
340 // If taxonomy doesn't exists we create it.
341 if ( ! taxonomy_exists( $taxonomy ) ) {
342 register_taxonomy(
343 $taxonomy,
344 'product_variation',
345 array(
346 'hierarchical' => false,
347 'label' => ucfirst( $attribute ),
348 'query_var' => true,
349 'rewrite' => array( 'slug' => sanitize_title( $attribute ) ),
350 ),
351 );
352 }
353
354 // Check if the Term name exist and if not we create it.
355 if ( ! term_exists( $term_name, $taxonomy ) ) {
356 wp_insert_term( $term_name, $taxonomy );
357 }
358
359 $term_slug = get_term_by( 'name', $term_name, $taxonomy )->slug;
360 // Get the post Terms names from the parent variable product.
361 $post_term_names = wp_get_post_terms( $group_id, $taxonomy, array( 'fields' => 'names' ) );
362 // Check if the post term exist and if not we set it in the parent variable product.
363 if ( ! in_array( $term_name, $post_term_names, true ) ) {
364 wp_set_post_terms( $group_id, $term_name, $taxonomy, true );
365 }
366 // Set/save the attribute data in the product variation.
367 update_post_meta( $variation_id, 'attribute_' . $taxonomy, $term_slug );
368 }
369
370 // featured image.
371 $zi_disable_itemimage_sync = get_option( 'cmbird_zoho_disable_image_sync_status' );
372 if ( ! empty( $item_image ) && ! $zi_disable_itemimage_sync ) {
373 $image_class = new CMBIRD_Image_ZI();
374 $image_class->cmbird_zi_get_image( $item_id, $item_name, $variation_id, $item_image );
375 }
376
377 update_post_meta( $variation_id, 'zi_item_id', $item_id );
378 // Sync variation data with parent variable product.
379 WC_Product_Variable::sync( $group_id );
380 // Regenerate lookup table for attributes.
381 $lookup_data_store = new LookupDataStore();
382 $lookup_data_store->create_data_for_product( $group_id );
383 // End group item add process.
384 unset( $attribute_arr );
385 }
386 wc_delete_product_transients( $group_id ); // Clear/refresh cache.
387 // end of grouped item creation.
388 } else {
389 // fwrite($fd, PHP_EOL . 'Inside simple items');
390 // fwrite($fd, PHP_EOL . 'Item description Simple : ' . $item_description);
391 $row_item = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s", $item_id ) );
392 $mapped_product_id = $row_item->post_id;
393 // simple product.
394 // fwrite($fd, PHP_EOL . 'Before Match check');
395 $pdt_id = '';
396 if ( ! empty( $mapped_product_id ) && null !== $mapped_product_id ) {
397 $product_found = wc_get_product( $mapped_product_id );
398 if ( ! $product_found ) {
399 // remove all postmeta of that product id.
400 $wpdb->delete( $wpdb->postmeta, array( 'post_id' => $mapped_product_id ) );
401 } else {
402 $pdt_id = $mapped_product_id;
403 }
404 } elseif ( empty( $item['is_combo_product'] ) ) {
405 // fwrite($fd, PHP_EOL . 'Inside create product');
406
407 // Check if Category is selected before creating simple item.
408 if ( 'publish' === $item_status ) {
409 $opt_category = get_option( 'cmbird_zoho_item_category' );
410 $opt_category = maybe_unserialize( $opt_category );
411 $category_id = $item['category_id'];
412 if ( $opt_category ) {
413 if ( in_array( $category_id, $opt_category, true ) ) {
414 $product_class = new CMBIRD_Products_ZI_Export();
415 $pdt_id = $product_class->cmbird_zi_product_to_woocommerce( $item, $item_stock );
416 }
417 }
418 }
419 // fwrite($fd, PHP_EOL . 'After adding it : ' . $pdt_id);
420 }
421
422 // If there is product id then update metadata.
423 if ( ! empty( $pdt_id ) ) {
424 $simple_product = wc_get_product( $pdt_id );
425 // update the name if its allowed.
426 $zi_disable_itemname_sync = get_option( 'cmbird_zoho_disable_name_sync_status' );
427 if ( ! $zi_disable_itemname_sync ) {
428 $simple_product->set_name( $item_name );
429 $slug = sanitize_title( $item_name );
430 $simple_product->set_slug( $slug );
431 }
432 // update the zi_item_id using the product instance.
433 $simple_product->update_meta_data( 'zi_item_id', $item_id );
434 // update the status using set_status().
435 $simple_product->set_status( $item_status );
436 // Update the product SKU.
437 $simple_product->set_sku( $item_sku );
438 // price.
439 $sale_price = $simple_product->get_sale_price();
440 $simple_product->set_regular_price( $item_price );
441 if ( empty( $sale_price ) ) {
442 $simple_product->set_price( $item_price );
443 }
444 // Update Purchase price.
445 $simple_product->update_meta_data( '_cost_price', $item['purchase_rate'] );
446 // description.
447 $zi_disable_itemdescription_sync = get_option( 'cmbird_zoho_disable_description_sync_status' );
448 if ( ! empty( $item_description ) && ! $zi_disable_itemdescription_sync ) {
449 $simple_product->set_short_description( $item_description );
450 }
451 // Brand update if taxonomy product_brand(s) exists.
452 if ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
453 wp_set_object_terms( $pdt_id, $item_brand, 'product_brand' );
454 } elseif ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
455 wp_set_object_terms( $pdt_id, $item_brand, 'product_brand' );
456 }
457 // stock.
458 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
459 if ( ! $zi_disable_stock_sync ) {
460 // fwrite( $fd, PHP_EOL . 'Inside1' );
461 if ( 'NULL' !== gettype( $item_stock ) ) {
462 // fwrite( $fd, PHP_EOL . 'Inside1.1' );
463 // Set manage stock to yes.
464 $simple_product->set_manage_stock( true );
465 // Update stock for simple product.
466 $simple_product->set_stock_quantity( number_format( $item_stock, 0, '.', '' ) );
467 if ( $item_stock > 0 ) {
468 // fwrite( $fd, PHP_EOL . 'Inside2' );
469 // Update stock status.
470 $simple_product->set_stock_status( 'instock' );
471 wp_set_post_terms( $pdt_id, 'instock', 'product_visibility', true );
472 } else {
473 // fwrite($fd, PHP_EOL . 'Inside3');
474 $stock_status = $simple_product->backorders_allowed() ? 'onbackorder' : 'outofstock';
475 $simple_product->set_stock_status( $stock_status );
476 wp_set_post_terms( $pdt_id, $stock_status, 'product_visibility', true );
477 }
478 }
479 }
480 // fwrite($fd, PHP_EOL . 'After stock');
481 // Update weight & dimensions of simple product.
482 $simple_product->set_weight( $weight );
483 $simple_product->set_length( $length );
484 $simple_product->set_width( $width );
485 $simple_product->set_height( $height );
486
487 // featured image.
488 $zi_disable_itemimage_sync = get_option( 'cmbird_zoho_disable_image_sync_status' );
489 if ( ! empty( $item_image ) && ! $zi_disable_itemimage_sync ) {
490 $image_class = new CMBIRD_Image_ZI();
491 $image_class->cmbird_zi_get_image( $item_id, $item_name, $pdt_id, $item_image );
492 }
493
494 // category.
495 if ( ! empty( $item_category ) && empty( $group_name ) ) {
496 $term = get_term_by( 'name', $item_category, 'product_cat' );
497 $term_id = $term->term_id;
498 if ( empty( $term_id ) ) {
499 $term = wp_insert_term(
500 $item_category,
501 'product_cat',
502 array(
503 'parent' => 0,
504 )
505 );
506 $term_id = $term->term_id;
507 }
508 // Remove "uncategorized" category if assigned.
509 $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' );
510 if ( $uncategorized_term && has_term( $uncategorized_term->term_id, 'product_cat', $pdt_id ) ) {
511 wp_remove_object_terms( $pdt_id, $uncategorized_term->term_id, 'product_cat' );
512 }
513 if ( ! is_wp_error( $term_id ) && isset( $term->term_id ) ) {
514 $existing_terms = wp_get_object_terms( $pdt_id, 'product_cat' );
515 if ( $existing_terms && count( $existing_terms ) > 0 ) {
516 $import_class = new CMBIRD_Products_ZI();
517 $is_term_exist = $import_class->zi_check_terms_exists( $existing_terms, $term_id );
518 if ( ! $is_term_exist ) {
519 $simple_product->update_meta_data( 'zi_category_id', $item['category_id'] );
520 wp_add_object_terms( $pdt_id, $term_id, 'product_cat' );
521 }
522 } else {
523 $simple_product->update_meta_data( 'zi_category_id', $item['category_id'] );
524 wp_set_object_terms( $pdt_id, $term_id, 'product_cat' );
525 }
526 }
527 }
528
529 // Update the custom fields if the custom fields are not empty.
530 if ( ! empty( $custom_fields ) ) {
531 $import_class = new CMBIRD_Products_ZI();
532 $import_class->sync_item_custom_fields( $custom_fields, $pdt_id );
533 }
534
535 // Map taxes while syncing product from zoho.
536 if ( $item['tax_id'] && ! $this->is_tax_enabled() ) {
537 $zi_common_class = new CMBIRD_Common_Functions();
538 $woo_tax_class = $zi_common_class->get_tax_class_by_percentage( $item['tax_percentage'] );
539 $simple_product->set_tax_status( 'taxable' );
540 $simple_product->set_tax_class( $woo_tax_class );
541 }
542 $simple_product->save();
543 wc_delete_product_transients( $pdt_id );
544 }
545 }
546 $response = new WP_REST_Response();
547 $response->set_data( 'success on variable product' );
548 $response->set_status( 200 );
549
550 // fclose( $fd ); // close logfile.
551
552 return $response;
553 }
554
555 /**
556 * Process queued webhook requests
557 * Called by WordPress cron system when queue processing is scheduled
558 */
559 public function process_queued_requests() {
560 $queue_key = 'cmbird_request_queue';
561 $current_queue = get_option( $queue_key, array() );
562
563 if ( empty( $current_queue ) || ! is_array( $current_queue ) ) {
564 return;
565 }
566
567 $current_time = time();
568 $processed_count = 0;
569 $max_process_per_batch = 10; // Process up to 10 requests per batch.
570
571 // Process queue items.
572 foreach ( $current_queue as $index => $queue_item ) {
573 if ( $processed_count >= $max_process_per_batch ) {
574 break; // Don't process too many at once.
575 }
576
577 // Check if request is too old (older than 5 minutes).
578 if ( ( $current_time - $queue_item['timestamp'] ) > 300 ) {
579 unset( $current_queue[ $index ] );
580 continue;
581 }
582
583 // Simulate the original request processing.
584 try {
585 if ( isset( $queue_item['data'] ) && ! empty( $queue_item['data'] ) ) {
586 $data = $queue_item['data'];
587
588 // Convert JSONString if present.
589 if ( array_key_exists( 'JSONString', $data ) ) {
590 $data = str_replace( '\\', '', $data['JSONString'] );
591 }
592
593 // Process the data.
594 $this->process( $data );
595 ++$processed_count;
596 }
597 } catch ( Exception $e ) {
598 // Log error but continue processing other items.
599 error_log( 'CommerceBird Webhook Queue Processing Error: ' . $e->getMessage() );
600 }
601
602 // Remove processed item from queue.
603 unset( $current_queue[ $index ] );
604 }
605
606 // Re-index array after unsetting elements.
607 $current_queue = array_values( $current_queue );
608
609 // Update the queue.
610 update_option( $queue_key, $current_queue );
611
612 // Schedule next processing if queue still has items.
613 if ( ! empty( $current_queue ) && ! wp_next_scheduled( 'cmbird_process_webhook_queue' ) ) {
614 wp_schedule_single_event( $current_time + 30, 'cmbird_process_webhook_queue' );
615 }
616 }
617
618 /**
619 * Cleanup old rate limiting data
620 * Called by WordPress scheduled delete action
621 */
622 public function cleanup_rate_limit_data() {
623 global $wpdb;
624
625 // Clean up old transients related to rate limiting.
626 $wpdb->query(
627 "DELETE FROM {$wpdb->options}
628 WHERE option_name LIKE '_transient_cmbird_rate_limit_%'
629 OR option_name LIKE '_transient_timeout_cmbird_rate_limit_%'"
630 );
631
632 // Clean up old processing locks (older than 1 hour).
633 $wpdb->query(
634 $wpdb->prepare(
635 "DELETE FROM {$wpdb->options}
636 WHERE option_name LIKE 'cmbird_processing_payload_%'
637 AND option_value < %d",
638 time() - 3600
639 )
640 );
641 }
642
643 /**
644 * @param $inventory_adjustment
645 * @param wpdb $wpdb
646 *
647 * @return WP_REST_Response
648 */
649 public function inventory_adjustment( $inventory_adjustment ): WP_REST_Response {
650 global $wpdb;
651 $item = $inventory_adjustment;
652 $line_items = $item['line_items'];
653 // get first item from line items array.
654 $item_id = $line_items[0]['item_id'];
655 $adjusted_stock = $line_items[0]['quantity_adjusted'];
656
657 $row_item = $wpdb->get_row(
658 $wpdb->prepare(
659 "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s",
660 $item_id
661 )
662 );
663 $mapped_product_id = $row_item->post_id;
664
665 if ( ! empty( $mapped_product_id ) ) {
666 // stock.
667 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
668 $product = wc_get_product( $mapped_product_id );
669 // Check if the product is in stock.
670 if ( ! $zi_disable_stock_sync ) {
671 if ( $product->is_in_stock() ) {
672 // Get stock quantity.
673 $stock_quantity = $product->get_stock_quantity();
674 $new_stock = $stock_quantity + $adjusted_stock;
675 $product->set_stock_quantity( $new_stock );
676 } else {
677 $product->set_stock_quantity( $adjusted_stock );
678 $product->set_stock_status( 'instock' );
679 $product->set_manage_stock( true );
680 }
681 $product->save();
682 }
683 }
684
685 $response = new WP_REST_Response();
686 $response->set_data( 'Inventory Adjustment successful' );
687 $response->set_status( 200 );
688
689 return $response;
690 }
691 }
692