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