PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.7.5
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.7.5
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / includes / woo-functions.php
commercebird / includes Last commit date
classes 4 months ago sync 4 months ago index.php 1 year ago woo-functions.php 4 months ago
woo-functions.php
892 lines
1 <?php
2
3 /**
4 * All WooCommerce related functions.
5 *
6 * @category WooCommerce
7 * @package CommerceBird
8 * @author Fawad Tiemoerie <info@roadmapstudios.com>
9 * @license GNU General Public License v3.0
10 * @link https://commercebird.com
11 */
12
13 if ( ! defined( 'ABSPATH' ) ) {
14 exit;
15 }
16
17 /**
18 * Helper functions to ensure correct handling of Data being transferred via rest api.
19 *
20 * @param WC_Product $product The product object.
21 * @param WP_REST_Request $request The request object.
22 * @param boolean $is_creating True if the product is being created, false if it is being updated.
23 */
24 function cmbird_clear_product_cache( $product, $request, $is_creating ) {
25 if ( $is_creating ) {
26 $product_id = $product->get_id();
27 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
28 $cmbird_zi_product_sync = get_option( 'cmbird_zoho_disable_product_sync_status' );
29 if ( ! empty( $zoho_inventory_access_token ) && ! $cmbird_zi_product_sync ) {
30 // if request contain meta zi_item_id, then return.
31 if ( isset( $request['meta']['zi_item_id'] ) ) {
32 return;
33 }
34 $product_handler = new CMBIRD_Products_ZI_Export();
35 $product_handler->cmbird_zi_product_sync( $product_id );
36 }
37
38 // Sync to Zoho CRM as well.
39 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
40 if ( ! empty( $zoho_crm_access_token ) ) {
41 $cmbird_zcrm_product_sync = get_option( 'cmbird_zoho_crm_disable_product_sync_status' );
42 if ( ! $cmbird_zcrm_product_sync ) {
43 $product = wc_get_product( $product_id );
44 if ( $product ) {
45 $zcrm_product_handler = new CMBIRD_ZCRM_Product();
46 $zcrm_product_handler->cmbird_sync_product_to_zoho( $product );
47 }
48 }
49 }
50
51 wc_delete_product_transients( $product_id );
52 }
53 }
54 add_action( 'woocommerce_rest_insert_product_object', 'cmbird_clear_product_cache', 10, 3 );
55
56
57
58 /**
59 * Function to update the Contact in Zoho when customer updates address on frontend
60 *
61 * @param int $user_id - User ID.
62 */
63 function cmbird_update_contact_via_accountpage( $user_id ) {
64 // Return if this change is not made from the my account page.
65 if ( ! is_user_logged_in() ) {
66 return;
67 }
68 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
69 if ( ! empty( $zoho_inventory_access_token ) ) {
70 $contact_class_handle = new CMBIRD_Contact_ZI();
71 $contact_class_handle->cmbird_contact_update_function( $user_id );
72
73 global $wpdb;
74 $meta_key_search = 'zi_contact_person_id%';
75 $query = $wpdb->get_results(
76 $wpdb->prepare(
77 "SELECT meta_key, meta_value
78 FROM {$wpdb->usermeta}
79 WHERE user_id = %d
80 AND meta_key LIKE %s",
81 $user_id,
82 $meta_key_search
83 )
84 ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- User-specific meta query, not suitable for caching.
85 if ( ! empty( $query ) ) {
86 // Usermeta keys containing 'zi_contact_person_id' exist.
87 foreach ( $query as $row ) {
88 $contact_class_handle->cmbird_update_contact_person( $user_id, $row->meta_value );
89 }
90 } else {
91 return;
92 }
93 } else {
94 return;
95 }
96 }
97 add_action( 'profile_update', 'cmbird_update_contact_via_accountpage' );
98
99 add_action( 'woocommerce_update_product', 'cmbird_zi_product_sync_class', 10, 1 );
100 add_action( 'wp_ajax_zoho_admin_product_sync', 'cmbird_zi_product_sync_class' );
101 /**
102 * Sync a product to Zoho Inventory and Zoho CRM.
103 *
104 * If the product is a variable product, it will sync all variations to Zoho Inventory and Zoho CRM.
105 * If the product is a simple product or variation, it will sync the product to Zoho Inventory and Zoho CRM.
106 *
107 * @param int $product_id The ID of the product to sync.
108 *
109 * @return void
110 */
111 function cmbird_zi_product_sync_class( $product_id ) {
112 // Bail out during stock-reduction triggered by order placement.
113 if ( did_action( 'woocommerce_reduce_order_stock' ) ) {
114 return;
115 }
116
117 // Also ignore other AJAX updates unless they’re our manual sync request.
118 if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) {
119 if ( empty( $_REQUEST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['nonce'] ) ), 'zoho_admin_product_sync' ) ) {
120 // not a user-initiated Zoho sync call, skip it.
121 return;
122 }
123 }
124
125 if ( ! is_admin() ) {
126 return;
127 }
128 if ( ! current_user_can( 'manage_woocommerce' ) ) {
129 return;
130 }
131
132 if ( ! $product_id ) {
133 // Check nonce for security.
134 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'zoho_admin_product_sync' ) ) {
135 wp_send_json_error( 'Nonce verification failed' );
136 } else {
137 $product_id = isset( $_POST['post_id'] ) ? intval( $_POST['post_id'] ) : 0;
138 if ( ! $product_id ) {
139 wp_send_json_error( 'Invalid Product ID' );
140 }
141 }
142 }
143 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
144 $cmbird_zi_product_sync = get_option( 'cmbird_zoho_disable_product_sync_status' );
145 if ( ! $cmbird_zi_product_sync && $zoho_inventory_access_token ) {
146 $product_handler = new CMBIRD_Products_ZI_Export();
147 $product_handler->cmbird_zi_product_sync( $product_id );
148 }
149
150 // Sync to Zoho CRM as well.
151 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
152 if ( ! empty( $zoho_crm_access_token ) ) {
153 $cmbird_zcrm_product_sync = get_option( 'cmbird_zoho_crm_disable_product_sync_status' );
154 if ( ! $cmbird_zcrm_product_sync ) {
155 $product = wc_get_product( $product_id );
156 if ( $product ) {
157 $zcrm_product_handler = new CMBIRD_ZCRM_Product();
158 $sync_result = $zcrm_product_handler->cmbird_sync_product_to_zoho( $product );
159
160 // Handle different response types based on product type.
161 if ( $product->is_type( 'variable' ) && is_array( $sync_result ) ) {
162 // Variable product returns array with details.
163 $success_count = $sync_result['success_count'];
164 $failed_count = $sync_result['failed_count'];
165 $parent_id = $sync_result['parent_product_id'];
166
167 // If parent_id is not in result, get it from post meta (for existing products).
168 if ( ! $parent_id ) {
169 $parent_id = get_post_meta( $product_id, 'zcrm_product_id', true );
170 }
171
172 if ( $success_count > 0 ) {
173 update_post_meta( $product_id, 'zcrm_product_sync_status', 'success' );
174 $message = sprintf(
175 'Variable product synced: %d successful, %d failed. Parent ID: %s',
176 $success_count,
177 $failed_count,
178 $parent_id ? $parent_id : 'N/A'
179 );
180 update_post_meta( $product_id, 'zcrm_product_sync_message', $message );
181 } else {
182 update_post_meta( $product_id, 'zcrm_product_sync_status', 'failed' );
183 update_post_meta( $product_id, 'zcrm_product_sync_message', 'Failed to sync variable product and variations to Zoho CRM' );
184 }
185 } elseif ( $sync_result ) {
186 // Simple product or variation returns single ID.
187 update_post_meta( $product_id, 'zcrm_product_sync_status', 'success' );
188 update_post_meta( $product_id, 'zcrm_product_sync_message', 'Product synced successfully to Zoho CRM with ID: ' . $sync_result );
189 } else {
190 update_post_meta( $product_id, 'zcrm_product_sync_status', 'failed' );
191 update_post_meta( $product_id, 'zcrm_product_sync_message', 'Failed to sync product to Zoho CRM' );
192 }
193 }
194 }
195 }
196
197 // if its variable product but without variations, then sync it.
198 $product = wc_get_product( $product_id );
199 if ( $product->is_type( 'variable' ) ) {
200 $variations = $product->get_children();
201 if ( isset( $variations ) && count( $variations ) === 0 ) {
202 $zi_product_id = get_post_meta( $product_id, 'zi_item_id', true );
203 if ( ! empty( $zi_product_id ) ) {
204 // First get the item_group from Zoho Inventory using the zi_item_id and then sync the variations of that item group.
205 $get_call = new CMBIRD_API_Handler_Zoho();
206 $zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
207 if ( ! empty( $zoho_inventory_oid ) ) {
208 $zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
209 $url_item = "{$zoho_inventory_url}inventory/v1/itemgroups/{$zi_product_id}?organization_id=$zoho_inventory_oid";
210 $json = $get_call->execute_curl_call_get( $url_item );
211 $code = $json->code;
212 if ( is_wp_error( $json ) ) {
213 update_post_meta( $product_id, 'zi_product_errmsg', 'Error fetching item group from Zoho Inventory: ' . $json->get_error_message() );
214 return;
215 }
216 if ( 0 === $code || '0' === $code ) {
217 if ( isset( $json->item_group ) ) {
218 $product_handler = new CMBIRD_Products_ZI();
219 $product_handler->import_variable_product_variations( $json->item_group, $product_id );
220 }
221 }
222 }
223 }
224 }
225 }
226 }
227
228 /**
229 * Bulk-action to sync products from WooCommerce to Zoho
230 *
231 * @param array $bulk_array - array of bulk actions.
232 * @return array $bulk_array - array of bulk actions.
233 */
234 function cmbird_zi_sync_all_items_to_zoho( $bulk_array ) {
235 $bulk_array['sync_item_to_zoho'] = 'Sync to Zoho';
236 // add option to unmap items from zoho.
237 $bulk_array['unmap_item_to_zoho'] = 'Unmap from Zoho';
238 return $bulk_array;
239 }
240 add_filter( 'bulk_actions-edit-product', 'cmbird_zi_sync_all_items_to_zoho' );
241
242 add_filter( 'handle_bulk_actions-edit-product', 'cmbird_zi_sync_all_items_to_zoho_handler', 10, 3 );
243 function cmbird_zi_sync_all_items_to_zoho_handler( $redirect, $action, $object_ids ) {
244 // let's remove query args first.
245 $redirect = remove_query_arg( 'sync_item_to_zoho_done', $redirect );
246 $redirect = remove_query_arg( 'unmap_item_to_zoho_done', $redirect );
247
248 // do something for "Make Draft" bulk action.
249 if ( 'sync_item_to_zoho' === $action ) {
250
251 foreach ( $object_ids as $post_id ) {
252 // Sync to Zoho Inventory.
253 $product_handler = new CMBIRD_Products_ZI_Export();
254 $product_handler->cmbird_zi_product_sync( $post_id );
255
256 // Sync to Zoho CRM as well.
257 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
258 if ( ! empty( $zoho_crm_access_token ) ) {
259 $cmbird_zcrm_product_sync = get_option( 'cmbird_zoho_crm_disable_product_sync_status' );
260 if ( ! $cmbird_zcrm_product_sync ) {
261 $product = wc_get_product( $post_id );
262 if ( $product ) {
263 $zcrm_product_handler = new CMBIRD_ZCRM_Product();
264 $zcrm_product_handler->cmbird_sync_product_to_zoho( $product );
265 }
266 }
267 }
268 }
269
270 // do not forget to add query args to URL because we will show notices later.
271 $redirect = add_query_arg( 'sync_item_to_zoho_done', count( $object_ids ), $redirect );
272
273 } elseif ( 'unmap_item_to_zoho' === $action ) {
274 foreach ( $object_ids as $post_id ) {
275 $nonce = wp_create_nonce( 'zi_product_unmap_hook' );
276 cmbird_zi_product_unmap_hook( $post_id, $nonce );
277 }
278 // do not forget to add query args to URL because we will show notices later.
279 $redirect = add_query_arg( 'unmap_item_to_zoho_done', count( $object_ids ), $redirect );
280 }
281
282 return $redirect;
283 }
284
285 // output the message of bulk action.
286 add_action( 'admin_notices', 'cmbird_sync_item_to_zoho_notices' );
287 function cmbird_sync_item_to_zoho_notices() {
288 // verify nonce.
289 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) ), 'bulk-edit-products' ) ) {
290 return;
291 }
292 if ( ! empty( $_REQUEST['sync_item_to_zoho_done'] ) ) {
293 echo '<div id="message" class="updated notice is-dismissible">
294 <p>Products Synced. If product is not synced, please click on Edit Product to see the API response.</p>
295 </div>';
296 }
297 if ( ! empty( $_REQUEST['unmap_item_to_zoho_done'] ) ) {
298 echo '<div id="message" class="updated notice is-dismissible">
299 <p>Products Unmapped from Zoho.</p>
300 </div>';
301 }
302 }
303
304 /**
305 * Function to be called by ajax hook when unmap button called.
306 * This function remove zoho mapped id.
307 */
308 add_action( 'wp_ajax_zi_product_unmap_hook', 'cmbird_zi_product_unmap_hook' );
309 function cmbird_zi_product_unmap_hook( $product_id, $nonce = '' ) {
310 // if nonce is not there in the request, then check if it is passed as parameter.
311 if ( ! $nonce ) {
312 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
313 }
314 // verify nonce.
315 if ( ! $nonce || ! wp_verify_nonce( $nonce, 'zi_product_unmap_hook' ) ) {
316 wp_send_json_error( 'Nonce verification failed' );
317 }
318 if ( ! $product_id && isset( $_POST['product_id'] ) ) {
319 $product_id = sanitize_text_field( wp_unslash( $_POST['product_id'] ) );
320 }
321
322 if ( $product_id ) {
323 $product = wc_get_product( $product_id );
324 // If this is variable items then unmap all of it's variations.
325 if ( $product->is_type( 'variable' ) ) {
326 $variations = $product->get_children();
327 if ( isset( $variations ) && count( $variations ) > 0 ) {
328 foreach ( $variations as $child ) {
329 delete_post_meta( $child['variation_id'], 'zi_item_id' );
330 delete_post_meta( $child['variation_id'], 'zi_account_id' );
331 delete_post_meta( $child['variation_id'], 'zi_account_name' );
332 delete_post_meta( $child['variation_id'], 'zi_category_id' );
333 delete_post_meta( $child['variation_id'], 'zi_inventory_account_id' );
334 delete_post_meta( $child['variation_id'], 'zi_purchase_account_id' );
335 }
336 }
337 }
338 delete_post_meta( $product_id, 'zi_item_id' );
339 delete_post_meta( $product_id, 'zi_account_id' );
340 delete_post_meta( $product_id, 'zi_account_name' );
341 delete_post_meta( $product_id, 'zi_category_id' );
342 delete_post_meta( $product_id, 'zi_inventory_account_id' );
343 delete_post_meta( $product_id, 'zi_purchase_account_id' );
344
345 // Also unmap from Zoho CRM.
346 delete_post_meta( $product_id, 'zcrm_product_id' );
347 delete_post_meta( $product_id, 'zcrm_product_sync_status' );
348 delete_post_meta( $product_id, 'zcrm_product_sync_message' );
349
350 // update message.
351 update_post_meta( $product_id, 'zi_product_errmsg', 'Product is Unmapped from Zoho Inventory and CRM' );
352 }
353 }
354
355 /**
356 * Function to be called by ajax hook when unmap button called.
357 * This function remove zoho mapped id.
358 */
359 add_action( 'wp_ajax_zi_customer_unmap_hook', 'cmbird_zi_customer_unmap_hook' );
360 function cmbird_zi_customer_unmap_hook( $order_id ) {
361 // verify Nonce.
362 if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['nonce'] ) ), 'zi_customer_unmap_hook' ) ) {
363 wp_send_json_error( 'Nonce verification failed' );
364 }
365 if ( ! $order_id && isset( $_POST['order_id'] ) ) {
366 $order_id = sanitize_text_field( wp_unslash( $_POST['order_id'] ) );
367 }
368
369 $order = wc_get_order( $order_id );
370 $customer_id = $order->get_user_id();
371
372 if ( $customer_id ) {
373 delete_user_meta( $customer_id, 'zi_contact_id' );
374 delete_user_meta( $customer_id, 'zi_contact_persons_id' );
375 delete_user_meta( $customer_id, 'zi_contactperson_id_0' );
376 delete_user_meta( $customer_id, 'zi_contactperson_id_1' );
377 delete_user_meta( $customer_id, 'zi_currency_code' );
378 delete_user_meta( $customer_id, 'zi_currency_id' );
379 delete_user_meta( $customer_id, 'zi_created_time' );
380 delete_user_meta( $customer_id, 'zi_last_modified_time' );
381 delete_user_meta( $customer_id, 'zi_primary_contact_id' );
382 delete_user_meta( $customer_id, 'zi_billing_address_id' );
383 delete_user_meta( $customer_id, 'zi_shipping_address_id' );
384
385 $order->add_order_note( 'Zoho Sync: Customer is now unmapped. Please try syncing the order again' );
386 $order->save();
387 }
388 }
389
390 /**
391 * Add WordPress Meta box to show sync response
392 */
393 function cmbird_product_metabox() {
394 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
395 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
396 if ( ! $zoho_inventory_access_token && ! $zoho_crm_access_token ) {
397 return;
398 }
399 add_meta_box(
400 'zoho-product-sync',
401 __( 'Zoho Inventory', 'commercebird' ),
402 'cmbird_product_metabox_callback',
403 'product',
404 'side',
405 'high'
406 );
407 // normal metabox for zoho inventory body request.
408 add_meta_box(
409 'zoho-product-sync-normal',
410 __( 'Zoho Inventory - Full Details', 'commercebird' ),
411 'cmbird_product_metabox_body_request',
412 'product',
413 'normal',
414 'high'
415 );
416 }
417
418 /**
419 * Shows the full API request body when syncing the product to Zoho Inventory.
420 *
421 * @param WP_Post $post The current post object.
422 */
423 function cmbird_product_metabox_body_request( $post ) {
424 $body_request = get_post_meta( $post->ID, 'zi_product_body_request', true );
425
426 if ( empty( $body_request ) ) {
427 echo '<p>No API request body available. Try syncing the product first.</p>';
428 return;
429 }
430
431 // Format the JSON for better readability if it's valid JSON.
432 $formatted_json = $body_request;
433 $decoded = json_decode( $body_request, true );
434 if ( json_last_error() === JSON_ERROR_NONE ) {
435 $formatted_json = wp_json_encode( $decoded, JSON_PRETTY_PRINT );
436 }
437
438 echo '<h4>API Request Body (JSON):</h4>';
439 echo '<textarea readonly style="width:100%; height:400px; font-family:monospace; font-size:12px; border:1px solid # ccc; padding:10px;">' . esc_textarea( $formatted_json ) . '</textarea>';
440 echo '<p><small>This is the actual JSON data that was sent to Zoho Inventory API. You can copy this content.</small></p>';
441
442 // Add a copy button for convenience.
443 echo '<button type="button" onclick="copyBodyRequest()" style="margin-top:5px;" class="button">Copy to Clipboard</button>';
444 echo '<script>
445 function copyBodyRequest() {
446 const textarea = document.querySelector(\'textarea[readonly]\');
447 textarea.select();
448 document.execCommand(\'copy\');
449 alert(\'Request body copied to clipboard!\');
450 }
451 </script>';
452 }
453 /**
454 * Callback for product metabox to show sync response.
455 *
456 * @param WP_Post $post The current post object.
457 */
458 function cmbird_product_metabox_callback( $post ) {
459 $response = get_post_meta( $post->ID, 'zi_product_errmsg' );
460 echo 'API Response: ' . esc_html( implode( $response ) ) . '<br>';
461
462 // Show CRM sync status.
463 $zcrm_sync_status = get_post_meta( $post->ID, 'zcrm_product_sync_status', true );
464 $zcrm_sync_message = get_post_meta( $post->ID, 'zcrm_product_sync_message', true );
465 if ( $zcrm_sync_status ) {
466 $status_color = 'success' === $zcrm_sync_status ? 'green' : 'red';
467 echo '<p style="color:' . esc_attr( $status_color ) . ';"><strong>CRM Sync:</strong> ' . esc_html( $zcrm_sync_message ) . '</p>';
468 }
469
470 // Generate nonce.
471 $nonce = wp_create_nonce( 'zoho_admin_product_sync' );
472 $nonce_unmap = wp_create_nonce( 'zi_product_unmap_hook' );
473 $post_id = $post->ID;
474 echo '<br><a href="javascript:void(0)" style="width:100%; text-align: center;" class="button button-primary" onclick="zoho_admin_product_ajax(' . esc_attr( $post_id ) . ', \'' . esc_attr( $nonce ) . '\')">Sync Product</a>';
475 echo '<br><a href="javascript:void(0)" style="margin-top:10px; background:#b32d2e; border-color: # b32d2e; width:100%; text-align: center;" class="button button-primary" onclick="zoho_admin_unmap_product_ajax(' . esc_attr( $post_id ) . ', \'' . esc_attr( $nonce_unmap ) . '\')">Unmap this Product</a>';
476 $product = wc_get_product( $post->ID );
477 $product_type = $product->get_type();
478 if ( 'variable' === $product_type || 'variable-subscription' === $product_type ) {
479 echo '<p class="howto" style="color:#b32d2e;"><strong>Important : </strong> Please ensure all variations have price and SKU</p>';
480 }
481 // echo the zi_category_id.
482 $zi_category_id = get_post_meta( $post->ID, 'zi_category_id', true );
483 if ( $zi_category_id ) {
484 echo '<p class="howto"><strong>Zoho Category: </strong>' . esc_html( $zi_category_id ) . '</p>';
485 }
486 // make api call to get the zoho item details.
487 $zi_item_id = get_post_meta( $post->ID, 'zi_item_id', true );
488 if ( $zi_item_id && is_admin() ) {
489 $zoho_inventory_oid = get_option( 'cmbird_zoho_inventory_oid' );
490 $zoho_inventory_url = get_option( 'cmbird_zoho_inventory_url' );
491 $urlitem = "{$zoho_inventory_url}inventory/v1/items/{$zi_item_id}?organization_id=$zoho_inventory_oid";
492 // fwrite( $fd, PHP_EOL . 'URL : ' . $urlitem );
493
494 sleep( 1 ); // to avoid 429 error.
495 $execute_curl_call = new CMBIRD_API_Handler_Zoho();
496 $json = $execute_curl_call->execute_curl_call_get( $urlitem );
497 $code = (int) property_exists( $json, 'code' ) ? $json->code : '0';
498 if ( '0' === $code || 0 === $code ) {
499 // echo the item details here that are in array called "item".
500 $item = $json->item;
501 $actual_available_stock = (int) property_exists( $item, 'actual_available_stock' ) ? $item->actual_available_stock : '0';
502 $rate = (int) property_exists( $item, 'rate' ) ? $item->rate : '0';
503 echo '<p class="howto"><strong>Rate: </strong>' . esc_html( $rate ) . '</p>';
504 // echo the actual_available_stock.
505 echo '<p class="howto"><strong>Available Stock: </strong>' . esc_html( $actual_available_stock ) . '</p>';
506 // echo the zoho category name from the item.
507 $category_name = (string) property_exists( $item, 'category_name' ) ? $item->category_name : '';
508 if ( ! empty( $category_name ) || 'variable' === $product_type || 'variable-subscription' === $product_type ) {
509 echo '<p class="howto"><strong>Category Name: </strong>' . esc_html( $category_name ) . '</p>';
510 }
511 // echo all properties from item except description.
512 echo '<details><summary>More Details</summary>';
513 echo '<ul>';
514 foreach ( $item as $key => $value ) {
515 if ( 'description' === $key ) {
516 continue; // skip description.
517 }
518 // Skip empty arrays.
519 if ( is_array( $value ) && empty( $value ) ) {
520 continue;
521 }
522 echo '<li><strong>' . esc_html( $key ) . ': </strong>';
523 if ( is_array( $value ) || is_object( $value ) ) {
524 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug display, output is escaped.
525 echo '<pre>' . esc_html( print_r( $value, true ) ) . '</pre>';
526 } else {
527 echo esc_html( $value );
528 }
529 echo '</li>';
530 }
531 echo '</ul>';
532 echo '</details>';
533 echo '<br>';
534 }
535 }
536 }
537 add_action( 'add_meta_boxes', 'cmbird_product_metabox' );
538
539
540 /**
541 * Add Zoho and Exact Item IDs to Product and Variations
542 *
543 * @return void
544 */
545 add_action( 'woocommerce_product_options_pricing', 'cmbird_item_id_field' );
546 add_action( 'woocommerce_variation_options_pricing', 'cmbird_item_id_variation_field', 10, 3 );
547 function cmbird_item_id_field() {
548 woocommerce_wp_text_input(
549 array(
550 'id' => 'eo_item_id',
551 'label' => esc_html__( 'Exact Item ID', 'commercebird' ),
552 'class' => 'readonly',
553 'desc_tip' => true,
554 'description' => esc_html__( 'This is the Exact Item ID of this product. You cannot change this', 'commercebird' ),
555 )
556 );
557 woocommerce_wp_text_input(
558 array(
559 'id' => 'zi_item_id',
560 'label' => esc_html__( 'Zoho Item ID', 'commercebird' ),
561 'class' => 'readonly',
562 'desc_tip' => true,
563 'description' => esc_html__( 'This is the Zoho Item ID of this product. You cannot change this', 'commercebird' ),
564 )
565 );
566 }
567 function cmbird_item_id_variation_field( $loop, $variation_data, $variation ) {
568 woocommerce_wp_text_input(
569 array(
570 'id' => 'eo_item_id[' . $loop . ']',
571 'class' => 'readonly',
572 'label' => esc_html__( 'Exact Item ID', 'commercebird' ),
573 'value' => get_post_meta( $variation->ID, 'eo_item_id', true ),
574 'desc_tip' => true,
575 'description' => esc_html__( 'This is the Exact Item ID of this product. You cannot change this', 'commercebird' ),
576 )
577 );
578 woocommerce_wp_text_input(
579 array(
580 'id' => 'zi_item_id[' . $loop . ']',
581 'class' => 'readonly',
582 'label' => esc_html__( 'Zoho Item ID', 'commercebird' ),
583 'value' => get_post_meta( $variation->ID, 'zi_item_id', true ),
584 'desc_tip' => true,
585 'description' => esc_html__( 'This is the Zoho Item ID of this product. You cannot change this', 'commercebird' ),
586 )
587 );
588 }
589
590 /**
591 * Adds 'Zoho Sync' column header to 'Orders' page immediately after 'Total' column.
592 *
593 * @param string[] $columns
594 * @return string[] $new_columns
595 */
596 function cmbird_zi_sync_column_orders_overview( $columns ) {
597 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
598 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
599 if ( empty( $zoho_inventory_access_token ) && empty( $zoho_crm_access_token ) ) {
600 return $columns;
601 }
602 $new_columns = array();
603 foreach ( $columns as $column_name => $column_info ) {
604
605 $new_columns[ $column_name ] = $column_info;
606
607 if ( 'order_total' === $column_name ) {
608 $new_columns['zoho_sync'] = __( 'Zoho Sync', 'commercebird' );
609 }
610 }
611 return $new_columns;
612 }
613 add_filter( 'manage_woocommerce_page_wc-orders_columns', 'cmbird_zi_sync_column_orders_overview', 20 );
614
615 /**
616 * Adding Sync Status for Orders Column
617 *
618 * @param string $column Column name.
619 * @param int $order_id $order id.
620 * @return void
621 */
622 function cmbird_zi_add_zoho_orders_content( $column, $order_id ) {
623 $zi_url = get_option( 'cmbird_zoho_inventory_url' );
624 $zi_visit_url = str_replace( 'www.zohoapis', 'inventory.zoho', $zi_url );
625 switch ( $column ) {
626 case 'zoho_sync':
627 // Get custom order meta data.
628 $order = wc_get_order( $order_id );
629 $zi_order_id = $order->get_meta( 'zi_salesorder_id', true, 'edit' );
630 $zcrm_order_id = $order->get_meta( 'zcrm_sales_order_id', true, 'edit' );
631
632 if ( $zi_order_id || $zcrm_order_id ) {
633 echo '<span class="dashicons dashicons-yes-alt" style="color:green;"></span>';
634
635 // Only show the external link if we have a ZI order ID for the URL.
636 if ( $zi_order_id ) {
637 $url = $zi_visit_url . 'app#/salesorders/' . $zi_order_id;
638 echo '<a href="' . esc_url( $url ) . '" target="_blank"> <span class="dashicons dashicons-external" style="color:green;"></span> </a>';
639 }
640 } else {
641 echo '<span class="dashicons dashicons-dismiss" style="color:red;"></span>';
642 }
643 unset( $order );
644 break;
645 }
646 }
647 add_action( 'manage_woocommerce_page_wc-orders_custom_column', 'cmbird_zi_add_zoho_orders_content', 20, 2 );
648
649 /**
650 * Adds 'Zoho Sync' column content.
651 *
652 * @param string[] $column name of column being displayed
653 */
654 function cmbird_zi_add_zoho_column_content( $column ) {
655 global $post;
656 $post_type = get_post_type( $post );
657
658 if ( 'zoho_sync' === $column && 'product' === $post_type ) {
659 $product_id = $post->ID;
660 $zi_product_id = get_post_meta( $product_id, 'zi_item_id' );
661 if ( $zi_product_id ) {
662 echo '<span class="dashicons dashicons-yes-alt" style="color:green;"></span>';
663 } else {
664 echo '<span class="dashicons dashicons-dismiss" style="color:red;"></span>';
665 }
666 }
667 }
668 add_action( 'manage_product_posts_custom_column', 'cmbird_zi_add_zoho_column_content' );
669
670 /**
671 * Adds 'Zoho Sync' column header to 'Products' page.
672 *
673 * @param string[] $columns
674 * @return string[] $new_columns
675 */
676 function cmbird_zi_sync_column_products_overview( $columns ) {
677 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
678 if ( empty( $zoho_inventory_access_token ) ) {
679 return $columns;
680 }
681 $new_columns = array();
682
683 foreach ( $columns as $column_name => $column_info ) {
684
685 $new_columns[ $column_name ] = $column_info;
686
687 if ( 'product_cat' === $column_name ) {
688 $new_columns['zoho_sync'] = __( 'Zoho Sync', 'commercebird' );
689 }
690 }
691
692 return $new_columns;
693 }
694 add_filter( 'manage_edit-product_columns', 'cmbird_zi_sync_column_products_overview', 20 );
695
696 /**
697 * Make 'Zoho Sync' column filterable.
698 */
699 function cmbird_zi_sync_column_filterable() {
700 global $typenow;
701
702 if ( 'product' === $typenow ) {
703 // code here.
704 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin product filter, no data modification.
705 $value = isset( $_GET['zoho_sync_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['zoho_sync_filter'] ) ) : '';
706
707 echo '<select name="zoho_sync_filter">';
708 echo '<option value="">Zoho Sync Filter</option>';
709
710 // Count synced products.
711 $synced_count = new WP_Query(
712 array(
713 'post_type' => 'product',
714 'meta_query' => array(
715 array(
716 'key' => 'zi_item_id',
717 'compare' => 'EXISTS',
718 ),
719 ),
720 'fields' => 'ids',
721 )
722 );
723 $synced_count = $synced_count->found_posts;
724 $synced_label = 'Synced';
725 if ( $synced_count > 0 ) {
726 $synced_label .= ' (' . $synced_count . ')';
727 }
728 echo '<option value="synced" ' . selected( $value, 'synced', false ) . '>' . esc_html( $synced_label ) . '</option>';
729
730 // Count not synced products.
731 $not_synced_count = new WP_Query(
732 array(
733 'post_type' => 'product',
734 'meta_query' => array(
735 array(
736 'key' => 'zi_item_id',
737 'compare' => 'NOT EXISTS',
738 ),
739 ),
740 'fields' => 'ids',
741 )
742 );
743 $not_synced_count = $not_synced_count->found_posts;
744 $not_synced_label = 'Not Synced';
745 if ( $not_synced_count > 0 ) {
746 $not_synced_label .= ' (' . $not_synced_count . ')';
747 }
748 echo '<option value="not_synced" ' . selected( $value, 'not_synced', false ) . '>' . esc_html( $not_synced_label ) . '</option>';
749
750 echo '</select>';
751 }
752 }
753 add_action( 'restrict_manage_posts', 'cmbird_zi_sync_column_filterable' );
754
755 /**
756 * Modify the product query based on the filter.
757 *
758 * @param WP_Query $query The query object.
759 */
760 function cmbird_zi_sync_column_filter_query( $query ) {
761 global $typenow, $pagenow;
762
763 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin product filter query, no data modification.
764 if ( 'product' === $typenow && 'edit.php' === $pagenow && isset( $_GET['zoho_sync_filter'] ) && $_GET['zoho_sync_filter'] !== '' ) {
765 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin product filter query, no data modification.
766 $value = sanitize_text_field( wp_unslash( $_GET['zoho_sync_filter'] ) );
767
768 $meta_query = array();
769
770 if ( 'synced' === $value ) {
771 $meta_query[] = array(
772 'key' => 'zi_item_id',
773 'compare' => 'EXISTS',
774 );
775 } elseif ( 'not_synced' === $value ) {
776 $meta_query[] = array(
777 'relation' => 'OR',
778 array(
779 'key' => 'zi_item_id',
780 'compare' => 'NOT EXISTS',
781 ),
782 array(
783 'key' => 'zi_item_id',
784 'value' => '',
785 'compare' => '=',
786 ),
787 );
788 }
789
790 $query->set( 'meta_query', $meta_query );
791 }
792 }
793 add_action( 'pre_get_posts', 'cmbird_zi_sync_column_filter_query' );
794
795 /**
796 * Change Action Scheduler default purge to 1 week
797 *
798 * @return int
799 */
800 function cmbird_action_scheduler_purge() {
801 return WEEK_IN_SECONDS;
802 }
803 add_filter( 'action_scheduler_retention_period', 'cmbird_action_scheduler_purge' );
804
805 add_filter(
806 'action_scheduler_default_cleaner_statuses',
807 function ( $statuses ) {
808 $statuses[] = ActionScheduler_Store::STATUS_FAILED;
809 return $statuses;
810 }
811 );
812
813 /**
814 * Register the custom order statuses "shipped" and "fulfilled"
815 *
816 * @return void
817 */
818 function cmbird_register_custom_order_statuses() {
819 register_post_status(
820 'wc-shipped',
821 array(
822 'label' => _x( 'Shipped', 'Order status', 'commercebird' ),
823 'public' => true,
824 'exclude_from_search' => false,
825 'show_in_admin_all_list' => true,
826 'show_in_admin_status_list' => true,
827 // translators: %s is the number of shipped orders.
828 'label_count' => _n_noop( 'Shipped <span class="count">(%s)</span>', 'Shipped <span class="count">(%s)</span>', 'commercebird' ),
829 )
830 );
831 register_post_status(
832 'wc-fulfilled',
833 array(
834 'label' => _x( 'Fulfilled', 'Order status', 'commercebird' ),
835 'public' => true,
836 'exclude_from_search' => false,
837 'show_in_admin_all_list' => true,
838 'show_in_admin_status_list' => true,
839 // translators: %s is the number of fulfilled orders.
840 'label_count' => _n_noop( 'Fulfilled <span class="count">(%s)</span>', 'Fulfilled <span class="count">(%s)</span>', 'commercebird' ),
841 )
842 );
843 }
844 add_action( 'init', 'cmbird_register_custom_order_statuses' );
845 /**
846 * Add custom order statuses to list of WC Order statuses
847 *
848 * @param array $order_statuses Existing order statuses.
849 * @return array Modified order statuses.
850 */
851 function cmbird_add_custom_order_statuses( $order_statuses ) {
852 $order_statuses['wc-shipped'] = _x( 'Shipped', 'Order status', 'commercebird' );
853 $order_statuses['wc-fulfilled'] = _x( 'Fulfilled', 'Order status', 'commercebird' );
854 return $order_statuses;
855 }
856 add_filter( 'wc_order_statuses', 'cmbird_add_custom_order_statuses' );
857
858 /**
859 * Modify the Products API to include brands.
860 *
861 * TODO: This is a temporary solution. We need to find a better way to include brands in the API response.
862 *
863 * @param $response The response object.
864 * @param $post The post object.
865 * @param $request The request object.
866 * @return mixed
867 */
868 // add_filter( 'woocommerce_rest_prepare_product_object', 'cmbird_rest_api_prepare_brands_tax', 10, 3 );
869 // function cmbird_rest_api_prepare_brands_tax( $response, $object, $request ) {.
870 // $product_id = $object->get_id();
871 // if ( empty( $response->data['product_brands'] ) ) {.
872 // $terms = array();
873 // foreach ( wp_get_post_terms( $product_id, 'product_brands' ) as $term ) {.
874 // $terms[] = array(
875 // 'id' => $term->term_id,.
876 // 'name' => $term->name,.
877 // 'slug' => $term->slug,.
878 // );
879 // }.
880 // $response->data['product_brands'] = $terms;
881 // }.
882 // return $response;
883 // }.
884
885 // function cmbird_prepare_insert_product( $product, $request ) {.
886 // if ( isset( $request['product_brands'] ) ) {.
887 // wp_set_post_terms( $request['id'], $request['product_brands'], 'product_brands', false );
888 // }.
889 // return $product;
890 // }.
891 // add_filter( 'woocommerce_rest_pre_insert_product_object', 'cmbird_prepare_insert_product', 10, 2 );
892