PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.6.5
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.6.5
3.0.3 3.0.2 3.0.1 trunk 2.2.14 2.2.15 2.2.16 2.2.17 2.2.18 2.2.19 2.3.0 2.3.1 2.3.10 2.3.11 2.3.12 2.3.13 2.3.14 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 2.5.0 2.5.1 2.5.2 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.6.5 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.91 2.7.92 2.7.93 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 2.8.5 2.9.0 2.9.1 2.9.2 2.9.3 3.0.0
commercebird / includes / woo-functions.php
commercebird / includes Last commit date
classes 6 months ago sync 10 months ago index.php 1 year ago woo-functions.php 7 months ago
woo-functions.php
882 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 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 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- Debug display, output is escaped.
494 echo '<pre>' . esc_html( print_r( $value, true ) ) . '</pre>';
495 } else {
496 echo esc_html( $value );
497 }
498 echo '</li>';
499 }
500 echo '</ul>';
501 echo '</details>';
502 echo '<br>';
503 }
504 }
505 }
506 add_action( 'add_meta_boxes', 'cmbird_product_metabox' );
507
508
509 /**
510 * Add Zoho and Exact Item IDs to Product and Variations
511 *
512 * @return void
513 */
514 add_action( 'woocommerce_product_options_pricing', 'cmbird_item_id_field' );
515 add_action( 'woocommerce_variation_options_pricing', 'cmbird_item_id_variation_field', 10, 3 );
516 function cmbird_item_id_field() {
517 woocommerce_wp_text_input(
518 array(
519 'id' => '_cost_price',
520 'class' => 'readonly',
521 'wrapper_class' => 'form-row',
522 'label' => esc_html__( 'Cost Price', 'commercebird' ),
523 'data_type' => 'price',
524 'description' => esc_html__( 'You can edit this via the CommerceBird App', 'commercebird' ),
525 )
526 );
527 woocommerce_wp_text_input(
528 array(
529 'id' => 'eo_item_id',
530 'label' => esc_html__( 'Exact Item ID', 'commercebird' ),
531 'class' => 'readonly',
532 'desc_tip' => true,
533 'description' => esc_html__( 'This is the Exact Item ID of this product. You cannot change this', 'commercebird' ),
534 )
535 );
536 woocommerce_wp_text_input(
537 array(
538 'id' => 'zi_item_id',
539 'label' => esc_html__( 'Zoho Item ID', 'commercebird' ),
540 'class' => 'readonly',
541 'desc_tip' => true,
542 'description' => esc_html__( 'This is the Zoho Item ID of this product. You cannot change this', 'commercebird' ),
543 )
544 );
545 }
546 function cmbird_item_id_variation_field( $loop, $variation_data, $variation ) {
547 woocommerce_wp_text_input(
548 array(
549 'id' => 'cost_price[' . $loop . ']',
550 'class' => 'readonly',
551 'wrapper_class' => 'form-row',
552 'data_type' => 'price',
553 'label' => esc_html__( 'Cost Price', 'commercebird' ),
554 'value' => get_post_meta( $variation->ID, '_cost_price', true ),
555 'description' => esc_html__( 'You can edit this via the CommerceBird App', 'commercebird' ),
556 )
557 );
558 woocommerce_wp_text_input(
559 array(
560 'id' => 'eo_item_id[' . $loop . ']',
561 'class' => 'readonly',
562 'label' => esc_html__( 'Exact Item ID', 'commercebird' ),
563 'value' => get_post_meta( $variation->ID, 'eo_item_id', true ),
564 'desc_tip' => true,
565 'description' => esc_html__( 'This is the Exact Item ID of this product. You cannot change this', 'commercebird' ),
566 )
567 );
568 woocommerce_wp_text_input(
569 array(
570 'id' => 'zi_item_id[' . $loop . ']',
571 'class' => 'readonly',
572 'label' => esc_html__( 'Zoho Item ID', 'commercebird' ),
573 'value' => get_post_meta( $variation->ID, 'zi_item_id', true ),
574 'desc_tip' => true,
575 'description' => esc_html__( 'This is the Zoho Item ID of this product. You cannot change this', 'commercebird' ),
576 )
577 );
578 }
579
580 /**
581 * Adds 'Zoho Sync' column header to 'Orders' page immediately after 'Total' column.
582 *
583 * @param string[] $columns
584 * @return string[] $new_columns
585 */
586 function cmbird_zi_sync_column_orders_overview( $columns ) {
587 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
588 $zoho_crm_access_token = get_option( 'cmbird_zoho_crm_access_token' );
589 if ( empty( $zoho_inventory_access_token ) && empty( $zoho_crm_access_token ) ) {
590 return $columns;
591 }
592 $new_columns = array();
593 foreach ( $columns as $column_name => $column_info ) {
594
595 $new_columns[ $column_name ] = $column_info;
596
597 if ( 'order_total' === $column_name ) {
598 $new_columns['zoho_sync'] = __( 'Zoho Sync', 'commercebird' );
599 }
600 }
601 return $new_columns;
602 }
603 add_filter( 'manage_woocommerce_page_wc-orders_columns', 'cmbird_zi_sync_column_orders_overview', 20 );
604
605 /**
606 * Adding Sync Status for Orders Column
607 *
608 * @param string $column Column name.
609 * @param int $order_id $order id.
610 * @return void
611 */
612 function cmbird_zi_add_zoho_orders_content( $column, $order_id ) {
613 $zi_url = get_option( 'cmbird_zoho_inventory_url' );
614 $zi_visit_url = str_replace( 'www.zohoapis', 'inventory.zoho', $zi_url );
615 switch ( $column ) {
616 case 'zoho_sync':
617 // Get custom order meta data.
618 $order = wc_get_order( $order_id );
619 $zi_order_id = $order->get_meta( 'zi_salesorder_id', true, 'edit' );
620 $zcrm_order_id = $order->get_meta( 'zcrm_sales_order_id', true, 'edit' );
621
622 if ( $zi_order_id || $zcrm_order_id ) {
623 echo '<span class="dashicons dashicons-yes-alt" style="color:green;"></span>';
624
625 // Only show the external link if we have a ZI order ID for the URL.
626 if ( $zi_order_id ) {
627 $url = $zi_visit_url . 'app#/salesorders/' . $zi_order_id;
628 echo '<a href="' . esc_url( $url ) . '" target="_blank"> <span class="dashicons dashicons-external" style="color:green;"></span> </a>';
629 }
630 } else {
631 echo '<span class="dashicons dashicons-dismiss" style="color:red;"></span>';
632 }
633 unset( $order );
634 break;
635 }
636 }
637 add_action( 'manage_woocommerce_page_wc-orders_custom_column', 'cmbird_zi_add_zoho_orders_content', 20, 2 );
638
639 /**
640 * Adds 'Zoho Sync' column content.
641 *
642 * @param string[] $column name of column being displayed
643 */
644 function cmbird_zi_add_zoho_column_content( $column ) {
645 global $post;
646 $post_type = get_post_type( $post );
647
648 if ( 'zoho_sync' === $column && 'product' === $post_type ) {
649 $product_id = $post->ID;
650 $zi_product_id = get_post_meta( $product_id, 'zi_item_id' );
651 if ( $zi_product_id ) {
652 echo '<span class="dashicons dashicons-yes-alt" style="color:green;"></span>';
653 } else {
654 echo '<span class="dashicons dashicons-dismiss" style="color:red;"></span>';
655 }
656 }
657 }
658 add_action( 'manage_product_posts_custom_column', 'cmbird_zi_add_zoho_column_content' );
659
660 /**
661 * Adds 'Zoho Sync' column header to 'Products' page.
662 *
663 * @param string[] $columns
664 * @return string[] $new_columns
665 */
666 function cmbird_zi_sync_column_products_overview( $columns ) {
667 $zoho_inventory_access_token = get_option( 'cmbird_zoho_inventory_access_token' );
668 if ( empty( $zoho_inventory_access_token ) ) {
669 return $columns;
670 }
671 $new_columns = array();
672
673 foreach ( $columns as $column_name => $column_info ) {
674
675 $new_columns[ $column_name ] = $column_info;
676
677 if ( 'product_cat' === $column_name ) {
678 $new_columns['zoho_sync'] = __( 'Zoho Sync', 'commercebird' );
679 }
680 }
681
682 return $new_columns;
683 }
684 add_filter( 'manage_edit-product_columns', 'cmbird_zi_sync_column_products_overview', 20 );
685
686 /**
687 * Make 'Zoho Sync' column filterable.
688 */
689 function cmbird_zi_sync_column_filterable() {
690 global $typenow;
691
692 if ( 'product' === $typenow ) {
693 // code here.
694 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin product filter, no data modification.
695 $value = isset( $_GET['zoho_sync_filter'] ) ? sanitize_text_field( wp_unslash( $_GET['zoho_sync_filter'] ) ) : '';
696
697 echo '<select name="zoho_sync_filter">';
698 echo '<option value="">Zoho Sync Filter</option>';
699
700 // Count synced products.
701 $synced_count = new WP_Query(
702 array(
703 'post_type' => 'product',
704 'meta_query' => array(
705 array(
706 'key' => 'zi_item_id',
707 'compare' => 'EXISTS',
708 ),
709 ),
710 'fields' => 'ids',
711 )
712 );
713 $synced_count = $synced_count->found_posts;
714 $synced_label = 'Synced';
715 if ( $synced_count > 0 ) {
716 $synced_label .= ' (' . $synced_count . ')';
717 }
718 echo '<option value="synced" ' . selected( $value, 'synced', false ) . '>' . esc_html( $synced_label ) . '</option>';
719
720 // Count not synced products.
721 $not_synced_count = new WP_Query(
722 array(
723 'post_type' => 'product',
724 'meta_query' => array(
725 array(
726 'key' => 'zi_item_id',
727 'compare' => 'NOT EXISTS',
728 ),
729 ),
730 'fields' => 'ids',
731 )
732 );
733 $not_synced_count = $not_synced_count->found_posts;
734 $not_synced_label = 'Not Synced';
735 if ( $not_synced_count > 0 ) {
736 $not_synced_label .= ' (' . $not_synced_count . ')';
737 }
738 echo '<option value="not_synced" ' . selected( $value, 'not_synced', false ) . '>' . esc_html( $not_synced_label ) . '</option>';
739
740 echo '</select>';
741 }
742 }
743 add_action( 'restrict_manage_posts', 'cmbird_zi_sync_column_filterable' );
744
745 /**
746 * Modify the product query based on the filter.
747 *
748 * @param WP_Query $query The query object.
749 */
750 function cmbird_zi_sync_column_filter_query( $query ) {
751 global $typenow, $pagenow;
752
753 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin product filter query, no data modification.
754 if ( 'product' === $typenow && 'edit.php' === $pagenow && isset( $_GET['zoho_sync_filter'] ) && $_GET['zoho_sync_filter'] !== '' ) {
755 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Admin product filter query, no data modification.
756 $value = sanitize_text_field( wp_unslash( $_GET['zoho_sync_filter'] ) );
757
758 $meta_query = array();
759
760 if ( 'synced' === $value ) {
761 $meta_query[] = array(
762 'key' => 'zi_item_id',
763 'compare' => 'EXISTS',
764 );
765 } elseif ( 'not_synced' === $value ) {
766 $meta_query[] = array(
767 'relation' => 'OR',
768 array(
769 'key' => 'zi_item_id',
770 'compare' => 'NOT EXISTS',
771 ),
772 array(
773 'key' => 'zi_item_id',
774 'value' => '',
775 'compare' => '=',
776 ),
777 );
778 }
779
780 $query->set( 'meta_query', $meta_query );
781 }
782 }
783 add_action( 'pre_get_posts', 'cmbird_zi_sync_column_filter_query' );
784
785 /**
786 * Change Action Scheduler default purge to 1 week
787 *
788 * @return int
789 */
790 function cmbird_action_scheduler_purge() {
791 return WEEK_IN_SECONDS;
792 }
793 add_filter( 'action_scheduler_retention_period', 'cmbird_action_scheduler_purge' );
794
795 add_filter(
796 'action_scheduler_default_cleaner_statuses',
797 function ( $statuses ) {
798 $statuses[] = ActionScheduler_Store::STATUS_FAILED;
799 return $statuses;
800 }
801 );
802
803 /**
804 * Register the custom order statuses "shipped" and "fulfilled"
805 *
806 * @return void
807 */
808 function cmbird_register_custom_order_statuses() {
809 register_post_status(
810 'wc-shipped',
811 array(
812 'label' => _x( 'Shipped', 'Order status', 'commercebird' ),
813 'public' => true,
814 'exclude_from_search' => false,
815 'show_in_admin_all_list' => true,
816 'show_in_admin_status_list' => true,
817 // translators: %s is the number of shipped orders.
818 'label_count' => _n_noop( 'Shipped <span class="count">(%s)</span>', 'Shipped <span class="count">(%s)</span>', 'commercebird' ),
819 )
820 );
821 register_post_status(
822 'wc-fulfilled',
823 array(
824 'label' => _x( 'Fulfilled', 'Order status', 'commercebird' ),
825 'public' => true,
826 'exclude_from_search' => false,
827 'show_in_admin_all_list' => true,
828 'show_in_admin_status_list' => true,
829 // translators: %s is the number of fulfilled orders.
830 'label_count' => _n_noop( 'Fulfilled <span class="count">(%s)</span>', 'Fulfilled <span class="count">(%s)</span>', 'commercebird' ),
831 )
832 );
833 }
834 add_action( 'init', 'cmbird_register_custom_order_statuses' );
835 /**
836 * Add custom order statuses to list of WC Order statuses
837 *
838 * @param array $order_statuses Existing order statuses.
839 * @return array Modified order statuses.
840 */
841 function cmbird_add_custom_order_statuses( $order_statuses ) {
842 $order_statuses['wc-shipped'] = _x( 'Shipped', 'Order status', 'commercebird' );
843 $order_statuses['wc-fulfilled'] = _x( 'Fulfilled', 'Order status', 'commercebird' );
844 return $order_statuses;
845 }
846 add_filter( 'wc_order_statuses', 'cmbird_add_custom_order_statuses' );
847
848 /**
849 * Modify the Products API to include brands.
850 *
851 * TODO: This is a temporary solution. We need to find a better way to include brands in the API response.
852 *
853 * @param $response The response object.
854 * @param $post The post object.
855 * @param $request The request object.
856 * @return mixed
857 */
858 // add_filter( 'woocommerce_rest_prepare_product_object', 'cmbird_rest_api_prepare_brands_tax', 10, 3 );
859 // function cmbird_rest_api_prepare_brands_tax( $response, $object, $request ) {.
860 // $product_id = $object->get_id();
861 // if ( empty( $response->data['product_brands'] ) ) {.
862 // $terms = array();
863 // foreach ( wp_get_post_terms( $product_id, 'product_brands' ) as $term ) {.
864 // $terms[] = array(
865 // 'id' => $term->term_id,.
866 // 'name' => $term->name,.
867 // 'slug' => $term->slug,.
868 // );
869 // }.
870 // $response->data['product_brands'] = $terms;
871 // }.
872 // return $response;
873 // }.
874
875 // function cmbird_prepare_insert_product( $product, $request ) {.
876 // if ( isset( $request['product_brands'] ) ) {.
877 // wp_set_post_terms( $request['id'], $request['product_brands'], 'product_brands', false );
878 // }.
879 // return $product;
880 // }.
881 // add_filter( 'woocommerce_rest_pre_insert_product_object', 'cmbird_prepare_insert_product', 10, 2 );
882