PluginProbe ʕ •ᴥ•ʔ
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). / 2.3.2
CommerceBird – AI Command Center, ERP Integrations & B2B for WooCommerce (Zoho, Exact Online). v2.3.2
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 / admin / includes / Actions / Sync / ExactOnlineSync.php
commercebird / admin / includes / Actions / Sync Last commit date
ExactOnlineSync.php 1 year ago ZohoCRMSync.php 1 year ago index.php 1 year ago
ExactOnlineSync.php
478 lines
1 <?php
2
3 namespace CommerceBird\Admin\Actions\Sync;
4
5 if ( ! defined( 'ABSPATH' ) ) {
6 exit;
7 }
8
9 use CommerceBird\Admin\Actions\Ajax\ExactOnlineAjax;
10 use CommerceBird\Admin\Connectors\CommerceBird;
11
12 class ExactOnlineSync {
13
14
15 /**
16 * Sync data from Exact Online.
17 *
18 * @param string $type product|customer
19 * @param array $data to sync from Exact Online
20 * @param bool $import import or update
21 * @return mixed
22 */
23 public static function sync( string $type, array $data, bool $import = false ) {
24 if ( empty( $type ) ) {
25 return false;
26 }
27 if ( $import ) {
28 self::import( $type, $data );
29 } else {
30 self::update( $type, $data );
31 }
32 }
33 /**
34 * Import data from Exact Online.
35 *
36 * for product data will be like,
37 * {
38 * "Code":string,
39 * "Description": string,
40 * "ID": string,
41 * "IsSalesItem": bool,
42 * "PictureName": null|string,
43 * "PictureUrl": string,
44 * "StandardSalesPrice": float,
45 * "Stock": int
46 * },
47 *
48 * for Order data will be like,
49 * {
50 * "MainContact": null|string,
51 * "Email": null|string,
52 * "ID": string,
53 * "Name": string,
54 * "AddressLine1": null|string,
55 * "AddressLine2": null|string,
56 * "City": string,
57 * "Country": string,
58 * "Phone": null|string,
59 * "Postcode": string
60 * }
61 * @param string $type of provided data;
62 * @param array $data of import
63 * @return mixed
64 */
65 public static function import( string $type, array $data ) {
66 // add logging
67 // $fd = fopen( __DIR__ . '/import.txt', 'w+' );
68 $endpoint = '';
69 $payload = array();
70
71 switch ( $type ) {
72 case 'product':
73 $endpoint = '/wc/v3/products/batch';
74 $filtered_data = array_filter( $data, function ($item) {
75 // Exclude item if product already exists via SKU
76 return ! wc_get_product_id_by_sku( $item['Code'] );
77 } );
78 $payload = array(
79 'create' => array_map( function ($item) {
80 // Skip image import if PictureName is 'placeholder_item'
81 if ( isset( $item['PictureName'] ) && $item['PictureName'] !== 'placeholder_item' ) {
82 $images = array();
83 $image_id = self::get_existing_image_id( $item['PictureName'] );
84 // If image exists, add it to the images array, else upload the image and add it to the images array
85 if ( $image_id ) {
86 $images[] = array( 'id' => $image_id );
87 } else {
88 $image_id = self::upload_image( $item['PictureUrl'], $item['PictureName'] );
89 // If image upload is successful, add it to the images array
90 if ( $image_id ) {
91 $images[] = array( 'id' => $image_id );
92 }
93 }
94 }
95 // Check if category exists with $item['ItemGroupDescription'], else create it and get the ID
96 if ( isset( $item['ItemGroupDescription'] ) ) {
97 $term = get_term_by( 'name', $item['ItemGroupDescription'], 'product_cat' );
98 $term_id = $term->term_id;
99 if ( empty( $term_id ) ) {
100 $term = wp_insert_term(
101 $item['ItemGroupDescription'],
102 'product_cat',
103 array(
104 'parent' => 0,
105 )
106 );
107 $term_id = $term['term_id'];
108 }
109 } else {
110 $term_id = 0;
111 }
112 return array(
113 'name' => $item['Description'],
114 'sku' => $item['Code'],
115 'status' => 'publish',
116 'type' => 'simple',
117 'regular_price' => (string) $item['StandardSalesPrice'],
118 'images' => $images,
119 'categories' => array(
120 array(
121 'id' => $term_id,
122 ),
123 ),
124 'meta_data' => array(
125 array(
126 'key' => 'eo_item_id',
127 'value' => $item['ID'],
128 ),
129 array(
130 'key' => '_cost_price',
131 'value' => $item['CostPriceStandard'],
132 ),
133 array(
134 'key' => 'eo_unit',
135 'value' => $item['Unit'],
136 ),
137 ),
138 );
139 }, $filtered_data )
140 );
141 break;
142
143 case 'customer':
144 $endpoint = '/wc/v3/customers/batch';
145 $filtered_data = array_filter( $data, function ($item) {
146 // Exclude item if customer already exists via email
147 return ! get_user_by( 'email', $item['Email'] );
148 } );
149 $payload = array(
150 'create' => array_map( function ($item) {
151 if ( empty( $item['Email'] ) ) {
152 return null;
153 }
154 $names = explode( ' ', $item['Name'] );
155 $first_name = $names[0] ?? '';
156 $last_name = $names[1] ?? '';
157 $address = array(
158 'first_name' => $first_name,
159 'last_name' => $last_name,
160 'address_1' => $item['AddressLine1'] ?? '',
161 'address_2' => $item['AddressLine2'] ?? '',
162 'city' => $item['City'] ?? '',
163 'country' => $item['Country'] ?? '',
164 'postcode' => $item['Postcode'] ?? '',
165 'phone' => $item['Phone'] ?? '',
166 'email' => $item['Email'],
167 );
168 return array(
169 'email' => $item['Email'],
170 'first_name' => $first_name,
171 'last_name' => $last_name,
172 'billing' => $address,
173 'shipping' => $address,
174 'meta_data' => array(
175 array(
176 'key' => 'eo_account_id',
177 'value' => $item['ID'],
178 ),
179 array(
180 'key' => 'eo_contact_id',
181 'value' => $item['MainContact'] ?? '',
182 ),
183 ),
184 );
185 }, array_filter( $filtered_data, fn( $item ) => ! empty( $item['Email'] ) ) )
186 );
187 break;
188
189 default:
190 return false;
191 }
192
193 if ( empty( $endpoint ) || empty( $payload['create'] ) ) {
194 return false;
195 }
196 // log the payload
197 // fwrite( $fd, print_r( $payload, true ) );
198 $request = new \WP_REST_Request( 'POST', $endpoint );
199 $request->set_body_params( $payload );
200 $response = rest_do_request( $request );
201 // error_log the message if response is error
202 if ( is_wp_error( $response ) ) {
203 error_log( 'Error ExactOnline Import: ' . $response->get_error_message() );
204 }
205 // fwrite( $fd, print_r( $response, true ) );
206 // fclose( stream: $fd );
207 return $response;
208 }
209
210 /**
211 * Update data based on Exact Online.
212 * @param string $type of provided data
213 * @return mixed
214 */
215 public static function update( string $type, array $data ) {
216 // $fd = fopen( __DIR__ . '/update.txt', 'w+' );
217 $endpoint = '';
218 $payload = array();
219
220 switch ( $type ) {
221 case 'product':
222 $filtered_data = array_filter( $data, function ($item) {
223 // Exclude item if product does not exists via SKU
224 return wc_get_product_id_by_sku( $item['Code'] );
225 } );
226 $endpoint = '/wc/v3/products/batch';
227 $payload = array(
228 'update' => array_map( function ($item) {
229 // Check if product exists via SKU, else get the product ID by title
230 $product_id = wc_get_product_id_by_sku( $item['Code'] ) ?: self::get_product_id_by_title( $item['Description'] );
231 // Featured image
232 if ( isset( $item['PictureName'] ) && $item['PictureName'] !== 'placeholder_item' ) {
233 $image_id = self::get_existing_image_id( $item['PictureName'] );
234 // If image exists, add it to the images array, else upload the image and add it to the images array
235 if ( $image_id ) {
236 set_post_thumbnail( $product_id, $image_id );
237 update_post_meta( $image_id, '_wp_attachment_image_alt', $item['Description'] );
238 update_post_meta( $product_id, '_thumbnail_id', $image_id );
239 wp_update_image_subsizes( $image_id );
240 } else {
241 $image_id = self::upload_image( $item['PictureUrl'], $item['PictureName'] );
242 // If image upload is successful, add it to the images array
243 if ( $image_id ) {
244 set_post_thumbnail( $product_id, $image_id );
245 update_post_meta( $image_id, '_wp_attachment_image_alt', $item['Description'] );
246 update_post_meta( $product_id, '_thumbnail_id', $image_id );
247 wp_update_image_subsizes( $image_id );
248 }
249 }
250 }
251 if ( isset( $item['ItemGroupDescription'] ) ) {
252 // Check if term exists by name
253 $term = get_term_by( 'name', $item['ItemGroupDescription'], 'product_cat' );
254 $term_id = $term->term_id;
255 if ( empty( $term_id ) ) {
256 $term = wp_insert_term(
257 $item['ItemGroupDescription'],
258 'product_cat',
259 array(
260 'parent' => 0,
261 )
262 );
263 $term_id = $term['term_id'];
264 }
265 // update product category directly
266 wp_set_object_terms( $product_id, $term_id, 'product_cat' );
267 }
268 return array(
269 'id' => $product_id,
270 'regular_price' => (string) $item['StandardSalesPrice'],
271 'meta_data' => array(
272 array( 'key' => 'eo_item_id', 'value' => $item['ID'] ),
273 array( 'key' => '_cost_price', 'value' => $item['CostPriceStandard'] ),
274 array( 'key' => 'eo_unit', 'value' => $item['Unit'] ),
275 ),
276 );
277 }, $filtered_data )
278 );
279 break;
280
281 case 'customer':
282 $endpoint = '/wc/v3/customers/batch';
283 $payload = array(
284 'update' => array_map( function ($item) {
285 $user = get_user_by( 'email', $item['Email'] );
286 return $user ? array(
287 'id' => $user->ID,
288 'meta_data' => array(
289 array( 'key' => 'eo_account_id', 'value' => $item['ID'] ),
290 array( 'key' => 'eo_contact_id', 'value' => $item['MainContact'] ?? '' ),
291 ),
292 ) : null;
293 }, array_filter( $data, fn( $item ) => get_user_by( 'email', $item['Email'] ) ) )
294 );
295 break;
296
297 case 'orders':
298 $endpoint = '/wc/v3/orders/batch';
299 $payload = array(
300 'update' => array_map( function ($item) {
301 return array(
302 'id' => $item['Description'],
303 'meta_data' => array(
304 array( 'key' => 'eo_order_id', 'value' => $item['OrderID'] ),
305 array( 'key' => 'eo_order_number', 'value' => $item['OrderNumber'] ),
306 ),
307 );
308 }, $data )
309 );
310 break;
311
312 default:
313 return false;
314 }
315 // fwrite( $fd, print_r( $payload, true ) );
316 if ( empty( $endpoint ) || empty( $payload['update'] ) ) {
317 return false;
318 }
319 $request = new \WP_REST_Request( 'POST', $endpoint );
320 $request->set_body_params( $payload );
321 $response = rest_do_request( $request );
322 // error_log the message if response is error
323 if ( is_wp_error( $response ) ) {
324 error_log( 'Error ExactOnline Update: ' . $response->get_error_message() );
325 }
326 // fwrite( $fd, print_r( $response, true ) );
327 // fclose( $fd );
328 return $response;
329 }
330
331 private static function get_product_id_by_title( string $product_title ) {
332 // Set up the query arguments
333 $args = array(
334 'post_type' => 'product',
335 'posts_per_page' => 1,
336 'fields' => 'ids',
337 's' => $product_title, // Search by product title
338 );
339
340 // Run the query
341 $query = new \WP_Query( $args );
342
343 // Get the product ID from the query results
344 $product_id = $query->post_count > 0 ? $query->posts[0] : 0;
345
346 // Reset post data
347 wp_reset_postdata();
348
349 return $product_id;
350 }
351
352 public static function get_payment_status_via_cron() {
353 // execute get_payment_status of ExactOnlineAjax class
354 $ajax = new ExactOnlineAjax();
355 $ajax->get_payment_status();
356 }
357
358 /**
359 * Process the payment status of the order via Exact Online.
360 * @param array $
361 * @return void
362 */
363 public static function cmbird_payment_status() {
364 $args = func_get_args();
365 $order_id = $args[0];
366 if ( empty( $order_id ) ) {
367 return;
368 }
369 $order = wc_get_order( $order_id );
370 $object = array();
371 $order_id = $order->get_id();
372 $object['OrderID'] = $order_id;
373 $customer_id = $order->get_customer_id();
374 // get the eo_account_id from the user meta
375 $object['AccountID'] = get_user_meta( $customer_id, 'eo_account_id', true );
376 $response = ( new CommerceBird() )->payment_status( $object );
377 // check response contains "Payment_Status" key
378 if ( ! isset( $response['Payment_Status'] ) ) {
379 return;
380 }
381 // if response is Paid then update the order status to completed
382 if ( 'Paid' === $response['Payment_Status'] ) {
383 // set order as paid
384 if ( $order->get_status() === 'completed' ) {
385 return;
386 }
387 $order->payment_complete();
388 $order->update_status( 'completed', __( 'Payment processed in Exact Online', 'commercebird' ) );
389 $order->save();
390 } elseif ( 'Unpaid' === $response['Payment_Status'] ) {
391 if ( $order->get_status() === 'on-hold' ) {
392 return;
393 }
394 $order->update_status( 'on-hold', __( 'Payment not processed in Exact Online', 'commercebird' ) );
395 $order->save();
396 }
397 }
398
399 /**
400 * Check if the image exists in the media library.
401 * @param string $picture_name
402 * @return $ID of the image if it exists, otherwise false
403 */
404 private static function get_existing_image_id( $picture_name ) {
405 global $wpdb;
406
407 // sanitize the picture name
408 $picture_name = sanitize_file_name( $picture_name );
409
410 $query = $wpdb->prepare( "
411 SELECT ID FROM {$wpdb->posts}
412 WHERE post_type = 'attachment'
413 AND post_title = %s
414 LIMIT 1
415 ", $picture_name );
416
417 $attachment_id = $wpdb->get_var( $query );
418 return $attachment_id ? $attachment_id : false;
419 }
420
421 /**
422 * Upload the product image from Exact Online.
423 * @param string $product_id
424 * @param string $picture_name
425 * @return $attachment_id of the uploaded image if successful, otherwise false
426 */
427 private static function upload_image( $picture_url, $picture_name ) {
428 require_once ABSPATH . 'wp-admin/includes/media.php';
429 require_once ABSPATH . 'wp-admin/includes/file.php';
430 require_once ABSPATH . 'wp-admin/includes/image.php';
431
432 // Fetch image content
433 $response = wp_safe_remote_get( $picture_url, array( 'timeout' => 10 ) );
434
435 if ( is_wp_error( $response ) ) {
436 error_log( 'Error fetching image: ' . $response->get_error_message() );
437 return false;
438 }
439
440 $image_data = wp_remote_retrieve_body( $response );
441 if ( empty( $image_data ) ) {
442 return false;
443 }
444
445 // Generate filename
446 $upload_dir = wp_upload_dir();
447 $filename = sanitize_file_name( $picture_name );
448 $file_path = $upload_dir['path'] . '/' . $filename;
449
450 // Save the file
451 file_put_contents( $file_path, $image_data );
452
453 // Check if file was saved correctly
454 if ( ! file_exists( $file_path ) ) {
455 return false;
456 }
457
458 // Prepare file array for WordPress
459 $file = array(
460 'name' => $filename,
461 'type' => mime_content_type( $file_path ),
462 'tmp_name' => $file_path,
463 'size' => filesize( $file_path ),
464 );
465
466 // Upload to WordPress Media Library
467 $attachment_id = media_handle_sideload( $file, 0 );
468
469 // Check for errors
470 if ( is_wp_error( $attachment_id ) ) {
471 error_log( 'Error attaching image: ' . $attachment_id->get_error_message() );
472 return false;
473 }
474
475 return $attachment_id;
476 }
477 }
478