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