PluginProbe
ChatHelp – Click to Chat Button, WooCommerce Chat to Order & Floating Chat Form / trunk
ChatHelp – Click to Chat Button, WooCommerce Chat to Order & Floating Chat Form vtrunk
3.5.9 3.5.8 3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.0 3.5.1 3.4.2 3.4.1 3.4.0 3.3.2 trunk 1.6.1 1.6.2 1.6.3 1.6.4 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 All 76 releases
chat-help / src / Frontend / Frontend.php

Frontend.php in ChatHelp – Click to Chat Button, WooCommerce Chat to Order & Floating Chat Form trunk, at src/Frontend/Frontend.php

456 lines 22.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * The public-facing functionality of the plugin.
5 *
6 * Defines the plugin name, version, and two examples hooks for how to
7 * enqueue the public-facing stylesheet and JavaScript.
8 *
9 * @link https://themeatelier.net
10 * @since 1.0.0
11 *
12 * @package chat-help
13 * @subpackage chat-help/src/Frontend
14 * @author ThemeAtelier<themeatelierbd@gmail.com>
15 */
16
17 namespace ThemeAtelier\ChatHelp\Frontend;
18
19 use ThemeAtelier\ChatHelp\Frontend\Templates\items\Buttons;
20 use ThemeAtelier\ChatHelp\Frontend\Templates\ButtonTemplate;
21 use ThemeAtelier\ChatHelp\Frontend\Templates\FormTemplate;
22 use ThemeAtelier\ChatHelp\Frontend\Templates\SingleTemplate;
23 use ThemeAtelier\ChatHelp\Frontend\Helpers\Helpers;
24 use ThemeAtelier\ChatHelp\Frontend\Templates\SingleTemplateInput;
25
26 if (! defined('ABSPATH')) {
27 die;
28 } // Cannot access directly.
29 /**
30 * The Frontend class to manage all public facing stuffs.
31 *
32 * @since 1.0.0
33 */
34 class Frontend
35 {
36 /**
37 * System / web-safe font families that never need a Google Font stylesheet
38 * (already available locally). Mirrors chat-help-pro's list one-for-one so
39 * the Typography font-family feature behaves identically across versions.
40 *
41 * @var array<int,string>
42 */
43 const SAFE_WEB_FONTS = [
44 'Arial', 'Arial Black', 'Helvetica', 'Times New Roman', 'Courier New',
45 'Tahoma', 'Verdana', 'Impact', 'Trebuchet MS', 'Comic Sans MS',
46 'Lucida Console', 'Lucida Sans Unicode', 'Georgia', 'Palatino Linotype',
47 ];
48
49 /**
50 * Enqueue a Google Font stylesheet for a resolved typography font-family
51 * (the `chat_help_typography` field's `font-family` sub-option). Ported from
52 * chat-help-pro so the free version loads the chosen font exactly the same
53 * way: the modern `/css2` endpoint, a real `wp_enqueue_style()` handle
54 * (deduped, filterable, `null` version so no cache-busting query string),
55 * skipped entirely for `SAFE_WEB_FONTS` system fonts.
56 *
57 * Used by both the real frontend (`enqueue_scripts()`, hooked to
58 * `wp_enqueue_scripts` so it prints in `<head>`) and the admin live preview
59 * (`PreviewRest::render_assets()`, which needs the returned handle to
60 * `wp_styles()->do_items()` it into the preview iframe's head) — one
61 * function, so the two can never diverge.
62 *
63 * @param string $font_family The selected font family name.
64 * @return string The registered style handle, or '' if nothing was enqueued.
65 */
66 public static function enqueue_google_font($font_family)
67 {
68 $font_family = trim((string) $font_family);
69 if (! $font_family || in_array($font_family, self::SAFE_WEB_FONTS, true)) {
70 return '';
71 }
72 $handle = 'chat-help-google-font-' . sanitize_key($font_family);
73 $font_url_family = str_replace(' ', '+', $font_family);
74 wp_enqueue_style($handle, "https://fonts.googleapis.com/css2?family={$font_url_family}&display=swap", [], null);
75 return $handle;
76 }
77
78 /**
79 * The slug of this plugin.
80 *
81 * @since 1.0.0
82 * @access private
83 * @var string $plugin_slug The slug of this plugin.
84 */
85 private $plugin_slug;
86
87 /**
88 * The min of this plugin.
89 *
90 * @since 1.0.0
91 * @access private
92 * @var string $min The slug of this plugin.
93 */
94 private $min;
95
96 public $unique_id;
97
98 /**
99 * Initialize the class and set its properties.
100 *
101 * @since 1.0.0
102 * @param string $plugin_name The name of the plugin.
103 * @param string $version The version of this plugin.
104 */
105 public function __construct()
106 {
107 $this->unique_id = uniqid();
108 $this->min = defined('WP_DEBUG') && WP_DEBUG ? '' : '.min';
109 add_action('wp_footer', array($this, 'chat_help_content'));
110 add_action('wp_ajax_handle_form_submission', [$this, 'handle_form_submission']);
111 add_action('wp_ajax_nopriv_handle_form_submission', [$this, 'handle_form_submission']);
112
113 add_filter('kses_allowed_protocols', [$this, 'allow_whatsapp_protocol']);
114 add_action('wp_head', array($this, 'chat_help_header_script'), 1);
115 add_action('login_head', array($this, 'chat_help_header_script'), 1);
116 add_action('register_head', array($this, 'chat_help_header_script'), 1);
117 }
118
119 function chat_help_header_script()
120 {
121 $options = get_option('cwp_option');
122 $alternative_wHelpBubble = isset($options['alternative_wHelpBubble']) ? $options['alternative_wHelpBubble'] : "";
123 ?>
124 <script type="text/javascript" class="chat_help_inline_js">
125 var alternativeWHelpBubble = "<?php echo esc_attr($alternative_wHelpBubble); ?>";
126 </script>
127 <?php
128 }
129
130 public function allow_whatsapp_protocol($protocols)
131 {
132 $protocols[] = 'whatsapp';
133 return $protocols;
134 }
135
136 /**
137 * Register the stylesheets for the public-facing side of the site.
138 *
139 * @since 1.0.0
140 */
141 public static function enqueue_scripts()
142 {
143 $options = get_option('cwp_option');
144 $ch_settings = get_option('ch_settings');
145 $wa_custom_css = isset($ch_settings['whatsapp-custom-css']) ? $ch_settings['whatsapp-custom-css'] : '';
146 $wa_custom_js = isset($ch_settings['whatsapp-custom-js']) ? $ch_settings['whatsapp-custom-js'] : '';
147 $auto_show_popup = isset($options['autoshow-popup']) ? $options['autoshow-popup'] : '';
148 $auto_open_popup_timeout = isset($options['auto_open_popup_timeout']) ? $options['auto_open_popup_timeout'] : 0;
149 $open_in_new_tab = Helpers::open_in_new_tab_target($ch_settings);
150
151 $google_analytics = isset($ch_settings['google_analytics']) ? $ch_settings['google_analytics'] : '';
152 $event_name = isset($ch_settings['event_name']) ? $ch_settings['event_name'] : '';
153 $google_analytics_parameter = isset($ch_settings['google_analytics_parameter']) ? $ch_settings['google_analytics_parameter'] : array();
154 $analytics_parameter = [];
155 $analytics_parameter['google_analytics'] = $google_analytics;
156 $analytics_parameter['event_name'] = $event_name;
157 $whatsapp_group = isset($options['opt-group']) ? $options['opt-group'] : '';
158 $site_title = get_bloginfo('name'); // WordPress site title
159 $current_title = wp_get_document_title();
160 $current_url = home_url(add_query_arg(null, null)); // Current full URL
161
162 if (is_array($google_analytics_parameter)) {
163 foreach ($google_analytics_parameter as &$param) {
164 if (isset($param['event_parameter_value']) && is_string($param['event_parameter_value'])) {
165 $value = $param['event_parameter_value'];
166
167 // Handle {number}, {title}, {url}
168 switch ($param['event_parameter']) {
169 case 'title':
170 $value = $site_title;
171 break;
172 case 'current_title':
173 $value = $current_title;
174 break;
175 case 'url':
176 $value = $current_url;
177 break;
178 }
179
180 $param['event_parameter_value'] = $value;
181 }
182 }
183 unset($param);
184 }
185
186 $analytics_parameter['google_analytics_parameter'] = $google_analytics_parameter;
187
188 wp_enqueue_style('ico-font');
189 wp_enqueue_style('chat-help-style');
190
191 // Load the chosen Typography Google Font in <head> (skipped for
192 // system fonts). The font-family itself is applied by inline_style().
193 $chat_help_typography = isset($options['chat_help_typography']) ? $options['chat_help_typography'] : array();
194 self::enqueue_google_font(isset($chat_help_typography['font-family']) ? $chat_help_typography['font-family'] : '');
195
196 $custom_css = '';
197 if ($wa_custom_css) {
198 $custom_css .= $wa_custom_css;
199 }
200
201 wp_add_inline_style('chat-help-style', $custom_css);
202 wp_enqueue_script('moment', array('jquery'), '1.0', true);
203 wp_enqueue_script('moment-timezone-with-data');
204 wp_enqueue_script('jquery_validate');
205 wp_enqueue_script('chat-help-script');
206 wp_enqueue_script('tma-bubble-stack');
207
208 $chat_help_frontend_scripts = array(
209 'autoShowPopup' => $auto_show_popup,
210 'autoOpenPopupTimeout' => $auto_open_popup_timeout,
211 'analytics_parameter' => $analytics_parameter,
212 );
213 wp_localize_script('chat-help-script', 'chat_help_script', $chat_help_frontend_scripts);
214 if (! empty($wa_custom_js)) {
215 wp_add_inline_script('chat-help-script', $wa_custom_js);
216 }
217
218 wp_localize_script(
219 'chat-help-script',
220 'chat_help_frontend_scripts',
221 array(
222 'ajaxurl' => admin_url('admin-ajax.php'),
223 'nonce' => wp_create_nonce('chat_help_nonce'),
224 'open_in_new_tab' => $open_in_new_tab,
225 'ip' => esc_sql(sanitize_text_field($_SERVER['REMOTE_ADDR'])),
226 )
227 );
228 }
229
230 public function chat_help_content()
231 {
232 $unique_id = "chat_help_button_$this->unique_id";
233 $options = get_option('cwp_option');
234 $ch_settings = get_option('ch_settings');
235 $whatsapp_message_template = isset($options['whatsapp_message_template']) ? $options['whatsapp_message_template'] : '';
236 $chat_type = isset($options['chat_layout']) ? $options['chat_layout'] : 'form';
237 $random = wp_rand(1, 13);
238 $bubble_type = Buttons::buttons($options, $ch_settings);
239
240 $should_display_element = Helpers::should_display_element($options);
241 if ($should_display_element) {
242 self::render_chat_template($chat_type, $options, $ch_settings, $bubble_type, $random, $whatsapp_message_template, $unique_id);
243 }
244
245 // Every value inside the block is esc_attr()'d (or charset-stripped for
246 // the font name) where it's interpolated; wp_kses here validates the
247 // <style> wrapper itself without re-encoding the CSS text.
248 echo wp_kses(
249 self::inline_style($options, $unique_id),
250 array('style' => array('type' => true, 'class' => true))
251 );
252 }
253
254 /**
255 * The positioning/color CSS custom-property block, scoped to `#$unique_id`
256 * (the widget wrapper's id — see the templates' `id="' . esc_attr($unique_id)`
257 * markup). `chat-help-style.css` reads these vars globally (e.g.
258 * `background: var(--wHelp-color-primary)`), so this small per-instance
259 * block is what actually applies a widget's saved position/colors.
260 *
261 * Extracted out of chat_help_content() (same output, just returned instead
262 * of echoed) so the admin Live Preview can render the identical style block
263 * for an unsaved options array without duplicating this template.
264 *
265 * @param array $options The widget's options (cwp_option or ch_meta shape).
266 * @param string $unique_id The id of the widget wrapper element this styles.
267 * @return string
268 */
269 public static function inline_style($options, $unique_id)
270 {
271 $bubble_button_tooltip_background = isset($options['bubble_button_tooltip_background']) ? $options['bubble_button_tooltip_background'] : '#f5f7f9';
272 $bubble_button_tooltip_width = isset($options['bubble_button_tooltip_width']) ? $options['bubble_button_tooltip_width'] : 190;
273 // Right
274 $right_bottom = isset($options['right_bottom']) ? $options['right_bottom'] : array();
275 $right_bottom_value_bottom = isset($right_bottom['bottom']) ? $right_bottom['bottom'] : '25';
276 $right_bottom_value_right = isset($right_bottom['right']) ? $right_bottom['right'] : '30';
277 $right_bottom_unit = isset($right_bottom['unit']) ? $right_bottom['unit'] : 'px';
278
279 // Left
280 $left_bottom = isset($options['left_bottom']) ? $options['left_bottom'] : array();
281 $left_bottom_value_bottom = isset($left_bottom['bottom']) ? $left_bottom['bottom'] : '25';
282 $left_bottom_value_left = isset($left_bottom['left']) ? $left_bottom['left'] : '30';
283 $left_bottom_unit = isset($left_bottom['unit']) ? $left_bottom['unit'] : 'px';
284
285 // Right Middle
286 $right_middle = isset($options['right_middle']) ? $options['right_middle'] : array();
287 $right_middle_value_right = isset($right_middle['right']) ? $right_middle['right'] : '0';
288 $right_middle_unit = isset($right_middle['unit']) ? $right_middle['unit'] : 'px';
289
290 // Left Middle
291 $left_middle = isset($options['left_middle']) ? $options['left_middle'] : array();
292 $left_middle_value_left = isset($left_middle['left']) ? $left_middle['left'] : '0';
293 $left_middle_unit = isset($left_middle['unit']) ? $left_middle['unit'] : 'px';
294
295 // Tablet positioning
296 $enable_tablet_positioning = isset($options['enable-positioning-tablet']) ? $options['enable-positioning-tablet'] : '';
297 $bubble_position_tablet = isset($options['bubble-position-tablet']) ? $options['bubble-position-tablet'] : 'right_bottom';
298
299 $color_settings = isset($options['color_settings']) ? $options['color_settings'] : '';
300 $primary = !empty($color_settings['primary']) ? $color_settings['primary'] : '#118c7e';
301 $secondary = !empty($color_settings['secondary']) ? $color_settings['secondary'] : '#118c7e';
302
303 // Chat bubble typography (`chat_help_typography` field) — only
304 // `font-family` is enabled on that field, so that's all that's applied.
305 // The selector mirrors the field's schema `output` key exactly (keep both
306 // in sync). `.wHelp button` is required because browsers don't inherit
307 // `font-family` into form controls, so submit buttons ("Send Message")
308 // would otherwise fall back to the default UI font. The Google Font
309 // stylesheet is enqueued separately (frontend: enqueue_scripts(); preview:
310 // PreviewRest::render_assets()) via enqueue_google_font().
311 $chat_help_typography = isset($options['chat_help_typography']) ? $options['chat_help_typography'] : array();
312 $typography_font_family = isset($chat_help_typography['font-family']) ? trim($chat_help_typography['font-family']) : '';
313 // Sanitized for direct output inside the raw-text <style> element below.
314 // Entities aren't decoded inside <style>, so no escaping function may
315 // introduce them there — instead strip everything that isn't part of a
316 // real font-family name. That also makes the esc_attr() at the echo a
317 // guaranteed no-op (nothing encodable survives this charset).
318 $typography_font_family = preg_replace('/[^A-Za-z0-9 _\-]/', '', $typography_font_family);
319
320 ob_start();
321 ?>
322 <style type="text/css" class="chat_help_inline_css">
323 <?php if ($typography_font_family) : ?>
324 .wHelp,.wHelp-multi,.wHelp-multi input, .advance_button, .wHelp__popup__content input, .wHelp__popup__content textarea, .wHelp button {
325 font-family: "<?php echo esc_attr($typography_font_family); // a no-op: the preg_replace above leaves nothing for esc_attr to encode, so no entities can reach the <style> text ?>";
326 }
327 <?php endif; ?>
328 #<?php echo esc_attr($unique_id); ?> {
329 --right_bottom_value_bottom: <?php echo esc_attr($right_bottom_value_bottom . $right_bottom_unit) ?>;
330 --right_bottom_value_right: <?php echo esc_attr($right_bottom_value_right . $right_bottom_unit) ?>;
331 --left_bottom_value_bottom: <?php echo esc_attr($left_bottom_value_bottom . $left_bottom_unit) ?>;
332 --left_bottom_value_left: <?php echo esc_attr($left_bottom_value_left . $left_bottom_unit) ?>;
333 --right_middle_value_right: <?php echo esc_attr($right_middle_value_right . $right_middle_unit) ?>;
334 --left_middle_value_left: <?php echo esc_attr($left_middle_value_left . $left_middle_unit) ?>;
335 --enable_tablet_positioning: <?php echo esc_attr($enable_tablet_positioning . $bubble_position_tablet) ?>;
336 --bubble_button_tooltip_background: <?php echo esc_attr($bubble_button_tooltip_background) ?>;
337 --bubble_button_tooltip_width: <?php echo esc_attr($bubble_button_tooltip_width) ?>px;
338
339 --wHelp-color-primary: <?php echo esc_attr($primary); ?>;
340 --wHelp-color-secondary: <?php echo esc_attr($secondary); ?>;
341 }
342 </style>
343 <?php
344 return ob_get_clean();
345 }
346
347 public static function render_chat_template($chat_type, $options, $ch_settings, $bubble_type, $random, $whatsapp_message_template, $unique_id)
348 {
349 $type_of_whatsapp = isset($options['type_of_whatsapp']) ? $options['type_of_whatsapp'] : '';
350 $opt_number = isset($options['opt-number']) ? $options['opt-number'] : '';
351 $opt_group = isset($options['opt-group']) ? $options['opt-group'] : '';
352
353 switch ($chat_type) {
354 case 'off':
355 break;
356 case 'button':
357 if (('number' === $type_of_whatsapp && !empty($opt_number) || ('group' === $type_of_whatsapp && !empty($opt_group)))) {
358 ButtonTemplate::buttonTemplate($options, $ch_settings, $bubble_type, $unique_id);
359 }
360 break;
361 case 'agent':
362 if (('number' === $type_of_whatsapp && !empty($opt_number) || ('group' === $type_of_whatsapp && !empty($opt_group)))) {
363 SingleTemplate::singleTemplate($options, $ch_settings, $bubble_type, $random, $whatsapp_message_template, $unique_id, $chat_type);
364 }
365 break;
366 case 'agent_input':
367 if (('number' === $type_of_whatsapp && !empty($opt_number) || ('group' === $type_of_whatsapp && !empty($opt_group)))) {
368 SingleTemplateInput::singleTemplateInput($options, $ch_settings, $bubble_type, $random, $whatsapp_message_template, $unique_id, $chat_type);
369 }
370 break;
371 case 'form':
372 if (('number' === $type_of_whatsapp && !empty($opt_number)) || ('group' === $type_of_whatsapp && !empty($opt_group))) {
373 FormTemplate::formTemplate($options, $ch_settings, $bubble_type, $random, $whatsapp_message_template, $unique_id);
374 }
375 break;
376
377 default:
378 }
379 }
380
381 public function handle_form_submission()
382 {
383 // Verify the nonce
384 if (!isset($_POST['nonce']) || !wp_verify_nonce(wp_unslash($_POST['nonce']), 'chat_help_nonce')) {
385 wp_send_json_error('Invalid nonce');
386 wp_die();
387 }
388
389 parse_str($_POST['data'], $formData);
390 $product_id = isset($_POST['product_id']) ? sanitize_text_field($_POST['product_id']) : '';
391 $current_url = isset($_POST['current_url']) ? sanitize_url($_POST['current_url']) : '';
392 $current_title = isset($_POST['current_title']) ? sanitize_text_field($_POST['current_title']) : '';
393 $agentName = isset($_POST['agentName']) ? sanitize_text_field($_POST['agentName']) : '';
394 $options = get_option('cwp_option');
395 $ch_settings = get_option('ch_settings');
396 $agent_number = isset($_POST['number']) && $_POST['number'] !== ''
397 ? sanitize_text_field(wp_unslash($_POST['number']))
398 : (isset($options['opt-number']) ? $options['opt-number'] : '');
399 $whatsapp_group = isset($_POST['group']) && $_POST['group'] !== ''
400 ? esc_url_raw(wp_unslash($_POST['group']))
401 : (isset($options['opt-group']) ? $options['opt-group'] : '');
402 $type_of_whatsapp = isset($_POST['type']) && $_POST['type'] === 'group' ? 'group' : 'number';
403 $url_for_desktop = isset($ch_settings['url_for_desktop']) ? $ch_settings['url_for_desktop'] : '';
404 $url_for_mobile = isset($ch_settings['url_for_mobile']) ? $ch_settings['url_for_mobile'] : '';
405 $chat_layout = isset($options['chat_layout']) ? $options['chat_layout'] : '';
406 if ('agent_input' === $chat_layout) {
407 $template = isset($options['agent_with_input_prefilled_message']) ? $options['agent_with_input_prefilled_message'] : '';
408 } else {
409 $template = isset($options['whatsapp_message_template']) ? $options['whatsapp_message_template'] : '';
410 }
411 $chat_help_leads = isset($options['chat_help_leads']) ? $options['chat_help_leads'] : true;
412
413 $form = true;
414 $message = Helpers::replacement_vars($template, $form, $formData, $product_id, $current_url, $current_title);
415 $url = Helpers::whatsAppUrl($agent_number, $type_of_whatsapp, $whatsapp_group, $url_for_desktop, $url_for_mobile, $message);
416
417 if ($chat_help_leads) {
418 $userInfo = isset($_POST['userInfo']) && is_array($_POST['userInfo']) ? array_map('sanitize_text_field', $_POST['userInfo']) : [];
419
420 $current_user_id = get_current_user_id();
421 if ($current_user_id) {
422 $current_user = get_userdata($current_user_id);
423 if ($current_user) {
424 $extraUserData = [
425 'wp_user_id' => $current_user->ID,
426 'wp_username' => $current_user->user_login,
427 'wp_first_name' => get_user_meta($current_user->ID, 'first_name', true),
428 'wp_last_name' => get_user_meta($current_user->ID, 'last_name', true),
429 'wp_email' => $current_user->user_email,
430 ];
431 $userInfo = array_merge($userInfo, $extraUserData);
432 }
433 }
434
435 global $wpdb;
436 $tableUsers = $wpdb->prefix . 'chat_help_leads';
437 $wpdb->insert(
438 $tableUsers,
439 [
440 'field' => maybe_serialize($formData),
441 'meta' => maybe_serialize($userInfo),
442 'agent_name' => $agentName,
443 ],
444 ['%s', '%s', '%s']
445 );
446 }
447
448 wp_send_json_success(array(
449 'whatsAppURL' => $url,
450 'type' => $type_of_whatsapp,
451 'message' => $message,
452 ));
453 wp_die();
454 }
455 }
456