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

421 lines 11.1 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_UPDATE_PERIOD = 1800;
20 const SELLKIT_CONTACT_SEGMENTATION_LOCATION_API = 'sjYudEYJDjBMDDoKyxHd'; // TODO we should get it dynamically.
21 const SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE = 'sellkit_contact_segmentation_force_update';
22
23 /**
24 * Class instance.
25 *
26 * @since 1.1.0
27 * @var Contact_Data_Updater
28 */
29 private static $instance = null;
30
31 /**
32 * Contact data.
33 *
34 * @since 1.1.0
35 * @var array Contact data.
36 */
37 public $data;
38
39 /**
40 * New contact data.
41 *
42 * @since 1.1.0
43 * @var array Contact data.
44 */
45 public static $new_data = [];
46
47 /**
48 * Check if data is expired or not.
49 *
50 * @since 1.1.0
51 * @var boolean Is data expired.
52 */
53 public $is_expired = false;
54
55 /**
56 * Contact_Data constructor.
57 *
58 * @since 1.1.0
59 */
60 public function __construct() {
61 $this->data = Contact_Data::$historical_data;
62
63 if ( ! is_user_logged_in() ) {
64 $this->check_data_expiration();
65 $this->update_browser_language();
66 $this->update_device();
67 $this->update_utm_data();
68
69 // Update historical data.
70 if ( $this->is_expired ) {
71 $this->update_locations();
72 }
73
74 add_action( 'wp', [ $this, 'update_viewed_details' ], 10 );
75 add_action( 'wp', [ $this, 'maybe_update_data' ], 9999 );
76 return;
77 }
78
79 $this->check_data_expiration();
80 $this->update_utm_data();
81
82 add_action( 'wp', [ $this, 'update_viewed_details' ], 10 );
83
84 $force_update = get_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, true );
85
86 if ( empty( $this->data ) ) {
87 $this->is_expired = true;
88 $force_update = true;
89 }
90
91 // Update historical data.
92 if ( $this->is_expired ) {
93 $this->update_locations();
94 $this->update_device();
95 }
96
97 if ( true == $force_update ) { //phpcs:ignore
98 $this->update_order_info();
99 }
100
101 add_action( 'wp', [ $this, 'maybe_update_data' ], 9999 );
102 }
103
104 /**
105 * If updating is required start to update.
106 *
107 * @since 1.1.0
108 */
109 public function maybe_update_data() {
110 if ( empty( self::$new_data ) ) {
111 return;
112 }
113
114 if ( ! is_user_logged_in() && is_array( self::$new_data ) ) {
115 setcookie( 'sellkit_contact_segmentation', wp_json_encode( array_merge( Contact_Data::$historical_data, self::$new_data ) ), 2147483647, '/' );
116 return;
117 }
118
119 if ( empty( Contact_Data::$historical_data ) && ! empty( self::$new_data ) ) {
120 self::$new_data['email'] = Contact_Data::get_user_email();
121
122 sellkit()->db->insert( 'contact_segmentation', self::$new_data );
123
124 update_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, false );
125
126 return;
127 }
128
129 self::update_contact_segmentation( self::$new_data, Contact_Data::get_user_email() );
130 }
131
132 /**
133 * Get the class instance.
134 *
135 * @since 1.1.0
136 */
137 public static function get_instance() {
138 if ( null === self::$instance ) {
139 self::$instance = new self();
140 }
141
142 return self::$instance;
143 }
144
145 /**
146 * Update utm data.
147 *
148 * @since 1.1.0
149 */
150 private function update_utm_data() {
151 $utm_source = filter_input( INPUT_GET, 'utm_source', FILTER_SANITIZE_STRING );
152 $utm_campaign = filter_input( INPUT_GET, 'utm_campaign', FILTER_SANITIZE_STRING );
153 $utm_medium = filter_input( INPUT_GET, 'utm_medium', FILTER_SANITIZE_STRING );
154 $utm_content = filter_input( INPUT_GET, 'utm_content', FILTER_SANITIZE_STRING );
155 $utm_term = filter_input( INPUT_GET, 'utm_term', FILTER_SANITIZE_STRING );
156
157 if ( $this->check_utm_is_new( $utm_source, 'utm_source' ) ) {
158 self::$new_data['utm_source'] = $utm_source;
159 }
160
161 if ( $this->check_utm_is_new( $utm_campaign, 'utm_campaign' ) ) {
162 self::$new_data['utm_campaign'] = $utm_campaign;
163 }
164
165 if ( $this->check_utm_is_new( $utm_content, 'utm_content' ) ) {
166 self::$new_data['utm_content'] = $utm_content;
167 }
168
169 if ( $this->check_utm_is_new( $utm_term, 'utm_term' ) ) {
170 self::$new_data['utm_term'] = $utm_term;
171 }
172
173 if ( $this->check_utm_is_new( $utm_medium, 'utm_medium' ) ) {
174 self::$new_data['utm_medium'] = $utm_medium;
175 }
176 }
177
178 /**
179 * Update locations data from API.
180 *
181 * @since 1.1.0
182 */
183 private function update_locations() {
184 $ip = sellkit_get_ip();
185
186 // Adding test api for localhost.
187 if ( '127.0.0.1' === $ip ) {
188 $ip = '46.166.182.68';
189 }
190
191 $response = wp_remote_get( 'https://location.stg.growmatik.ai/v1', [
192 'timeout' => 10,
193 'body' => [
194 'apiKey' => self::SELLKIT_CONTACT_SEGMENTATION_LOCATION_API,
195 'ip' => $ip,
196 ],
197 ] );
198
199 $response_code = wp_remote_retrieve_response_code( $response );
200
201 if ( ! is_wp_error( $response ) && ! empty( $response['body'] ) && 200 === (int) $response_code ) {
202 $location_data = json_decode( $response['body'] );
203 self::$new_data['visitor_country'] = strtolower( $location_data->country );
204 self::$new_data['visitor_city'] = strtolower( $location_data->city );
205 self::$new_data['updated_at'] = time();
206 }
207 }
208
209 /**
210 * Updates device types.
211 *
212 * @since 1.1.0
213 */
214 private function update_device() {
215 $detector = new Mobile_Detect();
216
217 if ( $detector->isMobile() ) {
218 self::$new_data['user_device'] = 'mobile';
219 return;
220 }
221
222 if ( $detector->isTablet() ) {
223 self::$new_data['user_device'] = 'tablet';
224 return;
225 }
226
227 self::$new_data['user_device'] = 'desktop';
228 }
229
230 /**
231 * Update browser language.
232 *
233 * @since 1.1.0
234 */
235 private function update_browser_language() {
236 $data = explode( ',', $_SERVER['HTTP_ACCEPT_LANGUAGE'] ); // phpcs:ignore
237 $language = ! empty( $data[0] ) ? $data[0] : '';
238
239 if ( empty( $language ) ) {
240 return;
241 }
242
243 if ( empty( $this->data['browser_language'] ) || ( $this->data['browser_language'] !== $language ) ) {
244 self::$new_data['browser_language'] = $language;
245 }
246 }
247
248 /**
249 * Checks utm value.
250 *
251 * @param string $new_utm New Utm.
252 * @param string $type Utm type.
253 * @return bool
254 */
255 private function check_utm_is_new( $new_utm, $type ) {
256 if ( empty( $new_utm ) ) {
257 return false;
258 }
259
260 if ( empty( $this->data[ $type ] ) ) {
261 return true;
262 }
263
264 if ( $new_utm !== $this->data[ $type ] ) {
265 return true;
266 }
267 }
268
269 /**
270 * Checks if data is expired or not.
271 *
272 * @since 1.1.0
273 */
274 private function check_data_expiration() {
275 if ( empty( $this->data['updated_at'] ) ) {
276 $this->is_expired = true;
277 return;
278 }
279
280 if ( $this->data['updated_at'] <= time() - self::SELLKIT_CONTACT_SEGMENTATION_UPDATE_PERIOD ) {
281 $this->is_expired = true;
282 }
283 }
284
285 /**
286 * Updates user information.
287 *
288 * @since 1.1.0
289 */
290 public function update_order_info() {
291 $current_user = wp_get_current_user();
292
293 $args = [
294 'customer_id' => $current_user->ID,
295 'post_status' => 'completed',
296 'post_type' => 'shop_order',
297 'limit' => -1,
298 'orderby' => 'id',
299 'order' => 'DESC',
300 ];
301
302 $orders = wc_get_orders( $args );
303 $order_number = count( $orders );
304 $total_spent = 0;
305 $first_order = ! empty( end( $orders ) ) ? end( $orders ) : '';
306 $last_order = ! empty( $orders[0] ) ? $orders[0] : '';
307 $purchased_products = [];
308 $purchased_categories = [];
309 $billing_countries = [];
310 $billing_cities = [];
311 $shipping_countries = [];
312 $shipping_cities = [];
313
314 foreach ( $orders as $order ) {
315 $total_spent += $order->get_total();
316 $billing_countries[] = $order->get_billing_country();
317 $billing_cities[] = $order->get_billing_city();
318 $shipping_countries[] = $order->get_shipping_country();
319 $shipping_cities[] = $order->get_shipping_city();
320
321 foreach ( $order->get_items() as $item ) {
322 $purchased_products[] = $item['product_id'];
323
324 $term_list = wp_get_post_terms( $item['product_id'], 'product_cat', [ 'fields' => 'ids' ] );
325 $purchased_categories = array_merge( $purchased_categories, $term_list );
326 }
327 }
328
329 $first_order_date = $first_order ? strtotime( $first_order->get_date_completed()->date( 'Y-m-d H:i:s' ) ) : '';
330 $last_order_date = $last_order ? strtotime( $last_order->get_date_completed()->date( 'Y-m-d H:i:s' ) ) : '';
331
332 self::$new_data['total_order_count'] = $order_number;
333 self::$new_data['total_spent'] = $total_spent;
334 self::$new_data['first_order_date'] = $first_order_date;
335 self::$new_data['last_order_date'] = $last_order_date;
336 self::$new_data['purchased_product'] = array_unique( $purchased_products );
337 self::$new_data['billing_country'] = array_unique( $billing_countries );
338 self::$new_data['billing_city'] = array_unique( $billing_cities );
339 self::$new_data['shipping_country'] = array_unique( $shipping_countries );
340 self::$new_data['shipping_city'] = array_unique( $shipping_cities );
341 self::$new_data['purchased_category'] = array_unique( $purchased_categories );
342
343 if ( $order_number > 0 ) {
344 self::$new_data['user_type'] = 'customer';
345 return;
346 }
347
348 self::$new_data['user_type'] = 'lead';
349 }
350
351 /**
352 * Force update data.
353 *
354 * @since 1.1.0
355 * @param string $order_id Order id.
356 */
357 public static function force_update_data( $order_id ) {
358 $user_id = get_post_meta( $order_id, '_customer_user', true );
359
360 if ( empty( $user_id ) ) {
361 $order = wc_get_order( $order_id );
362 $user = $order->get_billing_email();
363 $user_id = email_exists( $user );
364 }
365
366 if ( ! empty( $user_id ) ) {
367 update_user_meta( $user_id, self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, true );
368 }
369 }
370
371 /**
372 * Update viewed products. it should be done in wp hook so we have to update the data directly.
373 * it would be better to update these data with the main updating process.
374 *
375 * @since 1.1.0
376 */
377 public function update_viewed_details() {
378 $old_data = Contact_Data::$historical_data;
379
380 if ( is_singular( 'product' ) ) {
381 $product_id = get_the_ID();
382 $new_products = [];
383
384 if ( ! empty( $old_data['viewed_product'] ) ) {
385 $new_products = $old_data['viewed_product'];
386 }
387
388 if ( empty( $old_data['viewed_product'] ) || ! in_array( $product_id, $old_data['viewed_product'], false ) ) { // phpcs:ignore
389 self::$new_data['viewed_product'] = array_unique( array_merge( [ $product_id ], $new_products ) );
390 }
391 }
392
393 $queries = get_queried_object();
394 if ( ! empty( $queries->taxonomy ) && 'product_cat' === $queries->taxonomy ) {
395 $current_cat_id = $queries->term_id;
396 $new_cats = [];
397
398 if ( ! empty( $old_data['viewed_category'] ) ) {
399 $new_cats = $old_data['viewed_category'];
400 }
401
402 if ( empty( $old_data['viewed_category'] ) || ! in_array( $current_cat_id, $old_data['viewed_category'], false ) ) { // phpcs:ignore
403 self::$new_data['viewed_category'] = array_unique( array_merge( [ $current_cat_id ], $new_cats ) );
404 }
405 }
406 }
407
408 /**
409 * Updates contact segmentation data.
410 *
411 * @since 1.1.0
412 * @param array $data Data.
413 * @param string $email Email.
414 */
415 public static function update_contact_segmentation( $data, $email ) {
416 sellkit()->db->update( 'contact_segmentation', $data, [ 'email' => $email ] );
417
418 update_user_meta( get_current_user_id(), self::SELLKIT_CONTACT_SEGMENTATION_FORCE_UPDATE, false );
419 }
420 }
421