PluginProbe
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster / 2.3.0
SellKit – Funnel builder and checkout optimizer for WooCommerce to sell more, faster v2.3.0
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 2.3.0, at includes/contact-segmentation/contact-data-updater.php

538 lines 13.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 $this->update_browser_language();
83
84 add_action( 'wp', [ $this, 'update_viewed_details' ], 10 );
85
86 $force_update = get_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, true );
87
88 if ( empty( $this->data ) ) {
89 $force_update = true;
90 }
91
92 $this->maybe_update_ip();
93 $this->maybe_update_locations();
94 $this->maybe_update_device();
95
96 if ( true == $force_update || empty( self::$new_data['user_type'] ) ) { //phpcs:ignore
97 $this->update_order_info();
98 }
99
100 add_action( 'wp', [ $this, 'maybe_update_data' ], 9999 );
101 }
102
103 /**
104 * If updating is required start to update.
105 *
106 * @since 1.1.0
107 */
108 public function maybe_update_data() {
109 if ( empty( self::$new_data ) ) {
110 return;
111 }
112
113 if ( ! is_user_logged_in() && is_array( self::$new_data ) ) {
114 setcookie( 'sellkit_contact_segmentation', wp_json_encode( array_merge( Contact_Data::$historical_data, self::$new_data ) ), 2147483647, '/' );
115 return;
116 }
117
118 if ( empty( Contact_Data::$historical_data ) && ! empty( self::$new_data ) ) {
119 self::$new_data['email'] = Contact_Data::get_user_email();
120
121 sellkit()->db->insert( 'contact_segmentation', self::$new_data );
122
123 update_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, false );
124
125 return;
126 }
127
128 self::update_contact_segmentation( self::$new_data, Contact_Data::get_user_email() );
129 }
130
131 /**
132 * Get the class instance.
133 *
134 * @since 1.1.0
135 */
136 public static function get_instance() {
137 if ( null === self::$instance ) {
138 self::$instance = new self();
139 }
140
141 return self::$instance;
142 }
143
144 /**
145 * Update utm data.
146 *
147 * @since 1.1.0
148 */
149 private function update_utm_data() {
150 $utm_source = $this->check_utm_data( 'utm_source' );
151 $utm_campaign = $this->check_utm_data( 'utm_campaign' );
152 $utm_medium = $this->check_utm_data( 'utm_medium' );
153 $utm_content = $this->check_utm_data( 'utm_content' );
154 $utm_term = $this->check_utm_data( 'utm_term' );
155
156 if ( $this->check_utm_is_new( $utm_source, 'utm_source' ) ) {
157 self::$new_data['utm_source'] = $utm_source;
158 }
159
160 if ( $this->check_utm_is_new( $utm_campaign, 'utm_campaign' ) ) {
161 self::$new_data['utm_campaign'] = $utm_campaign;
162 }
163
164 if ( $this->check_utm_is_new( $utm_content, 'utm_content' ) ) {
165 self::$new_data['utm_content'] = $utm_content;
166 }
167
168 if ( $this->check_utm_is_new( $utm_term, 'utm_term' ) ) {
169 self::$new_data['utm_term'] = $utm_term;
170 }
171
172 if ( $this->check_utm_is_new( $utm_medium, 'utm_medium' ) ) {
173 self::$new_data['utm_medium'] = $utm_medium;
174 }
175 }
176
177 /**
178 * Check utm data.
179 *
180 * @param string $utm Utm name.
181 * @since 1.2.8
182 * @return string|null
183 */
184 private function check_utm_data( $utm ) {
185 $value = sellkit_htmlspecialchars( INPUT_GET, $utm );
186
187 if ( ! empty( $value ) ) {
188 return $value;
189 }
190
191 $data = array_change_key_case( $_GET, CASE_LOWER ); //phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
192
193 if ( empty( $data[ $utm ] ) ) {
194 return $value;
195 }
196
197 return sanitize_text_field( $data[ $utm ] );
198 }
199
200 /**
201 * Update locations data from API.
202 *
203 * @since 1.1.0
204 */
205 private function maybe_update_locations() {
206 $ip = sellkit_get_ip();
207
208 if ( ! empty( $this->data['ip'] ) && $ip === $this->data['ip'] && isset( $this->data['visitor_country'] ) && ! empty( $this->data['visitor_country'] ) ) {
209 return;
210 }
211
212 $account_id = sellkit_get_option( 'geolocation_account_id' );
213 $api_key = sellkit_get_option( 'geolocation_licence_key' );
214
215 if ( empty( $api_key ) || empty( $account_id ) ) {
216 return;
217 }
218
219 $auth = base64_encode( $account_id . ':' . $api_key );
220
221 $response = wp_remote_get( 'https://geoip.maxmind.com/geoip/v2.1/city/' . $ip, [
222 'timeout' => 10,
223 'headers' => [
224 'Authorization' => 'Basic ' . $auth,
225 'Accept' => 'application/json',
226 ],
227 ] );
228
229 $response_code = wp_remote_retrieve_response_code( $response );
230
231 if ( ! is_wp_error( $response ) && ! empty( $response['body'] ) && 200 === (int) $response_code ) {
232 $location_data = json_decode( $response['body'] );
233
234 if ( isset( $location_data->country->names->en ) ) {
235 self::$new_data['visitor_country'] = strtolower( $location_data->country->names->en );
236 }
237
238 if ( isset( $location_data->city->names->en ) ) {
239 self::$new_data['visitor_city'] = strtolower( $location_data->city->names->en );
240 }
241 }
242
243 self::$new_data['updated_at'] = time();
244 }
245
246 /**
247 * Update user type.
248 *
249 * @since 1.2.3
250 */
251 private function update_user_type() {
252 if ( empty( $this->data['user_type'] ) ) {
253 self::$new_data['user_type'] = 'first_time_visitor';
254 }
255
256 if ( ! empty( $this->data['user_type'] ) ) {
257 self::$new_data['user_type'] = 'returning_visitor';
258 }
259 }
260
261 /**
262 * Updates device types.
263 *
264 * @since 1.1.0
265 */
266 private function maybe_update_device() {
267 $detector = new Mobile_Detect();
268 $current_display = 'desktop';
269
270 if ( $detector->isMobile() ) {
271 $current_display = 'mobile';
272 }
273
274 if ( $detector->isTablet() ) {
275 $current_display = 'tablet';
276 }
277
278 if ( ! empty( $this->data['user_device'] ) && $this->data['user_device'] === $current_display ) {
279 return;
280 }
281
282 self::$new_data['user_device'] = $current_display;
283 }
284
285 /**
286 * Update browser language.
287 *
288 * @since 1.1.0
289 */
290 private function update_browser_language() {
291 $data = [];
292
293 if ( ! empty( $_SERVER['HTTP_ACCEPT_LANGUAGE'] ) ) {
294 $data = explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ); // phpcs:ignore
295 }
296
297 $language = ! empty( $data[0] ) ? $data[0] : '';
298
299 if ( empty( $language ) ) {
300 return;
301 }
302
303 if ( empty( $this->data['browser_language'] ) || ( $this->data['browser_language'] !== $language ) ) {
304 self::$new_data['browser_language'] = $language;
305 }
306 }
307
308 /**
309 * Updates the IP if it's changed.
310 *
311 * @since 1.2.1
312 */
313 private function maybe_update_ip() {
314 $ip = sellkit_get_ip();
315
316 if ( ! empty( $this->data['ip'] ) && $this->data['ip'] === $ip ) {
317 return;
318 }
319
320 self::$new_data['ip'] = $ip;
321 }
322
323 /**
324 * Checks utm value.
325 *
326 * @param string $new_utm New Utm.
327 * @param string $type Utm type.
328 * @return bool
329 */
330 private function check_utm_is_new( $new_utm, $type ) {
331 if ( empty( $new_utm ) ) {
332 return false;
333 }
334
335 if ( empty( $this->data[ $type ] ) ) {
336 return true;
337 }
338
339 if ( $new_utm !== $this->data[ $type ] ) {
340 return true;
341 }
342 }
343
344 /**
345 * Updates user information.
346 *
347 * @since 1.1.0
348 */
349 public function update_order_info() {
350 $current_user = wp_get_current_user();
351
352 $args = [
353 'customer_id' => $current_user->ID,
354 'post_status' => 'completed',
355 'post_type' => 'shop_order',
356 'limit' => -1,
357 'orderby' => 'id',
358 'order' => 'DESC',
359 ];
360
361 if ( ! sellkit()->has_valid_dependencies() ) {
362 return;
363 }
364
365 $orders = wc_get_orders( $args );
366 $order_number = count( $orders );
367 $total_spent = 0;
368 $first_order = ! empty( end( $orders ) ) ? end( $orders ) : '';
369 $last_order = ! empty( $orders[0] ) ? $orders[0] : '';
370 $purchased_products = [];
371 $purchased_categories = [];
372 $billing_countries = [];
373 $billing_cities = [];
374 $shipping_countries = [];
375 $shipping_cities = [];
376
377 foreach ( $orders as $order ) {
378 $total_spent += $order->get_total();
379 $billing_countries[] = $order->get_billing_country();
380 $billing_cities[] = $order->get_billing_city();
381 $shipping_countries[] = $order->get_shipping_country();
382 $shipping_cities[] = $order->get_shipping_city();
383
384 foreach ( $order->get_items() as $item ) {
385 $purchased_products[] = $item['product_id'];
386
387 $term_list = wp_get_post_terms( $item['product_id'], 'product_cat', [ 'fields' => 'ids' ] );
388 $purchased_categories = array_merge( $purchased_categories, $term_list );
389 }
390 }
391
392 $first_order_date = $first_order && ! empty( $first_order->get_date_completed() ) ? strtotime( $first_order->get_date_completed()->date( 'Y-m-d H:i:s' ) ) : '';
393 $last_order_date = $last_order && ! empty( $last_order->get_date_completed() ) ? strtotime( $last_order->get_date_completed()->date( 'Y-m-d H:i:s' ) ) : '';
394
395 self::$new_data['total_order_count'] = $order_number;
396 self::$new_data['total_spent'] = $total_spent;
397 self::$new_data['first_order_date'] = $first_order_date;
398 self::$new_data['last_order_date'] = $last_order_date;
399 self::$new_data['purchased_product'] = array_unique( $purchased_products );
400 self::$new_data['billing_country'] = array_unique( $billing_countries );
401 self::$new_data['billing_city'] = array_unique( $billing_cities );
402 self::$new_data['shipping_country'] = array_unique( $shipping_countries );
403 self::$new_data['shipping_city'] = array_unique( $shipping_cities );
404 self::$new_data['purchased_category'] = array_unique( $purchased_categories );
405
406 if ( $order_number > 0 ) {
407 self::$new_data['user_type'] = 'customer';
408 return;
409 }
410
411 self::$new_data['user_type'] = 'lead';
412 }
413
414 /**
415 * Force update data.
416 *
417 * @since 1.1.0
418 * @param string $order_id Order id.
419 */
420 public static function force_update_data( $order_id ) {
421 $user_id = get_post_meta( $order_id, '_customer_user', true );
422
423 if ( empty( $user_id ) ) {
424 $order = wc_get_order( $order_id );
425 $user = $order->get_billing_email();
426 $user_id = email_exists( $user );
427 }
428
429 if ( ! empty( $user_id ) ) {
430 update_user_meta( $user_id, self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, true );
431 }
432 }
433
434 /**
435 * Update url query string.
436 *
437 * @since 1.2.3
438 */
439 public function update_url_query_string() {
440 $old_vars = (array) Contact_Data::$historical_data;
441
442 $invalid_vars = [
443 'doing_wp_cron',
444 'customize_changeset_uuid',
445 'customize_theme',
446 'customize_messenger_channel',
447 'elementor_library',
448 'elementor-preview',
449 'ver',
450 'jupiterx-layout-builder-type',
451 'jupiterx-layout-builder-preview',
452 'preview-id',
453 ];
454
455 $valid_vars = [];
456 $new_vars = [];
457
458 $query_vars = $_GET; //phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
459
460 if ( ! isset( $old_vars['url_query_string'] ) ) {
461 $old_vars['url_query_string'] = [];
462 }
463
464 foreach ( $query_vars as $key => $value ) {
465 if ( is_object( $old_vars['url_query_string'] ) ) {
466 $old_vars['url_query_string'] = (array) $old_vars['url_query_string'];
467 }
468
469 if (
470 in_array( $key, $invalid_vars, true ) ||
471 in_array( $value, $old_vars['url_query_string'], true )
472 ) {
473 continue;
474 }
475
476 $valid_vars[] = sanitize_text_field( $key );
477 }
478
479 if ( ! empty( $old_vars['url_query_string'] ) ) {
480 $new_vars = $old_vars['url_query_string'];
481 }
482
483 if ( ! empty( $valid_vars ) ) {
484 self::$new_data['url_query_string'] = array_unique( array_merge( $valid_vars, $new_vars ) );
485 }
486 }
487
488 /**
489 * Update viewed products. it should be done in wp hook so we have to update the data directly.
490 * it would be better to update these data with the main updating process.
491 *
492 * @since 1.1.0
493 */
494 public function update_viewed_details() {
495 $old_data = Contact_Data::$historical_data;
496
497 if ( is_singular( 'product' ) ) {
498 $product_id = get_the_ID();
499 $new_products = [];
500
501 if ( ! empty( $old_data['viewed_product'] ) ) {
502 $new_products = $old_data['viewed_product'];
503 }
504
505 if ( empty( $old_data['viewed_product'] ) || ! in_array( $product_id, $old_data['viewed_product'], false ) ) { // phpcs:ignore
506 self::$new_data['viewed_product'] = array_unique( array_merge( [ $product_id ], $new_products ) );
507 }
508 }
509
510 $queries = get_queried_object();
511 if ( ! empty( $queries->taxonomy ) && 'product_cat' === $queries->taxonomy ) {
512 $current_cat_id = $queries->term_id;
513 $new_cats = [];
514
515 if ( ! empty( $old_data['viewed_category'] ) ) {
516 $new_cats = $old_data['viewed_category'];
517 }
518
519 if ( empty( $old_data['viewed_category'] ) || ! in_array( $current_cat_id, $old_data['viewed_category'], false ) ) { // phpcs:ignore
520 self::$new_data['viewed_category'] = array_unique( array_merge( [ $current_cat_id ], $new_cats ) );
521 }
522 }
523 }
524
525 /**
526 * Updates contact segmentation data.
527 *
528 * @since 1.1.0
529 * @param array $data Data.
530 * @param string $email Email.
531 */
532 public static function update_contact_segmentation( $data, $email ) {
533 sellkit()->db->update( 'contact_segmentation', $data, [ 'email' => $email ] );
534
535 update_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, false );
536 }
537 }
538