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