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