PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.6.5
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.6.5
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 / zoho-inventory / class-import-items.php
commercebird / includes / classes / zoho-inventory Last commit date
class-cmbird-categories-zi.php 7 months ago class-cmbird-image-zi.php 9 months ago class-import-items.php 5 months ago class-import-price-list.php 9 months ago class-multi-currency.php 7 months ago class-order-sync.php 7 months ago class-product.php 7 months ago class-users-contact.php 9 months ago index.php 1 year ago
class-import-items.php
1869 lines
1 <?php
2
3 /**
4 * Class to import Products from Zoho to WooCommerce
5 *
6 * @package zoho_inventory_api
7 */
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 use Automattic\WooCommerce\Internal\ProductAttributesLookup\LookupDataStore;
13
14 /**
15 * Class to import Products from Zoho Inventory to WooCommerce.
16 *
17 * Handles syncing of simple products, variable products, grouped items,
18 * and composite items from Zoho Inventory to WooCommerce.
19 *
20 * @package zoho_inventory_api
21 */
22 class CMBIRD_Products_ZI {
23
24
25 private $config;
26 private $is_tax_enabled;
27
28 private $wc_decimal_separator;
29 private $wc_thousand_separator;
30
31 private $wc_price_decimal_separator;
32
33 public function __construct() {
34 $this->initialize_config();
35 add_action( 'init', array( $this, 'initialize_config' ) );
36 }
37
38 public function initialize_config() {
39 if ( ! function_exists( 'wc_get_price_decimal_separator' ) ) {
40 return; // Prevents error if WooCommerce is not active.
41 }
42
43 $this->config = array(
44 'ProductZI' => array(
45 'OID' => get_option( 'cmbird_zoho_inventory_oid' ),
46 'APIURL' => get_option( 'cmbird_zoho_inventory_url' ),
47 ),
48 'Settings' => array(
49 'disable_description' => get_option( 'cmbird_zoho_disable_description_sync_status' ),
50 'disable_name' => get_option( 'cmbird_zoho_disable_name_sync_status' ),
51 'disable_price' => get_option( 'cmbird_zoho_disable_price_sync_status' ),
52 'disable_stock' => get_option( 'cmbird_zoho_disable_stock_sync_status' ),
53 'enable_accounting_stock' => get_option( 'cmbird_zoho_enable_accounting_stock_status' ),
54 'enable_location_stock' => get_option( 'cmbird_zoho_enable_locationstock_status' ),
55 'zoho_location_id' => get_option( 'cmbird_zoho_location_id_status' ),
56 'disable_image' => get_option( 'cmbird_zoho_disable_image_sync_status' ),
57 ),
58 // Get WooCommerce settings.
59 'WooCommerce' => array(
60 'decimal_separator' => wc_get_price_decimal_separator(),
61 'thousand_separator' => wc_get_price_thousand_separator(),
62 'price_decimal_separator' => wc_get_price_decimals(),
63 'tax_enabled' => 'yes' === get_option( 'woocommerce_calc_taxes' ),
64 ),
65 );
66
67 // Check if WooCommerce taxes are enabled and store the result.
68 $this->is_tax_enabled = $this->config['WooCommerce']['tax_enabled'];
69
70 // Check the WooCommerce settings for decimal and thousand separators.
71 $this->wc_decimal_separator = $this->config['WooCommerce']['decimal_separator'];
72 $this->wc_thousand_separator = $this->config['WooCommerce']['thousand_separator'];
73 $this->wc_price_decimal_separator = $this->config['WooCommerce']['price_decimal_separator'];
74 }
75
76 /**
77 * Get the PHP memory limit in bytes.
78 *
79 * @return int Memory limit in bytes, or 0 if unlimited.
80 */
81 private function get_memory_limit() {
82 $memory_limit = ini_get( 'memory_limit' );
83 if ( '-1' === $memory_limit ) {
84 return 0; // Unlimited.
85 }
86
87 // Convert to bytes.
88 $unit = strtoupper( substr( $memory_limit, -1 ) );
89 $value = (int) $memory_limit;
90
91 switch ( $unit ) {
92 case 'G':
93 $value *= 1024 * 1024 * 1024;
94 break;
95 case 'M':
96 $value *= 1024 * 1024;
97 break;
98 case 'K':
99 $value *= 1024;
100 break;
101 }
102
103 return $value;
104 }
105
106 /**
107 * Function to retrieve item details and sync items.
108 *
109 * @param string $url - URL to get details.
110 * @return mixed return true if data false if error.
111 */
112 public function zi_item_bulk_sync( $url ) {
113 // $fd = fopen( __DIR__ . '/zi_item_bulk_sync.txt', 'a+' );
114
115 global $wpdb;
116 $execute_curl_call = new CMBIRD_API_Handler_Zoho();
117 $json = $execute_curl_call->execute_curl_call_get( $url );
118 $code = $json->code;
119
120 $is_tax_enabled = $this->is_tax_enabled;
121
122 // Memory management: Set memory limit if needed.
123 if ( function_exists( 'wp_raise_memory_limit' ) ) {
124 wp_raise_memory_limit();
125 }
126
127 // $message = $json->message;
128 // fwrite($fd, PHP_EOL . '$json->item : ' . print_r($json, true));
129
130 if ( 0 === $code || '0' === $code ) {
131 $processed_count = 0;
132
133 foreach ( $json->items as $arr ) {
134 // Memory cleanup every 10 items (reduced from 50 for better memory management).
135 if ( $processed_count > 0 && $processed_count % 10 === 0 ) {
136 wp_cache_flush();
137 if ( function_exists( 'gc_collect_cycles' ) ) {
138 gc_collect_cycles();
139 }
140 }
141
142 // fwrite( $fd, PHP_EOL . '$arr : ' . print_r( $arr, true ) );
143 if ( ( ! empty( $arr->item_id ) ) && ! ( $arr->is_combo_product ) ) {
144 // fwrite($fd, PHP_EOL . 'Item Id found : ' . $arr->item_id);
145
146 $product_res = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key='zi_item_id' AND meta_value=%s", $arr->item_id ) );
147
148 if ( $product_res && ! empty( $product_res->post_id ) ) {
149 $pdt_id = $product_res->post_id;
150 // Load the Product Object.
151 $product = wc_get_product( $pdt_id );
152 if ( empty( $product ) || ! $product ) {
153 continue;
154 }
155
156 $zi_disable_item_description_sync = $this->config['Settings']['disable_description'];
157 if ( ! empty( $arr->description ) && ! $zi_disable_item_description_sync ) {
158 $product->set_short_description( $arr->description );
159 }
160
161 if ( ! empty( $arr->status ) ) {
162 $status = 'active' === $arr->status ? 'publish' : 'draft';
163 $product->set_status( $status );
164 }
165
166 $zi_disable_itemname_sync = $this->config['Settings']['disable_name'];
167 if ( ( ! $zi_disable_itemname_sync ) && ! empty( $arr->name ) ) {
168 $product->set_name( $arr->name );
169 // set slug but use sanitize_title to ensure it is safe.
170 $product->set_slug( sanitize_title( $arr->name ) );
171 }
172
173 if ( ! empty( $arr->sku ) ) {
174 $product->set_sku( $arr->sku );
175 }
176
177 $zi_disable_itemprice_sync = $this->config['Settings']['disable_price'];
178 if ( ! empty( $arr->rate ) && ! $zi_disable_itemprice_sync ) {
179 $product->set_regular_price( $arr->rate );
180 $sale_price = $product->get_sale_price();
181 if ( empty( $sale_price ) ) {
182 $product->set_price( $arr->rate );
183 }
184 }
185
186 if ( isset( $arr->package_details ) ) {
187 $details = $arr->package_details;
188 if ( is_object( $details ) ) {
189 $product->set_weight( floatval( $details->weight ) );
190 $product->set_length( floatval( $details->length ) );
191 $product->set_width( floatval( $details->width ) );
192 $product->set_height( floatval( $details->height ) );
193 }
194 }
195
196 // Update Purchase Rate as Cost Price.
197 $product->update_meta_data( '_cost_price', $arr->purchase_rate );
198
199 // To check status of stock sync option.
200 $zi_disable_stock_sync = $this->config['Settings']['disable_stock'];
201 if ( ! $zi_disable_stock_sync && isset( $arr->available_for_sale_stock ) ) {
202 $stock = '';
203 // Update stock.
204 $accounting_stock = $this->config['Settings']['enable_accounting_stock'];
205 // Sync from specific location check.
206 $zi_enable_locationstock = $this->config['Settings']['enable_location_stock'];
207 if ( $zi_enable_locationstock && isset( $arr->locations ) ) {
208 $locations = $arr->locations;
209 $location_id = $this->config['Settings']['zoho_location_id'];
210 foreach ( $locations as $location ) {
211 if ( $location->location_id === $location_id ) {
212 if ( $accounting_stock ) {
213 $stock = $location->location_available_for_sale_stock;
214 } else {
215 $stock = $location->location_actual_available_for_sale_stock;
216 }
217 }
218 }
219 } elseif ( $accounting_stock ) {
220 $stock = $arr->available_for_sale_stock;
221 } else {
222 $stock = $arr->actual_available_for_sale_stock;
223 }
224
225 if ( is_numeric( $stock ) ) {
226 $product->set_manage_stock( true );
227 $product->set_stock_quantity( $stock );
228 if ( $stock > 0 ) {
229 $product->set_stock_status( 'instock' );
230 } else {
231 $backorder_status = $product->backorders_allowed();
232 $status = ( 'yes' === $backorder_status ) ? 'onbackorder' : 'outofstock';
233 $product->set_stock_status( $status );
234 }
235 }
236 }
237
238 if ( ! empty( $arr->tax_id ) && ! $is_tax_enabled ) {
239 $zi_common_class = new CMBIRD_Common_Functions();
240 $woo_tax_class = $zi_common_class->get_tax_class_by_percentage( $arr->tax_percentage );
241 $product->set_tax_status( 'taxable' );
242 $product->set_tax_class( $woo_tax_class );
243 }
244 $product->save();
245 // Sync ACF Fields.
246 $this->sync_item_custom_fields( $arr->custom_fields, $pdt_id );
247 // Clear/refresh cache.
248 wc_delete_product_transients( $pdt_id );
249
250 // Explicitly unset product object to free memory.
251 unset( $product );
252 }
253 }
254
255 ++$processed_count;
256 }
257
258 // Final memory cleanup after processing all items.
259 wp_cache_flush();
260 if ( function_exists( 'gc_collect_cycles' ) ) {
261 gc_collect_cycles();
262 }
263
264 // Clear any temporary variables.
265 unset( $json, $execute_curl_call );
266 } else {
267 return false;
268 }
269 // fclose( $fd );
270 // Return if synced.
271 return true;
272 }
273
274 /**
275 * Function to add items recursively by cron job.
276 *
277 * @param number $page - Page number for getting item with pagination.
278 * @param number $category - Category id to get item of specific category.
279 * @return mixed
280 */
281 public function sync_item_recursively() {
282 // $fd = fopen( __DIR__ . '/simple-items-sync.txt', 'a+' );
283
284 $args = func_get_args();
285 // fwrite( $fd, PHP_EOL . 'Args ' . print_r( $args, true ) );
286 if ( is_array( $args ) ) {
287 if ( isset( $args['page'] ) && isset( $args['category'] ) ) {
288 $page = $args['page'];
289 $category = $args['category'];
290 } elseif ( isset( $args[0] ) && isset( $args[1] ) ) {
291 $page = $args[0];
292 $category = $args[1];
293 } elseif ( isset( $args[0] ) && ! isset( $args[1] ) ) {
294 $page = $args[0]['page'];
295 $category = $args[0]['category'];
296 } else {
297 return;
298 }
299 } else {
300 return;
301 }
302
303 $zoho_inventory_oid = $this->config['ProductZI']['OID'];
304 $zoho_inventory_url = $this->config['ProductZI']['APIURL'];
305 $timeout = ini_get( 'max_execution_time' ) ? ini_get( 'max_execution_time' ) : 60;
306 // Dynamically set per_page based on timeout.
307 if ( $timeout <= 30 ) {
308 $per_page = 20;
309 } elseif ( $timeout <= 60 ) {
310 $per_page = 50;
311 } else {
312 $per_page = 100;
313 }
314
315 $url = sprintf(
316 '%sinventory/v1/items?organization_id=%s&category_id=%s&page=%d&per_page=%d&sort_column=last_modified_time',
317 $zoho_inventory_url,
318 $zoho_inventory_oid,
319 $category,
320 $page,
321 $per_page
322 );
323 $execute_curl_call = new CMBIRD_API_Handler_Zoho();
324 $json = $execute_curl_call->execute_curl_call_get( $url );
325 $code = (int) property_exists( $json, 'code' ) ? $json->code : '0';
326
327 global $wpdb;
328
329 /* Response for item sync with sync button. For cron sync blank array will return. */
330 $response_msg = array();
331 if ( empty( $code ) && property_exists( $json, 'items' ) ) {
332 $item_ids = array();
333 // log items.
334 // fwrite( $fd, PHP_EOL . 'Items : ' . print_r( $json, true ) );
335 // before your foreach, initialize once.
336 $logger = wc_get_logger();
337 $context = array( 'source' => 'cmbird_item_sync' );
338
339 foreach ( $json->items as $arr ) {
340 $prod_id = wc_get_product_id_by_sku( $arr->sku );
341 // Flag to enable or disable sync.
342 $allow_to_import = true;
343 // Check if product exists with same sku.
344 if ( $prod_id ) {
345 $zi_item_id = get_post_meta( $prod_id, 'zi_item_id', true );
346 if ( empty( $zi_item_id ) ) {
347 // Map existing item with zoho id.
348 update_post_meta( $prod_id, 'zi_item_id', $arr->item_id );
349 $allow_to_import = true;
350 }
351 }
352 if ( isset( $arr->is_combo_product ) && isset( $arr->group_id ) ) {
353 // If product not exists normal behavior of item sync.
354 $allow_to_import = false;
355 }
356 if ( isset( $arr->group_id ) && ! empty( $arr->group_id ) ) {
357 $this->sync_variation_of_group( $arr );
358 continue;
359 }
360
361 // Remove duplicated product based on the sku, remove also postmeta.
362 // run query to check if product exists twice with same sku. Check based on SKU.
363 if ( $prod_id && ! empty( $arr->sku ) ) {
364 $duplicate_product = $wpdb->get_row( $wpdb->prepare( "SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_sku' AND meta_value = %s AND post_id != %d LIMIT 1", $arr->sku, $prod_id ) );
365 if ( $duplicate_product ) {
366 // Remove the duplicate product.
367 wp_delete_post( $duplicate_product->post_id, true );
368 $prod_id = '';
369 }
370 }
371
372 // Get the post id by doing a meta query on the postmeta table.
373 if ( empty( $prod_id ) ) {
374 $pdt = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1", $arr->item_id ) );
375 $pdt_id = $pdt ? $pdt->post_id : '';
376 } else {
377 $pdt_id = $prod_id;
378 }
379
380 if ( empty( $pdt_id ) && $allow_to_import && 'active' === $arr->status ) {
381 // Create the product if it does not exist.
382 try {
383 $product = new WC_Product();
384 $product->set_name( $arr->name );
385 $product->set_status( 'publish' );
386 if ( isset( $arr->sku ) && ! empty( $arr->sku ) ) {
387 $product->set_sku( $arr->sku );
388 }
389 $product->set_regular_price( $arr->rate );
390 $pdt_id = $product->save();
391 if ( $pdt_id ) {
392 update_post_meta( $pdt_id, 'zi_item_id', $arr->item_id );
393 }
394 } catch ( Exception $e ) {
395 // Log the error so you can inspect it in WooCommerce > Status > Logs.
396 $message = sprintf(
397 'Error creating product for Zoho Item # %s (SKU: %s): %s',
398 $arr->item_id,
399 isset( $arr->sku ) ? $arr->sku : '(no sku)',
400 $e->getMessage()
401 );
402 $logger->error( $message, $context );
403 continue;
404 }
405 }
406
407 if ( $pdt_id ) {
408 if ( ! empty( $arr->category_name ) ) {
409 $term = get_term_by( 'name', $arr->category_name, 'product_cat' );
410 $term_id = $term->term_id;
411 if ( empty( $term_id ) ) {
412 $term = wp_insert_term(
413 $arr->category_name,
414 'product_cat',
415 array(
416 'parent' => 0,
417 )
418 );
419 $term_id = $term['term_id'];
420 }
421 if ( $term_id ) {
422 $existing_terms = wp_get_object_terms( $pdt_id, 'product_cat' );
423 if ( $existing_terms && count( $existing_terms ) > 0 ) {
424 $is_terms_exist = $this->zi_check_terms_exists( $existing_terms, $term_id );
425 if ( ! $is_terms_exist ) {
426 update_post_meta( $pdt_id, 'zi_category_id', $category );
427 wp_add_object_terms( $pdt_id, $term_id, 'product_cat' );
428 }
429 } else {
430 update_post_meta( $pdt_id, 'zi_category_id', $category );
431 wp_set_object_terms( $pdt_id, $term_id, 'product_cat' );
432 }
433 }
434 // Remove "uncategorized" category if assigned.
435 $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' );
436 if ( $uncategorized_term && has_term( $uncategorized_term->term_id, 'product_cat', $pdt_id ) ) {
437 wp_remove_object_terms( $pdt_id, $uncategorized_term->term_id, 'product_cat' );
438 }
439 }
440
441 if ( ! empty( $arr->brand ) ) {
442 // check if the Brand taxonomy exists and then update the term.
443 if ( taxonomy_exists( 'product_brand' ) ) {
444 wp_set_object_terms( $pdt_id, $arr->brand, 'product_brand' );
445 }
446 }
447 // Sync Featured Image if not disabled.
448 $zi_disable_image_sync = $this->config['Settings']['disable_image'];
449 if ( ! empty( $arr->image_document_id ) && ! $zi_disable_image_sync ) {
450 $image_class = new CMBIRD_Image_ZI();
451 $image_class->cmbird_zi_get_image( $arr->item_id, $arr->name, $pdt_id, $arr->image_name );
452 }
453
454 $item_ids[] = $arr->item_id;
455 } // end of wpdb post_id check.
456 }
457 if ( ! empty( $item_ids ) ) {
458 // Chunk item IDs into smaller batches to prevent memory exhaustion.
459 // Process 20 items at a time instead of all at once.
460 $chunk_size = 20;
461 $item_chunks = array_chunk( $item_ids, $chunk_size );
462 $total_chunks = count( $item_chunks );
463
464 foreach ( $item_chunks as $chunk_index => $chunk ) {
465 $item_id_str = implode( ',', $chunk );
466 // fwrite($fd, PHP_EOL . 'Before Bulk sync - Chunk ' . ($chunk_index + 1) . ' of ' . $total_chunks);
467 $item_details_url = "{$zoho_inventory_url}inventory/v1/itemdetails?item_ids={$item_id_str}&organization_id={$zoho_inventory_oid}";
468 $this->zi_item_bulk_sync( $item_details_url );
469
470 // Memory cleanup after each chunk.
471 wp_cache_flush();
472 if ( function_exists( 'gc_collect_cycles' ) ) {
473 gc_collect_cycles();
474 }
475
476 // Check memory usage and stop if approaching limit.
477 $memory_limit = $this->get_memory_limit();
478 $memory_used = memory_get_usage( true );
479 if ( $memory_limit > 0 && $memory_used > ( $memory_limit * 0.9 ) ) {
480 $logger = wc_get_logger();
481 $context = array( 'source' => 'cmbird_item_sync' );
482 $logger->warning(
483 sprintf(
484 'Memory limit approaching (%.2f%% used). Stopping sync at chunk %d of %d.',
485 ( $memory_used / $memory_limit ) * 100,
486 $chunk_index + 1,
487 $total_chunks
488 ),
489 $context
490 );
491 break;
492 }
493 }
494
495 // Safety check: Prevent infinite loops by limiting max page depth.
496 $max_pages = 1000; // Maximum pages to process (adjust based on your needs).
497 if ( isset( $json->page_context ) && $json->page_context->has_more_page && $page < $max_pages ) {
498 $next_page = $page + 1;
499 $data = array(
500 'page' => $next_page,
501 'category' => $category,
502 );
503 // fwrite( $fd, PHP_EOL . 'Data: ' . print_r( $data, true ) );
504 // Check for existing schedule with same parameters to prevent duplicates.
505 $existing_schedule = as_has_scheduled_action( 'import_simple_items_cron', array( $data ), 'commercebird' );
506 if ( ! $existing_schedule ) {
507 // Schedule with a slight delay to prevent race conditions.
508 as_schedule_single_action( time() + 5, 'import_simple_items_cron', array( $data ), 'commercebird' );
509 // fwrite( $fd, PHP_EOL . 'Scheduled' );
510 }
511 } elseif ( $page >= $max_pages ) {
512 // Log warning if max pages reached.
513 $logger = wc_get_logger();
514 $context = array( 'source' => 'cmbird_item_sync' );
515 $logger->warning( "Max page limit ($max_pages) reached for category $category. Stopping pagination to prevent infinite loop.", $context );
516 }
517 array_push( $response_msg, $this->zi_response_message( $code, $json->message ) );
518 }
519 }
520 // fclose( $fd );
521 return $response_msg;
522 }
523
524 /**
525 * Update or Create Custom Fields of Product
526 *
527 * @param array|object $custom_fields - item object coming in from simple item recursive.
528 * @param int $pdt_id - product id.
529 * @return void
530 */
531 public function sync_item_custom_fields( $custom_fields, $pdt_id ) {
532 if ( empty( $custom_fields ) || empty( $pdt_id ) ) {
533 return;
534 }
535
536 foreach ( $custom_fields as $custom_field ) {
537 // Extract data from custom field.
538 $api_name = isset( $custom_field->api_name ) ? $custom_field->api_name : $custom_field['api_name'];
539 $value = isset( $custom_field->value ) ? $custom_field->value : $custom_field['value'];
540
541 // Check if both API name and value are present.
542 if ( ! empty( $api_name ) && ! empty( $value ) ) {
543 // Check if ACF function exists.
544 if ( function_exists( 'update_field' ) ) {
545 // Update ACF field.
546 update_field( $api_name, $value, $pdt_id );
547 } else {
548 // Fall back to update post meta.
549 update_post_meta( $pdt_id, $api_name, $value );
550 }
551 }
552 }
553 }
554
555
556 /**
557 * Function to add group items recursively by manual sync
558 *
559 * @param [number] $page - Page number for getting group item with pagination.
560 * @param [number] $category - Category id to get group item of specific category.
561 * @param [string] $source - Source from where function is calling : 'cron'/'sync'.
562 * @return mixed
563 */
564 public function sync_groupitem_recursively() {
565 // $fd = fopen( __DIR__ . '/sync_groupitem_recursively.txt', 'a+' );
566
567 $args = func_get_args();
568 if ( ! empty( $args ) ) {
569 if ( is_array( $args ) ) {
570 if ( isset( $args['page'] ) && isset( $args['category'] ) ) {
571 $page = $args['page'];
572 $category = $args['category'];
573 } elseif ( isset( $args[0] ) && isset( $args[1] ) ) {
574 $page = $args[0];
575 $category = $args[1];
576 } elseif ( isset( $args[0] ) && ! isset( $args[1] ) ) {
577 $page = $args[0]['page'];
578 $category = $args[0]['category'];
579 } else {
580 return;
581 }
582 } else {
583 return;
584 }
585
586 // Memory management: Set memory limit if needed.
587 if ( function_exists( 'wp_raise_memory_limit' ) ) {
588 wp_raise_memory_limit( 'admin' );
589 }
590
591 // fwrite($fd, PHP_EOL . 'Test name Update ' . print_r($data, true));
592 global $wpdb;
593 $zoho_inventory_oid = $this->config['ProductZI']['OID'];
594 $zoho_inventory_url = $this->config['ProductZI']['APIURL'];
595
596 $url = $zoho_inventory_url . 'inventory/v1/itemgroups/?organization_id=' . $zoho_inventory_oid . '&category_id=' . $category . '&page=' . $page . '&per_page=20&filter_by=Status.Active';
597 // fwrite($fd, PHP_EOL . '$url : ' . $url);
598
599 $execute_curl_call = new CMBIRD_API_Handler_Zoho();
600 $json = $execute_curl_call->execute_curl_call_get( $url );
601
602 $code = $json->code;
603 // $message = $json->message;
604
605 $response_msg = array();
606
607 if ( '0' === $code || 0 === $code ) {
608 $zi_disable_description_sync = $this->config['Settings']['disable_description'];
609 $zi_disable_name_sync = $this->config['Settings']['disable_name'];
610 // Find the term_id by searching for the option where value matches the category.
611 global $wpdb;
612 $term_id = null;
613 if ( ! empty( $category ) ) {
614 // Query directly for the matching option.
615 $option_name = $wpdb->get_var( $wpdb->prepare( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE %s AND option_value = %s LIMIT 1", 'cmbird_zoho_id_for_term_id_%', $category ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Need to find category mapping.
616 if ( ! empty( $option_name ) ) {
617 // Extract term_id from option_name (e.g., 'cmbird_zoho_id_for_term_id_37' -> '37').
618 $term_id = str_replace( 'cmbird_zoho_id_for_term_id_', '', $option_name );
619 }
620 // fwrite( $fd, PHP_EOL . 'Category: ' . $category . ' -> term_id: ' . $term_id );
621 }
622 // fwrite( $fd, PHP_EOL . '$json->itemgroups : ' . print_r( $json->itemgroups, true ) );
623 $processed_count = 0;
624 foreach ( $json->itemgroups as $gp_arr ) {
625 // Memory cleanup every 10 group items.
626 if ( $processed_count > 0 && $processed_count % 10 === 0 ) {
627 wp_cache_flush();
628 if ( function_exists( 'gc_collect_cycles' ) ) {
629 gc_collect_cycles();
630 }
631 }
632 $zi_group_id = $gp_arr->group_id;
633 $zi_group_name = $gp_arr->group_name;
634 // fwrite($fd, PHP_EOL . '$itemGroup : ' . print_r($gp_arr, true));
635 // skip if there is no first attribute.
636 $zi_group_attribute1 = $gp_arr->attribute_id1;
637 if ( empty( $zi_group_attribute1 ) ) {
638 continue;
639 }
640
641 // Get Group ID.
642 $group_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1", $zi_group_id ) );
643 if ( empty( $group_id ) ) {
644 // search by zi_group_name if not found by zi_item_id.
645 $group_id = $wpdb->get_var( $wpdb->prepare( "SELECT p.ID FROM $wpdb->posts p WHERE p.post_title = %s AND p.post_type = 'product' AND p.post_status IN ('publish', 'draft', 'private') LIMIT 1", $zi_group_name ) );
646 }
647 // array_push( $response_msg, $this->zi_response_message( 'SUCCESS', 'Zoho Group Item Synced: ' . $zi_group_name, $group_id ) );
648 // end insert group product.
649 // variable items.
650 // fwrite($fd, PHP_EOL . '$group_id exists ' . $group_id);
651 if ( ! empty( $group_id ) ) {
652 $existing_parent_product = wc_get_product( $group_id );
653 // fwrite($fd, PHP_EOL . 'Existing group Id');
654 if ( ! empty( $gp_arr->description ) && ! $zi_disable_description_sync ) {
655 $existing_parent_product->set_short_description( $gp_arr->description );
656 }
657 if ( ! empty( $gp_arr->name ) && ! $zi_disable_name_sync ) {
658 $existing_parent_product->set_name( $gp_arr->name );
659 // santize the name for slug and save the slug.
660 $slug = sanitize_title( $gp_arr->name );
661 $existing_parent_product->set_slug( $slug );
662 }
663 // add zi_category_id as meta.
664 $existing_parent_product->update_meta_data( 'zi_category_id', $gp_arr->category_id );
665 if ( ! empty( $term_id ) ) {
666 wp_set_object_terms( $group_id, (int) $term_id, 'product_cat' );
667 // remove the uncategorized term from the product.
668 wp_remove_object_terms( $group_id, 'uncategorized', 'product_cat' );
669 }
670 // create attributes if not exists.
671 $attributes = $existing_parent_product->get_attributes();
672 if ( empty( $attributes ) ) {
673 // Create or Update the Attributes.
674 $attr_created = $this->sync_attributes_of_group( $gp_arr, $group_id );
675 // fwrite($fd, PHP_EOL . '$attr_created ' . $attr_created);
676 }
677 $variations_check = $existing_parent_product->get_children();
678 if ( empty( $variations_check ) ) {
679 // fwrite( $fd, PHP_EOL . 'No Variations found' );
680 $this->import_variable_product_variations( $gp_arr, $group_id );
681 } else {
682 // Sort the variations.
683 $data_store = $existing_parent_product->get_data_store();
684 $data_store->sort_all_product_variations( $group_id );
685 }
686 $existing_parent_product->save();
687 // ACF Fields.
688 if ( ! empty( $gp_arr->custom_fields ) ) {
689 // fwrite($fd, PHP_EOL . 'Custom Fields : ' . print_r($gp_arr->custom_fields, true));
690 $this->sync_item_custom_fields( $gp_arr->custom_fields, $group_id );
691 }
692 // update Brand.
693 if ( ! empty( $gp_arr->brand ) ) {
694 // check if the Brand or Brands taxonomy exists and then update the term.
695 if ( taxonomy_exists( 'product_brand' ) ) {
696 wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
697 } elseif ( taxonomy_exists( 'product_brand' ) ) {
698 wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
699 }
700 }
701 } else {
702 // Create the parent variable product.
703 $parent_product = new WC_Product_Variable();
704 $parent_product->set_name( $zi_group_name );
705 $parent_product->set_status( 'publish' );
706 $parent_product->set_short_description( $gp_arr->description );
707 $parent_product->add_meta_data( 'zi_item_id', $zi_group_id );
708 $parent_product->add_meta_data( 'zi_category_id', $category );
709 $group_id = $parent_product->save();
710 // Sync category by finding it first.
711 if ( ! empty( $term_id ) && term_exists( $term_id, 'product_cat' ) ) {
712 wp_set_object_terms( $group_id, (int) $term_id, 'product_cat' );
713 }
714 // Add category.
715 if ( ! empty( $term_id ) ) {
716 wp_set_object_terms( $group_id, (int) $term_id, 'product_cat' );
717 }
718 // update Brand.
719 if ( ! empty( $gp_arr->brand ) ) {
720 // check if the Brand or Brands taxonomy exists and then update the term.
721 if ( taxonomy_exists( 'product_brand' ) ) {
722 wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
723 } elseif ( taxonomy_exists( 'product_brand' ) ) {
724 wp_set_object_terms( $group_id, $gp_arr->brand, 'product_brand' );
725 }
726 }
727 // fwrite($fd, PHP_EOL . 'New $group_id ' . $group_id);
728 // ACF Fields.
729 if ( ! empty( $gp_arr->custom_fields ) ) {
730 // fwrite($fd, PHP_EOL . 'Custom Fields : ' . print_r($gp_arr->custom_fields, true));
731 $this->sync_item_custom_fields( $gp_arr->custom_fields, $group_id );
732 }
733 // Create or Update the Attributes.
734 $attr_created = $this->sync_attributes_of_group( $gp_arr, $group_id );
735
736 if ( ! empty( $group_id ) && $attr_created ) {
737 $this->import_variable_product_variations( $gp_arr, $group_id );
738 }
739 } // end of create variable product.
740
741 ++$processed_count;
742 } // end foreach group items.
743
744 // Final memory cleanup after processing all group items.
745 wp_cache_flush();
746 if ( function_exists( 'gc_collect_cycles' ) ) {
747 gc_collect_cycles();
748 }
749
750 // Clear any temporary variables.
751 unset( $json, $execute_curl_call );
752
753 if ( isset( $json ) && isset( $json->page_context ) && $json->page_context->has_more_page ) {
754 $data = array(
755 'page' => $page + 1,
756 'category' => $category,
757 );
758 $existing_schedule = as_has_scheduled_action( 'import_group_items_cron', $data );
759 // Check if the scheduled action exists.
760 if ( ! $existing_schedule ) {
761 as_schedule_single_action( time(), 'import_group_items_cron', $data );
762 }
763 }
764 array_push( $response_msg, $this->zi_response_message( $code, isset( $json->message ) ? $json->message : '' ) );
765 }
766 // End of logging.
767 // fclose( $fd );
768 return $response_msg;
769 } else {
770 return;
771 }
772 }
773
774 /**
775 * Callback function for importing a variable product and its variations.
776 *
777 * @param object $gp_arr - Group item object.
778 * @param int $group_id - Group item id.
779 * @return void
780 */
781 public function import_variable_product_variations( $gp_arr, $group_id ): void {
782 // $fd = fopen( __DIR__ . '/import_variable_product_variations.txt', 'a+' );
783
784 if ( empty( $gp_arr ) || empty( $group_id ) ) {
785 return;
786 }
787
788 global $wpdb;
789 $product = wc_get_product( $group_id );
790
791 // separators.
792 $wc_decimal_separator = $this->wc_decimal_separator;
793 $wc_thousand_separator = $this->wc_thousand_separator;
794 $wc_price_decimal_separator = $this->wc_price_decimal_separator;
795
796 if ( $product ) {
797 $item_group = $gp_arr;
798 $items = $item_group->items;
799 $attribute_name1 = $item_group->attribute_name1;
800 $attribute_name2 = $item_group->attribute_name2;
801 $attribute_name3 = $item_group->attribute_name3;
802
803 // fwrite( $fd, PHP_EOL . 'Items : ' . print_r( $items, true ) );
804 // get the options for stock sync.
805 $zi_enable_locationstock = $this->config['Settings']['enable_location_stock'];
806 $location_id = $this->config['Settings']['zoho_location_id'];
807 $accounting_stock = $this->config['Settings']['enable_accounting_stock'];
808 $zi_disable_stock_sync = $this->config['Settings']['disable_stock'];
809 $locations = array();
810
811 foreach ( $items as $item ) {
812 // reset this array.
813 $attribute_arr = array();
814 $variation_id = '';
815 $status = $item->status === 'active' ? 'publish' : 'draft';
816
817 $zi_item_id = $item->item_id;
818 $variation_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1", $zi_item_id ) );
819
820 if ( ! empty( $variation_id ) ) {
821 $v_product = wc_get_product( $variation_id );
822 // Check if the product object is valid.
823 if ( $v_product && is_a( $v_product, 'WC_Product' ) ) {
824 if ( $v_product->is_type( 'simple' ) ) {
825 wp_delete_post( $variation_id, true );
826 }
827 }
828 }
829 // SKU check of the variation, if exits then remove it.
830 if ( ! empty( $item->sku ) ) {
831 $sku_prod_id = wc_get_product_id_by_sku( $item->sku );
832 $v_product = wc_get_product( $sku_prod_id );
833 // Check if the product object is valid.
834 if ( $v_product && is_a( $v_product, 'WC_Product' ) ) {
835 if ( $v_product->is_type( 'simple' ) ) {
836 wp_delete_post( $variation_id, true );
837 }
838 }
839 }
840 if ( ! empty( $variation_id ) ) {
841 continue;
842 }
843 // Stock mode check.
844 $locations = $item->locations;
845
846 if ( $zi_enable_locationstock && $location_id ) {
847 foreach ( $locations as $location ) {
848 if ( $location->location_id === $location_id ) {
849 if ( $accounting_stock ) {
850 $stock = isset( $location->location_available_stock );
851 } else {
852 $stock = isset( $location->location_actual_available_stock );
853 }
854 }
855 }
856 } elseif ( $accounting_stock ) {
857 $stock = $item->available_stock;
858 } else {
859 $stock = $item->actual_available_stock;
860 }
861 // number format the rate.
862 $rate = number_format( $item->rate, $wc_price_decimal_separator, $wc_decimal_separator, $wc_thousand_separator );
863
864 $attribute_name11 = $item->attribute_option_name1;
865 $attribute_name12 = $item->attribute_option_name2;
866 $attribute_name13 = $item->attribute_option_name3;
867 // fwrite( $fd, PHP_EOL . '>>> Item: ' . $item->item_id . ' | SKU: ' . $item->sku . ' | opt1: ' . $attribute_name11 . ' | opt2: ' . $attribute_name12 . ' | opt3: ' . $attribute_name13 );
868 // Prepare the variation data.
869 if ( ! empty( $attribute_name1 ) ) {
870 $sanitized_name1 = wc_sanitize_taxonomy_name( $attribute_name1 );
871 $attribute_arr[ $sanitized_name1 ] = $attribute_name11;
872 }
873 if ( ! empty( $attribute_name2 ) ) {
874 $sanitized_name2 = wc_sanitize_taxonomy_name( $attribute_name2 );
875 $attribute_arr[ $sanitized_name2 ] = $attribute_name12;
876 }
877 if ( ! empty( $attribute_name3 ) ) {
878 $sanitized_name3 = wc_sanitize_taxonomy_name( $attribute_name3 );
879 $attribute_arr[ $sanitized_name3 ] = $attribute_name13;
880 }
881 // fwrite( $fd, PHP_EOL . '$attribute_arr : ' . print_r( $attribute_arr, true ) );
882 // Loop through the variations and create them.
883 try {
884 $variation_post = array(
885 'post_title' => $product->get_name(),
886 'post_name' => 'product-' . $group_id . '-variation',
887 'post_status' => $status,
888 'post_parent' => $group_id,
889 'post_type' => 'product_variation',
890 'guid' => $product->get_permalink(),
891 );
892 // Creating the product variation.
893 $variation_id = wp_insert_post( $variation_post );
894 $variation = new WC_Product_Variation( $variation_id );
895 $variation->set_regular_price( $rate );
896 $variation->set_sku( $item->sku );
897 if ( ! $zi_disable_stock_sync && $stock > 0 ) {
898 $variation->set_stock_quantity( $stock );
899 $variation->set_manage_stock( true );
900 $variation->set_stock_status( '' );
901 } else {
902 $variation->set_manage_stock( false );
903 }
904 $variation->add_meta_data( 'zi_item_id', $item->item_id );
905 } catch ( Exception $e ) {
906 // fwrite( $fd, PHP_EOL . 'Error : ' . $e->getMessage() );
907 continue;
908 }
909
910 // Build variation attributes array for WooCommerce API.
911 $variation_attributes = array();
912
913 // Get the variation attributes with correct attribute values.
914 foreach ( $attribute_arr as $attribute => $term_name ) {
915 $taxonomy = 'pa_' . $attribute;
916 // If taxonomy doesn't exists we create it.
917 if ( ! taxonomy_exists( $taxonomy ) ) {
918 register_taxonomy(
919 $taxonomy,
920 'product_variation',
921 array(
922 'hierarchical' => false,
923 'label' => ucfirst( $attribute ),
924 'query_var' => true,
925 'rewrite' => array( 'slug' => sanitize_title( $attribute ) ),
926 ),
927 );
928 }
929
930 // fwrite( $fd, PHP_EOL . '=== Processing Attribute: ' . $attribute . ' | Term Name: ' . $term_name . ' | Taxonomy: ' . $taxonomy );
931 // fwrite( $fd, PHP_EOL . 'Term Name: ' . $term_name . ' for Taxonomy: ' . $taxonomy );
932 // Check if the Term name exist and if not we create it.
933 $term = get_term_by( 'name', $term_name, $taxonomy );
934 if ( ! $term ) {
935 // Try by slug in case the term exists but with different capitalization.
936 $term = get_term_by( 'slug', sanitize_title( $term_name ), $taxonomy );
937 if ( ! $term ) {
938 $term_name_formatted = str_replace( array( '-', '_' ), ' ', $term_name );
939 $term_name_formatted = ucwords( $term_name_formatted );
940 $term = wp_insert_term(
941 $term_name_formatted,
942 $taxonomy,
943 array(
944 'slug' => sanitize_title( $term_name ),
945 )
946 );
947 if ( is_wp_error( $term ) ) {
948 // fwrite( $fd, PHP_EOL . 'Error creating term: ' . $term->get_error_message() );
949 continue;
950 }
951 }
952 }
953 $term_slug = $term ? $term->slug : sanitize_title( $term_name );
954 $term_id = $term ? ( is_object( $term ) ? $term->term_id : ( is_array( $term ) ? $term['term_id'] : 0 ) ) : 0;
955 // fwrite( $fd, PHP_EOL . 'Final term_slug: ' . $term_slug . ' | term_id: ' . $term_id );
956 // Get the post Terms from the parent variable product.
957 $post_term_ids = wp_get_post_terms( $group_id, $taxonomy, array( 'fields' => 'ids' ) );
958 // Check if the post term exist and if not we set it in the parent variable product.
959 if ( $term_id && ! in_array( $term_id, $post_term_ids, true ) ) {
960 wp_set_post_terms( $group_id, $term_id, $taxonomy, true );
961 }
962 // Add to variation attributes array using WooCommerce API format.
963 $variation_attributes[ $taxonomy ] = $term_slug;
964 }
965
966 // Set all attributes at once using WooCommerce API.
967 if ( ! empty( $variation_attributes ) ) {
968 $variation->set_attributes( $variation_attributes );
969 $variation_id = $variation->save();
970 // fwrite( $fd, PHP_EOL . '[VARIATION] Set attributes for variation ' . $variation_id . ': ' . print_r( $variation_attributes, true ) );
971 }
972
973 // update purchase price as meta data.
974 if ( ! empty( $item->purchase_rate ) ) {
975 update_post_meta( $variation_id, '_cost_price', $item->purchase_rate );
976 }
977
978 // Featured Image of variation.
979 if ( ! empty( $item->image_name ) ) {
980 $image_class = new CMBIRD_Image_ZI();
981 $variation_image_id = $image_class->cmbird_zi_get_image( $item->item_id, $item->name, $variation_id, $item->image_name );
982 if ( ! has_post_thumbnail( $group_id ) ) {
983 if ( $variation_image_id ) {
984 set_post_thumbnail( $group_id, $variation_image_id );
985 }
986 }
987 }
988 }
989 // End group item add process.
990 // array_push($response_msg, $this->zi_response_message('SUCCESS', 'Zoho variable item created for zoho item id ' . $zi_item_id, $variation_id));
991
992 // After all variations are created/updated, save the parent product to trigger WooCommerce sync.
993 if ( $product && is_a( $product, 'WC_Product_Variable' ) ) {
994 // Delete the transient cache for this product.
995 delete_transient( 'wc_product_children_' . $group_id );
996 // Force WooCommerce to regenerate the available variations.
997 WC_Product_Variable::sync( $group_id );
998 // Save the parent product to ensure all changes are persisted.
999 $product->save();
1000 }
1001 // End of Logging.
1002 // fclose( $fd );
1003 }
1004 }
1005
1006 /**
1007 * Update or Create the Product Attributes for the Variable Item Sync
1008 *
1009 * @param object $gp_arr - the group item object from Zoho Inventory.
1010 * @param int $group_id - the parent product id in WooCommerce.
1011 * @return bool - true if attributes were created successfully, false otherwise
1012 */
1013 public function sync_attributes_of_group( $gp_arr, $group_id ) {
1014 // $fd = fopen( __DIR__ . '/sync_attributes_of_group.txt', 'a+' );
1015 // Check if the group item has attributes.
1016 if ( empty( $gp_arr->attribute_name1 ) ) {
1017 return false;
1018 }
1019 // Create attributes.
1020 $success = true; // Track the success of attribute creation.
1021 $attributes_data = array();
1022 $attribute_count = 0;
1023
1024 // Loop through the attribute names.
1025 for ( $i = 1; $i <= 3; $i++ ) {
1026 $attribute_name_key = 'attribute_name' . $i;
1027 $attribute_option_name_key = 'attribute_option_name' . $i;
1028
1029 // Get the attribute name.
1030 $attribute_name = $gp_arr->$attribute_name_key;
1031
1032 if ( ! empty( $attribute_name ) ) {
1033 // Check if the attribute is already added to the attributes array.
1034 if ( ! isset( $attributes_data[ $attribute_name ] ) ) {
1035 // Create the attribute and add it to the attributes array.
1036 $attribute = array(
1037 'name' => $attribute_name,
1038 'position' => $attribute_count,
1039 'visible' => true,
1040 'variation' => true,
1041 'options' => array(),
1042 );
1043
1044 // Loop through the items and retrieve attribute options.
1045 $attribute_options = array();
1046 foreach ( $gp_arr->items as $item ) {
1047 $attribute_option = $item->$attribute_option_name_key;
1048 if ( ! empty( $attribute_option ) && ! in_array( $attribute_option, $attribute_options, true ) ) {
1049 $attribute_options[] = $attribute_option;
1050 }
1051 }
1052
1053 // Set the attribute options.
1054 $attribute['options'] = $attribute_options;
1055
1056 $attributes_data[] = $attribute;
1057 ++$attribute_count;
1058 }
1059 }
1060 }
1061 // fwrite( $fd, PHP_EOL . '$attributes : ' . print_r( $attributes_data, true ) );
1062
1063 // Assign the attributes to the parent product.
1064 if ( count( $attributes_data ) > 0 ) {
1065
1066 // Loop through defined attribute data.
1067 foreach ( $attributes_data as $key => $attribute_array ) {
1068 if ( isset( $attribute_array['name'] ) && isset( $attribute_array['options'] ) ) {
1069 // Clean attribute name to get the taxonomy.
1070 $taxonomy = 'pa_' . wc_sanitize_taxonomy_name( $attribute_array['name'] );
1071 $option_term_ids = array();
1072 $existing_terms = array();
1073 // Create the attribute if it doesn't exist.
1074 if ( ! taxonomy_exists( $taxonomy ) ) {
1075 // Clean attribute label for better display.
1076 $attribute_label = ucfirst( $attribute_array['name'] );
1077
1078 // Register the new attribute taxonomy.
1079 $attribute_args = array(
1080 'slug' => $taxonomy,
1081 'name' => $attribute_label,
1082 'type' => 'select',
1083 'order_by' => 'menu_order',
1084 'has_archives' => false,
1085 );
1086
1087 $result = wc_create_attribute( $attribute_args );
1088 register_taxonomy( $taxonomy, array( 'product' ), array() );
1089
1090 if ( ! is_wp_error( $result ) ) {
1091 // fwrite($fd, PHP_EOL . 'result : ' . $result);
1092 // Loop through defined attribute data options (terms values).
1093 foreach ( $attribute_array['options'] as $option ) {
1094 // Check if the term exists for the attribute taxonomy.
1095 $term = term_exists( $option, $taxonomy );
1096 // Also check by slug in case option is slug-formatted.
1097 $term_by_slug = get_term_by( 'slug', sanitize_title( $option ), $taxonomy );
1098 if ( $term_by_slug ) {
1099 $term = array( 'term_id' => $term_by_slug->term_id );
1100 }
1101 if ( ! $term ) {
1102 // Term doesn't exist, create a new one.
1103 // Convert slug-formatted names to proper names.
1104 $term_name = str_replace( array( '-', '_' ), ' ', $option );
1105 $term_name = ucwords( $term_name );
1106 $term = wp_insert_term(
1107 $term_name,
1108 $taxonomy,
1109 array(
1110 'slug' => sanitize_title( $term_name ),
1111 )
1112 );
1113 if ( is_wp_error( $term ) ) {
1114 // fwrite( $fd, PHP_EOL . 'Error creating term: ' . $term->get_error_message() );
1115 continue;
1116 }
1117 }
1118 // Add term ID to the array for assignment to parent product.
1119 $term_id = is_array( $term ) ? $term['term_id'] : $term;
1120 $option_term_ids[] = $term_id;
1121 }
1122 } else {
1123 $success = false;
1124 // return error message.
1125 $error_string = $result->get_error_message();
1126 return $success;
1127 }
1128 } else {
1129 // Taxonomy exists, get existing terms.
1130 $existing_terms = get_terms(
1131 array(
1132 'taxonomy' => $taxonomy,
1133 'hide_empty' => false,
1134 )
1135 );
1136 }
1137
1138 if ( $existing_terms ) {
1139 foreach ( $attribute_array['options'] as $option ) {
1140 $match_found = false;
1141 foreach ( $existing_terms as $existing_term ) {
1142 // Check both name and slug to handle cases where Zoho sends slug-formatted names.
1143 if ( $existing_term->name === $option || $existing_term->slug === sanitize_title( $option ) ) {
1144 $option_term_ids[] = $existing_term->term_id;
1145 $match_found = true;
1146 break;
1147 }
1148 }
1149 // fwrite( $fd, PHP_EOL . 'Option "' . $option . '" match found: ' . ( $match_found ? 'Yes' : 'No' ) );
1150 if ( ! $match_found ) {
1151 // Check if the term exists for the attribute taxonomy.
1152 $term = get_term_by( 'slug', sanitize_title( $option ), $taxonomy );
1153 // fwrite( $fd, PHP_EOL . 'Term by slug check for option "' . $option . '": ' . print_r( $term, true ) );
1154 if ( $term ) {
1155 $term = array( 'term_id' => $term->term_id );
1156 }
1157 if ( ! $term ) {
1158 // Term doesn't exist, create it.
1159 // Convert slug-formatted names to proper names.
1160 $term_name = str_replace( array( '-', '_' ), ' ', $option );
1161 $term_name = ucwords( $term_name );
1162 $term = wp_insert_term( $term_name, $taxonomy );
1163 if ( ! is_wp_error( $term ) ) {
1164 // Get the term ID.
1165 $term_id = is_array( $term ) ? $term['term_id'] : $term;
1166 $option_term_ids[] = $term_id;
1167 } else {
1168 $success = false;
1169 }
1170 } else {
1171 // Get the existing term ID.
1172 $term_id = $term['term_id'];
1173 $option_term_ids[] = $term_id;
1174 }
1175 }
1176 }
1177 }
1178 // Set the selected terms for the product.
1179 // fwrite( $fd, PHP_EOL . '[SYNC_ATTR] Setting terms for taxonomy: ' . $taxonomy . ' | Term IDs: ' . print_r( $option_term_ids, true ) );
1180 wp_set_object_terms( $group_id, $option_term_ids, $taxonomy, false );
1181 }
1182 // Build the attribute array for product metadata.
1183 $attributes_data[ $key ]['name'] = $taxonomy;
1184 $attributes_data[ $key ]['value'] = '';
1185 $attributes_data[ $key ]['is_visible'] = $attribute_array['visible'] ?? true;
1186 $attributes_data[ $key ]['is_variation'] = $attribute_array['variation'] ?? true;
1187 $attributes_data[ $key ]['is_taxonomy'] = 1;
1188 }
1189 }
1190
1191 // Save the attributes to the parent product metadata.
1192 if ( ! empty( $attributes_data ) ) {
1193 update_post_meta( $group_id, '_product_attributes', $attributes_data );
1194 }
1195 // fclose($fd);
1196 return $success;
1197 }
1198
1199 /**
1200 * Update or create variation in WooCommerce if Group-ID already exists in wpdB
1201 *
1202 * @param object $item - item object coming in from simple item recursive function.
1203 * @return: void
1204 */
1205 public function sync_variation_of_group( $item ) {
1206 // $fd = fopen( __DIR__ . '/sync_variation_of_group.txt', 'a+' );
1207
1208 // log the item.
1209 // fwrite( $fd, PHP_EOL . 'Item : ' . print_r( $item, true ) );
1210
1211 global $wpdb;
1212 // Stock mode check.
1213 $zi_disable_stock_sync = $this->config['Settings']['disable_stock'];
1214 $accounting_stock = $this->config['Settings']['enable_accounting_stock'];
1215 $is_tax_enabled = $this->is_tax_enabled;
1216
1217 if ( $accounting_stock ) {
1218 $stock = $item->available_stock;
1219 } else {
1220 $stock = $item->actual_available_stock;
1221 }
1222
1223 $item_id = $item->item_id;
1224 // $item_category = $item->category_name;
1225 $groupid = property_exists( $item, 'group_id' ) ? $item->group_id : 0;
1226 // find parent variable product.
1227 $group_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1", $groupid ) );
1228 // fwrite($fd, PHP_EOL . 'Row Data : ' . print_r($row, true));
1229 // fwrite($fd, PHP_EOL . 'Row $group_id : ' . $group_id);
1230 $stock_quantity = $stock < 0 ? 0 : $stock;
1231 // fwrite($fd, PHP_EOL . 'Before group item sync : ' . $group_id);
1232 if ( ! empty( $group_id ) ) {
1233 // fwrite( $fd, PHP_EOL . 'Inside item sync : ' . $item->name );
1234 // Brand.
1235 if ( isset( $item->brand ) && ! empty( $group_id ) ) {
1236 if ( taxonomy_exists( 'product_brand' ) ) {
1237 wp_set_object_terms( $group_id, $item->brand, 'product_brand' );
1238 }
1239 }
1240
1241 $variation_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s LIMIT 1", $item_id ) );
1242 if ( ! empty( $item->sku ) && empty( $variation_id ) ) {
1243 $variation_id = wc_get_product_id_by_sku( $item->sku );
1244 }
1245 if ( $variation_id ) {
1246 // fwrite( $fd, PHP_EOL . 'Variation ID exists : ' . $variation_id );
1247 // if product type is not variation then return.
1248 $v_product = wc_get_product( $variation_id );
1249 // Check if the product object is valid.
1250 if ( $v_product && is_a( $v_product, 'WC_Product' ) ) {
1251 if ( $v_product->is_type( 'simple' ) ) {
1252 wp_delete_post( $variation_id, true );
1253 $variation_id = '';
1254 // Continue to create new variation below.
1255 }
1256 }
1257 // If variation already exists then update it.
1258 $variation = new WC_Product_Variation( $variation_id );
1259 // SKU - Imported.
1260 if ( ! empty( $item->sku ) ) {
1261 $variation->set_sku( $item->sku );
1262 }
1263 // update purchase price as meta data.
1264 if ( ! empty( $item->purchase_rate ) ) {
1265 update_post_meta( $variation_id, '_cost_price', $item->purchase_rate );
1266 }
1267 // Price - Imported.
1268 $zi_disable_price_sync = $this->config['Settings']['disable_price'];
1269 $variation_sale_price = $variation->get_sale_price();
1270 if ( empty( $variation_sale_price ) && ! $zi_disable_price_sync ) {
1271 $variation->set_sale_price( $item->rate );
1272 }
1273 $variation->set_regular_price( $item->rate );
1274 // Set Tax Class.
1275 if ( $item->tax_id && $is_tax_enabled ) {
1276 $zi_common_class = new CMBIRD_Common_Functions();
1277 $woo_tax_class = $zi_common_class->get_tax_class_by_percentage( $item->tax_percentage );
1278 $variation->set_tax_status( 'taxable' );
1279 $variation->set_tax_class( $woo_tax_class );
1280 }
1281 // Stock Imported code.
1282 if ( ! $zi_disable_stock_sync && is_numeric( $stock_quantity ) ) {
1283 // fwrite( $fd, PHP_EOL . 'Stock Quantity : ' . $stock_quantity );
1284 $variation->set_manage_stock( true );
1285 if ( $stock_quantity > 0 ) {
1286 $variation->set_manage_stock( true );
1287 $variation->set_stock_quantity( $stock_quantity );
1288 $variation->set_stock_status( 'instock' );
1289 } elseif ( $stock_quantity <= 0 ) {
1290 $variation->set_manage_stock( true );
1291 $variation->set_stock_quantity( $stock_quantity );
1292 $stock_status = $variation->backorders_allowed() ? 'onbackorder' : 'outofstock';
1293 $variation->set_stock_status( $stock_status );
1294 }
1295 }
1296 // Featured Image of variation.
1297 if ( ! empty( $item->image_document_id ) ) {
1298 $image_class = new CMBIRD_Image_ZI();
1299 $variation_image_id = $image_class->cmbird_zi_get_image( $item->item_id, $item->name, $variation_id, $item->image_name );
1300 if ( ! has_post_thumbnail( $group_id ) ) {
1301 if ( $variation_image_id ) {
1302 set_post_thumbnail( $group_id, $variation_image_id );
1303 }
1304 }
1305 }
1306 // enable or disable based on status from Zoho.
1307 $status = ( 'active' === $item->status ) ? 'publish' : 'draft';
1308 $variation->set_status( $status );
1309 $variation->save();
1310 // clear cache.
1311 wc_delete_product_transients( $variation_id );
1312 } else {
1313 // create new variation.
1314 // if status is not active then return.
1315 if ( 'active' !== $item->status ) {
1316 return;
1317 }
1318
1319 $attribute_name1 = $item->attribute_option_name1;
1320 $attribute_name2 = $item->attribute_option_name2;
1321 $attribute_name3 = $item->attribute_option_name3;
1322 // Prepare the variation data.
1323 $attribute_arr = array();
1324 if ( ! empty( $attribute_name1 ) ) {
1325 $sanitized_name1 = wc_sanitize_taxonomy_name( $item->attribute_name1 );
1326 $attribute_arr[ $sanitized_name1 ] = $attribute_name1;
1327 }
1328 if ( ! empty( $attribute_name2 ) ) {
1329 $sanitized_name2 = wc_sanitize_taxonomy_name( $item->attribute_name2 );
1330 $attribute_arr[ $sanitized_name2 ] = $attribute_name2;
1331 }
1332 if ( ! empty( $attribute_name3 ) ) {
1333 $sanitized_name3 = wc_sanitize_taxonomy_name( $item->attribute_name3 );
1334 $attribute_arr[ $sanitized_name3 ] = $attribute_name3;
1335 }
1336 // fwrite( $fd, PHP_EOL . 'Attributes_arr: ' . print_r( $attribute_arr, true ) );
1337
1338 // here actually create new variation because sku not found.
1339 $variation = new WC_Product_Variation();
1340 $variation->set_parent_id( $group_id );
1341 $variation->set_status( 'publish' );
1342 $variation->set_regular_price( $item->rate );
1343 $variation->set_sku( $item->sku );
1344 if ( ! $zi_disable_stock_sync ) {
1345 $variation->set_stock_quantity( $stock_quantity );
1346 $variation->set_manage_stock( true );
1347 $variation->set_stock_status( '' );
1348 } else {
1349 $variation->set_manage_stock( false );
1350 }
1351 $variation->add_meta_data( 'zi_item_id', $item->item_id );
1352 $variation_id = $variation->save();
1353
1354 // Get the variation attributes with correct attribute values.
1355 foreach ( $attribute_arr as $attribute => $term_name ) {
1356 $taxonomy = $attribute;
1357 // If taxonomy doesn't exists we create it.
1358 if ( ! taxonomy_exists( $taxonomy ) ) {
1359 register_taxonomy(
1360 $taxonomy,
1361 'product_variation',
1362 array(
1363 'hierarchical' => false,
1364 'label' => ucfirst( $attribute ),
1365 'query_var' => true,
1366 'rewrite' => array( 'slug' => sanitize_title( $attribute ) ),
1367 ),
1368 );
1369 }
1370
1371 // Check if the Term name exist and if not we create it.
1372 $term = get_term_by( 'name', $term_name, $taxonomy );
1373 if ( empty( $term ) ) {
1374 // Try by slug in case the term exists but with different capitalization.
1375 $term = get_term_by( 'slug', sanitize_title( $term_name ), $taxonomy );
1376 }
1377 if ( empty( $term ) ) {
1378 // Term doesn't exist, create it
1379 wp_insert_term( $term_name, $taxonomy );
1380 $term = get_term_by( 'name', $term_name, $taxonomy );
1381 }
1382 $term_slug = $term ? $term->slug : sanitize_title( $term_name );
1383 $term_id = $term ? $term->term_id : 0;
1384 // Get the post Terms from the parent variable product.
1385 $post_term_ids = wp_get_post_terms( $group_id, $taxonomy, array( 'fields' => 'ids' ) );
1386 // Check if the post term exist and if not we set it in the parent variable product.
1387 if ( $term_id && ! in_array( $term_id, $post_term_ids, true ) ) {
1388 wp_set_post_terms( $group_id, $term_id, $taxonomy, true );
1389 }
1390 // Set/save the attribute data in the product variation.
1391 update_post_meta( $variation_id, 'attribute_' . $taxonomy, $term_slug );
1392 }
1393
1394 // update purchase price as meta data.
1395 if ( ! empty( $item->purchase_rate ) ) {
1396 update_post_meta( $variation_id, '_cost_price', $item->purchase_rate );
1397 }
1398 // Stock.
1399 if ( ! empty( $stock ) && ! $zi_disable_stock_sync ) {
1400 update_post_meta( $variation_id, 'manage_stock', true );
1401 if ( $stock > 0 ) {
1402 update_post_meta( $variation_id, '_stock', $stock );
1403 update_post_meta( $variation_id, '_stock_status', 'instock' );
1404 } else {
1405 $backorder_status = get_post_meta( $group_id, '_backorders', true );
1406 update_post_meta( $variation_id, '_stock', $stock );
1407 if ( 'yes' === $backorder_status ) {
1408 update_post_meta( $variation_id, '_stock_status', 'onbackorder' );
1409 } else {
1410 update_post_meta( $variation_id, '_stock_status', 'outofstock' );
1411 }
1412 }
1413 }
1414 // Featured Image of variation.
1415 if ( ! empty( $item->image_document_id ) ) {
1416 $image_class = new CMBIRD_Image_ZI();
1417 $variation_image_id = $image_class->cmbird_zi_get_image( $item->item_id, $item->name, $variation_id, $item->image_name );
1418 if ( ! has_post_thumbnail( $group_id ) ) {
1419 if ( $variation_image_id ) {
1420 set_post_thumbnail( $group_id, $variation_image_id );
1421 }
1422 }
1423 }
1424 update_post_meta( $variation_id, 'zi_item_id', $item_id );
1425 WC_Product_Variable::sync( $group_id );
1426 // Regenerate lookup table for attributes.
1427 $lookup_data_store = new LookupDataStore();
1428 $lookup_data_store->create_data_for_product( $group_id );
1429 // End group item add process.
1430 unset( $attribute_arr );
1431 }
1432 // end of grouped item updating.
1433 }
1434 }
1435
1436 /**
1437 * Helper Function to check if child of composite items already synced or not
1438 *
1439 * @param string $composite_zoho_id - zoho composite item id to check if it's child are already synced.
1440 * @param string $zi_url - zoho api url.
1441 * @param string $zi_org_id - zoho organization token.
1442 * @param string $prod_id - woocommerce product id.
1443 * @return array | bool of child id and metadata if child item already synced else will return false.
1444 */
1445 public function zi_check_if_child_synced_already( $composite_zoho_id, $zi_url, $zi_org_id, $prod_id ) {
1446 if ( $prod_id ) {
1447 $bundle_childs = WC_PB_DB::query_bundled_items(
1448 array(
1449 'return' => 'id=>product_id',
1450 'bundle_id' => array( $prod_id ),
1451 )
1452 );
1453 }
1454 global $wpdb;
1455
1456 $url = $zi_url . 'inventory/v1/compositeitems/' . $composite_zoho_id . '?organization_id=' . $zi_org_id;
1457
1458 $execute_curl_call = new CMBIRD_API_Handler_Zoho();
1459 $json = $execute_curl_call->execute_curl_call_get( $url );
1460 $code = $json->code;
1461 // Flag to allow sync of parent composite item.
1462 $allow_sync = false;
1463 // Array of child object metadata.
1464 $product_array = array(); // [{prod_id:'',metadata:{key:'',value:''}},...].
1465 if ( '0' === $code || 0 === $code ) {
1466 foreach ( $json->composite_item->mapped_items as $child_item ) {
1467 $prod_meta = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->postmeta WHERE meta_key = 'zi_item_id' AND meta_value = %s", $child_item->item_id ) );
1468 // If any child will not have zoho id in meta field then process will return false and syncing will be skipped for given item.
1469 if ( ! empty( $prod_meta->post_id ) ) {
1470
1471 $allow_sync = true;
1472 $product = wc_get_product( $prod_meta->post_id );
1473 $stock_status = 'out_of_stock';
1474 if ( $child_item->stock_on_hand > 0 ) {
1475 $stock_status = 'in_stock';
1476 } elseif ( $product && $product->backorders_allowed() ) {
1477 $stock_status = 'onbackorder';
1478 }
1479 $prod_obj = (object) array(
1480 'prod_id' => $prod_meta->post_id,
1481 'metadata' => (object) array(
1482 'quantity_min' => max( 1, $child_item->quantity ),
1483 'quantity_max' => max( 1, $child_item->quantity ),
1484 'stock_status' => $stock_status,
1485 'max_stock' => $child_item->stock_on_hand,
1486 ),
1487 );
1488 if ( is_array( $bundle_childs ) && ! empty( $bundle_childs ) ) {
1489 $index = array_search( $prod_meta->post_id, $bundle_childs );
1490 unset( $bundle_childs[ $index ] );
1491 }
1492 array_push( $product_array, $prod_obj );
1493 } else {
1494 continue;
1495 }
1496 }
1497 }
1498 if ( is_array( $bundle_childs ) && ! empty( $bundle_childs ) ) {
1499 foreach ( $bundle_childs as $item_id => $val ) {
1500 WC_PB_DB::delete_bundled_item( $item_id );
1501 }
1502 }
1503 if ( $allow_sync ) {
1504 return $product_array;
1505 }
1506 return false;
1507 }
1508 /**
1509 * Mapping of bundled product
1510 *
1511 * @param number $product_id - Product id of child item of bundle product.
1512 * @param number $bundle_id - Bundle id of product.
1513 * @param number $menu_order - Listing order of child product ($menu_order will useful at composite product details page).
1514 * @return number - bundle item id.
1515 */
1516 public function add_bundle_product( $product_id, $bundle_id, $menu_order = 0 ) {
1517 $bundle_items = WC_PB_DB::query_bundled_items(
1518 array(
1519 'return' => 'id=>product_id',
1520 'bundle_id' => array( $bundle_id ),
1521 'product_id' => array( $product_id ),
1522 )
1523 );
1524 $data = array(
1525 'menu_order' => $menu_order,
1526 );
1527
1528 if ( count( $bundle_items ) > 0 ) {
1529 $result = WC_PB_DB::update_bundled_item( $bundle_id, $data );
1530 return $result;
1531 } else {
1532 // create data array of bundle item.
1533 $data = array(
1534 'product_id' => $product_id,
1535 'bundle_id' => $bundle_id,
1536 'menu_order' => $menu_order,
1537 );
1538 $bundle_id = WC_PB_DB::add_bundled_item( $data );
1539 return $bundle_id;
1540 }
1541 }
1542
1543 /**
1544 * Create or update bundle item metadata
1545 *
1546 * @param number $bundle_item_id bundle item id.
1547 * @param string $meta_key - metadata key.
1548 * @param string $meta_value - metadata value.
1549 * @return float|int $result - metadata id.
1550 */
1551 public function zi_update_bundle_meta( $bundle_item_id, $meta_key, $meta_value ) {
1552 // first get metadata from db.
1553 $metadata = WC_PB_DB::get_bundled_item_meta( $bundle_item_id, $meta_key );
1554 if ( $metadata ) {
1555 $result = WC_PB_DB::update_bundled_item_meta( $bundle_item_id, $meta_key, $meta_value );
1556 } else {
1557 $result = WC_PB_DB::add_bundled_item_meta( $bundle_item_id, $meta_key, $meta_value );
1558 }
1559 return $result;
1560 }
1561
1562 /**
1563 * Function to sync composite item from zoho to woocommerce
1564 *
1565 * @param integer $page - Page number of composite item data.
1566 * @param string $category - Category id of composite data.
1567 * @return mixed - mostly array of response message.
1568 */
1569 public function recursively_sync_composite_item_from_zoho( $page, $category ) {
1570 // Start logging.
1571 // $fd = fopen( __DIR__ . '/recursively_sync_composite_item_from_zoho.txt', 'a+' );
1572
1573 global $wpdb;
1574 $zi_org_id = $this->config['ProductZI']['OID'];
1575 $zi_url = $this->config['ProductZI']['APIURL'];
1576
1577 $current_user = wp_get_current_user();
1578 $admin_author_id = $current_user->ID;
1579 if ( ! $admin_author_id ) {
1580 $admin_author_id = 1;
1581 }
1582
1583 $url = $zi_url . 'inventory/v1/compositeitems/?organization_id=' . $zi_org_id . '&filter_by=Status.Active&category_id=' . $category . '&page=' . $page;
1584
1585 $execute_curl_call = new CMBIRD_API_Handler_Zoho();
1586 $json = $execute_curl_call->execute_curl_call_get( $url );
1587 $code = $json->code;
1588 // $message = $json->message;
1589 // fwrite($fd, PHP_EOL . '$json : ' . print_r($json, true));
1590 // Response for item sync with sync button. For cron sync blank array will return.
1591 $response_msg = array();
1592 if ( '0' === $code || 0 === $code ) {
1593 if ( empty( $json->composite_items ) ) {
1594 array_push( $response_msg, $this->zi_response_message( 'ERROR', 'No composite item to sync for category : ' . $category ) );
1595 return $response_msg;
1596 }
1597 // Accounting stock mode check.
1598 $accounting_stock = $this->config['Settings']['enable_accounting_stock'];
1599 foreach ( $json->composite_items as $comp_item ) {
1600 // fwrite( $fd, PHP_EOL . 'Composite Item : ' . print_r( $comp_item, true ) );
1601 // Sync stock from specific location check.
1602 $zi_enable_locationstock = $this->config['Settings']['enable_locationstock'];
1603 $location_id = $this->config['Settings']['location_id'];
1604 $locations = $comp_item->locations;
1605
1606 if ( true === $zi_enable_locationstock ) {
1607 foreach ( $locations as $location ) {
1608 if ( $location->location_id === $location_id ) {
1609 if ( $accounting_stock ) {
1610 $stock = $location->location_available_for_sale_stock;
1611 } else {
1612 $stock = $location->location_actual_available_for_sale_stock;
1613 }
1614 }
1615 }
1616 } elseif ( $accounting_stock ) {
1617 $stock = $comp_item->available_for_sale_stock;
1618 } else {
1619 $stock = $comp_item->actual_available_for_sale_stock;
1620 }
1621
1622 // ----------------- Create composite item in woocommerce--------------.
1623 // Code to skip sync with item already exists with same sku.
1624 $prod_id = wc_get_product_id_by_sku( $comp_item->sku );
1625 // Flag to enable or disable sync.
1626 $allow_to_import = false;
1627 // Check if product exists with same sku.
1628 if ( $prod_id ) {
1629 $zi_item_id = get_post_meta( $prod_id, 'zi_item_id', true );
1630 if ( $zi_item_id === $comp_item->composite_item_id ) {
1631 // If product is with same sku and zi_item_id mapped.
1632 // Do not import ...
1633 $allow_to_import = false;
1634 } else {
1635 // Map existing item with zoho id.
1636 update_post_meta( $prod_id, 'zi_item_id', $comp_item->composite_item_id );
1637 $allow_to_import = false;
1638 }
1639 } else {
1640 // If product not exists normal bahaviour of item sync.
1641 $allow_to_import = true;
1642 }
1643 $zoho_comp_item_id = $comp_item->composite_item_id;
1644 if ( $comp_item->composite_item_id ) {
1645 $child_items = $this->zi_check_if_child_synced_already( $zoho_comp_item_id, $zi_url, $zi_org_id, $prod_id );
1646 // Check if child items already synced with zoho.
1647 if ( ! $child_items ) {
1648 array_push( $response_msg, $this->zi_response_message( 'ERROR', 'Child not synced for composite item : ' . $zoho_comp_item_id ) );
1649 continue;
1650 }
1651 $product_res = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->postmeta} WHERE meta_key = 'zi_item_id' AND meta_value = %s", $zoho_comp_item_id ) );
1652 if ( ! empty( $product_res->post_id ) ) {
1653 $com_prod_id = $product_res->post_id;
1654 }
1655 // Check if item is allowed to import or not.
1656 if ( $allow_to_import ) {
1657 $product_class = new CMBIRD_Products_ZI_Export();
1658 $item_array = json_decode( wp_json_encode( $comp_item ), true );
1659 $com_prod_id = $product_class->cmbird_zi_product_to_woocommerce( $item_array, $stock, 'composite' );
1660 update_post_meta( $com_prod_id, 'zi_item_id', $zoho_comp_item_id );
1661 }
1662 }
1663 // Map composite items to database.
1664 if ( ! empty( $com_prod_id ) ) {
1665 wp_set_object_terms( $com_prod_id, 'bundle', 'product_type' );
1666 foreach ( $child_items as $child_prod ) {
1667 // Adding product to bundle.
1668 $child_bundle_id = $this->add_bundle_product( $child_prod->prod_id, $com_prod_id );
1669 if ( $child_bundle_id ) {
1670 foreach ( $child_prod->metadata as $bundle_meta_key => $bundle_meta_val ) {
1671 $this->zi_update_bundle_meta( $child_bundle_id, $bundle_meta_key, $bundle_meta_val );
1672 }
1673 }
1674 }
1675 }
1676 // --------------------------------------------------------------------.
1677
1678 $is_synced_flag = false; // loggin purpose only .
1679
1680 $product = wc_get_product( $com_prod_id );
1681 foreach ( $comp_item as $key => $value ) {
1682 if ( 'status' === $key ) {
1683 if ( ! empty( $com_prod_id ) ) {
1684 $status = 'active' === $value ? 'publish' : 'draft';
1685 $product->set_status( $status );
1686 }
1687 }
1688 if ( 'description' === $key ) {
1689 if ( ! empty( $com_prod_id ) && ! empty( $value ) ) {
1690 $product->set_short_description( $value );
1691 }
1692 }
1693 if ( 'name' === $key ) {
1694 if ( ! empty( $com_prod_id ) ) {
1695 $product->set_name( $value );
1696 }
1697 }
1698 if ( 'sku' === $key ) {
1699 if ( ! empty( $com_prod_id ) ) {
1700 $product->set_sku( $value );
1701 }
1702 }
1703 // Check if stock sync allowed by plugin.
1704 if ( 'available_stock' === $key || 'actual_available_stock' === $key ) {
1705 $zi_disable_stock_sync = $this->config['Settings']['disable_stock'];
1706 if ( ! $zi_disable_stock_sync ) {
1707 if ( $stock ) {
1708 if ( ! empty( $com_prod_id ) ) {
1709 // If value is less than 0 default 1.
1710 $stock_quantity = $stock < 0 ? 0 : $stock;
1711 $product->set_manage_stock( true );
1712 $product->set_stock_quantity( $stock_quantity );
1713 if ( $stock_quantity > 0 ) {
1714 $status = 'instock';
1715 } else {
1716 $backorder_status = $product->backorders_allowed();
1717 $status = $backorder_status ? 'onbackorder' : 'outofstock';
1718 }
1719 $product->set_stock_status( $status );
1720 update_post_meta( $com_prod_id, '_wc_pb_bundled_items_stock_status', $status );
1721 }
1722 }
1723 }
1724 }
1725 if ( 'rate' === $key ) {
1726 if ( ! empty( $com_prod_id ) ) {
1727 $sale_price = $product->get_sale_price();
1728 if ( empty( $sale_price ) ) {
1729 $product->set_regular_price( $value );
1730 $product->set_price( $value );
1731 update_post_meta( $com_prod_id, '_wc_pb_base_price', $value );
1732 update_post_meta( $com_prod_id, '_wc_pb_base_regular_price', $value );
1733 update_post_meta( $com_prod_id, '_wc_sw_max_regular_price', $value );
1734 } else {
1735 $product->set_regular_price( $value );
1736 update_post_meta( $com_prod_id, '_wc_pb_base_price', $value );
1737 update_post_meta( $com_prod_id, '_wc_pb_base_regular_price', $value );
1738 update_post_meta( $com_prod_id, '_wc_sw_max_regular_price', $value );
1739 }
1740 }
1741 }
1742 $product->save();
1743
1744 if ( 'image_document_id' === $key ) {
1745 if ( ! empty( $com_prod_id ) && ! empty( $value ) ) {
1746 $image_class = new CMBIRD_Image_ZI();
1747 $image_class->cmbird_zi_get_image( $zoho_comp_item_id, $comp_item->name, $com_prod_id, $comp_item->image_name );
1748 }
1749 }
1750 if ( 'category_name' === $key ) {
1751 if ( ! empty( $com_prod_id ) && $comp_item->category_name != '' ) {
1752 $term = get_term_by( 'name', $comp_item->category_name, 'product_cat' );
1753 $term_id = $term->term_id;
1754 if ( empty( $term_id ) ) {
1755 $term = wp_insert_term(
1756 $comp_item->category_name,
1757 'product_cat',
1758 array(
1759 'parent' => 0,
1760 )
1761 );
1762 $term_id = $term['term_id'];
1763 }
1764 if ( $term_id ) {
1765 $existing_terms = wp_get_object_terms( $com_prod_id, 'product_cat' );
1766 if ( $existing_terms && count( $existing_terms ) > 0 ) {
1767 $is_terms_exist = $this->zi_check_terms_exists( $existing_terms, $term_id );
1768 if ( ! $is_terms_exist ) {
1769 update_post_meta( $com_prod_id, 'zi_category_id', $category );
1770 wp_add_object_terms( $com_prod_id, $term_id, 'product_cat' );
1771 }
1772 } else {
1773 update_post_meta( $com_prod_id, 'zi_category_id', $category );
1774 wp_set_object_terms( $com_prod_id, $term_id, 'product_cat' );
1775 }
1776 }
1777 // Remove "uncategorized" category if assigned.
1778 $uncategorized_term = get_term_by( 'slug', 'uncategorized', 'product_cat' );
1779 if ( $uncategorized_term && has_term( $uncategorized_term->term_id, 'product_cat', $com_prod_id ) ) {
1780 wp_remove_object_terms( $com_prod_id, $uncategorized_term->term_id, 'product_cat' );
1781 }
1782 }
1783 }
1784 }
1785
1786 // sync dimensions and weight.
1787 $item_url = "{$zi_url}inventory/v1/compositeitems/{$zoho_comp_item_id}?organization_id={$zi_org_id}";
1788 $this->zi_item_dimension_weight( $item_url, $com_prod_id, true );
1789
1790 // If item synced append to log : logging purpose only.
1791 if ( $is_synced_flag ) {
1792 array_push( $response_msg, $this->zi_response_message( 'SUCCESS', 'Composite item synced for id : ' . $comp_item->composite_item_id, $com_prod_id ) );
1793 }
1794 }
1795
1796 if ( $json->page_context->has_more_page ) {
1797 ++$page;
1798 $this->recursively_sync_composite_item_from_zoho( $page, $category );
1799 }
1800 } else {
1801 array_push( $response_msg, $this->zi_response_message( $code, $json->message ) );
1802 }
1803 // fclose( $fd ); // End of logging.
1804
1805 return $response_msg;
1806 }
1807
1808 /**
1809 * Function to retrieve item details, update weight and dimensions.
1810 *
1811 * @param string $url - URL to ge details.
1812 * @return mixed return true if data false if error.
1813 */
1814 public function zi_item_dimension_weight( $url, $product_id, $is_composite = false ) {
1815 // $fd = fopen(__DIR__ . '/zi_item_dimension_weight.txt', 'a+');
1816 // Check if item is for syncing purpose.
1817 $execute_curl_call = new CMBIRD_API_Handler_Zoho();
1818 $json = $execute_curl_call->execute_curl_call_get( $url );
1819 $code = $json->code;
1820 $message = $json->message;
1821 if ( 0 === $code || '0' === $code ) {
1822 if ( $is_composite ) {
1823 // fwrite($fd, PHP_EOL . '$json : ' . print_r($json, true));
1824 $details = $json->composite_item->package_details;
1825 } else {
1826 $details = $json->item->package_details;
1827 }
1828 $product = wc_get_product( $product_id );
1829 $product->set_weight( floatval( $details->weight ) );
1830 $product->set_length( floatval( $details->length ) );
1831 $product->set_width( floatval( $details->width ) );
1832 $product->set_height( floatval( $details->height ) );
1833 $product->save();
1834 } else {
1835 false;
1836 }
1837 // fclose($fd);
1838 }
1839
1840 /**
1841 * Create response object based on data.
1842 *
1843 * @param mixed $index_col - Index value error message.
1844 * @param string $message - Response message.
1845 * @return object
1846 */
1847 public function zi_response_message( $index_col, $message, $woo_id = '' ) {
1848 return (object) array(
1849 'resp_id' => $index_col,
1850 'message' => $message,
1851 'woo_prod_id' => $woo_id,
1852 );
1853 }
1854
1855 /**
1856 * Helper Function to check if terms already exists.
1857 */
1858 public function zi_check_terms_exists( $existing_terms, $term_id ) {
1859 foreach ( $existing_terms as $woo_existing_term ) {
1860 if ( $woo_existing_term->term_id === $term_id ) {
1861 return true;
1862 } else {
1863 return false;
1864 }
1865 }
1866 }
1867 }
1868 $cmbird_products_zi = new CMBIRD_Products_ZI();
1869