PluginProbe
Spoki – Chat Buttons and WooCommerce Notifications / 2.8.2
Spoki – Chat Buttons and WooCommerce Notifications v2.8.2
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.8.2, at spoki.php

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