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