PluginProbe
Spoki – Chat Buttons and WooCommerce Notifications / 2.9.3
Spoki – Chat Buttons and WooCommerce Notifications v2.9.3
2.17.4 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.10.0 2.11.0 2.11.1 2.12.0 2.12.1 2.12.2 2.12.3 2.13.0 2.14.0 All 69 releases
spoki / spoki.php

spoki.php in Spoki – Chat Buttons and WooCommerce Notifications 2.9.3, at spoki.php

1,442 lines 62.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /* @wordpress-plugin
3 * Plugin Name: Spoki - Chat Buttons and WooCommerce Notifications
4 * Description: Integrate WhatsApp on your Business! Use Spoki to recover abandoned carts, add chat buttons and send order status notifications via WhatsApp.
5 * Version: 2.9.3
6 * Author: Reddoak Srl
7 * Author URI: https://reddoak.com
8 * Text Domain: spoki
9 * Domain Path: /languages
10 * License: GPLv2
11 */
12
13 define('SPOKI_PLUGIN_FILE', __FILE__);
14 define('SPOKI_BASE', plugin_basename(SPOKI_PLUGIN_FILE));
15 define('SPOKI_DIR', plugin_dir_path(SPOKI_PLUGIN_FILE));
16 define('SPOKI_URL', plugins_url('/', SPOKI_PLUGIN_FILE));
17 define('SPOKI_SETTING_TABLE', 'spoki_setting');
18 define('SPOKI_ABANDONMENT_TABLE', 'spoki_abandonment');
19
20 include_once SPOKI_DIR . 'includes/constants.php';
21 include_once SPOKI_DIR . 'includes/spoki-functions.php';
22 include_once SPOKI_DIR . 'modules/abandoned-carts/spoki-abandoned-carts.php';
23
24 add_filter('cron_schedules', 'add_cron_interval');
25 function add_cron_interval($schedules)
26 {
27 $schedules['every_day'] = array(
28 'interval' => 86400,
29 'display' => esc_html__('Every day'),);
30 return $schedules;
31 }
32
33 add_action('plugins_loaded', function () {
34 /** Init Spoki */
35 Spoki();
36 }, 9);
37
38
39 function Spoki()
40 {
41 return WpSpoki::instance();
42 }
43
44 final class WpSpoki
45 {
46 protected static $_instance = null;
47 public $version = '';
48 private $langs = '';
49 public $shop = [];
50
51 private $api_plan = SPOKI_BASE_API . "plan/";
52 private $api_status = SPOKI_BASE_API . "status/";
53 private $api_enable_flex = SPOKI_BASE_API . "enable/";
54 private $api_account = SPOKI_BASE_API . "account/";
55
56 public static function instance()
57 {
58 if (is_null(self::$_instance)) {
59 self::$_instance = new self();
60 }
61 return self::$_instance;
62 }
63
64 private function __construct()
65 {
66 $data = get_file_data(__FILE__, array('ver' => 'Version', 'langs' => 'Domain Path'));
67 $this->version = $data['ver'];
68 $this->langs = $data['langs'];
69 $this->options = get_option(SPOKI_OPTIONS);
70
71 $billing_data = isset($this->options['billing_data']) ? $this->options['billing_data'] : [];
72 $has_fixed_support_button = isset($this->options['buttons']['fixed_support_button_check']) && $this->options['buttons']['fixed_support_button_check'] == 1;
73 $has_product_item_listing_button = isset($this->options['woocommerce']['product_item_listing_button_check']) && $this->options['woocommerce']['product_item_listing_button_check'] == 1;
74 $has_cart_button = isset($this->options['woocommerce']['cart_button_check']) && $this->options['woocommerce']['cart_button_check'] == 1;
75 $has_single_product_button = isset($this->options['woocommerce']['single_product_button_check']) && $this->options['woocommerce']['single_product_button_check'] == 1;
76 $has_abandoned_carts = isset($this->options['abandoned_carts']['enable_tracking']) && $this->options['abandoned_carts']['enable_tracking'] == 1;
77 $has_order_updated_notification = (isset($this->options['woocommerce']['order_updated']) && $this->options['woocommerce']['order_updated'] == 1);
78 $has_order_created_notification = (isset($this->options['woocommerce']['order_created']) && $this->options['woocommerce']['order_created'] == 1);
79 $has_order_deleted_notification = (isset($this->options['woocommerce']['order_deleted']) && $this->options['woocommerce']['order_deleted'] == 1);
80 $has_order_note_added_notification = (isset($this->options['woocommerce']['order_note_added']) && $this->options['woocommerce']['order_note_added'] == 1);
81 $has_leave_review_notification = (isset($this->options['woocommerce']['leave_review']) && $this->options['woocommerce']['leave_review'] == 1);
82 $has_notifications = (
83 $has_order_updated_notification ||
84 $has_order_created_notification ||
85 $has_order_deleted_notification ||
86 $has_order_note_added_notification ||
87 $has_leave_review_notification
88 );
89 $has_order_created_to_seller_notification = (isset($this->options['woocommerce']['order_created_to_seller']) && $this->options['woocommerce']['order_created_to_seller'] == 1);
90 $has_cart_recovered_to_seller_notification = (isset($this->options['abandoned_carts']['notify_to_admin']) && $this->options['abandoned_carts']['notify_to_admin'] == 1);
91 $is_auto_update_disabled = (isset($this->options['disable_auto_update']) && $this->options['disable_auto_update'] == 1);
92 $abandoned_carts_waiting_minutes = (isset($this->options['abandoned_carts']['waiting_minutes'])) ? intval($this->options['abandoned_carts']['waiting_minutes']) : 15;
93
94 $this->shop = [
95 "plugin_version" => $this->version,
96 "name" => $this->options['shop_name'] ?? get_bloginfo('name'),
97 "url" => get_bloginfo('url'),
98 "email" => $this->options['email'] ?? get_bloginfo('admin_email'),
99 "language" => $this->options['language'] ?? get_bloginfo('language'),
100 "telephone" => ($this->options['prefix'] ?? '') . ($this->options['telephone'] ?? ''),
101 "contact_link" => isset($this->options['account_info']['contact_link']) ? $this->options['account_info']['contact_link'] : null,
102 "billing_data" => $billing_data,
103 "has_fixed_support_button" => $has_fixed_support_button,
104 "has_product_item_listing_button" => $has_product_item_listing_button,
105 "has_cart_button" => $has_cart_button,
106 "has_single_product_button" => $has_single_product_button,
107 "has_abandoned_carts" => $has_abandoned_carts,
108 "has_order_updated_notification" => $has_order_updated_notification,
109 "has_order_created_notification" => $has_order_created_notification,
110 "has_order_deleted_notification" => $has_order_deleted_notification,
111 "has_order_note_added_notification" => $has_order_note_added_notification,
112 "has_leave_review_notification" => $has_leave_review_notification,
113 "has_notifications" => $has_notifications,
114 "has_order_created_to_seller_notification" => $has_order_created_to_seller_notification,
115 "has_cart_recovered_to_seller_notification" => $has_cart_recovered_to_seller_notification,
116 "abandoned_carts_waiting_minutes" => $abandoned_carts_waiting_minutes,
117 "is_auto_update_disabled" => $is_auto_update_disabled,
118 ];
119
120 register_activation_hook(SPOKI_PLUGIN_FILE, array($this, 'activation_reset'));
121 register_deactivation_hook(SPOKI_PLUGIN_FILE, array($this, 'deactivation_reset'));
122
123 add_action('init', 'spoki_update_po_file');
124 add_action('admin_menu', array($this, 'add_option_menu'));
125 add_action('admin_menu', array($this, 'add_submenu'), 99);
126 add_filter("plugin_action_links_" . plugin_basename(__FILE__), array($this, 'plugin_settings_link'));
127 add_action('wp_enqueue_scripts', array($this, 'add_styles'));
128 add_action('admin_enqueue_scripts', array($this, 'add_admin_styles'));
129 add_action('admin_enqueue_scripts', array($this, 'add_admin_scripts'));
130 add_action('updated_option', array($this, 'on_option_added'), 10, 3);
131 add_filter('auto_update_plugin', array($this, 'auto_update_plugin'), 10, 2);
132 add_action('spoki_cron_hook', array($this, 'check_secret_status'));
133 if (!wp_next_scheduled('spoki_cron_hook')) {
134 $this->check_secret_status();
135 wp_schedule_event(time(), 'every_day', 'spoki_cron_hook');
136 }
137
138 $this->includes();
139 $this->render_buttons();
140 $this->handle_woocommerce();
141 $this->handle_abandoned_carts();
142
143 add_action('elementor/widgets/widgets_registered', array($this, 'register_elementor_widgets'));
144
145 }
146
147 /**
148 * Include all Spoki required files
149 */
150 private function includes()
151 {
152 require_once SPOKI_DIR . 'includes/spoki-functions.php';
153 }
154
155 /**
156 * Get the setting link of the plugin
157 *
158 * @param $links
159 * @return mixed
160 */
161 public function plugin_settings_link($links)
162 {
163 $settings_link = '<a href="admin.php?page=' . SPOKI_PLUGIN_NAME . '">' . __('Settings', SPOKI_PLUGIN_NAME) . '</a>';
164 array_unshift($links, $settings_link);
165 return $links;
166 }
167
168 /**
169 * Add the Spoki option to the menu
170 */
171 public function add_option_menu()
172 {
173 add_menu_page(
174 'Spoki Options',
175 "Spoki",
176 'manage_options',
177 'spoki',
178 array($this, 'render_setup_page'),
179 plugins_url() . '/' . SPOKI_PLUGIN_NAME . '/assets/images/logo.svg',
180 98
181 );
182 add_action('admin_init', array($this, 'register_option_var'));
183 }
184
185 /**
186 * Add the Spoki option submenu
187 */
188 function add_submenu()
189 {
190 $has_woocommerce = ('woocommerce/woocommerce.php');
191 $is_pro = isset($this->options['account_info']['plan']['is_pro']) && $this->options['account_info']['plan']['is_pro'] == true;
192
193 add_submenu_page('spoki', __('Statistics', "spoki"), __('Statistics', "spoki"), 'manage_options', "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=welcome");
194 add_submenu_page('spoki', __('Buttons', "spoki"), __('Buttons', "spoki"), 'manage_options', "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=buttons");
195 add_submenu_page('spoki', __('Customer Notifications', "spoki"), __('Customer Notifications', "spoki"), 'manage_options', "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=customer-notifications");
196 add_submenu_page('spoki', __('Seller Notifications', "spoki"), __('Seller Notifications', "spoki"), 'manage_options', "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=seller-notifications");
197 add_submenu_page('spoki', __('Abandoned Carts', "spoki"), __('Abandoned Carts', "spoki"), 'manage_options', "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=abandoned-carts");
198 add_submenu_page('spoki', __('Settings', "spoki"), __('Settings', "spoki"), 'manage_options', "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=settings");
199 add_submenu_page('spoki', __('Invite a friend', "spoki"), __('Invite a friend', "spoki"), 'manage_options', "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=invite-a-friend");
200 if (!$is_pro) {
201 add_submenu_page('spoki', __('Upgrade', "spoki"), "<span class='color-spoki text-bold'>⇡ " . __('Upgrade', "spoki") . "</span>", 'manage_options', $this->get_plan_link(true));
202 }
203
204 if ($has_woocommerce) {
205 add_submenu_page(
206 'woocommerce',
207 __('Spoki', "spoki"),
208 __('Spoki', "spoki"),
209 'manage_options',
210 "admin.php?page=" . SPOKI_PLUGIN_NAME . "&tab=welcome",
211 null,
212 8
213 );
214 }
215
216 }
217
218 /**
219 * Register the Spoki option
220 */
221 public function register_option_var()
222 {
223 register_setting('wp-spoki-option', SPOKI_OPTIONS);
224 }
225
226 /**
227 * Add website spoki styles
228 */
229 public function add_styles()
230 {
231 $styles = ['buttons'];
232 foreach ($styles as $style) {
233 echo "<style id='spoki-style-$style'>";
234 include SPOKI_DIR . "assets/css/$style.css";
235 echo "</style>";
236 }
237 }
238
239 /**
240 * Add admin spoki styles
241 */
242 public function add_admin_styles()
243 {
244 $styles = ['admin.css', 'onboarding.css', 'account-overview.css', 'spoki-overview.css'];
245
246 foreach ($styles as $style) {
247 echo "<style>";
248 include_once SPOKI_DIR . 'assets/css/' . $style;
249 echo "</style>";
250 }
251
252 $this->add_styles();
253 }
254
255 /**
256 * Add admin spoki scripts
257 */
258 public function add_admin_scripts()
259 {
260 $scripts = ['spoki-admin.js', 'spoki-shadowed-buttons.js'];
261
262 foreach ($scripts as $script) {
263 echo "<script>";
264 include_once SPOKI_DIR . 'assets/js/' . $script;
265 echo "</script>";
266 }
267 }
268
269 /**
270 * Enable plugin autoupdate
271 */
272 function auto_update_plugin($update, $item)
273 {
274 $plugins = array('spoki');
275 $is_auto_update_disabled = (isset($this->options['disable_auto_update']) && $this->options['disable_auto_update'] == 1);
276 if (in_array($item->slug, $plugins) && !$is_auto_update_disabled)
277 return true;
278 else return $update;
279 }
280
281 /**
282 * Register widgets for Elementor
283 */
284 public function register_elementor_widgets()
285 {
286 if (spoki_has_elementor()) {
287 include_once SPOKI_DIR . 'widgets/class-elementor-spoki-button.php';
288 \Elementor\Plugin::instance()->widgets_manager->register_widget_type(new \Elementor_Spoki_Button_Widget());
289 }
290 }
291
292 /**
293 * Handle option submit
294 *
295 * @param $option
296 * @param $old_value
297 * @param $value
298 */
299 public function on_option_added($option, $old_value, $value)
300 {
301 if ($option == SPOKI_OPTIONS) {
302
303 /** Onboarding Without WooCommerce */
304 if (isset($value['onboarding']['without_wc'])) {
305 $this->update_options($value, [
306 "telephone" => $value['onboarding']['telephone'],
307 "prefix" => $value['onboarding']['prefix'],
308 "onboarding" => null,
309 ]);
310 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=buttons');
311 header("Location: {$url}");
312 exit;
313 }
314
315 /** Onboarding With WooCommerce */
316 if (isset($value['onboarding']['with_wc'])) {
317 $response = $this->create_spoki_account($value['onboarding']['prefix'] . $value['onboarding']['telephone'], $value['onboarding']['email'], $value['onboarding']['shop_name']);
318 if (isset($response['secret']) && isset($response['delivery_url'])) {
319 $this->update_options($value, [
320 "telephone" => $value['onboarding']['telephone'],
321 "prefix" => $value['onboarding']['prefix'],
322 "email" => $value['onboarding']['email'],
323 "shop_name" => $value['onboarding']['shop_name'],
324 "secret" => $response['secret'],
325 "delivery_url" => $response['delivery_url'],
326 "onboarding" => null,
327 "woocommerce" => [
328 "order_created" => 1,
329 "order_updated" => 1,
330 "order_deleted" => 1,
331 "order_note_added" => 1,
332 "leave_review" => 1,
333 "leave_review_days" => 5,
334 "order_created_to_seller" => 1,
335 ],
336 "abandoned_carts" => [
337 "enable_tracking" => 1,
338 "notify_to_admin" => 1,
339 ]
340 ]);
341 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=customer-notifications');
342 header("Location: {$url}");
343 exit;
344 }
345 }
346
347 /** Settings updated for registered account */
348 if (isset($value['is_settings'])) {
349 $keys_changed = ($value['secret'] != $old_value['secret']) || ($value['delivery_url'] != $old_value['delivery_url']);
350
351 $shop_name_changed = $value['shop_name'] != $old_value['shop_name'];
352 $email_changed = $value['email'] != $old_value['email'];
353 $telephone_changed = $value['telephone'] != $old_value['telephone'];
354 $prefix_changed = $value['prefix'] != $old_value['prefix'];
355 $contact_link_changed = $value['contact_link'] != $old_value['contact_link'];
356 $billing_data_changed = $value['billing_data'] != $old_value['billing_data'];
357
358 if ($shop_name_changed || $email_changed || $telephone_changed || $prefix_changed || $contact_link_changed || $billing_data_changed) {
359 $this->update_spoki_account($value['prefix'] . $value['telephone'], $value['email'], $value['shop_name'], $value['contact_link'], $value['billing_data']);
360 }
361
362 if ($keys_changed) {
363 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=welcome');
364 header("Location: {$url}");
365 exit;
366 }
367 }
368
369 /** Enable all notifications from dashboard */
370 if (isset($value['enable_notifications']) && $value['enable_notifications'] == 1) {
371 $this->update_options(get_option(SPOKI_OPTIONS), [
372 "enable_notifications" => 0,
373 "woocommerce" => [
374 "order_created" => 1,
375 "order_updated" => 1,
376 "order_deleted" => 1,
377 "order_note_added" => 1,
378 "leave_review" => 1,
379 "leave_review_days" => 5,
380 ]
381 ]);
382 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=customer-notifications');
383 header("Location: {$url}");
384 exit;
385 }
386
387 /** Enable order status notifications from dashboard */
388 if (isset($value['enable_order_status_notifications']) && $value['enable_order_status_notifications'] == 1) {
389 $this->update_options(get_option(SPOKI_OPTIONS), [
390 "enable_order_status_notifications" => 0,
391 "woocommerce" => [
392 "order_created" => 1,
393 "order_updated" => 1,
394 "order_deleted" => 1,
395 ]
396 ]);
397 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=customer-notifications');
398 header("Location: {$url}");
399 exit;
400 }
401
402 /** Enable seller notifications from dashboard */
403 if (isset($value['enable_seller_notifications']) && $value['enable_seller_notifications'] == 1) {
404 $this->update_options(get_option(SPOKI_OPTIONS), [
405 "enable_seller_notifications" => 0,
406 "woocommerce" => [
407 "order_created_to_seller" => 1,
408 ]
409 ]);
410 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=seller-notifications');
411 header("Location: {$url}");
412 exit;
413 }
414
415 /** Enable leave review notification from dashboard */
416 if (isset($value['enable_leave_review_notification']) && $value['enable_leave_review_notification'] == 1) {
417 $this->update_options(get_option(SPOKI_OPTIONS), [
418 "enable_leave_review_notification" => 0,
419 "woocommerce" => [
420 "leave_review" => 1,
421 "leave_review_days" => 5,
422 ]
423 ]);
424 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=customer-notifications');
425 header("Location: {$url}");
426 exit;
427 }
428
429 /** Enable enable order note added notification from dashboard */
430 if (isset($value['enable_order_note_added_notification']) && $value['enable_order_note_added_notification'] == 1) {
431 $this->update_options(get_option(SPOKI_OPTIONS), [
432 "enable_order_note_added_notification" => 0,
433 "woocommerce" => [
434 "order_note_added" => 1,
435 ]
436 ]);
437 $url = admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME . '&tab=customer-notifications');
438 header("Location: {$url}");
439 exit;
440 }
441 }
442 }
443
444 /**
445 * Handle WooCommerce features
446 */
447 public function handle_woocommerce()
448 {
449 if (isset($this->options['secret'])) {
450 include_once(ABSPATH . 'wp-admin/includes/plugin.php');
451 if (spoki_has_woocommerce()) {
452 add_action('woocommerce_checkout_update_order_meta', array($this, 'send_woocommerce_order_created_alert'), 1, 3);
453 add_action('woocommerce_order_status_changed', array($this, 'send_woocommerce_order_created_or_updated_alert'), 1, 3);
454 add_action('woocommerce_cancelled_order', array($this, 'send_woocommerce_order_deleted_alert'), 10, 3);
455 add_action('woocommerce_trash_order', array($this, 'send_woocommerce_order_deleted_alert'), 10, 3);
456 add_action('woocommerce_order_note_added', array($this, 'send_woocommerce_note_alert'), 10, 2);
457 add_action('woocommerce_order_status_completed', array($this, 'send_woocommerce_review_alert'), 10, 2);
458
459 if (isset($this->options['woocommerce']['cart_button_hide_checkout_button_check']) && $this->options['woocommerce']['cart_button_hide_checkout_button_check'] == 1) {
460 add_action('woocommerce_proceed_to_checkout', array($this, 'disable_checkout_button'), 1);
461 }
462 }
463 }
464 $this->fetch_secret_status();
465 }
466
467 /**
468 * Handle WooCommerce abandoned carts feature
469 */
470 public function handle_abandoned_carts()
471 {
472 $Abandonment = Spoki_Abandoned_Carts::instance();
473 if (isset($this->options['abandoned_carts']['enable_tracking']) && $this->options['abandoned_carts']['enable_tracking'] == 1) {
474 $this->initialize_cart_abandonment_tables();
475 $Abandonment->set_spoki_setting_by_meta("plugin_activated", "true");
476 do_action('spoki_cartflow_ca_init');
477 } elseif (wp_next_scheduled('spoki_abandoned_carts_cron_hook')) {
478 # Unschedule hook for versions <= v2.8.1
479 $Abandonment->unschedule_hook();
480 }
481 }
482
483 /**
484 * Create new database tables for plugin updates.
485 *
486 * @return void
487 */
488 public function initialize_cart_abandonment_tables()
489 {
490 include_once SPOKI_DIR . 'modules/abandoned-carts/spoki-abandoned-carts-db.php';
491 $db = Spoki_Abandoned_Carts_Db::instance();
492 $db->create_tables();
493 $db->init_tables();
494 }
495
496 /**
497 * Send the Spoki notification for order status created or updated
498 *
499 * @param $order_get_id
500 * @param $from
501 * @param $to
502 */
503 public function send_woocommerce_order_created_alert($order_get_id)
504 {
505 $this->send_woocommerce_order_alert($order_get_id, 'order.updated');
506 }
507
508 /**
509 * Send the Spoki notification for order status created or updated
510 *
511 * @param $order_get_id
512 * @param $from
513 * @param $to
514 */
515 public function send_woocommerce_order_created_or_updated_alert($order_get_id, $from, $to)
516 {
517 $this->send_woocommerce_order_alert($order_get_id, 'order.updated', ["from_status" => $from, "to_status" => $to]);
518 }
519
520 /**
521 * Send the Spoki notification for review
522 *
523 * @param $order_get_id
524 */
525 public function send_woocommerce_review_alert($order_get_id)
526 {
527 $review_link = (isset($this->options['woocommerce']['leave_review_link']) && $this->options['woocommerce']['leave_review_link'] != "") ? $this->options['woocommerce']['leave_review_link'] : "";
528 $review_link_days = (isset($this->options['woocommerce']['leave_review_days']) && $this->options['woocommerce']['leave_review_days'] != "") ? $this->options['woocommerce']['leave_review_days'] : 5;
529 $this->send_woocommerce_order_alert($order_get_id, 'order.review', ["review_link" => $review_link, "delay" => intval($review_link_days) * 24 * 60 * 60]);
530 }
531
532 /**
533 * Notify the Abandoned Cart Info Spoki notification
534 *
535 * @param $session_id
536 * @param $checkoutDetails
537 * @param $topic
538 * @return bool
539 */
540 public function notify_abandoned_cart_info($session_id, $checkoutDetails, $topic): bool
541 {
542 $other_fields = unserialize($checkoutDetails->other_fields);
543 $parts = explode(',', $other_fields['spoki_location']);
544 $country = $parts[0];
545 $data = [
546 "customer" => [
547 'email' => $checkoutDetails->email,
548 'phone' => sanitize_text_field($other_fields['spoki_phone_number']),
549 'country' => sanitize_text_field($country),
550 'first_name' => sanitize_text_field($other_fields['spoki_first_name']),
551 'last_name' => sanitize_text_field($other_fields['spoki_last_name']),
552 'name' => sanitize_text_field($other_fields['spoki_first_name']) . ' ' . sanitize_text_field($other_fields['spoki_last_name']),
553 ],
554 "checkout" => [
555 "checkout_url" => get_permalink($checkoutDetails->checkout_id) . '?session_id=' . $checkoutDetails->session_id,
556 "checkout_info" => $other_fields,
557 "cart_contacted_url" => get_bloginfo('url') . '/wp-json/api/v1/setCartAsContacted',
558 "total" => $checkoutDetails->cart_total,
559 "currency" => get_woocommerce_currency(),
560 ],
561 "session_id" => $session_id,
562 ];
563 return $this->spoki_send($data, $topic);
564 }
565
566 /**
567 * Send the Spoki notification for order deleted
568 *
569 * @param $order_get_id
570 */
571 public function send_woocommerce_order_deleted_alert($order_get_id)
572 {
573 $this->send_woocommerce_order_alert($order_get_id, 'order.deleted');
574 }
575
576 /**
577 * Send the Spoki notification for order status
578 *
579 * @param $order_get_id
580 * @param $topic
581 * @param $additional_data
582 */
583 public function send_woocommerce_order_alert($order_get_id, $topic, $additional_data = [])
584 {
585 $order = wc_get_order($order_get_id);
586
587 // Session Handling
588 $session = [];
589 try {
590 $session_id = WC()->session ? WC()->session->get('spoki_session_id') : null;
591 if (!$session_id && $this->shop['has_abandoned_carts']) {
592 $spoki_abandoned_carts = Spoki_Abandoned_Carts::instance();
593 $checkout = $spoki_abandoned_carts->get_order_checkout_details($order_get_id);
594 $other_fields = unserialize($checkout->other_fields);
595 $session_id = $other_fields['spoki_session_id'];
596 }
597 if ($session_id) {
598 $session = ['session_id' => $session_id];
599 }
600 } catch (Exception $e) {
601 // Can't set session_id
602 }
603
604 $order_data = array_merge(((isset($order) && false != $order ? $order->get_data() : ["id" => $order_get_id])), $additional_data);
605 if (!isset($order_data["to_status"])) {
606 $order_data["from_status"] = $order_data["status"];
607 $order_data["to_status"] = $order_data["status"];
608 }
609
610 $parts = explode(',', isset($order_data['billing']['country']) ? $order_data['billing']['country'] : '');
611 $country = $parts[0];
612 $customer = [
613 'email' => isset($order_data['billing']['email']) ? $order_data['billing']['email'] : '',
614 'phone' => isset($order_data['billing']['phone']) ? $order_data['billing']['phone'] : '',
615 'country' => $country ?? '',
616 'first_name' => (isset($order_data['billing']['first_name']) ? $order_data['billing']['first_name'] : ''),
617 'last_name' => (isset($order_data['billing']['last_name']) ? $order_data['billing']['last_name'] : ''),
618 'name' => (isset($order_data['billing']['first_name']) ? $order_data['billing']['first_name'] : '') . ' ' . (isset($order_data['billing']['last_name']) ? $order_data['billing']['last_name'] : ''),
619 ];
620 $this->spoki_send(array_merge([
621 "order" => $order_data,
622 "customer" => $customer,
623 ], $session), $topic);
624 }
625
626 /**
627 * Send the Spoki notification for tracking number
628 *
629 * @param $id
630 * @param $order
631 */
632 public function send_woocommerce_note_alert($id, $order)
633 {
634 $comment = get_comment($id);
635 if (isset($comment->comment_content)) {
636 $is_gls_tracking = substr(strtolower($comment->comment_content), 0, 4) == '[gls';
637 $is_user_tracking = substr(strtolower($comment->comment_content), 0, 10) == '[tracking]';
638
639 /** Send the message only if it is a tracking order note */
640 if ($comment->comment_type == 'order_note' && ($is_gls_tracking || $is_user_tracking)) {
641 $order_data = $order->get_data();
642 $this->send_woocommerce_order_alert($order_data['id'], 'order.tracking', ["note" => $comment]);
643 }
644 }
645 }
646
647 /**
648 * Send the Spoki notification
649 *
650 * @param $data
651 * @return bool
652 */
653 public function spoki_send($data, $topic): bool
654 {
655 $shop = ["shop" => $this->shop];
656
657 $request_params = array(
658 "headers" => array(
659 "Authorization" => $this->options['secret'],
660 "language" => $this->shop['language'],
661 "X-Wc-Webhook-Topic" => $topic,
662 ),
663 "body" => wp_json_encode(array_merge($data, $shop)),
664 "sslverify" => false,
665 "timeout" => 60,
666 );
667
668 // $url = 'https://spoki.requestcatcher.com/wp/';
669 $url = $this->options['delivery_url'];
670 $response = wp_remote_post($url, $request_params);
671 $code = wp_remote_retrieve_response_code($response);
672 return $code == 200;
673 }
674
675 /**
676 * Create a new Spoki Free account
677 *
678 * @param $telephone
679 * @param $email
680 * @param $shop_name
681 * @return array
682 */
683 private function create_spoki_account($telephone, $email, $shop_name): array
684 {
685 $request_params = array(
686 "headers" => array("Authorization" => $this->options['secret'], "language" => $this->shop['language']),
687 "body" => [
688 "phone" => $telephone,
689 "email" => $email,
690 "name" => $shop_name,
691 ],
692 "sslverify" => false,
693 "timeout" => 60,
694 );
695
696 $response = wp_remote_post($this->api_enable_flex, $request_params);
697 $code = wp_remote_retrieve_response_code($response);
698
699 if ($code >= 200 && $code < 300) {
700 $data = json_decode(wp_remote_retrieve_body($response), true);
701 return [
702 "secret" => isset($data['secret']) ? $data['secret'] : null,
703 "delivery_url" => isset($data['delivery_url']) ? $data['delivery_url'] : null,
704 ];
705 }
706 return [];
707 }
708
709
710 /**
711 * Update a Spoki account
712 *
713 * @param $phone
714 * @param $email
715 * @param $shop_name
716 * @param $contact_link
717 * @param $billing_data
718 */
719 private function update_spoki_account($phone, $email, $shop_name, $contact_link, $billing_data = [])
720 {
721 $request_params = array(
722 "headers" => array("Authorization" => $this->options['secret'], "language" => $this->shop['language']),
723 "body" => [
724 "phone" => $phone ?: '',
725 "email" => $email ?: '',
726 "name" => $shop_name ?: '',
727 "contact_link" => $contact_link ?: '',
728 "zip_code" => $billing_data['zip_code'] ?: '',
729 "province" => $billing_data['province'] ?: '',
730 "country" => $billing_data['country'] ?: '',
731 "route" => $billing_data['route'] ?: '',
732 "city" => $billing_data['city'] ?: '',
733 "vat_number" => $billing_data['vat_number'] ?: '',
734 "vat_name" => $billing_data['vat_name'] ?: '',
735 "c_f" => $billing_data['c_f'] ?: '',
736 "pec" => $billing_data['pec'] ?: '',
737 "sid" => $billing_data['sid'] ?: '',
738 ],
739 "sslverify" => false,
740 "timeout" => 60,
741 );
742 wp_remote_post($this->api_account, $request_params);
743 }
744
745 /**
746 * Fetch and update the account_info
747 */
748 public function fetch_account_info()
749 {
750 $request_params = [
751 "headers" => [
752 "Authorization" => $this->options['secret'],
753 "language" => $this->shop['language']
754 ],
755 "sslverify" => false,
756 "timeout" => 60,
757 ];
758 $response = wp_remote_get($this->api_plan, $request_params);
759 $account_info = json_decode(wp_remote_retrieve_body($response), true);
760 $this->update_options(get_option(SPOKI_OPTIONS), ["account_info" => $account_info]);
761 return $account_info;
762 }
763
764 /**
765 * Check the status of the spoki keys
766 *
767 * @return array|WP_Error
768 */
769 public function check_spoki_status()
770 {
771 $secret = $this->options['secret'] ?? null;
772 $has_woocommerce = spoki_has_woocommerce();
773
774 $body = [
775 "is_plugin_active" => true,
776 "plugin_version" => $this->version,
777 "url" => $this->shop['url'],
778 "name" => $this->shop['name'],
779 "language" => $this->shop['language'],
780 "email" => $this->shop['email'],
781 "telephone" => $this->shop['telephone'],
782 "has_woocommerce" => $has_woocommerce,
783 "is_widget_fixed_enabled" => $this->shop['has_fixed_support_button'],
784 "is_widget_woo_shop_enabled" => $this->shop['has_product_item_listing_button'],
785 "is_widget_woo_item_enabled" => $this->shop['has_single_product_button'],
786 "is_widget_woo_cart_enabled" => $this->shop['has_cart_button'],
787 "is_widget_woo_checkout_enabled" => $this->shop['has_cart_button'],
788 "is_abandoned_carts_enabled" => $this->shop['has_abandoned_carts'],
789 "is_woo_send_enabled" => $has_woocommerce && $this->shop['has_notifications'],
790 "is_order_updated_notification_enabled" => $this->shop['has_order_updated_notification'],
791 "is_order_created_notification_enabled" => $this->shop['has_order_created_notification'],
792 "is_order_deleted_notification_enabled" => $this->shop['has_order_deleted_notification'],
793 "is_order_note_added_notification_enabled" => $this->shop['has_order_note_added_notification'],
794 "is_leave_review_notification_enabled" => $this->shop['has_leave_review_notification'],
795 "has_cart_recovered_to_seller_notification" => $this->shop['has_cart_recovered_to_seller_notification'],
796 "contact_link" => $this->shop['contact_link'] ?: '',
797 "zip_code" => $this->shop['billing_data']['zip_code'] ?: '',
798 "province" => $this->shop['billing_data']['province'] ?: '',
799 "country" => $this->shop['billing_data']['country'] ?: '',
800 "route" => $this->shop['billing_data']['route'] ?: '',
801 "city" => $this->shop['billing_data']['city'] ?: '',
802 "vat_number" => $this->shop['billing_data']['vat_number'] ?: '',
803 "vat_name" => $this->shop['billing_data']['vat_name'] ?: '',
804 "c_f" => $this->shop['billing_data']['c_f'] ?: '',
805 "pec" => $this->shop['billing_data']['pec'] ?: '',
806 "sid" => $this->shop['billing_data']['sid'] ?: '',
807 ];
808
809 if ($this->shop['has_abandoned_carts']) {
810 $spoki_abandoned_carts = Spoki_Abandoned_Carts::instance();
811 $abandoned_report = $spoki_abandoned_carts->get_report_by_type(SPOKI_CART_ABANDONED_ORDER);
812 $recovered_report = $spoki_abandoned_carts->get_report_by_type(SPOKI_CART_COMPLETED_ORDER);
813 $body["abandoned_carts_reports"] = [
814 "abandoned" => $abandoned_report,
815 "recovered" => $recovered_report,
816 ];
817 }
818
819 $request_params = [
820 "headers" => [
821 "Authorization" => $secret,
822 "language" => $this->shop['language'],
823 ],
824 "body" => $body,
825 "sslverify" => false,
826 "timeout" => 60,
827 ];
828 return wp_remote_post($this->api_status, $request_params);
829 }
830
831 /**
832 * Render the admin setup page
833 */
834 public function render_setup_page()
835 {
836 require_once SPOKI_DIR . 'includes/page-setup.php';
837 $this->render_components();
838 }
839
840
841 /**
842 * Render the Spoki Components
843 */
844 public function render_components()
845 {
846 $components = ['abandoned-carts-info-dialog', 'customer-notifications-info-dialog', 'buttons-info-dialog'];
847 foreach ($components as $component) {
848 include_once SPOKI_DIR . "components/$component.php";
849 }
850 }
851
852 /**
853 * Render the WhatsApp buttons in the website
854 */
855 public function render_buttons()
856 {
857 $has_fixed_support_button = isset($this->options['buttons']['fixed_support_button_check']) && $this->options['buttons']['fixed_support_button_check'] == 1;
858 $has_product_item_listing_button = isset($this->options['woocommerce']['product_item_listing_button_check']) && $this->options['woocommerce']['product_item_listing_button_check'] == 1;
859 $has_cart_button = isset($this->options['woocommerce']['cart_button_check']) && $this->options['woocommerce']['cart_button_check'] == 1;
860 $has_single_product_button = isset($this->options['woocommerce']['single_product_button_check']) && $this->options['woocommerce']['single_product_button_check'] == 1;
861
862 // Floating Button
863 if ($has_fixed_support_button) {
864 add_action('wp_footer', array($this, 'render_floating_button'), 1);
865 }
866
867 // Product Item Listing Button
868 if ($has_product_item_listing_button) {
869 add_action('woocommerce_after_shop_loop_item', array($this, 'render_product_item_listing_button'), 99);
870 }
871
872 // Cart Page Button
873 if ($has_cart_button) {
874 add_action('woocommerce_after_cart_totals', array($this, 'render_cart_button'), 99);
875 }
876
877 // Single Product Page Button
878 if ($has_single_product_button) {
879 $position = isset($this->options['woocommerce']['single_product_button_position']) ? $this->options['woocommerce']['single_product_button_position'] : 'after_atc';
880 switch ($position) {
881 case 'under_atc':
882 add_action('woocommerce_after_add_to_cart_form', array($this, 'render_single_product_button'), 99);
883 break;
884 case 'after_shortdesc':
885 add_action('woocommerce_before_add_to_cart_form', array($this, 'render_single_product_button'), 99);
886 break;
887 case 'after_atc':
888 default:
889 add_action('woocommerce_after_add_to_cart_button', array($this, 'render_single_product_button'), 99);
890 break;
891 }
892 }
893
894 /**
895 * Render the spoki button wherever you want using the shortcode
896 * @param $attrs
897 * @param null $content
898 */
899 function spoki_button_shortcode($attrs, $content = null)
900 {
901 $phone = $attrs['phone'] ?? Spoki()->shop['telephone'];
902 $cta = $attrs['cta'] ?? '';
903 $title = $attrs['title'] ?? '';
904 $final_message = urlencode($attrs['message'] ?? '');
905 $additional_class = $attrs['additional_class'] ?? '';
906 $type = 5;
907 $color = $attrs['color'] ?? null;
908 $border_type = $attrs['border_type'] ?? null;
909 $margin = [
910 "top" => $attrs['margin_top'] ?? null,
911 "bottom" => $attrs['margin_bottom'] ?? null,
912 "left" => $attrs['margin_left'] ?? null,
913 "right" => $attrs['margin_right'] ?? null,
914 ];
915 $padding = [
916 "top" => $attrs['padding_top'] ?? null,
917 "bottom" => $attrs['padding_bottom'] ?? null,
918 "left" => $attrs['padding_left'] ?? null,
919 "right" => $attrs['padding_right'] ?? null,
920 ];
921 $font_size = $attrs['font_size'] ?? null;
922 $id = $attrs['id'] ?? null;
923 $class_names = $attrs['class_names'] ?? null;
924 $custom_css = $attrs['custom_css'] ?? null;
925 Spoki()->render_relative_button($phone, $cta, $title, $final_message, $additional_class, $type, $margin, $padding, $color, $border_type, $font_size, '', $id, $class_names, $custom_css);
926 }
927
928 add_shortcode('spoki_button', 'spoki_button_shortcode');
929
930 add_action('wp_footer', array($this, 'render_shadowed_buttons'));
931 }
932
933 public function disable_checkout_button()
934 {
935 remove_action('woocommerce_proceed_to_checkout', 'woocommerce_button_proceed_to_checkout', 20);
936 }
937
938 /**
939 *
940 */
941 public function render_shadowed_buttons()
942 {
943 echo "<script>";
944 include_once SPOKI_DIR . 'assets/js/spoki-shadowed-buttons.js';
945 echo "</script>";
946 }
947
948 /**
949 * Render the WhatsApp FAB on the website in every page
950 */
951 public function render_floating_button()
952 {
953 if (isset($this->options['telephone']) && $this->options['telephone'] != '') {
954
955 /** Check visibility conditions */
956 $hide_on_posts = isset($this->options['buttons']['fixed_support_button_hide_post_page']) && $this->options['buttons']['fixed_support_button_hide_post_page'] == 1;
957 if ($hide_on_posts && is_single() && 'post' == get_post_type()) {
958 return;
959 }
960 $hide_on_pages = isset($this->options['buttons']['fixed_support_button_hide_single_page']) && $this->options['buttons']['fixed_support_button_hide_single_page'] == 1;
961 if ($hide_on_pages && is_page() && 'page' == get_post_type()) {
962 return;
963 }
964 $hide_on_shop = isset($this->options['buttons']['fixed_support_button_hide_shop_page']) && $this->options['buttons']['fixed_support_button_hide_shop_page'] == 1;
965 if ($hide_on_shop && is_shop()) {
966 return;
967 }
968 $hide_on_product = isset($this->options['buttons']['fixed_support_button_hide_product_page']) && $this->options['buttons']['fixed_support_button_hide_product_page'] == 1;
969 if ($hide_on_product && is_product()) {
970 return;
971 }
972 $hide_on_cart = isset($this->options['buttons']['fixed_support_button_hide_cart_page']) && $this->options['buttons']['fixed_support_button_hide_cart_page'] == 1;
973 if ($hide_on_cart && is_cart()) {
974 return;
975 }
976 $hide_on_checkout = isset($this->options['buttons']['fixed_support_button_hide_checkout_page']) && $this->options['buttons']['fixed_support_button_hide_checkout_page'] == 1;
977 if ($hide_on_checkout && (is_checkout() || is_checkout_pay_page())) {
978 return;
979 }
980
981
982 /** Can render, go on */
983 $prefix = isset($this->options['prefix']) ? $this->options['prefix'] : '';
984 $phone = $prefix . $this->options['telephone'];
985 $text = $this->options['buttons']['fixed_support_button_text'];
986 $position = $this->options['buttons']['fixed_support_button_position'] == 'Left' ? 'left' : 'right';
987 $border_type = isset($this->options['buttons']['fixed_support_button_border']) ? $this->options['buttons']['fixed_support_button_border'] : 'circle';
988 $size = isset($this->options['buttons']['fixed_support_button_size']) ? $this->options['buttons']['fixed_support_button_size'] : '50';
989 $icon_size = '65%';
990 $border_radius = '50%';
991 if ($border_type == 'squared') {
992 $border_radius = '0';
993 } elseif ($border_type == 'rounded') {
994 $border_radius = '8px';
995 }
996 $color = isset($this->options['buttons']['fixed_support_button_color']) ? $this->options['buttons']['fixed_support_button_color'] : '#23D366';
997 $bottom_space = isset($this->options['buttons']['fixed_support_button_bottom_space']) ? $this->options['buttons']['fixed_support_button_bottom_space'] : '12';
998 $side_space = isset($this->options['buttons']['fixed_support_button_side_space']) ? $this->options['buttons']['fixed_support_button_side_space'] : '12';
999 $furl = get_bloginfo('url');
1000 $wa_link = SPOKI_BASE_API_BUTTONS . "?type=1&phone=$phone&text=" . urlencode($text) . "&furl=$furl";
1001
1002 $spoki_fixed_btn_label = "";
1003 $has_label = isset($this->options['buttons']['fixed_support_button_show_label']) && $this->options['buttons']['fixed_support_button_show_label'] == 1;
1004 if ($has_label) {
1005 $show_on_hover = (isset($this->options['buttons']['fixed_support_button_show_label_on_hover']) && $this->options['buttons']['fixed_support_button_show_label_on_hover'] == 1) ? 'hide-not-hover' : '';
1006 $label_space = intval($size) + 12;
1007 $label_content = (isset($this->options['buttons']['fixed_support_button_label_content']) && $this->options['buttons']['fixed_support_button_label_content'] != "") ? $this->options['buttons']['fixed_support_button_label_content'] : __('Chat with us 👋', "spoki");
1008 $label_font_size = (isset($this->options['buttons']['fixed_support_button_label_font_size']) && $this->options['buttons']['fixed_support_button_label_font_size'] != "") ? $this->options['buttons']['fixed_support_button_label_font_size'] : '16';
1009 $label_text_color = isset($this->options['buttons']['fixed_support_button_label_text_color']) ? $this->options['buttons']['fixed_support_button_label_text_color'] : '#333333';
1010 $label_background_color = isset($this->options['buttons']['fixed_support_button_label_background_color']) ? $this->options['buttons']['fixed_support_button_label_background_color'] : '#FFFFFF';
1011 $label_border_radius = $border_type == 'squared' ? '0' : '10';
1012 $label_delay = (intval((isset($this->options['buttons']['fixed_support_button_label_delay']) && $this->options['buttons']['fixed_support_button_label_delay'] != "") ? $this->options['buttons']['fixed_support_button_label_delay'] : '0') * 1000) + 1000;
1013 $spoki_fixed_btn_label = "<div class='spoki-fixed-btn-label $show_on_hover' style='display:none;$position:{$label_space}px;font-size:{$label_font_size}px;color:$label_text_color;background-color:$label_background_color;border-radius:{$label_border_radius}px;'>$label_content</div><script>(function (d) {setTimeout(function (){try {const shadowedButton = d.querySelector('#spoki-shadowed-fixed-button'); if (shadowedButton) {shadowedButton.shadowRoot.querySelector('.spoki-fixed-btn-label').style.display ='';}} catch (e) {console.log(e)}}, $label_delay)})(document)</script>";
1014 }
1015
1016 $spoki_fixed_btn_popup = "";
1017 $has_popup = isset($this->options['buttons']['fixed_support_button_show_popup']) && $this->options['buttons']['fixed_support_button_show_popup'] == 1;
1018 if ($has_popup) {
1019 $spoki_fixed_btn_popup_delay = (intval((isset($this->options['buttons']['fixed_support_button_popup_delay']) && $this->options['buttons']['fixed_support_button_popup_delay'] != "") ? $this->options['buttons']['fixed_support_button_popup_delay'] : '0') * 1000) + 1000;
1020 $spoki_fixed_btn_popup = "<span class='spoki-fixed-btn-popup' style='display: none;'>1</span><script>(function (d) {setTimeout(function (){try {const shadowedButton = d.querySelector('#spoki-shadowed-fixed-button'); if (shadowedButton) {shadowedButton.shadowRoot.querySelector('.spoki-fixed-btn-popup').style.display ='';}} catch (e) {console.log(e)}}, $spoki_fixed_btn_popup_delay)})(document)</script>";
1021 }
1022
1023 $hidden_mobile = isset($this->options['buttons']['fixed_support_button_hide_mobile']) && $this->options['buttons']['fixed_support_button_hide_mobile'] == 1 ? "hidden-mobile" : "";
1024 $hidden_tablet = isset($this->options['buttons']['fixed_support_button_hide_tablet']) && $this->options['buttons']['fixed_support_button_hide_tablet'] == 1 ? "hidden-tablet" : "";
1025 $hidden_desktop = isset($this->options['buttons']['fixed_support_button_hide_desktop']) && $this->options['buttons']['fixed_support_button_hide_desktop'] == 1 ? "hidden-desktop" : "";
1026 $class = "$hidden_mobile $hidden_tablet $hidden_desktop";
1027
1028 $chat_widget = "";
1029 $has_chat_widget = isset($this->options['buttons']['fixed_support_button_show_chat_widget']) && $this->options['buttons']['fixed_support_button_show_chat_widget'] == 1;
1030 if ($has_chat_widget) {
1031 $background_image_url = plugins_url() . '/' . SPOKI_PLUGIN_NAME . '/assets/images/wa-background.jpeg';
1032 $from_message = $this->options['buttons']['fixed_support_button_chat_widget_message'];
1033 $send_icon = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M1.101 21.757L23.8 12.028 1.101 2.3l.011 7.912 13.623 1.816-13.623 1.817-.011 7.912z"></path></svg>';
1034 $chat_widget_delay = intval((isset($this->options['buttons']['fixed_support_button_chat_widget_delay']) && $this->options['buttons']['fixed_support_button_chat_widget_delay'] != "") ? $this->options['buttons']['fixed_support_button_chat_widget_delay'] : '0') * 1000;
1035 $chat_widget_delay_script = "";
1036 if ($chat_widget_delay != 0) {
1037 $chat_widget_delay = $chat_widget_delay + 1000;
1038 $chat_widget_delay_script = "<script>(function (d) {setTimeout(function (){try {const shadowedButton = d.querySelector('#spoki-shadowed-fixed-button'); if (shadowedButton) {shadowedButton.shadowRoot.querySelector('#spoki-chat-preview').classList.remove('hidden')}} catch (e) {console.log(e)}}, $chat_widget_delay)})(document)</script>";
1039 }
1040 $chat_widget_script = "<script>(function (d) {setTimeout(function(){const shadowedButton = d.querySelector('#spoki-shadowed-fixed-button'); if (shadowedButton) {shadowedButton.shadowRoot.querySelector('#spoki-chat-link').addEventListener('click', function(e){e.preventDefault();document.querySelector('#spoki-shadowed-fixed-button').shadowRoot.querySelector('#spoki-chat-preview').classList.toggle('hidden')})}}, 1000)})(document)</script>$chat_widget_delay_script";
1041 $link = SPOKI_BASE_API_BUTTONS;
1042 $furl = get_bloginfo('url');
1043 $chat_widget = "<div id='spoki-chat-preview' class='hidden' style='bottom:{$size}px;background-image: url(\"{$background_image_url}\")'><div class='spoki-chat-preview-chat-message'>{$from_message}</div><div id='spoki-chat-preview-footer'><form method='get' target='_blank' action='$link'><input type='hidden' name='phone' value='{$phone}'/><input type='hidden' name='furl' value='{$furl}'/><input type='hidden' name='type' value='1'/><input id='spoki-chat-preview-message' type='text' name='text' value='$text' /><button id='spoki-chat-preview-send' type='submit'>{$send_icon}</button></form></div></div>$chat_widget_script";
1044 }
1045
1046 echo "<div id='spoki-shadowed-fixed-button' class='spoki-shadowed-button'><div id='spoki-fixed-btn' class='$class' style='$position:{$side_space}px;bottom:{$bottom_space}px;'>{$chat_widget}{$spoki_fixed_btn_label}{$spoki_fixed_btn_popup}<a id='spoki-chat-link' style='background-color:{$color};border-radius:$border_radius;height:{$size}px;width:{$size}px;' href='$wa_link' target='_blank' rel='nofollow'>" . spoki_get_wa_logo($size) . "</a></div></div>";
1047 }
1048 }
1049
1050 /**
1051 * Render the product button for every product in shop page
1052 */
1053 public function render_product_item_listing_button()
1054 {
1055 global $product;
1056 $prefix = isset($this->options['prefix']) ? $this->options['prefix'] : '';
1057 $phone = $prefix . $this->options['telephone'];
1058 $cta = __("Request support on WhatsApp", "spoki");
1059 if (isset($this->options['woocommerce']['product_item_listing_button_cta']) && !empty($this->options['woocommerce']['product_item_listing_button_cta'])) {
1060 $cta = $this->options['woocommerce']['product_item_listing_button_cta'];
1061 }
1062 $message = __("Hi, I want to buy:", "spoki");
1063 if (isset($this->options['woocommerce']['product_item_listing_button_text']) && !empty($this->options['woocommerce']['product_item_listing_button_text'])) {
1064 $message = $this->options['woocommerce']['product_item_listing_button_text'];
1065 }
1066 $color = isset($this->options['woocommerce']['product_item_listing_button_color']) ? $this->options['woocommerce']['product_item_listing_button_color'] : '#23D366';
1067 $font_size = isset($this->options['woocommerce']['product_item_listing_button_font_size']) ? $this->options['woocommerce']['product_item_listing_button_font_size'] : '12';
1068 $border_type = isset($this->options['woocommerce']['product_item_listing_button_border']) ? $this->options['woocommerce']['product_item_listing_button_border'] : 'rounded';
1069
1070 $margin = [
1071 "top" => isset($this->options['woocommerce']['product_item_listing_button_margin_top']) ? $this->options['woocommerce']['product_item_listing_button_margin_top'] : '4',
1072 "bottom" => isset($this->options['woocommerce']['product_item_listing_button_margin_bottom']) ? $this->options['woocommerce']['product_item_listing_button_margin_bottom'] : '4',
1073 "left" => isset($this->options['woocommerce']['product_item_listing_button_margin_left']) ? $this->options['woocommerce']['product_item_listing_button_margin_left'] : '0',
1074 "right" => isset($this->options['woocommerce']['product_item_listing_button_margin_right']) ? $this->options['woocommerce']['product_item_listing_button_margin_right'] : '0',
1075 ];
1076
1077 $padding = [
1078 "top" => isset($this->options['woocommerce']['product_item_listing_button_padding_top']) ? $this->options['woocommerce']['product_item_listing_button_padding_top'] : '8',
1079 "bottom" => isset($this->options['woocommerce']['product_item_listing_button_padding_bottom']) ? $this->options['woocommerce']['product_item_listing_button_padding_bottom'] : '8',
1080 "left" => isset($this->options['woocommerce']['product_item_listing_button_padding_left']) ? $this->options['woocommerce']['product_item_listing_button_padding_left'] : '14',
1081 "right" => isset($this->options['woocommerce']['product_item_listing_button_padding_right']) ? $this->options['woocommerce']['product_item_listing_button_padding_right'] : '14',
1082 ];
1083
1084 $this->render_product_button($product, $phone, $cta, $message, $margin, $padding, $color, $border_type, $font_size, true);
1085 }
1086
1087 /**
1088 * Render the button in the cart page
1089 */
1090 public function render_cart_button()
1091 {
1092 global $product;
1093 $prefix = isset($this->options['prefix']) ? $this->options['prefix'] : '';
1094 $phone = $prefix . $this->options['telephone'];
1095 $cta = __("Order via WhatsApp", "spoki");
1096 if (isset($this->options['woocommerce']['cart_button_cta']) && !empty($this->options['woocommerce']['cart_button_cta'])) {
1097 $cta = $this->options['woocommerce']['cart_button_cta'];
1098 }
1099 $message = __("Hi, I want to buy:", "spoki");
1100 if (isset($this->options['woocommerce']['cart_button_text']) && !empty($this->options['woocommerce']['cart_button_text'])) {
1101 $message = $this->options['woocommerce']['cart_button_text'];
1102 }
1103 $color = isset($this->options['woocommerce']['cart_button_color']) ? $this->options['woocommerce']['cart_button_color'] : '#23D366';
1104 $font_size = isset($this->options['woocommerce']['cart_button_font_size']) ? $this->options['woocommerce']['cart_button_font_size'] : '12';
1105 $icon_size = intval($font_size) < 16 ? 16 : $font_size;
1106 $final_message = urlencode($message);
1107 $products = WC()->cart->get_cart();
1108
1109 foreach ($products as $item) {
1110 $product_id = $item['product_id'];
1111 $qty = $item['quantity'];
1112 $product = wc_get_product($product_id);
1113 $product_url = $product->get_permalink();
1114 $product_title = $product->get_name();
1115 $price = wp_strip_all_tags(wc_price(wc_get_price_including_tax($product)));
1116 $encoded_title = urlencode($product_title);
1117 $encoded_product_url = urlencode($product_url);
1118 $final_message .= "%0D%0A%0D%0A(ID:%20$product_id)%20*$encoded_title*%20$price%20x$qty%0D%0A$encoded_product_url";
1119 }
1120 $furl = get_bloginfo('url');
1121 $href = SPOKI_BASE_API_BUTTONS . "?type=4&phone=$phone&text=$final_message&furl=$furl";
1122 $title = "$cta";
1123 $class = 'button spoki-button size-4';
1124 $border_type = isset($this->options['woocommerce']['cart_button_border']) ? $this->options['woocommerce']['cart_button_border'] : 'rounded';
1125
1126 $border_radius = '16px';
1127 if ($border_type == 'squared') {
1128 $border_radius = '0';
1129 }
1130
1131 $margin = [
1132 "top" => isset($this->options['woocommerce']['cart_button_margin_top']) ? $this->options['woocommerce']['cart_button_margin_top'] : '4',
1133 "bottom" => isset($this->options['woocommerce']['cart_button_margin_bottom']) ? $this->options['woocommerce']['cart_button_margin_bottom'] : '4',
1134 "left" => isset($this->options['woocommerce']['cart_button_margin_left']) ? $this->options['woocommerce']['cart_button_margin_left'] : '0',
1135 "right" => isset($this->options['woocommerce']['cart_button_margin_right']) ? $this->options['woocommerce']['cart_button_margin_right'] : '0',
1136 ];
1137 $padding = [
1138 "top" => isset($this->options['woocommerce']['cart_button_padding_top']) ? $this->options['woocommerce']['cart_button_padding_top'] : '8',
1139 "bottom" => isset($this->options['woocommerce']['cart_button_padding_bottom']) ? $this->options['woocommerce']['cart_button_padding_bottom'] : '8',
1140 "left" => isset($this->options['woocommerce']['cart_button_padding_left']) ? $this->options['woocommerce']['cart_button_padding_left'] : '14',
1141 "right" => isset($this->options['woocommerce']['cart_button_padding_right']) ? $this->options['woocommerce']['cart_button_padding_right'] : '14',
1142 ];
1143
1144 $margin_style = "margin-top:{$margin['top']}px;margin-bottom:{$margin['bottom']}px;margin-left:{$margin['left']}px;margin-right:{$margin['right']}px;";
1145 $padding_style = "padding-top:{$padding['top']}px;padding-bottom:{$padding['bottom']}px;padding-left:{$padding['left']}px;padding-right:{$padding['right']}px;";
1146
1147 echo "<div class='spoki-shadowed-button'><div class='spoki-button-relative' style='{$margin_style}'><a href='$href' target='_blank' rel='nofollow' title='$title' class='$class' style='background-color:$color;border-radius:$border_radius;font-size:{$font_size}px;{$padding_style}'>" . spoki_get_wa_logo($icon_size) . "<span>$cta</span></a></div></div>";
1148 }
1149
1150 /**
1151 * Render the button in the product page
1152 */
1153 public function render_single_product_button()
1154 {
1155 global $product;
1156 $prefix = isset($this->options['prefix']) ? $this->options['prefix'] : '';
1157 $phone = $prefix . $this->options['telephone'];
1158 $cta = __("Request support on WhatsApp", "spoki");
1159 if (isset($this->options['woocommerce']['single_product_button_cta']) && !empty($this->options['woocommerce']['single_product_button_cta'])) {
1160 $cta = $this->options['woocommerce']['single_product_button_cta'];
1161 }
1162 $message = __("Hi, I want to buy:", "spoki");
1163 if (isset($this->options['woocommerce']['single_product_button_text']) && !empty($this->options['woocommerce']['single_product_button_text'])) {
1164 $message = $this->options['woocommerce']['single_product_button_text'];
1165 }
1166 $position = isset($this->options['woocommerce']['single_product_button_position']) ? $this->options['woocommerce']['single_product_button_position'] : 'after_atc';
1167 $color = isset($this->options['woocommerce']['single_product_button_color']) ? $this->options['woocommerce']['single_product_button_color'] : '#23D366';
1168 $font_size = isset($this->options['woocommerce']['single_product_button_font_size']) ? $this->options['woocommerce']['single_product_button_font_size'] : '12';
1169 $border_type = isset($this->options['woocommerce']['single_product_button_border']) ? $this->options['woocommerce']['single_product_button_border'] : 'rounded';
1170
1171 $margin = [
1172 "top" => isset($this->options['woocommerce']['single_product_button_margin_top']) ? $this->options['woocommerce']['single_product_button_margin_top'] : '4',
1173 "bottom" => isset($this->options['woocommerce']['single_product_button_margin_bottom']) ? $this->options['woocommerce']['single_product_button_margin_bottom'] : '4',
1174 "left" => isset($this->options['woocommerce']['single_product_button_margin_left']) ? $this->options['woocommerce']['single_product_button_margin_left'] : '0',
1175 "right" => isset($this->options['woocommerce']['single_product_button_margin_right']) ? $this->options['woocommerce']['single_product_button_margin_right'] : '0',
1176 ];
1177
1178 $padding = [
1179 "top" => isset($this->options['woocommerce']['single_product_button_padding_top']) ? $this->options['woocommerce']['single_product_button_padding_top'] : '8',
1180 "bottom" => isset($this->options['woocommerce']['single_product_button_padding_bottom']) ? $this->options['woocommerce']['single_product_button_padding_bottom'] : '8',
1181 "left" => isset($this->options['woocommerce']['single_product_button_padding_left']) ? $this->options['woocommerce']['single_product_button_padding_left'] : '14',
1182 "right" => isset($this->options['woocommerce']['single_product_button_padding_right']) ? $this->options['woocommerce']['single_product_button_padding_right'] : '14',
1183 ];
1184
1185 $this->render_product_button($product, $phone, $cta, $message, $margin, $padding, $color, $border_type, $font_size, false, $position);
1186 }
1187
1188 /**
1189 * Render the button of a product
1190 *
1191 * @param $product
1192 * @param $phone
1193 * @param $cta
1194 * @param $message
1195 * @param null $position
1196 */
1197 public function render_product_button($product, $phone, $cta, $message, $margin, $padding, $color = '#23D366', $border_type = 'rounded', $font_size = '12', $is_listing = false, $position = null)
1198 {
1199 $product_url = $product->get_permalink();
1200 $product_title = $product->get_name();
1201 $product_id = $product->get_id();
1202 $price = wp_strip_all_tags(wc_price(wc_get_price_including_tax($product)));
1203 $encoded_message = urlencode($message);
1204 $encoded_title = urlencode($product_title);
1205 $encoded_product_url = urlencode($product_url);
1206 $final_message = "$encoded_message%0D%0A%0D%0A(ID:%20$product_id)%20*$encoded_title*%20$price%0D%0A$encoded_product_url";
1207 $type = $is_listing ? '2' : '3';
1208 $title = "$cta $product_title";
1209 $additional_class = sprintf('product_type_%s', $product->get_type());
1210
1211 $this->render_relative_button($phone, $cta, $title, $final_message, $additional_class, $type, $margin, $padding, $color, $border_type, $font_size, $position);
1212 }
1213
1214 /**
1215 * Render a relative button
1216 */
1217 public function render_relative_button($phone, $cta, $title, $final_message, $additional_class, $type, $margin, $padding, $color = '#23D366', $border_type = 'rounded', $font_size = '12', $position = null, $id = '', $class_names = '', $custom_css = '')
1218 {
1219 $class = sprintf('button spoki-button %s', $additional_class);
1220 if (isset($position)) {
1221 $class .= " size-2 $position";
1222 } else {
1223 $class .= " size-4";
1224 }
1225
1226 $border_radius = '16px';
1227 if ($border_type == 'squared') {
1228 $border_radius = '0';
1229 }
1230
1231 $furl = get_bloginfo('url');
1232 $href = SPOKI_BASE_API_BUTTONS . "?type={$type}&phone=$phone&text=$final_message&furl=$furl";
1233 $icon_size = intval($font_size) < 16 ? 16 : $font_size;
1234
1235 $margin_style = "margin-top:{$margin['top']}px;margin-bottom:{$margin['bottom']}px;margin-left:{$margin['left']}px;margin-right:{$margin['right']}px;";
1236 $padding_style = "padding-top:{$padding['top']}px;padding-bottom:{$padding['bottom']}px;padding-left:{$padding['left']}px;padding-right:{$padding['right']}px;";
1237
1238 echo "<div class='spoki-shadowed-button'><div class='spoki-button-relative $class_names' id='$id' style='$margin_style'><a href='$href' target='_blank' rel='nofollow' title='$title' class='$class' style='background-color:$color;border-radius:$border_radius;font-size:{$font_size}px;$padding_style'>" . spoki_get_wa_logo($icon_size) . "<span>$cta</span></a><style>$custom_css</style></div></div>";
1239 }
1240
1241 /**
1242 * Get the link to change plan
1243 *
1244 * @param $is_upgrade
1245 * @return string
1246 */
1247 public function get_plan_link($is_upgrade): string
1248 {
1249 $url = $this->shop['language'] == 'it-IT' ? 'https://spoki.it/spoki-flex-acquista-pacchetti-flex/?' : 'https://spoki.it/spoki-flex-acquista-pacchetti-flex/spoki-flex-packages/?';
1250
1251 if (isset($this->options['account_info']['upgrade_url'])) {
1252 $url = $this->options['account_info']['upgrade_url'];
1253 }
1254 if ($is_upgrade) {
1255 $url .= '&is_upgrade=true';
1256 }
1257 return $url;
1258 }
1259
1260 /**
1261 * Get the link to change plan to pro
1262 *
1263 * @return string
1264 */
1265 public function get_pro_plan_link(): string
1266 {
1267 return 'https://spoki.it/piani-e-prezzi-spoki-pro/?';
1268 }
1269
1270 /**
1271 * Update Spoki options
1272 *
1273 * @param $current_options
1274 * @param $new_options
1275 */
1276 private function update_options($current_options, $new_options)
1277 {
1278 $c_options = is_string($current_options) ? [] : $current_options;
1279 if (isset($new_options["woocommerce"])) {
1280 $woocommerce = [];
1281 if (isset($c_options["abandoned_carts"])) {
1282 $woocommerce = array_merge($c_options["woocommerce"], $new_options["woocommerce"]);
1283 } else {
1284 $woocommerce = $new_options["woocommerce"];
1285 }
1286 $new_options["woocommerce"] = $woocommerce;
1287 }
1288 if (isset($new_options["abandoned_carts"])) {
1289 $abandoned_carts = [];
1290 if (isset($c_options["abandoned_carts"])) {
1291 $abandoned_carts = array_merge($c_options["abandoned_carts"], $new_options["abandoned_carts"]);
1292 } else {
1293 $abandoned_carts = $new_options["abandoned_carts"];
1294 }
1295 $new_options["abandoned_carts"] = $abandoned_carts;
1296 }
1297 if (isset($new_options["secret_status"])) {
1298 $secret_status = [];
1299 if (isset($c_options["secret_status"])) {
1300 $secret_status = array_merge($c_options["secret_status"], $new_options["secret_status"]);
1301 } else {
1302 $secret_status = $new_options["secret_status"];
1303 }
1304 $new_options["secret_status"] = $secret_status;
1305 }
1306 if (isset($new_options["account_info"])) {
1307 $account_info = [];
1308 if (isset($c_options["account_info"])) {
1309 $account_info = array_merge($c_options["account_info"], $new_options["account_info"]);
1310 } else {
1311 $account_info = $new_options["account_info"];
1312 }
1313 $new_options["account_info"] = $account_info;
1314 }
1315 update_option(SPOKI_OPTIONS, array_merge($c_options, $new_options));
1316 $this->options = get_option(SPOKI_OPTIONS);
1317 }
1318
1319 /**
1320 * Check the secret status periodically
1321 */
1322 public function check_secret_status()
1323 {
1324 $this->fetch_secret_status(true);
1325 }
1326
1327 /**
1328 * Fetch the status of the Spoki keys
1329 */
1330 public function fetch_secret_status($force_checking = false)
1331 {
1332 $response = null;
1333 if ($force_checking) {
1334 $response = $this->check_spoki_status();
1335 }
1336
1337 if (!isset($this->options['secret']) || $this->options['secret'] == '' || !isset($this->options['delivery_url']) || $this->options['delivery_url'] == '') {
1338 $this->update_options(get_option(SPOKI_OPTIONS), ["secret_status" => [
1339 'secret' => isset($this->options['secret']) ? $this->options['secret'] : '',
1340 'delivery_url' => isset($this->options['delivery_url']) ? $this->options['delivery_url'] : '',
1341 'code' => 0,
1342 'message' => ''
1343 ]]);
1344 } else if (!isset($this->options['secret_status']) || $this->options['secret_status']['code'] != 200 || $this->options['secret'] != $this->options['secret_status']['secret'] || $this->options['delivery_url'] != $this->options['secret_status']['delivery_url']) {
1345 if (!$force_checking) {
1346 $response = $this->check_spoki_status();
1347 }
1348 $this->update_options(get_option(SPOKI_OPTIONS), ["secret_status" => [
1349 'secret' => isset($this->options['secret']) ? $this->options['secret'] : '',
1350 'delivery_url' => isset($this->options['delivery_url']) ? $this->options['delivery_url'] : '',
1351 'code' => wp_remote_retrieve_response_code($response),
1352 'message' => wp_remote_retrieve_response_message($response)
1353 ]]);
1354 }
1355 }
1356
1357 /**
1358 * Activation Reset
1359 */
1360 public function activation_reset()
1361 {
1362 register_uninstall_hook(SPOKI_PLUGIN_FILE, array($this, 'uninstall_plugin'));
1363 if (!class_exists('WooCommerce')) {
1364 return;
1365 }
1366 $this->initialize_cart_abandonment_tables();
1367 $Abandonment = Spoki_Abandoned_Carts::instance();
1368 $spokiDomain = $Abandonment->get_spoki_setting_by_meta("spoki_domain");
1369 $Abandonment->set_spoki_setting_by_meta("plugin_activated", "true");
1370 if ($spokiDomain != null || $spokiDomain != "")
1371 $Abandonment->save_webhook_url($spokiDomain);
1372 }
1373
1374 /**
1375 * Deactivation Reset
1376 */
1377 public function deactivation_reset()
1378 {
1379 $Abandonment = Spoki_Abandoned_Carts::instance();
1380 # Unschedule hook for versions <= v2.8.1
1381 $Abandonment->unschedule_hook();
1382 if (!class_exists('WooCommerce')) {
1383 return;
1384 }
1385 $spokiDomain = $Abandonment->get_spoki_setting_by_meta("spoki_domain");
1386 $Abandonment->set_spoki_setting_by_meta("plugin_activated", "false");
1387 if ($spokiDomain != null || $spokiDomain != "")
1388 $Abandonment->disable_webhook_url($spokiDomain);
1389 }
1390
1391 /**
1392 * Uninstall Plugin
1393 */
1394 public function uninstall_plugin()
1395 {
1396 if (!class_exists('WooCommerce')) {
1397 return;
1398 }
1399 $Abandonment = Spoki_Abandoned_Carts::instance();
1400 $spokiDomain = $Abandonment->get_spoki_setting_by_meta("spoki_domain");
1401 $Abandonment->set_spoki_setting_by_meta("plugin_activated", "false");
1402 if ($spokiDomain != null || $spokiDomain != "")
1403 $Abandonment->disable_webhook_url($spokiDomain);
1404 }
1405 }
1406
1407 /**
1408 * Executed on activation of the plugin.
1409 * Redirects the user after plugin activation.
1410 */
1411 function spoki_activation()
1412 {
1413 if (!((isset($_REQUEST['action']) && 'activate-selected' === $_REQUEST['action']) && (isset($_POST['checked']) && count($_POST['checked']) > 1))) {
1414 add_option('spoki_activation_redirect', true);
1415 }
1416 }
1417
1418 /**
1419 * Redirects the user after plugin activation.
1420 */
1421 function spoki_activation_redirect()
1422 {
1423 if (get_option('spoki_activation_redirect', false)) {
1424 delete_option('spoki_activation_redirect');
1425 exit(wp_redirect(admin_url('/admin.php?page=' . SPOKI_PLUGIN_NAME)));
1426 }
1427 }
1428
1429 /**
1430 * Executed on deactivation of the plugin
1431 */
1432 function spoki_deactivation()
1433 {
1434 // delete_option(SPOKI_OPTIONS);
1435 $timestamp = wp_next_scheduled('spoki_cron_hook');
1436 wp_unschedule_event($timestamp, 'spoki_cron_hook');
1437 }
1438
1439 register_deactivation_hook(__FILE__, 'spoki_deactivation');
1440 register_activation_hook(__FILE__, 'spoki_activation');
1441 add_action('admin_init', 'spoki_activation_redirect');
1442