PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 1.2.5
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v1.2.5
2.7.0 2.6.0 trunk 1.1.0 1.1.4 1.2.1 1.2.2 1.2.3 1.2.5 1.2.9 1.3.1 1.3.2 1.5.0 1.5.1 1.5.4 1.5.7 1.5.8 1.5.9 1.6.2 1.6.5 1.6.8 1.7.2 1.7.4 1.7.5 1.7.9 All 43 releases
sellkit / includes / contact-segmentation / contact-data-updater.php

contact-data-updater.php in SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster 1.2.5, at includes/contact-segmentation/contact-data-updater.php

501 lines 12.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Sellkit\Contact_Segmentation;
4
5 use Sellkit\Contact_Segmentation\libraries\Mobile_Detect;
6 use Sellkit\Database;
7
8 defined( 'ABSPATH' ) || die();
9
10 /**
11 * Class Contact Data Updater.
12 *
13 * @package Sellkit\Contact_Segmentation
14 * @SuppressWarnings(ExcessiveClassComplexity)
15 * @since 1.1.0
16 */
17 class Contact_Data_Updater {
18
19 const SELLKIT_CONTACT_SEGMENTATION_LOCATION_API = 'sjYudEYJDjBMDDoKyxHd'; // TODO we should get it dynamically.
20 const SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE = 'sellkit_contact_segmentation_force_update';
21
22 /**
23 * Class instance.
24 *
25 * @since 1.1.0
26 * @var Contact_Data_Updater
27 */
28 private static $instance = null;
29
30 /**
31 * Contact data.
32 *
33 * @since 1.1.0
34 * @var array Contact data.
35 */
36 public $data;
37
38 /**
39 * New contact data.
40 *
41 * @since 1.1.0
42 * @var array Contact data.
43 */
44 public static $new_data = [];
45
46 /**
47 * Check if data is expired or not.
48 *
49 * @since 1.1.0
50 * @var boolean Is data expired.
51 */
52 public $is_expired = false;
53
54 /**
55 * Contact_Data constructor.
56 *
57 * @since 1.1.0
58 */
59 public function __construct() {
60 if ( wp_doing_ajax() ) {
61 return;
62 }
63
64 $this->data = Contact_Data::$historical_data;
65
66 if ( ! is_user_logged_in() ) {
67 $this->update_browser_language();
68 $this->maybe_update_device();
69 $this->update_utm_data();
70 $this->maybe_update_ip();
71 $this->maybe_update_locations();
72 $this->update_user_type();
73 $this->update_url_query_string();
74
75 add_action( 'wp', [ $this, 'update_viewed_details' ], 10 );
76 add_action( 'wp', [ $this, 'maybe_update_data' ], 9999 );
77 return;
78 }
79
80 $this->update_utm_data();
81 $this->update_url_query_string();
82
83 add_action( 'wp', [ $this, 'update_viewed_details' ], 10 );
84
85 $force_update = get_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, true );
86
87 if ( empty( $this->data ) ) {
88 $force_update = true;
89 }
90
91 $this->maybe_update_ip();
92 $this->maybe_update_locations();
93 $this->maybe_update_device();
94
95 if ( true == $force_update ) { //phpcs:ignore
96 $this->update_order_info();
97 }
98
99 add_action( 'wp', [ $this, 'maybe_update_data' ], 9999 );
100 }
101
102 /**
103 * If updating is required start to update.
104 *
105 * @since 1.1.0
106 */
107 public function maybe_update_data() {
108 if ( empty( self::$new_data ) ) {
109 return;
110 }
111
112 if ( ! is_user_logged_in() && is_array( self::$new_data ) ) {
113 setcookie( 'sellkit_contact_segmentation', wp_json_encode( array_merge( Contact_Data::$historical_data, self::$new_data ) ), 2147483647, '/' );
114 return;
115 }
116
117 if ( empty( Contact_Data::$historical_data ) && ! empty( self::$new_data ) ) {
118 self::$new_data['email'] = Contact_Data::get_user_email();
119
120 sellkit()->db->insert( 'contact_segmentation', self::$new_data );
121
122 update_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, false );
123
124 return;
125 }
126
127 self::update_contact_segmentation( self::$new_data, Contact_Data::get_user_email() );
128 }
129
130 /**
131 * Get the class instance.
132 *
133 * @since 1.1.0
134 */
135 public static function get_instance() {
136 if ( null === self::$instance ) {
137 self::$instance = new self();
138 }
139
140 return self::$instance;
141 }
142
143 /**
144 * Update utm data.
145 *
146 * @since 1.1.0
147 */
148 private function update_utm_data() {
149 $utm_source = sellkit_htmlspecialchars( INPUT_GET, 'utm_source' );
150 $utm_campaign = sellkit_htmlspecialchars( INPUT_GET, 'utm_campaign' );
151 $utm_medium = sellkit_htmlspecialchars( INPUT_GET, 'utm_medium' );
152 $utm_content = sellkit_htmlspecialchars( INPUT_GET, 'utm_content' );
153 $utm_term = sellkit_htmlspecialchars( INPUT_GET, 'utm_term' );
154
155 if ( $this->check_utm_is_new( $utm_source, 'utm_source' ) ) {
156 self::$new_data['utm_source'] = $utm_source;
157 }
158
159 if ( $this->check_utm_is_new( $utm_campaign, 'utm_campaign' ) ) {
160 self::$new_data['utm_campaign'] = $utm_campaign;
161 }
162
163 if ( $this->check_utm_is_new( $utm_content, 'utm_content' ) ) {
164 self::$new_data['utm_content'] = $utm_content;
165 }
166
167 if ( $this->check_utm_is_new( $utm_term, 'utm_term' ) ) {
168 self::$new_data['utm_term'] = $utm_term;
169 }
170
171 if ( $this->check_utm_is_new( $utm_medium, 'utm_medium' ) ) {
172 self::$new_data['utm_medium'] = $utm_medium;
173 }
174 }
175
176 /**
177 * Update locations data from API.
178 *
179 * @since 1.1.0
180 */
181 private function maybe_update_locations() {
182 $ip = sellkit_get_ip();
183
184 if ( ! empty( $this->data['ip'] ) && $ip === $this->data['ip'] ) {
185 return;
186 }
187
188 $api_key = sellkit_get_option( 'geolocation_api_key' );
189
190 if ( empty( $api_key ) ) {
191 return;
192 }
193
194 $response = wp_remote_get( 'https://location.stg.growmatik.ai/v1', [
195 'timeout' => 10,
196 'body' => [
197 'apiKey' => $api_key,
198 'ip' => $ip,
199 ],
200 ] );
201
202 $response_code = wp_remote_retrieve_response_code( $response );
203
204 if ( ! is_wp_error( $response ) && ! empty( $response['body'] ) && 200 === (int) $response_code ) {
205 $location_data = json_decode( $response['body'] );
206 self::$new_data['visitor_country'] = strtolower( $location_data->country );
207 self::$new_data['visitor_city'] = strtolower( $location_data->city );
208 }
209
210 self::$new_data['updated_at'] = time();
211 }
212
213 /**
214 * Update user type.
215 *
216 * @since 1.2.3
217 */
218 private function update_user_type() {
219 if ( empty( $this->data['user_type'] ) ) {
220 self::$new_data['user_type'] = 'first_time_visitor';
221 }
222
223 if ( ! empty( $this->data['user_type'] ) ) {
224 self::$new_data['user_type'] = 'returning_visitor';
225 }
226 }
227
228 /**
229 * Updates device types.
230 *
231 * @since 1.1.0
232 */
233 private function maybe_update_device() {
234 $detector = new Mobile_Detect();
235 $current_display = 'desktop';
236
237 if ( $detector->isMobile() ) {
238 $current_display = 'mobile';
239 }
240
241 if ( $detector->isTablet() ) {
242 $current_display = 'tablet';
243 }
244
245 if ( ! empty( $this->data['user_device'] ) && $this->data['user_device'] === $current_display ) {
246 return;
247 }
248
249 self::$new_data['user_device'] = $current_display;
250 }
251
252 /**
253 * Update browser language.
254 *
255 * @since 1.1.0
256 */
257 private function update_browser_language() {
258 $data = [];
259
260 if ( ! empty( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
261 $data = explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ); // phpcs:ignore
262 }
263
264 $language = ! empty( $data[0] ) ? $data[0] : '';
265
266 if ( empty( $language ) ) {
267 return;
268 }
269
270 if ( empty( $this->data['browser_language'] ) || ( $this->data['browser_language'] !== $language ) ) {
271 self::$new_data['browser_language'] = $language;
272 }
273 }
274
275 /**
276 * Updates the IP if it's changed.
277 *
278 * @since 1.2.1
279 */
280 private function maybe_update_ip() {
281 $ip = sellkit_get_ip();
282
283 if ( ! empty( $this->data['ip'] ) && $this->data['ip'] === $ip ) {
284 return;
285 }
286
287 self::$new_data['ip'] = $ip;
288 }
289
290 /**
291 * Checks utm value.
292 *
293 * @param string $new_utm New Utm.
294 * @param string $type Utm type.
295 * @return bool
296 */
297 private function check_utm_is_new( $new_utm, $type ) {
298 if ( empty( $new_utm ) ) {
299 return false;
300 }
301
302 if ( empty( $this->data[ $type ] ) ) {
303 return true;
304 }
305
306 if ( $new_utm !== $this->data[ $type ] ) {
307 return true;
308 }
309 }
310
311 /**
312 * Updates user information.
313 *
314 * @since 1.1.0
315 */
316 public function update_order_info() {
317 $current_user = wp_get_current_user();
318
319 $args = [
320 'customer_id' => $current_user->ID,
321 'post_status' => 'completed',
322 'post_type' => 'shop_order',
323 'limit' => -1,
324 'orderby' => 'id',
325 'order' => 'DESC',
326 ];
327
328 $orders = wc_get_orders( $args );
329 $order_number = count( $orders );
330 $total_spent = 0;
331 $first_order = ! empty( end( $orders ) ) ? end( $orders ) : '';
332 $last_order = ! empty( $orders[0] ) ? $orders[0] : '';
333 $purchased_products = [];
334 $purchased_categories = [];
335 $billing_countries = [];
336 $billing_cities = [];
337 $shipping_countries = [];
338 $shipping_cities = [];
339
340 foreach ( $orders as $order ) {
341 $total_spent += $order->get_total();
342 $billing_countries[] = $order->get_billing_country();
343 $billing_cities[] = $order->get_billing_city();
344 $shipping_countries[] = $order->get_shipping_country();
345 $shipping_cities[] = $order->get_shipping_city();
346
347 foreach ( $order->get_items() as $item ) {
348 $purchased_products[] = $item['product_id'];
349
350 $term_list = wp_get_post_terms( $item['product_id'], 'product_cat', [ 'fields' => 'ids' ] );
351 $purchased_categories = array_merge( $purchased_categories, $term_list );
352 }
353 }
354
355 $first_order_date = $first_order ? strtotime( $first_order->get_date_completed()->date( 'Y-m-d H:i:s' ) ) : '';
356 $last_order_date = $last_order ? strtotime( $last_order->get_date_completed()->date( 'Y-m-d H:i:s' ) ) : '';
357
358 self::$new_data['total_order_count'] = $order_number;
359 self::$new_data['total_spent'] = $total_spent;
360 self::$new_data['first_order_date'] = $first_order_date;
361 self::$new_data['last_order_date'] = $last_order_date;
362 self::$new_data['purchased_product'] = array_unique( $purchased_products );
363 self::$new_data['billing_country'] = array_unique( $billing_countries );
364 self::$new_data['billing_city'] = array_unique( $billing_cities );
365 self::$new_data['shipping_country'] = array_unique( $shipping_countries );
366 self::$new_data['shipping_city'] = array_unique( $shipping_cities );
367 self::$new_data['purchased_category'] = array_unique( $purchased_categories );
368
369 if ( $order_number > 0 ) {
370 self::$new_data['user_type'] = 'customer';
371 return;
372 }
373
374 self::$new_data['user_type'] = 'lead';
375 }
376
377 /**
378 * Force update data.
379 *
380 * @since 1.1.0
381 * @param string $order_id Order id.
382 */
383 public static function force_update_data( $order_id ) {
384 $user_id = get_post_meta( $order_id, '_customer_user', true );
385
386 if ( empty( $user_id ) ) {
387 $order = wc_get_order( $order_id );
388 $user = $order->get_billing_email();
389 $user_id = email_exists( $user );
390 }
391
392 if ( ! empty( $user_id ) ) {
393 update_user_meta( $user_id, self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, true );
394 }
395 }
396
397 /**
398 * Update url query string.
399 *
400 * @since 1.2.3
401 */
402 public function update_url_query_string() {
403 $old_vars = (array) Contact_Data::$historical_data;
404
405 $invalid_vars = [
406 'doing_wp_cron',
407 'customize_changeset_uuid',
408 'customize_theme',
409 'customize_messenger_channel',
410 'elementor_library',
411 'elementor-preview',
412 'ver',
413 'jupiterx-layout-builder-type',
414 'jupiterx-layout-builder-preview',
415 'preview-id',
416 ];
417
418 $valid_vars = [];
419 $new_vars = [];
420
421 $query_vars = $_GET; //phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
422
423 if ( ! isset( $old_vars['url_query_string'] ) ) {
424 $old_vars['url_query_string'] = [];
425 }
426
427 foreach ( $query_vars as $key => $value ) {
428 if ( is_object( $old_vars['url_query_string'] ) ) {
429 $old_vars['url_query_string'] = (array) $old_vars['url_query_string'];
430 }
431
432 if (
433 in_array( $key, $invalid_vars, true ) ||
434 in_array( $value, $old_vars['url_query_string'], true )
435 ) {
436 continue;
437 }
438
439 $valid_vars[] = sanitize_text_field( $key );
440 }
441
442 if ( ! empty( $old_vars['url_query_string'] ) ) {
443 $new_vars = $old_vars['url_query_string'];
444 }
445
446 if ( ! empty( $valid_vars ) ) {
447 self::$new_data['url_query_string'] = array_unique( array_merge( $valid_vars, $new_vars ) );
448 }
449 }
450
451 /**
452 * Update viewed products. it should be done in wp hook so we have to update the data directly.
453 * it would be better to update these data with the main updating process.
454 *
455 * @since 1.1.0
456 */
457 public function update_viewed_details() {
458 $old_data = Contact_Data::$historical_data;
459
460 if ( is_singular( 'product' ) ) {
461 $product_id = get_the_ID();
462 $new_products = [];
463
464 if ( ! empty( $old_data['viewed_product'] ) ) {
465 $new_products = $old_data['viewed_product'];
466 }
467
468 if ( empty( $old_data['viewed_product'] ) || ! in_array( $product_id, $old_data['viewed_product'], false ) ) { // phpcs:ignore
469 self::$new_data['viewed_product'] = array_unique( array_merge( [ $product_id ], $new_products ) );
470 }
471 }
472
473 $queries = get_queried_object();
474 if ( ! empty( $queries->taxonomy ) && 'product_cat' === $queries->taxonomy ) {
475 $current_cat_id = $queries->term_id;
476 $new_cats = [];
477
478 if ( ! empty( $old_data['viewed_category'] ) ) {
479 $new_cats = $old_data['viewed_category'];
480 }
481
482 if ( empty( $old_data['viewed_category'] ) || ! in_array( $current_cat_id, $old_data['viewed_category'], false ) ) { // phpcs:ignore
483 self::$new_data['viewed_category'] = array_unique( array_merge( [ $current_cat_id ], $new_cats ) );
484 }
485 }
486 }
487
488 /**
489 * Updates contact segmentation data.
490 *
491 * @since 1.1.0
492 * @param array $data Data.
493 * @param string $email Email.
494 */
495 public static function update_contact_segmentation( $data, $email ) {
496 sellkit()->db->update( 'contact_segmentation', $data, [ 'email' => $email ] );
497
498 update_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, false );
499 }
500 }
501