PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.8.2
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.8.2
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 7 months ago class-api-for-exact-webhooks.php 4 months ago class-api-for-product-webhook.php 3 months ago class-api-for-shipping-status.php 9 months ago class-api-for-woo-order.php 4 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 5 months ago index.php 1 year ago trait-api-permission.php 7 months ago
class-api-for-product-webhook.php
703 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 ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $groupid is prepared.
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 ) ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- item id is sanitized.
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, $item_image, $variation_id );
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( '_cogs_total_value', $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_zoho_id( $item['tax_id'] )
280 ?? $zi_common_class->get_tax_class_by_percentage( $item['tax_percentage'] );
281 $variation->set_tax_status( 'taxable' );
282 $variation->set_tax_class( $woo_tax_class );
283 }
284 // weight & dimensions.
285 $variation->set_weight( $weight );
286 $variation->set_length( $length );
287 $variation->set_width( $width );
288 $variation->set_height( $height );
289
290 $variation->save(); // Save the data.
291 } elseif ( 'publish' === $item_status ) {
292 $attribute_name11 = $item['attribute_option_name1'];
293 $attribute_name12 = $item['attribute_option_name2'];
294 $attribute_name13 = $item['attribute_option_name3'];
295 // Prepare the variation data.
296 $attribute_arr = array();
297 if ( ! empty( $attribute_name11 ) ) {
298 $sanitized_name1 = wc_sanitize_taxonomy_name( $item['attribute_name1'] );
299 $attribute_arr[ $sanitized_name1 ] = $attribute_name11;
300 }
301 if ( ! empty( $attribute_name12 ) ) {
302 $sanitized_name2 = wc_sanitize_taxonomy_name( $item['attribute_name2'] );
303 $attribute_arr[ $sanitized_name2 ] = $attribute_name12;
304 }
305 if ( ! empty( $attribute_name13 ) ) {
306 $sanitized_name3 = wc_sanitize_taxonomy_name( $item['attribute_name3'] );
307 $attribute_arr[ $sanitized_name3 ] = $attribute_name13;
308 }
309
310 // here actually create new variation because sku not found.
311 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
312 $variation = new WC_Product_Variation();
313 $variation->set_parent_id( $group_id );
314 $variation->set_status( 'publish' );
315 $variation->set_regular_price( $item_price );
316 $variation->set_sku( $item_sku );
317 $variation->set_weight( $weight );
318 $variation->set_length( $length );
319 $variation->set_width( $width );
320 $variation->set_height( $height );
321 if ( ! $zi_disable_stock_sync ) {
322 $variation->set_stock_quantity( $item_stock );
323 $variation->set_manage_stock( true );
324 $variation->set_stock_status( '' );
325 } else {
326 $variation->set_manage_stock( false );
327 }
328 // Map taxes while syncing product from zoho.
329 if ( $item['tax_id'] && ! $this->is_tax_enabled() ) {
330 $zi_common_class = new CMBIRD_Common_Functions();
331 $woo_tax_class = $zi_common_class->get_tax_class_by_zoho_id( $item['tax_id'] )
332 ?? $zi_common_class->get_tax_class_by_percentage( $item['tax_percentage'] );
333 $variation->set_tax_status( 'taxable' );
334 $variation->set_tax_class( $woo_tax_class );
335 }
336 $variation->add_meta_data( 'zi_item_id', $item->item_id );
337 $variation_id = $variation->save();
338
339 // Get the variation attributes with correct attribute values.
340 foreach ( $attribute_arr as $attribute => $term_name ) {
341 $taxonomy = $attribute;
342 // If taxonomy doesn't exists we create it.
343 if ( ! taxonomy_exists( $taxonomy ) ) {
344 register_taxonomy(
345 $taxonomy,
346 'product_variation',
347 array(
348 'hierarchical' => false,
349 'label' => ucfirst( $attribute ),
350 'query_var' => true,
351 'rewrite' => array( 'slug' => sanitize_title( $attribute ) ),
352 ),
353 );
354 }
355
356 // Check if the Term name exist and if not we create it.
357 if ( ! term_exists( $term_name, $taxonomy ) ) {
358 wp_insert_term( $term_name, $taxonomy );
359 }
360
361 $term_slug = get_term_by( 'name', $term_name, $taxonomy )->slug;
362 // Get the post Terms names from the parent variable product.
363 $post_term_names = wp_get_post_terms( $group_id, $taxonomy, array( 'fields' => 'names' ) );
364 // Check if the post term exist and if not we set it in the parent variable product.
365 if ( ! in_array( $term_name, $post_term_names, true ) ) {
366 wp_set_post_terms( $group_id, $term_name, $taxonomy, true );
367 }
368 // Set/save the attribute data in the product variation.
369 update_post_meta( $variation_id, 'attribute_' . $taxonomy, $term_slug );
370 }
371
372 // featured image.
373 $zi_disable_itemimage_sync = get_option( 'cmbird_zoho_disable_image_sync_status' );
374 if ( ! empty( $item_image ) && ! $zi_disable_itemimage_sync ) {
375 $image_class = new CMBIRD_Image_ZI();
376 $image_class->cmbird_zi_get_image( $item_id, $item_name, $item_image, $variation_id );
377 }
378
379 update_post_meta( $variation_id, 'zi_item_id', $item_id );
380 // Sync variation data with parent variable product.
381 WC_Product_Variable::sync( $group_id );
382 // Regenerate lookup table for attributes.
383 $lookup_data_store = new LookupDataStore();
384 $lookup_data_store->create_data_for_product( $group_id );
385 // End group item add process.
386 unset( $attribute_arr );
387 }
388 wc_delete_product_transients( $group_id ); // Clear/refresh cache.
389 // end of grouped item creation.
390 } else {
391 // fwrite($fd, PHP_EOL . 'Inside simple items');
392 // fwrite($fd, PHP_EOL . 'Item description Simple : ' . $item_description);
393 $row_item = $wpdb->get_row(
394 $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s", $item_id )
395 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // item id is sanitized.
396 $mapped_product_id = $row_item->post_id;
397 // simple product.
398 // fwrite($fd, PHP_EOL . 'Before Match check');
399 $pdt_id = '';
400 if ( ! empty( $mapped_product_id ) && null !== $mapped_product_id ) {
401 $product_found = wc_get_product( $mapped_product_id );
402 if ( ! $product_found ) {
403 // remove all postmeta of that product id.
404 $wpdb->delete( $wpdb->postmeta, array( 'post_id' => $mapped_product_id ) );
405 } else {
406 $pdt_id = $mapped_product_id;
407 }
408 } elseif ( empty( $item['is_combo_product'] ) ) {
409 // fwrite($fd, PHP_EOL . 'Inside create product');
410
411 // Check if Category is selected before creating simple item.
412 if ( 'publish' === $item_status ) {
413 // cmbird_zoho_item_category is a plain array of Zoho category IDs.
414 $opt_category = maybe_unserialize( get_option( 'cmbird_zoho_item_category' ) );
415 $category_id = $item['category_id'];
416 if ( $opt_category ) {
417 if ( in_array( $category_id, $opt_category, true ) ) {
418 $product_class = new CMBIRD_Products_ZI_Export();
419 $pdt_id = $product_class->cmbird_zi_product_to_woocommerce( $item, $item_stock );
420 }
421 }
422 }
423 // fwrite($fd, PHP_EOL . 'After adding it : ' . $pdt_id);
424 }
425
426 // If there is product id then update metadata.
427 if ( ! empty( $pdt_id ) ) {
428 $simple_product = wc_get_product( $pdt_id );
429 // update the name if its allowed.
430 $zi_disable_itemname_sync = get_option( 'cmbird_zoho_disable_name_sync_status' );
431 if ( ! $zi_disable_itemname_sync ) {
432 $simple_product->set_name( $item_name );
433 $slug = sanitize_title( $item_name );
434 $simple_product->set_slug( $slug );
435 }
436 // update the zi_item_id using the product instance.
437 $simple_product->update_meta_data( 'zi_item_id', $item_id );
438 // update the status using set_status().
439 $simple_product->set_status( $item_status );
440 // Update the product SKU.
441 $simple_product->set_sku( $item_sku );
442 // price.
443 $sale_price = $simple_product->get_sale_price();
444 $simple_product->set_regular_price( $item_price );
445 if ( empty( $sale_price ) ) {
446 $simple_product->set_price( $item_price );
447 }
448 // Update Purchase price.
449 $simple_product->update_meta_data( key: '_cogs_total_value', value: $item['purchase_rate'] );
450 // description.
451 $zi_disable_itemdescription_sync = get_option( 'cmbird_zoho_disable_description_sync_status' );
452 if ( ! empty( $item_description ) && ! $zi_disable_itemdescription_sync ) {
453 $simple_product->set_short_description( $item_description );
454 }
455 // Brand update if taxonomy product_brand(s) exists.
456 if ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
457 wp_set_object_terms( $pdt_id, $item_brand, 'product_brand' );
458 } elseif ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
459 wp_set_object_terms( $pdt_id, $item_brand, 'product_brand' );
460 }
461 // stock.
462 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
463 if ( ! $zi_disable_stock_sync ) {
464 // fwrite( $fd, PHP_EOL . 'Inside1' );
465 if ( 'NULL' !== gettype( $item_stock ) ) {
466 // fwrite( $fd, PHP_EOL . 'Inside1.1' );
467 // Set manage stock to yes.
468 $simple_product->set_manage_stock( true );
469 // Update stock for simple product.
470 $simple_product->set_stock_quantity( number_format( $item_stock, 0, '.', '' ) );
471 if ( $item_stock > 0 ) {
472 // fwrite( $fd, PHP_EOL . 'Inside2' );
473 // Update stock status.
474 $simple_product->set_stock_status( 'instock' );
475 wp_set_post_terms( $pdt_id, 'instock', 'product_visibility', true );
476 } else {
477 // fwrite($fd, PHP_EOL . 'Inside3');
478 $stock_status = $simple_product->backorders_allowed() ? 'onbackorder' : 'outofstock';
479 $simple_product->set_stock_status( $stock_status );
480 wp_set_post_terms( $pdt_id, $stock_status, 'product_visibility', true );
481 }
482 }
483 }
484 // fwrite($fd, PHP_EOL . 'After stock');
485 // Update weight & dimensions of simple product.
486 $simple_product->set_weight( $weight );
487 $simple_product->set_length( $length );
488 $simple_product->set_width( $width );
489 $simple_product->set_height( $height );
490
491 // featured image.
492 $zi_disable_itemimage_sync = get_option( 'cmbird_zoho_disable_image_sync_status' );
493 if ( ! empty( $item_image ) && ! $zi_disable_itemimage_sync ) {
494 $image_class = new CMBIRD_Image_ZI();
495 $image_class->cmbird_zi_get_image( $item_id, $item_name, $item_image, $pdt_id );
496 }
497
498 // category.
499 if ( ! empty( $item_category ) && empty( $group_name ) ) {
500 $term = get_term_by( 'name', $item_category, 'product_cat' );
501 $term_id = $term->term_id;
502 if ( empty( $term_id ) ) {
503 $term = wp_insert_term(
504 $item_category,
505 'product_cat',
506 array(
507 'parent' => 0,
508 )
509 );
510 $term_id = $term->term_id;
511 }
512 // Remove "uncategorized" category if assigned.
513 $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' );
514 if ( $uncategorized_term && has_term( $uncategorized_term->term_id, 'product_cat', $pdt_id ) ) {
515 wp_remove_object_terms( $pdt_id, $uncategorized_term->term_id, 'product_cat' );
516 }
517 if ( ! is_wp_error( $term_id ) && isset( $term->term_id ) ) {
518 $existing_terms = wp_get_object_terms( $pdt_id, 'product_cat' );
519 if ( $existing_terms && count( $existing_terms ) > 0 ) {
520 $import_class = new CMBIRD_Products_ZI();
521 $is_term_exist = $import_class->zi_check_terms_exists( $existing_terms, $term_id );
522 if ( ! $is_term_exist ) {
523 $simple_product->update_meta_data( 'zi_category_id', $item['category_id'] );
524 wp_add_object_terms( $pdt_id, $term_id, 'product_cat' );
525 }
526 } else {
527 $simple_product->update_meta_data( 'zi_category_id', $item['category_id'] );
528 wp_set_object_terms( $pdt_id, $term_id, 'product_cat' );
529 }
530 }
531 }
532
533 // Update the custom fields if the custom fields are not empty.
534 if ( ! empty( $custom_fields ) ) {
535 $import_class = new CMBIRD_Products_ZI();
536 $import_class->sync_item_custom_fields( $custom_fields, $pdt_id );
537 }
538
539 // Map taxes while syncing product from zoho.
540 if ( $item['tax_id'] && ! $this->is_tax_enabled() ) {
541 $zi_common_class = new CMBIRD_Common_Functions();
542 $woo_tax_class = $zi_common_class->get_tax_class_by_zoho_id( $item['tax_id'] )
543 ?? $zi_common_class->get_tax_class_by_percentage( $item['tax_percentage'] );
544 $simple_product->set_tax_status( 'taxable' );
545 $simple_product->set_tax_class( $woo_tax_class );
546 }
547 $simple_product->save();
548 wc_delete_product_transients( $pdt_id );
549 }
550 }
551 $response = new WP_REST_Response();
552 $response->set_data( 'success on variable product' );
553 $response->set_status( 200 );
554
555 // fclose( $fd ); // close logfile.
556
557 return $response;
558 }
559
560 /**
561 * Process queued webhook requests
562 * Called by WordPress cron system when queue processing is scheduled
563 */
564 public function process_queued_requests() {
565 $queue_key = 'cmbird_request_queue';
566 $current_queue = get_option( $queue_key, array() );
567
568 if ( empty( $current_queue ) || ! is_array( $current_queue ) ) {
569 return;
570 }
571
572 $current_time = time();
573 $processed_count = 0;
574 $max_process_per_batch = 10; // Process up to 10 requests per batch.
575
576 // Process queue items.
577 foreach ( $current_queue as $index => $queue_item ) {
578 if ( $processed_count >= $max_process_per_batch ) {
579 break; // Don't process too many at once.
580 }
581
582 // Check if request is too old (older than 5 minutes).
583 if ( ( $current_time - $queue_item['timestamp'] ) > 300 ) {
584 unset( $current_queue[ $index ] );
585 continue;
586 }
587
588 // Simulate the original request processing.
589 try {
590 if ( isset( $queue_item['data'] ) && ! empty( $queue_item['data'] ) ) {
591 $data = $queue_item['data'];
592
593 // Convert JSONString if present.
594 if ( array_key_exists( 'JSONString', $data ) ) {
595 $data = str_replace( '\\', '', $data['JSONString'] );
596 }
597
598 // Process the data.
599 $this->process( $data );
600 ++$processed_count;
601 }
602 } catch ( Exception $e ) {
603 // Log error but continue processing other items.
604 error_log( 'CommerceBird Webhook Queue Processing Error: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Required for security logging.
605 }
606
607 // Remove processed item from queue.
608 unset( $current_queue[ $index ] );
609 }
610
611 // Re-index array after unsetting elements.
612 $current_queue = array_values( $current_queue );
613
614 // Update the queue.
615 update_option( $queue_key, $current_queue );
616
617 // Schedule next processing if queue still has items.
618 if ( ! empty( $current_queue ) && ! wp_next_scheduled( 'cmbird_process_webhook_queue' ) ) {
619 wp_schedule_single_event( $current_time + 30, 'cmbird_process_webhook_queue' );
620 }
621 }
622
623 /**
624 * Cleanup old rate limiting data
625 * Called by WordPress scheduled delete action
626 */
627 public function cleanup_rate_limit_data() {
628 global $wpdb;
629
630 // Clean up old transients related to rate limiting.
631 $wpdb->query(
632 $wpdb->prepare(
633 "DELETE FROM {$wpdb->options}
634 WHERE option_name LIKE %s
635 OR option_name LIKE %s",
636 $wpdb->esc_like( '_transient_cmbird_rate_limit_' ) . '%',
637 $wpdb->esc_like( '_transient_timeout_cmbird_rate_limit_' ) . '%'
638 )
639 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- safe here.
640
641 // Clean up old processing locks (older than 1 hour).
642 $wpdb->query(
643 $wpdb->prepare(
644 "DELETE FROM {$wpdb->options}
645 WHERE option_name LIKE %s
646 AND option_value < %d",
647 $wpdb->esc_like( 'cmbird_processing_payload_' ) . '%',
648 time() - 3600
649 )
650 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- time() is safe here.
651 }
652
653 /**
654 * Handles inventory adjustment webhook data.
655 *
656 * @param array $inventory_adjustment Inventory adjustment data.
657 *
658 * @return WP_REST_Response
659 */
660 public function inventory_adjustment( $inventory_adjustment ): WP_REST_Response {
661 global $wpdb;
662 $item = $inventory_adjustment;
663 $line_items = $item['line_items'];
664 // get first item from line items array.
665 $item_id = $line_items[0]['item_id'];
666 $adjusted_stock = $line_items[0]['quantity_adjusted'];
667
668 $row_item = $wpdb->get_row(
669 $wpdb->prepare(
670 "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s",
671 $item_id
672 )
673 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $item_id is prepared.
674 $mapped_product_id = $row_item->post_id;
675
676 if ( ! empty( $mapped_product_id ) ) {
677 // stock.
678 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
679 $product = wc_get_product( $mapped_product_id );
680 // Check if the product is in stock.
681 if ( ! $zi_disable_stock_sync ) {
682 if ( $product->is_in_stock() ) {
683 // Get stock quantity.
684 $stock_quantity = $product->get_stock_quantity();
685 $new_stock = $stock_quantity + $adjusted_stock;
686 $product->set_stock_quantity( $new_stock );
687 } else {
688 $product->set_stock_quantity( $adjusted_stock );
689 $product->set_stock_status( 'instock' );
690 $product->set_manage_stock( true );
691 }
692 $product->save();
693 }
694 }
695
696 $response = new WP_REST_Response();
697 $response->set_data( 'Inventory Adjustment successful' );
698 $response->set_status( 200 );
699
700 return $response;
701 }
702 }
703