PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.8.3
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.8.3
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / includes / classes / apis / class-api-for-product-webhook.php
commercebird / includes / classes / apis Last commit date
class-api-for-cmbird.php 2 months ago class-api-for-exact-webhooks.php 4 months ago class-api-for-product-webhook.php 2 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
706 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 $parent_product = wc_get_product( $group_id );
384 if ( $parent_product ) {
385 $lookup_data_store = new LookupDataStore();
386 $lookup_data_store->create_data_for_product( $parent_product );
387 }
388 // End group item add process.
389 unset( $attribute_arr );
390 }
391 wc_delete_product_transients( $group_id ); // Clear/refresh cache.
392 // end of grouped item creation.
393 } else {
394 // fwrite($fd, PHP_EOL . 'Inside simple items');
395 // fwrite($fd, PHP_EOL . 'Item description Simple : ' . $item_description);
396 $row_item = $wpdb->get_row(
397 $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s", $item_id )
398 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared // item id is sanitized.
399 $mapped_product_id = $row_item->post_id;
400 // simple product.
401 // fwrite($fd, PHP_EOL . 'Before Match check');
402 $pdt_id = '';
403 if ( ! empty( $mapped_product_id ) && null !== $mapped_product_id ) {
404 $product_found = wc_get_product( $mapped_product_id );
405 if ( ! $product_found ) {
406 // remove all postmeta of that product id.
407 $wpdb->delete( $wpdb->postmeta, array( 'post_id' => $mapped_product_id ) );
408 } else {
409 $pdt_id = $mapped_product_id;
410 }
411 } elseif ( empty( $item['is_combo_product'] ) ) {
412 // fwrite($fd, PHP_EOL . 'Inside create product');
413
414 // Check if Category is selected before creating simple item.
415 if ( 'publish' === $item_status ) {
416 // cmbird_zoho_item_category is a plain array of Zoho category IDs.
417 $opt_category = maybe_unserialize( get_option( 'cmbird_zoho_item_category' ) );
418 $category_id = $item['category_id'];
419 if ( $opt_category ) {
420 if ( in_array( $category_id, $opt_category, true ) ) {
421 $product_class = new CMBIRD_Products_ZI_Export();
422 $pdt_id = $product_class->cmbird_zi_product_to_woocommerce( $item, $item_stock );
423 }
424 }
425 }
426 // fwrite($fd, PHP_EOL . 'After adding it : ' . $pdt_id);
427 }
428
429 // If there is product id then update metadata.
430 if ( ! empty( $pdt_id ) ) {
431 $simple_product = wc_get_product( $pdt_id );
432 // update the name if its allowed.
433 $zi_disable_itemname_sync = get_option( 'cmbird_zoho_disable_name_sync_status' );
434 if ( ! $zi_disable_itemname_sync ) {
435 $simple_product->set_name( $item_name );
436 $slug = sanitize_title( $item_name );
437 $simple_product->set_slug( $slug );
438 }
439 // update the zi_item_id using the product instance.
440 $simple_product->update_meta_data( 'zi_item_id', $item_id );
441 // update the status using set_status().
442 $simple_product->set_status( $item_status );
443 // Update the product SKU.
444 $simple_product->set_sku( $item_sku );
445 // price.
446 $sale_price = $simple_product->get_sale_price();
447 $simple_product->set_regular_price( $item_price );
448 if ( empty( $sale_price ) ) {
449 $simple_product->set_price( $item_price );
450 }
451 // Update Purchase price.
452 $simple_product->update_meta_data( key: '_cogs_total_value', value: $item['purchase_rate'] );
453 // description.
454 $zi_disable_itemdescription_sync = get_option( 'cmbird_zoho_disable_description_sync_status' );
455 if ( ! empty( $item_description ) && ! $zi_disable_itemdescription_sync ) {
456 $simple_product->set_short_description( $item_description );
457 }
458 // Brand update if taxonomy product_brand(s) exists.
459 if ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
460 wp_set_object_terms( $pdt_id, $item_brand, 'product_brand' );
461 } elseif ( ! empty( $item_brand ) && taxonomy_exists( 'product_brand' ) ) {
462 wp_set_object_terms( $pdt_id, $item_brand, 'product_brand' );
463 }
464 // stock.
465 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
466 if ( ! $zi_disable_stock_sync ) {
467 // fwrite( $fd, PHP_EOL . 'Inside1' );
468 if ( 'NULL' !== gettype( $item_stock ) ) {
469 // fwrite( $fd, PHP_EOL . 'Inside1.1' );
470 // Set manage stock to yes.
471 $simple_product->set_manage_stock( true );
472 // Update stock for simple product.
473 $simple_product->set_stock_quantity( number_format( $item_stock, 0, '.', '' ) );
474 if ( $item_stock > 0 ) {
475 // fwrite( $fd, PHP_EOL . 'Inside2' );
476 // Update stock status.
477 $simple_product->set_stock_status( 'instock' );
478 wp_set_post_terms( $pdt_id, 'instock', 'product_visibility', true );
479 } else {
480 // fwrite($fd, PHP_EOL . 'Inside3');
481 $stock_status = $simple_product->backorders_allowed() ? 'onbackorder' : 'outofstock';
482 $simple_product->set_stock_status( $stock_status );
483 wp_set_post_terms( $pdt_id, $stock_status, 'product_visibility', true );
484 }
485 }
486 }
487 // fwrite($fd, PHP_EOL . 'After stock');
488 // Update weight & dimensions of simple product.
489 $simple_product->set_weight( $weight );
490 $simple_product->set_length( $length );
491 $simple_product->set_width( $width );
492 $simple_product->set_height( $height );
493
494 // featured image.
495 $zi_disable_itemimage_sync = get_option( 'cmbird_zoho_disable_image_sync_status' );
496 if ( ! empty( $item_image ) && ! $zi_disable_itemimage_sync ) {
497 $image_class = new CMBIRD_Image_ZI();
498 $image_class->cmbird_zi_get_image( $item_id, $item_name, $item_image, $pdt_id );
499 }
500
501 // category.
502 if ( ! empty( $item_category ) && empty( $group_name ) ) {
503 $term = get_term_by( 'name', $item_category, 'product_cat' );
504 $term_id = $term->term_id;
505 if ( empty( $term_id ) ) {
506 $term = wp_insert_term(
507 $item_category,
508 'product_cat',
509 array(
510 'parent' => 0,
511 )
512 );
513 $term_id = $term->term_id;
514 }
515 // Remove "uncategorized" category if assigned.
516 $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' );
517 if ( $uncategorized_term && has_term( $uncategorized_term->term_id, 'product_cat', $pdt_id ) ) {
518 wp_remove_object_terms( $pdt_id, $uncategorized_term->term_id, 'product_cat' );
519 }
520 if ( ! is_wp_error( $term_id ) && isset( $term->term_id ) ) {
521 $existing_terms = wp_get_object_terms( $pdt_id, 'product_cat' );
522 if ( $existing_terms && count( $existing_terms ) > 0 ) {
523 $import_class = new CMBIRD_Products_ZI();
524 $is_term_exist = $import_class->zi_check_terms_exists( $existing_terms, $term_id );
525 if ( ! $is_term_exist ) {
526 $simple_product->update_meta_data( 'zi_category_id', $item['category_id'] );
527 wp_add_object_terms( $pdt_id, $term_id, 'product_cat' );
528 }
529 } else {
530 $simple_product->update_meta_data( 'zi_category_id', $item['category_id'] );
531 wp_set_object_terms( $pdt_id, $term_id, 'product_cat' );
532 }
533 }
534 }
535
536 // Update the custom fields if the custom fields are not empty.
537 if ( ! empty( $custom_fields ) ) {
538 $import_class = new CMBIRD_Products_ZI();
539 $import_class->sync_item_custom_fields( $custom_fields, $pdt_id );
540 }
541
542 // Map taxes while syncing product from zoho.
543 if ( $item['tax_id'] && ! $this->is_tax_enabled() ) {
544 $zi_common_class = new CMBIRD_Common_Functions();
545 $woo_tax_class = $zi_common_class->get_tax_class_by_zoho_id( $item['tax_id'] )
546 ?? $zi_common_class->get_tax_class_by_percentage( $item['tax_percentage'] );
547 $simple_product->set_tax_status( 'taxable' );
548 $simple_product->set_tax_class( $woo_tax_class );
549 }
550 $simple_product->save();
551 wc_delete_product_transients( $pdt_id );
552 }
553 }
554 $response = new WP_REST_Response();
555 $response->set_data( 'success on variable product' );
556 $response->set_status( 200 );
557
558 // fclose( $fd ); // close logfile.
559
560 return $response;
561 }
562
563 /**
564 * Process queued webhook requests
565 * Called by WordPress cron system when queue processing is scheduled
566 */
567 public function process_queued_requests() {
568 $queue_key = 'cmbird_request_queue';
569 $current_queue = get_option( $queue_key, array() );
570
571 if ( empty( $current_queue ) || ! is_array( $current_queue ) ) {
572 return;
573 }
574
575 $current_time = time();
576 $processed_count = 0;
577 $max_process_per_batch = 10; // Process up to 10 requests per batch.
578
579 // Process queue items.
580 foreach ( $current_queue as $index => $queue_item ) {
581 if ( $processed_count >= $max_process_per_batch ) {
582 break; // Don't process too many at once.
583 }
584
585 // Check if request is too old (older than 5 minutes).
586 if ( ( $current_time - $queue_item['timestamp'] ) > 300 ) {
587 unset( $current_queue[ $index ] );
588 continue;
589 }
590
591 // Simulate the original request processing.
592 try {
593 if ( isset( $queue_item['data'] ) && ! empty( $queue_item['data'] ) ) {
594 $data = $queue_item['data'];
595
596 // Convert JSONString if present.
597 if ( array_key_exists( 'JSONString', $data ) ) {
598 $data = str_replace( '\\', '', $data['JSONString'] );
599 }
600
601 // Process the data.
602 $this->process( $data );
603 ++$processed_count;
604 }
605 } catch ( Exception $e ) {
606 // Log error but continue processing other items.
607 error_log( 'CommerceBird Webhook Queue Processing Error: ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- Required for security logging.
608 }
609
610 // Remove processed item from queue.
611 unset( $current_queue[ $index ] );
612 }
613
614 // Re-index array after unsetting elements.
615 $current_queue = array_values( $current_queue );
616
617 // Update the queue.
618 update_option( $queue_key, $current_queue );
619
620 // Schedule next processing if queue still has items.
621 if ( ! empty( $current_queue ) && ! wp_next_scheduled( 'cmbird_process_webhook_queue' ) ) {
622 wp_schedule_single_event( $current_time + 30, 'cmbird_process_webhook_queue' );
623 }
624 }
625
626 /**
627 * Cleanup old rate limiting data
628 * Called by WordPress scheduled delete action
629 */
630 public function cleanup_rate_limit_data() {
631 global $wpdb;
632
633 // Clean up old transients related to rate limiting.
634 $wpdb->query(
635 $wpdb->prepare(
636 "DELETE FROM {$wpdb->options}
637 WHERE option_name LIKE %s
638 OR option_name LIKE %s",
639 $wpdb->esc_like( '_transient_cmbird_rate_limit_' ) . '%',
640 $wpdb->esc_like( '_transient_timeout_cmbird_rate_limit_' ) . '%'
641 )
642 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- safe here.
643
644 // Clean up old processing locks (older than 1 hour).
645 $wpdb->query(
646 $wpdb->prepare(
647 "DELETE FROM {$wpdb->options}
648 WHERE option_name LIKE %s
649 AND option_value < %d",
650 $wpdb->esc_like( 'cmbird_processing_payload_' ) . '%',
651 time() - 3600
652 )
653 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- time() is safe here.
654 }
655
656 /**
657 * Handles inventory adjustment webhook data.
658 *
659 * @param array $inventory_adjustment Inventory adjustment data.
660 *
661 * @return WP_REST_Response
662 */
663 public function inventory_adjustment( $inventory_adjustment ): WP_REST_Response {
664 global $wpdb;
665 $item = $inventory_adjustment;
666 $line_items = $item['line_items'];
667 // get first item from line items array.
668 $item_id = $line_items[0]['item_id'];
669 $adjusted_stock = $line_items[0]['quantity_adjusted'];
670
671 $row_item = $wpdb->get_row(
672 $wpdb->prepare(
673 "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s",
674 $item_id
675 )
676 ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $item_id is prepared.
677 $mapped_product_id = $row_item->post_id;
678
679 if ( ! empty( $mapped_product_id ) ) {
680 // stock.
681 $zi_disable_stock_sync = get_option( 'cmbird_zoho_disable_stock_sync_status' );
682 $product = wc_get_product( $mapped_product_id );
683 // Check if the product is in stock.
684 if ( ! $zi_disable_stock_sync ) {
685 if ( $product->is_in_stock() ) {
686 // Get stock quantity.
687 $stock_quantity = $product->get_stock_quantity();
688 $new_stock = $stock_quantity + $adjusted_stock;
689 $product->set_stock_quantity( $new_stock );
690 } else {
691 $product->set_stock_quantity( $adjusted_stock );
692 $product->set_stock_status( 'instock' );
693 $product->set_manage_stock( true );
694 }
695 $product->save();
696 }
697 }
698
699 $response = new WP_REST_Response();
700 $response->set_data( 'Inventory Adjustment successful' );
701 $response->set_status( 200 );
702
703 return $response;
704 }
705 }
706