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