PluginProbe
Nudgify Social Proof / trunk
Nudgify Social Proof vtrunk
1.3.18 1.3.17 trunk 1.0.10 1.0.11 1.0.13 1.0.14 1.0.15 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.1.2 1.1.3 1.1.4 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 All 45 releases
nudgify / nudgify.php

nudgify.php in Nudgify Social Proof trunk, at nudgify.php

964 lines 34.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /*
4 Plugin Name: Nudgify Social Proof
5 Description: Install Nudgify on your WordPress website in less then 10 seconds. Integrate unique tracking code of Nudgify into every page of your website in one click.
6 Author: Nudgify
7 Version: 1.3.18
8 Author URI: https://nudgify.com
9 License: GPLv2
10 Plugin URI: https://nudgify.com/
11 */
12
13 defined('ABSPATH') or exit('Restricted access!');
14
15 define('NUDGIFY_PLUGIN_VERSION', '1.3.15');
16 define('NUDGIFY_PLUGIN_SLUG', 'nudgify');
17 define('NUDGIFY_PLUGIN_URL', plugin_dir_url(__FILE__));
18 define('NUDGIFY_PLUGIN_DIR', str_replace('\\', '/', dirname(__FILE__)));
19
20 require_once NUDGIFY_PLUGIN_DIR.'/includes/settings.php';
21 require_once NUDGIFY_PLUGIN_DIR.'/includes/functions.php';
22 require_once NUDGIFY_PLUGIN_DIR.'/sentry/autoload.php';
23
24 /**
25 * Serve a virtual firebase-messaging-sw.js without rewrite rules.
26 */
27 add_action('plugins_loaded', function () {
28 if (! isset($_SERVER['REQUEST_URI'])) {
29 return;
30 }
31
32 $requestUri = sanitize_text_field(wp_unslash($_SERVER['REQUEST_URI']));
33 if (strpos($requestUri, 'firebase-messaging-sw.js') === false) {
34 return;
35 }
36
37 if (ob_get_length()) {
38 ob_clean();
39 }
40
41 header('Content-Type: application/javascript; charset=utf-8');
42 header('Cache-Control: no-cache, no-store, must-revalidate');
43 header('Service-Worker-Allowed: /');
44
45 $sw_content = "// Nudgify Push Notification Service Worker (Wordpress)\n";
46 $sw_content .= "// Imports the actual service worker logic from CDN\n\n";
47 $sw_content .= 'importScripts("https://nudgify.ams3.cdn.digitaloceanspaces.com/nudgify-sw.js");';
48
49 echo $sw_content;
50 exit;
51 }, 1);
52
53 if (! class_exists('Nudgify')) {
54 class NudgifyOptions
55 {
56 const DO_MANUAL_SYNC = 'nudgify-domanualsync';
57
58 const OPTIONS_GROUP = 'nudgify-options';
59
60 const SAVED = 'nudgify-saved';
61
62 const SITE_KEY = 'nudgify-site-key';
63
64 const API_TOKEN = 'nudgify-api-token';
65
66 const CONNECTED = 'nudgify-connected';
67
68 const ENABLED = 'nudgify-enabled';
69
70 const AUTOSYNC = 'nudgify-autosync';
71
72 const options = [
73 self::SAVED,
74 self::ENABLED,
75 self::SITE_KEY,
76 self::API_TOKEN,
77 self::CONNECTED,
78 self::AUTOSYNC,
79 ];
80 }
81
82 class Nudgify
83 {
84 const DISCOUNT_NONCE_ACTION = 'nudgify_discount_nonce';
85
86 const DISCOUNT_NONCE_FIELD = 'nonce';
87
88 private $product;
89
90 private $sentry;
91
92 public function __construct()
93 {
94 $this->sentry = new NudgifySentryClient(
95 new NudgifySentryDirectEventCapture(new NudgifySentryDSN(NUDGIFY_SENTRY_DSN)),
96 [
97 new NudgifySentryEnvironmentReporter,
98 new NudgifySentryRequestReporter,
99 new NudgifySentryExceptionReporter,
100 new NudgifySentryClientSniffer,
101 new NudgifySentryClientIPDetector,
102 ]
103 );
104
105 $this->init_base();
106 // DELAY WooCommerce hook registration
107 add_action('plugins_loaded', [$this, 'init_woocommerce_orders'], 1);
108 // if (function_exists('wc_get_logger')) {
109 // $logger = wc_get_logger();
110 // $logger->info('init_woocommerce_orders called', ['source' => 'nudgify-init']);
111 // }
112 // try {
113 // $this->init_woocommerce_orders();
114 // } catch (\Throwable $th) {
115 // if (function_exists('wc_get_logger')) {
116 // $logger = wc_get_logger();
117 // $logger->info('init_woocommerce_orders called after delay', ['source' => 'nudgify-init']);
118 // $logger->info('Error: '.$th->getMessage(), ['source' => 'nudgify-init']);
119 // }
120 // }
121
122 }
123
124 public function init_base()
125 {
126 add_action('wp_head', [$this, 'print_pixel']);
127
128 add_action('admin_init', [$this, 'init_settings']);
129 add_action('admin_menu', [$this, 'init_menu']);
130
131 // add_action('admin_notices', [$this, 'configuration_notice']);
132
133 add_action('add_option_'.NudgifyOptions::SITE_KEY, [$this, 'connect'], 999, 0);
134 add_action('add_option_'.NudgifyOptions::API_TOKEN, [$this, 'connect'], 999, 0);
135 add_action('update_option_'.NudgifyOptions::SITE_KEY, [$this, 'connect'], 999, 0);
136 add_action('update_option_'.NudgifyOptions::API_TOKEN, [$this, 'connect'], 999, 0);
137
138 add_action('admin_action_'.NudgifyOptions::DO_MANUAL_SYNC, [$this, 'sync_orders_manually']);
139 add_action('admin_post_'.NudgifyOptions::DO_MANUAL_SYNC, [$this, 'sync_orders_manually']);
140 add_action('wp_ajax_'.NudgifyOptions::DO_MANUAL_SYNC, [$this, 'sync_orders_manually']);
141 }
142
143 public function init_woocommerce_orders()
144 {
145 // Log whether WooCommerce is detected using WooCommerce logger
146 if (function_exists('wc_get_logger')) {
147 $logger = wc_get_logger();
148 $logger->info('init_woocommerce_orders called', ['source' => 'nudgify-init']);
149 $logger->info('WooCommerce enabled: '.(nudgify_woocommerce_enabled() ? 'YES' : 'NO'), ['source' => 'nudgify-init']);
150 $logger->info('class_exists(WooCommerce): '.(class_exists('WooCommerce') ? 'YES' : 'NO'), ['source' => 'nudgify-init']);
151 $logger->info('function_exists(WC): '.(function_exists('WC') ? 'YES' : 'NO'), ['source' => 'nudgify-init']);
152 $logger->info('nudgify_woocommerce_enabled yes or not'.nudgify_woocommerce_enabled());
153 }
154
155 if (! nudgify_woocommerce_enabled()) {
156 if (function_exists('wc_get_logger')) {
157 $logger = wc_get_logger();
158 $logger->error('Exiting init_woocommerce_orders - WooCommerce not enabled', ['source' => 'nudgify-init']);
159 }
160
161 return;
162 }
163
164 if (function_exists('wc_get_logger')) {
165 $logger = wc_get_logger();
166 $logger->info('Registering WooCommerce hooks...', ['source' => 'nudgify-init']);
167 $logger->info('Registering Nudgify WooCommerce hooks', ['source' => 'nudgify-init']);
168 $logger->info('Manual sync action: admin_post_'.NudgifyOptions::DO_MANUAL_SYNC, ['source' => 'nudgify-init']);
169 $logger->info('About to register action: admin_post_'.NudgifyOptions::DO_MANUAL_SYNC, ['source' => 'nudgify-init']);
170 }
171
172 add_action('woocommerce_new_order', [$this, 'post_woocommerce_order']);
173 add_action('woocommerce_add_to_cart', [$this, 'post_woocommerce_add_to_cart'], 10, 6);
174 add_action('woocommerce_remove_cart_item', [$this, 'post_woocommerce_remove_from_cart'], 10, 6);
175 add_action('woocommerce_after_cart_item_quantity_update', [$this, 'post_woocommerce_update_cart_item_quantity'], 10, 6);
176
177 add_action('woocommerce_checkout_order_processed', [$this, 'post_woocommerce_order']);
178 add_action('woocommerce_order_status_cancelled', [$this, 'post_woocommerce_cancellation']);
179 add_action('woocommerce_order_status_refunded', [$this, 'post_woocommerce_cancellation']);
180
181 add_action('wp_ajax_nudgify_apply_discount', [$this, 'apply_discount']);
182 add_action('wp_ajax_nudgify_check_discount', [$this, 'check_discount_applied']);
183 add_action('wp_ajax_nopriv_nudgify_apply_discount', [$this, 'apply_discount']);
184 add_action('wp_ajax_nopriv_nudgify_check_discount', [$this, 'check_discount_applied']);
185
186 if (function_exists('wc_get_logger')) {
187 $logger = wc_get_logger();
188 $logger->info('WooCommerce hooks registered successfully', ['source' => 'nudgify-init']);
189 }
190
191 }
192
193 public function init_settings()
194 {
195 register_setting(NudgifyOptions::OPTIONS_GROUP, NudgifyOptions::SAVED, 'empty');
196 register_setting(NudgifyOptions::OPTIONS_GROUP, NudgifyOptions::SITE_KEY, [$this, 'sanitise_nudgify_site_key']);
197 register_setting(NudgifyOptions::OPTIONS_GROUP, NudgifyOptions::API_TOKEN, [$this, 'sanitise_nudgify_api_token']);
198 register_setting(NudgifyOptions::OPTIONS_GROUP, NudgifyOptions::CONNECTED, 'intval');
199 register_setting(NudgifyOptions::OPTIONS_GROUP, NudgifyOptions::ENABLED, 'intval');
200 register_setting(NudgifyOptions::OPTIONS_GROUP, NudgifyOptions::AUTOSYNC, 'intval');
201 }
202
203 public function init_menu()
204 {
205 add_menu_page(
206 'Nudgify',
207 'Nudgify',
208 'manage_options',
209 NUDGIFY_PLUGIN_SLUG,
210 [$this, 'options_form'],
211 NUDGIFY_PLUGIN_URL.'icon.png'
212 );
213
214 add_submenu_page(
215 NUDGIFY_PLUGIN_SLUG,
216 'Push Notifications',
217 'Push Notifications',
218 'manage_options',
219 'nudgify-push-notifications',
220 [$this, 'render_push_notifications_page']
221 );
222 }
223
224 public function render_push_notifications_page()
225 {
226 $this->options_form();
227 }
228
229 public function configuration_notice()
230 {
231 $screen = function_exists('get_current_screen') ? get_current_screen() : null;
232 $is_nudgify_screen = $screen && isset($screen->id) && ('toplevel_page_'.NUDGIFY_PLUGIN_SLUG === $screen->id);
233
234 if (get_option(NudgifyOptions::CONNECTED) && ! $is_nudgify_screen) {
235 return;
236 }
237
238 echo implode("\n", [
239 '<div class="notice notice-error is-dismissible">',
240 '<p>You need to complete your Nudgify set-up <a href="admin.php?page=nudgify">Complete set-up</a></p>',
241 '</div>',
242 ]);
243 }
244
245 public function print_pixel()
246 {
247 $uuid = get_option(NudgifyOptions::SITE_KEY);
248 $enabled = get_option(NudgifyOptions::ENABLED, true);
249
250 if (! $this->is_valid_site_key($uuid) || ! $enabled) {
251 return;
252 }
253
254 // These will be properly escaped with esc_js() when output
255 $url = NUDGIFY_PIXEL_BASE.'/pixel.js';
256
257 $pixelData = [];
258
259 if (nudgify_woocommerce_enabled()) {
260 $pixelData['data'] = [
261 'cart' => [
262 'amount' => $this->get_cart_value(),
263 'currency' => get_woocommerce_currency(),
264 ],
265 ];
266
267 if (is_product()) {
268 $post = get_post();
269 $this->product = wc_get_product($post->ID);
270
271 $productStock = $this->get_product_stock();
272
273 if (is_null($productStock) && $this->product->is_type('variable')) {
274 $productStock = 0;
275 $variations = $this->product->get_available_variations();
276 foreach ($variations as $variation) {
277 $productStock += intval($variation['max_qty']);
278 }
279 }
280
281 $pixelData['data']['product'] = [
282 'id' => $this->product->get_id(),
283 'stock' => $productStock,
284 'image' => $this->get_product_image($this->product),
285 ];
286 }
287
288 $pixelData['ajax'] = [
289 'url' => admin_url('admin-ajax.php'),
290 'nonce' => wp_create_nonce(self::DISCOUNT_NONCE_ACTION),
291 'nonce_field' => self::DISCOUNT_NONCE_FIELD,
292 ];
293 }
294 $pixelDataString = $this->pixel_data_string($pixelData);
295
296 $variantWatcher = '';
297 if (nudgify_woocommerce_enabled()) {
298 $variantWatcher = implode("\n", [
299 ' (function ($) { ',
300 ' if (! $) return;',
301 ' if (typeof $.noConflict === "function") $ = $.noConflict();',
302 ' if (! typeof $.prototype.on === "function") return;',
303 ' $(document).on("show_variation", function (event, variant) { ',
304 ' if (!variant.is_in_stock) return; ',
305 ' window.nudgify.product({ ',
306 ' id: variant.variation_id || null, ',
307 ' stock: variant.max_qty || null, ',
308 ' image: variant.image.thumb_src || null, ',
309 ' }) ',
310 ' }); ',
311 ' })(window.jQuery || null); ',
312 ]);
313 }
314
315 // Output tracking pixel script with proper JavaScript escaping
316 echo '<script>';
317 echo wp_kses($pixelDataString, [])."\n";
318 echo wp_kses($variantWatcher, [])."\n";
319 echo '(function(w){'."\n";
320 echo ' var k="nudgify",n=w[k]||(w[k]={});'."\n";
321 echo ' n.uuid='.wp_json_encode($uuid).';'."\n";
322 echo ' var d=document,s=d.createElement("script");'."\n";
323 echo ' s.src='.wp_json_encode($url).';'."\n";
324 echo ' s.async=1;'."\n";
325 echo ' s.charset="utf-8";'."\n";
326 echo ' d.getElementsByTagName("head")[0].appendChild(s)'."\n";
327 echo '})(window)'."\n";
328 echo '</script>';
329 }
330
331 public function post_woocommerce_order($orderId, $orderDetails = [])
332 {
333 $enabled = get_option(NudgifyOptions::ENABLED, true);
334 $autosync = get_option(NudgifyOptions::AUTOSYNC, true);
335
336 if (! (nudgify_woocommerce_enabled() && $enabled && $autosync)) {
337 return;
338 }
339
340 $siteKey = get_option(NudgifyOptions::SITE_KEY);
341 $apiToken = get_option(NudgifyOptions::API_TOKEN);
342
343 $order = wc_get_order($orderId);
344 $data = $this->prepare_order_data($order, $siteKey);
345
346 $response = $this->post(NUDGIFY_ENDPOINT_WEBHOOK, $data, $apiToken);
347
348 return $response['successful'];
349 }
350
351 public function post_woocommerce_add_to_cart($cart_id, $product_id = null, $quantity = 0)
352 {
353 $enabled = get_option(NudgifyOptions::ENABLED, true);
354 $autosync = get_option(NudgifyOptions::AUTOSYNC, true);
355
356 if (! (nudgify_woocommerce_enabled() && $enabled && $autosync)) {
357 return;
358 }
359
360 $siteKey = get_option(NudgifyOptions::SITE_KEY);
361 $apiToken = get_option(NudgifyOptions::API_TOKEN);
362
363 if (is_null(WC()->session->get('nudgify_cart_id'))) {
364 WC()->session->set('nudgify_cart_id', uniqid());
365 }
366
367 $cart_id = WC()->session->get('nudgify_cart_id');
368
369 $data = [
370 'action' => 'add_to_cart',
371 'site_key' => $siteKey,
372 'cart_id' => $cart_id,
373 'product_id' => $product_id,
374 'quantity' => $quantity,
375 'product' => $this->prepare_product_data($product_id),
376 ];
377
378 $response = $this->post(NUDGIFY_ENDPOINT_WEBHOOK, $data, $apiToken);
379
380 return $response['successful'];
381 }
382
383 public function post_woocommerce_update_cart_item_quantity($cart_item_key, $quantity = 0, $old_quantity = 0, $cart = null)
384 {
385 $enabled = get_option(NudgifyOptions::ENABLED, true);
386 $autosync = get_option(NudgifyOptions::AUTOSYNC, true);
387
388 if (! (nudgify_woocommerce_enabled() && $enabled && $autosync)) {
389 return;
390 }
391
392 if ($quantity == $old_quantity) {
393 return;
394 }
395
396 $siteKey = get_option(NudgifyOptions::SITE_KEY);
397 $apiToken = get_option(NudgifyOptions::API_TOKEN);
398
399 if (is_null(WC()->session->get('nudgify_cart_id'))) {
400 WC()->session->set('nudgify_cart_id', uniqid());
401 }
402
403 $cart_id = WC()->session->get('nudgify_cart_id');
404
405 $product_id = $cart->cart_contents[$cart_item_key]['product_id'];
406
407 $data = [
408 'action' => 'update_cart_item',
409 'site_key' => $siteKey,
410 'cart_id' => $cart_id,
411 'product_id' => $product_id,
412 'quantity' => $quantity,
413 'product' => $this->prepare_product_data($product_id),
414 ];
415
416 $response = $this->post(NUDGIFY_ENDPOINT_WEBHOOK, $data, $apiToken);
417
418 return $response['successful'];
419 }
420
421 public function post_woocommerce_remove_from_cart($cart_item_key, $cart = null)
422 {
423 $enabled = get_option(NudgifyOptions::ENABLED, true);
424 $autosync = get_option(NudgifyOptions::AUTOSYNC, true);
425
426 if (! (nudgify_woocommerce_enabled() && $enabled && $autosync)) {
427 return;
428 }
429
430 if (is_null(WC()->session->get('nudgify_cart_id'))) {
431 return;
432 }
433
434 $cart_id = WC()->session->get('nudgify_cart_id');
435
436 $siteKey = get_option(NudgifyOptions::SITE_KEY);
437 $apiToken = get_option(NudgifyOptions::API_TOKEN);
438
439 $product_id = $cart->cart_contents[$cart_item_key]['product_id'];
440
441 $data = [
442 'action' => 'remove_from_cart',
443 'cart_item_key' => $cart_item_key,
444 'site_key' => $siteKey,
445 'cart_id' => $cart_id,
446 'product_id' => $product_id,
447 'product' => $this->prepare_product_data($product_id),
448 ];
449
450 $response = $this->post(NUDGIFY_ENDPOINT_WEBHOOK, $data, $apiToken);
451
452 return $response['successful'];
453 }
454
455 public function post_woocommerce_cancellation($orderId)
456 {
457 $enabled = get_option(NudgifyOptions::ENABLED, true);
458 $autosync = get_option(NudgifyOptions::AUTOSYNC, true);
459
460 if (! (nudgify_woocommerce_enabled() && $enabled && $autosync)) {
461 return;
462 }
463
464 $siteKey = get_option(NudgifyOptions::SITE_KEY);
465 $apiToken = get_option(NudgifyOptions::API_TOKEN);
466
467 $data = [
468 'site_key' => $siteKey,
469 'order_id' => $orderId,
470 'action' => 'cancelled',
471 ];
472
473 $response = $this->post(NUDGIFY_ENDPOINT_WEBHOOK, $data, $apiToken);
474
475 return $response['successful'];
476 }
477
478 private function log_sync($level, $message, $context = [])
479 {
480 if (function_exists('wc_get_logger')) {
481 $logger = wc_get_logger();
482 $context['source'] = 'nudgify-manual-sync';
483 $logger->log($level, $message, $context);
484 }
485 }
486
487 public function sync_orders_manually()
488 {
489 $this->log_sync('info', 'Starting manual order sync...');
490
491 if (! current_user_can('manage_options')) {
492 $this->log_sync('error', 'User does not have manage_options capability');
493 echo wp_kses_post(nudgify_build_feedback_message('manualsync', '419'));
494
495 exit();
496 }
497
498 $nonce = isset($_REQUEST['nudgify_manual_sync_nonce']) ? sanitize_text_field(wp_unslash($_REQUEST['nudgify_manual_sync_nonce'])) : '';
499
500 if (! wp_verify_nonce($nonce, 'nudgify_manual_sync_nonce')) {
501 $this->log_sync('error', 'Nonce verification failed');
502 echo wp_kses_post(nudgify_build_feedback_message('manualsync', '419'));
503
504 exit();
505 }
506
507 $this->log_sync('info', 'Nonce verified successfully');
508
509 $enabled = get_option(NudgifyOptions::ENABLED, true);
510 $siteKey = get_option(NudgifyOptions::SITE_KEY);
511 $apiToken = get_option(NudgifyOptions::API_TOKEN);
512
513 $this->log_sync('info', 'Settings check', [
514 'enabled' => $enabled ? 'Yes' : 'No',
515 'site_key' => $siteKey ? 'Present' : 'Missing',
516 'api_token' => $apiToken ? 'Present' : 'Missing',
517 ]);
518
519 if (! (nudgify_woocommerce_enabled() && $enabled)) {
520 $this->log_sync('error', 'WooCommerce not enabled or Nudgify disabled');
521 echo wp_kses_post(nudgify_build_feedback_message('manualsync', '423'));
522
523 exit();
524 }
525
526 if (! ($siteKey && $apiToken)) {
527 $this->log_sync('error', 'Missing Site Key or API Token');
528 echo wp_kses_post(nudgify_build_feedback_message('manualsync', '424'));
529
530 exit();
531 }
532
533 $data = [
534 'site_key' => $siteKey,
535 'orders' => [],
536 ];
537
538 $acceptedStatuses = array_filter(array_keys(wc_get_order_statuses()), function ($status) {
539 return ! in_array($status, ['wc-refunded', 'wc-cancelled']);
540 });
541
542 $this->log_sync('info', 'Accepted order statuses: '.implode(', ', $acceptedStatuses));
543
544 $orders = wc_get_orders([
545 'limit' => 30,
546 'orderby' => 'date',
547 'order' => 'DESC',
548 'status' => $acceptedStatuses,
549 ]);
550
551 $this->log_sync('info', 'Found '.count($orders).' orders to sync');
552
553 foreach ($orders as $order) {
554 // guard agains OrderRefund or OrderCancelled
555 if (! method_exists($order, 'get_billing_last_name')) {
556 $this->log_sync('warning', 'Skipping order ID '.$order->get_id().' - missing get_billing_last_name method');
557
558 continue;
559 }
560
561 $orderData = $this->prepare_order_data($order, $siteKey);
562 $orderData['ip'] = $order->get_customer_ip_address();
563
564 $data['orders'][] = $orderData;
565 $this->log_sync('debug', 'Prepared order ID: '.$order->get_id());
566 }
567
568 $this->log_sync('info', 'Sending '.count($data['orders']).' orders to Nudgify API', [
569 'endpoint' => NUDGIFY_ENDPOINT_SYNC,
570 'order_count' => count($data['orders']),
571 ]);
572
573 $response = $this->post(NUDGIFY_ENDPOINT_SYNC, $data, $apiToken);
574
575 $this->log_sync('info', 'API Response received', [
576 'code' => $response['code'],
577 'successful' => $response['successful'] ? 'Yes' : 'No',
578 ]);
579
580 if (isset($response['message'])) {
581 $this->log_sync('info', 'API Response Message: '.$response['message']);
582 }
583
584 if ($response['successful']) {
585 update_option(NudgifyOptions::CONNECTED, 1);
586 $this->log_sync('info', 'SUCCESS: Orders synced successfully');
587 echo wp_kses_post(nudgify_build_feedback_message('manualsync', '200'));
588 } else {
589 $this->log_sync('error', 'FAILED: Orders sync failed', [
590 'code' => $response['code'],
591 'message' => isset($response['message']) ? $response['message'] : 'No message',
592 ]);
593 echo wp_kses_post(nudgify_build_feedback_message('manualsync', $response['code']));
594 }
595
596 exit();
597 }
598
599 public function apply_discount()
600 {
601 if (! $this->verify_discount_nonce()) {
602 wp_send_json(['status' => false], 403);
603 }
604
605 $discountCode = isset($_POST['code']) ? sanitize_text_field(wp_unslash($_POST['code'])) : null;
606
607 if ($discountCode && ! WC()->cart->has_discount($discountCode)) {
608 WC()->cart->apply_coupon($discountCode);
609 echo wp_json_encode(['status' => true]);
610 } else {
611 echo wp_json_encode(['status' => false]);
612 }
613
614 wp_die();
615 }
616
617 public function check_discount_applied()
618 {
619 if (! $this->verify_discount_nonce()) {
620 wp_send_json(['status' => false], 403);
621 }
622
623 $discountCode = isset($_POST['code']) ? sanitize_text_field(wp_unslash($_POST['code'])) : null;
624
625 $response = ['status' => false, 'valid' => false];
626
627 if ($discountCode) {
628 if (WC()->cart->has_discount($discountCode)) {
629 $response['status'] = true;
630 }
631
632 // check for valid discount code
633 if (wc_get_coupon_id_by_code($discountCode)) {
634 $response['valid'] = true;
635 }
636 }
637
638 echo wp_json_encode($response);
639 wp_die();
640 }
641
642 public function connect()
643 {
644 $siteKey = get_option(NudgifyOptions::SITE_KEY);
645 $apiToken = get_option(NudgifyOptions::API_TOKEN);
646 $enabled = get_option(NudgifyOptions::ENABLED, true);
647
648 // it has been disabled.
649 if (! $enabled) {
650 return;
651 }
652
653 if (empty($siteKey) || empty($apiToken)) {
654 update_option(NudgifyOptions::CONNECTED, 0);
655
656 return;
657 }
658
659 $data = [
660 'integration_identity' => NUDGIFY_INTEGRATION_NAME,
661 'site_key' => $siteKey,
662 ];
663
664 $response = $this->post(NUDGIFY_ENDPOINT_AUTH, $data, $apiToken);
665
666 if ($response['successful']) {
667 update_option(NudgifyOptions::CONNECTED, 1);
668 } else {
669 update_option(NudgifyOptions::CONNECTED, 0);
670 }
671
672 $this->add_feedback_message('connect', $response['code']);
673
674 return true;
675 }
676
677 public function sanitise_nudgify_site_key($siteKey)
678 {
679 $siteKey = trim($siteKey);
680
681 if (! $this->is_valid_site_key($siteKey) || empty($siteKey)) {
682 add_settings_error(
683 NudgifyOptions::SITE_KEY,
684 esc_attr('settings_updated'),
685 'Please make sure you provide the correct site key',
686 'error'
687 );
688 }
689
690 return $siteKey;
691 }
692
693 public function sanitise_nudgify_api_token($apiToken)
694 {
695 $apiToken = trim($apiToken);
696
697 if (strlen($apiToken) !== 60 && ! empty($apiToken)) {
698 add_settings_error(
699 NudgifyOptions::API_TOKEN,
700 esc_attr('settings_updated'),
701 'Please make sure you provide the correct API key',
702 'error'
703 );
704 }
705
706 return $apiToken;
707 }
708
709 public function options_form()
710 {
711 require_once NUDGIFY_PLUGIN_DIR.'/includes/options.php';
712 }
713
714 public function add_feedback_message($group, $code)
715 {
716 $data = json_encode([
717 'group' => $group,
718 'code' => $code,
719 ]);
720
721 set_transient("nudgify_feedback_message_{$group}", $data, 5 * MINUTE_IN_SECONDS);
722 }
723
724 private function log($message, $data)
725 {
726 $data['debug'] = [
727 'nudgify_plugin_version' => NUDGIFY_PLUGIN_VERSION,
728 'site_url' => get_site_url(),
729 'php_version' => PHP_VERSION,
730 'wordpress_version' => get_bloginfo('version'),
731 'woocommerce_version' => nudgify_woocommerce_version(),
732 ];
733
734 $this->sentry->captureException(new Exception($message), null, $data);
735 }
736
737 private function pixel_data_string($data)
738 {
739 if (empty($data)) {
740 return '';
741 }
742
743 $ini_precision = ini_get('precision');
744 $ini_serialize_precision = ini_get('serialize_precision');
745
746 if (function_exists('ini_set') && version_compare(phpversion(), '7.1', '>=')) {
747 ini_set('precision', 17);
748 ini_set('serialize_precision', -1);
749 }
750
751 $pixelDataString = 'window.nudgify = window.nudgify || {};';
752 $pixelDataString .= 'window.nudgify = Object.assign(window.nudgify, '.wp_json_encode($data).');';
753
754 if (function_exists('ini_set') && version_compare(phpversion(), '7.1', '>=')) {
755 ini_set('precision', $ini_precision);
756 ini_set('serialize_precision', $ini_serialize_precision);
757 }
758
759 return $pixelDataString;
760 }
761
762 private function verify_discount_nonce()
763 {
764 return (bool) check_ajax_referer(self::DISCOUNT_NONCE_ACTION, self::DISCOUNT_NONCE_FIELD, false);
765 }
766
767 private function prepare_order_data($order, $siteKey)
768 {
769 $lastname = $order->get_billing_last_name();
770 if (strlen($lastname) > 0) {
771 $lastname = $lastname[0];
772 }
773
774 return [
775 'site_key' => $siteKey,
776 'order_id' => $order->get_id(),
777 'action' => 'created',
778 'email' => $order->get_billing_email(),
779 'name' => implode(' ', [$order->get_billing_first_name(), $lastname]),
780 'city' => $order->get_billing_city(),
781 'state' => $order->get_billing_state(),
782 'country' => $order->get_billing_country(),
783 'ip' => $this->get_order_ip($order),
784 'date' => $this->get_order_date($order),
785 'line_items' => $this->get_order_items($order),
786 ];
787 }
788
789 /**
790 * Prepares the product data.
791 *
792 * @param int $product_id The ID of the product.
793 * @return array The prepared product data.
794 */
795 private function prepare_product_data($product_id)
796 {
797 $product = wc_get_product($product_id);
798 if ($product) {
799 return [
800 'item_id' => $product->get_id(),
801 'item_variation_id' => $product->get_id(),
802 'item_name' => $product->get_title(),
803 'item_link' => get_permalink($product->get_id()),
804 'image_url' => $this->get_product_image($product),
805 ];
806 }
807
808 return [];
809 }
810
811 private function get_order_items($order)
812 {
813 $products = [];
814
815 $items = $order->get_items();
816
817 foreach ($items as $item) {
818 $product = $item->get_product();
819
820 $products[] = [
821 'item_id' => $item->get_product_id(),
822 'item_variation_id' => $product->get_id(),
823 'item_name' => $product->get_title(),
824 'item_link' => get_permalink($product->get_id()),
825 'image_url' => $this->get_product_image($product),
826 ];
827 }
828
829 return $products;
830 }
831
832 private function get_order_date($order)
833 {
834 if (method_exists($order, 'get_date_created')) {
835 $date = $order->get_date_created();
836 if (! empty($date)) {
837 return gmdate('Y-m-d H:i:s', $date->getTimestamp());
838 }
839 }
840
841 return gmdate('Y-m-d H:i:s', time());
842 }
843
844 private function get_order_ip($order)
845 {
846 $ips = [];
847 $ipKeys = [
848 'HTTP_CLIENT_IP',
849 'HTTP_X_FORWARDED_FOR',
850 'HTTP_X_FORWARDED',
851 'REMOTE_ADDR',
852 ];
853
854 foreach ($ipKeys as $key) {
855 if (! empty($_SERVER[$key])) {
856 $ips[] = sanitize_text_field(wp_unslash($_SERVER[$key]));
857 }
858 }
859
860 $ips[] = $order->get_customer_ip_address();
861
862 $ips = array_unique($ips);
863
864 return reset($ips);
865 }
866
867 private function is_valid_site_key($siteKey)
868 {
869 $UUIDv4 = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/Di';
870
871 return preg_match($UUIDv4, $siteKey);
872 }
873
874 private function get_product_stock()
875 {
876 if (! $this->product) {
877 return null;
878 }
879
880 return $this->product->get_stock_quantity();
881 }
882
883 private function get_product_image($product)
884 {
885 if (! $product) {
886 return null;
887 }
888
889 $image_id = $product->get_image_id();
890
891 return wp_get_attachment_image_url($image_id, 'thumbnail');
892 }
893
894 private function get_cart_value()
895 {
896 $cart = WC()->cart;
897
898 if (! $cart) {
899 return 0;
900 }
901
902 return floatval($cart->get_cart_contents_total() + $cart->get_taxes_total());
903 }
904
905 private function post($url, $data, $apiToken)
906 {
907 $response = wp_remote_post($url, [
908 'body' => wp_json_encode($data),
909 'headers' => [
910 'Authorization' => "Bearer $apiToken",
911 'Content-Type' => 'application/json',
912 ],
913 ]);
914
915 if (is_wp_error($response)) {
916 $this->log("[NudgifyError] $url", [
917 'data' => $data,
918 'errors' => $response->get_error_messages(),
919 ]);
920 $output = [
921 'code' => 500,
922 'successful' => false,
923 'message' => 'Error completing action',
924 ];
925 } else {
926 $output = $response['response'];
927 $output['successful'] = $output['code'] == 200;
928 }
929
930 return $output;
931 }
932 }
933
934 $nudgify = new Nudgify;
935 }
936
937 register_uninstall_hook(__FILE__, 'nudgify_uninstall_hook');
938 register_activation_hook(__FILE__, 'nudgify_activation_hook');
939
940 function nudgify_uninstall_hook()
941 {
942 if (! current_user_can('activate_plugins')) {
943 return;
944 }
945
946 foreach (NudgifyOptions::options as $option) {
947 delete_option($option);
948 }
949 }
950
951 function nudgify_activation_hook()
952 {
953 if (! current_user_can('activate_plugins')) {
954 return;
955 }
956
957 update_option(NudgifyOptions::SITE_KEY, get_option(NudgifyOptions::SITE_KEY, ''));
958 update_option(NudgifyOptions::API_TOKEN, get_option(NudgifyOptions::API_TOKEN, ''));
959 update_option(NudgifyOptions::CONNECTED, get_option(NudgifyOptions::CONNECTED, 0));
960 update_option(NudgifyOptions::ENABLED, get_option(NudgifyOptions::ENABLED, 1));
961 update_option(NudgifyOptions::AUTOSYNC, get_option(NudgifyOptions::AUTOSYNC, 1));
962 update_option(NudgifyOptions::SAVED, get_option(NudgifyOptions::SAVED, time()));
963 }
964