PluginProbe
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO / 1.4.14
Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO v1.4.14
1.4.14 1.4.13 1.4.12 1.4.11 1.4.10 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.54 1.3.53 1.3.52 1.3.51 1.3.50 1.3.49 1.3.48 1.3.47 1.3.46 1.3.45 All 177 releases
disco / libs / LiveChat / LiveChat.php

LiveChat.php in Discount Rules for WooCommerce – Disco | Dynamic Pricing, Conditions, Bulk, Bundle, BOGO 1.4.14, at libs/LiveChat/LiveChat.php

516 lines 14.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Disco Plugin - LiveChat Widget Integration
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 /**
11 * Allowed admin pages for LiveChat widget.
12 */
13 function disco_livechat_allowed_pages() {
14 return array( 'disco-create-discount', 'help-and-docs' );
15 }
16
17 /**
18 * The LiveChat widget script.
19 * - No consent: shows fake bubble → consent modal → saves setting → loads LiveChat.
20 * - Consent given: loads LiveChat with custom bubble directly.
21 */
22 function disco_livechat_script() {
23 $current_page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : '';
24 $consent_given = (bool) \Disco\App\Utility\Settings::get( 'live_chat_support' );
25 $settings_url = esc_url( rest_url( 'disco/v1/settings/' ) );
26 $rest_nonce = wp_create_nonce( 'wp_rest' );
27 ?>
28 <!-- Disco Plugin: LiveChat Widget -->
29 <style>
30 #disco-chat-bubble {
31 display: none;
32 position: fixed;
33 bottom: 8px;
34 right: 8px;
35 width: 45px;
36 height: 45px;
37 border-radius: 50%;
38 background: #0bc88a;
39 box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
40 cursor: pointer;
41 z-index: 999997;
42 align-items: center;
43 justify-content: center;
44 border: none;
45 outline: none;
46 padding: 0;
47 transition: transform 0.15s ease, box-shadow 0.15s ease;
48 }
49 #disco-chat-bubble:hover {
50 transform: scale(1.08);
51 box-shadow: 0 6px 20px rgba(0, 0, 0, 0.32);
52 }
53 #disco-chat-bubble::before {
54 content: 'Need help? Let\'s chat';
55 position: absolute;
56 right: calc(100% + 10px);
57 top: 50%;
58 transform: translateY(-50%);
59 background: #48cd89;
60 color: #ffffff;
61 font-size: 14px;
62 font-weight: 600;
63 white-space: nowrap;
64 padding: 8px 14px;
65 border-radius: 8px;
66 opacity: 0;
67 pointer-events: none;
68 transition: opacity 0.15s ease;
69 }
70 #disco-chat-bubble::after {
71 content: '';
72 position: absolute;
73 right: calc(100% + 2px);
74 top: 50%;
75 transform: translateY(-50%);
76 border: 5px solid transparent;
77 border-left-color: #48cd89;
78 opacity: 0;
79 pointer-events: none;
80 transition: opacity 0.15s ease;
81 }
82 #disco-chat-bubble:hover::before,
83 #disco-chat-bubble:hover::after {
84 opacity: 1;
85 }
86 #disco-chat-bubble svg {
87 width: 26px;
88 height: 26px;
89 fill: #ffffff;
90 pointer-events: none;
91 }
92 #disco-chat-modal-overlay {
93 display: none;
94 position: fixed;
95 inset: 0;
96 background: rgba(0, 0, 0, 0.55);
97 z-index: 999998;
98 align-items: center;
99 justify-content: center;
100 }
101 #disco-chat-modal-overlay.disco-modal-open {
102 display: flex;
103 }
104 #disco-chat-modal-box {
105 background: #ffffff;
106 border: 1px solid #e2e8f0;
107 border-radius: 20px;
108 max-width: 480px;
109 width: 90%;
110 box-shadow: 0 24px 64px rgba(0, 0, 0, 0.1), 0 4px 16px rgba(0, 0, 0, 0.06);
111 overflow: hidden;
112 position: relative;
113 }
114 #disco-chat-modal-header {
115 background: #d7f5e2;
116 background-image: radial-gradient(ellipse at 80% 0%, rgba(34, 197, 94, 0.22) 0%, rgba(34, 197, 94, 0) 70%);
117 height: 155px;
118 position: relative;
119 display: flex;
120 flex-direction: column;
121 align-items: center;
122 justify-content: flex-end;
123 padding-bottom: 20px;
124 }
125 #disco-chat-modal-icon {
126 position: absolute;
127 top: 23px;
128 left: 50%;
129 transform: translateX(-50%);
130 background: #0bc88a;
131 width: 56px;
132 height: 56px;
133 border-radius: 50%;
134 display: flex;
135 align-items: center;
136 justify-content: center;
137 }
138 #disco-chat-modal-icon svg {
139 width: 28px;
140 height: 28px;
141 fill: #ffffff;
142 }
143 #disco-chat-modal-title {
144 margin: 0 0 4px;
145 font-size: 20px;
146 font-weight: 600;
147 color: #000000;
148 text-align: center;
149 line-height: 1.3;
150 }
151 #disco-chat-modal-subtitle {
152 margin: 0;
153 font-size: 14px;
154 color: rgba(37, 37, 37, 0.8);
155 text-align: center;
156 line-height: 1.4;
157 }
158 #disco-chat-modal-body {
159 padding: 24px 36px 0;
160 }
161 #disco-chat-modal-desc {
162 margin: 0 0 20px;
163 font-size: 14px;
164 color: #000000;
165 text-align: center;
166 line-height: 1.6;
167 }
168 #disco-chat-modal-actions {
169 display: flex;
170 flex-direction: column;
171 gap: 12px;
172 margin-bottom: 20px;
173 }
174 #disco-chat-btn-enable {
175 background: #0bc88a;
176 box-shadow: 0 4px 14px rgba(22, 163, 74, 0.35), 0 1px 3px rgba(0, 0, 0, 0.1);
177 border: none;
178 border-radius: 12px;
179 height: 48px;
180 width: 100%;
181 color: #ffffff;
182 font-size: 14.5px;
183 font-weight: 600;
184 text-align: center;
185 cursor: pointer;
186 letter-spacing: 0.145px;
187 transition: background 0.15s ease;
188 }
189 #disco-chat-btn-enable:hover {
190 background: #0ab57d;
191 }
192 #disco-chat-btn-enable:disabled {
193 opacity: 0.65;
194 cursor: not-allowed;
195 }
196 #disco-chat-btn-cancel {
197 background: #f1f5f9;
198 border: 1px solid #e2e8f0;
199 border-radius: 12px;
200 height: 45px;
201 width: 100%;
202 color: #475569;
203 font-size: 13.5px;
204 text-align: center;
205 cursor: pointer;
206 transition: background 0.15s ease;
207 }
208 #disco-chat-btn-cancel:hover {
209 background: #e8edf2;
210 }
211 #disco-chat-error {
212 display: none;
213 color: #d63638;
214 font-size: 13px;
215 margin: 0;
216 text-align: center;
217 }
218 #disco-chat-trust {
219 border-top: 1px dashed #e2e8f0;
220 padding: 12px 0 16px;
221 text-align: center;
222 }
223 #disco-chat-trust p {
224 margin: 0;
225 font-size: 11.5px;
226 color: #475569;
227 }
228 #disco-chat-trust a {
229 color: #16a34a;
230 text-decoration: none;
231 }
232 #disco-chat-trust a:hover {
233 text-decoration: underline;
234 }
235 </style>
236
237 <button id="disco-chat-bubble" aria-label="<?php echo esc_attr__( 'Open live chat', 'disco' ); ?>" type="button">
238 <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
239 <path d="M20 2H4C2.9 2 2 2.9 2 4v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 10H6V10h12v2zm0-3H6V7h12v2z"/>
240 </svg>
241 </button>
242
243 <div id="disco-chat-modal-overlay" role="dialog" aria-modal="true" aria-labelledby="disco-chat-modal-title">
244 <div id="disco-chat-modal-box">
245 <div id="disco-chat-modal-header">
246 <div id="disco-chat-modal-icon" aria-hidden="true">
247 <svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
248 <path d="M20 2H4C2.9 2 2 2.9 2 4v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm-2 10H6V10h12v2zm0-3H6V7h12v2z"/>
249 </svg>
250 </div>
251 <h3 id="disco-chat-modal-title"><?php echo esc_html__( "Need help? Let's chat", 'disco' ); ?></h3>
252 <p id="disco-chat-modal-subtitle"><?php echo esc_html__( 'Get instant support from the Disco team', 'disco' ); ?></p>
253 </div>
254 <div id="disco-chat-modal-body">
255 <p id="disco-chat-modal-desc"><?php echo esc_html__( 'Enable our live support chat and get answers in real time — no tickets, no waiting. Our team is ready to help you get the most out of Disco.', 'disco' ); ?></p>
256 <div id="disco-chat-modal-actions">
257 <p id="disco-chat-error" role="alert"></p>
258 <button id="disco-chat-btn-enable" type="button"><?php echo esc_html__( 'Yes, enable live chat', 'disco' ); ?></button>
259 <button id="disco-chat-btn-cancel" type="button"><?php echo esc_html__( "No thanks, I'll manage on my own", 'disco' ); ?></button>
260 </div>
261 <div id="disco-chat-trust">
262 <p>
263 <?php
264 $allowed_tags = array(
265 'a' => array(
266 'href' => array(),
267 'target' => array(),
268 'rel' => array(),
269 ),
270 );
271 echo wp_kses(
272 sprintf(
273 /* translators: 1: livechat.com link, 2: Terms of Use link, 3: Privacy Policy link */
274 __( 'Powered by %1$s · %2$s · %3$s', 'disco' ),
275 '<p>Live Chat</p>',
276 '<a href="https://www.livechat.com/legal/terms-of-use/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html__( 'Terms', 'disco' ) . '</a>',
277 '<a href="https://www.livechat.com/legal/privacy-policy/" target="_blank" rel="noopener noreferrer nofollow">' . esc_html__( 'Policy', 'disco' ) . '</a>'
278 ),
279 $allowed_tags
280 );
281 ?>
282 </p>
283 </div>
284 </div>
285 </div>
286 </div>
287
288 <script>
289
290 (function() {
291 const currentPage = '<?php echo esc_js( $current_page ); ?>';
292 let consentGiven = <?php echo $consent_given ? 'true' : 'false'; ?>;
293 const settingsUrl = '<?php echo esc_js( $settings_url ); ?>';
294 const restNonce = '<?php echo esc_js( $rest_nonce ); ?>';
295 const i18n = {
296 enabling: '<?php echo esc_js( __( 'Enabling...', 'disco' ) ); ?>',
297 enableBtn: '<?php echo esc_js( __( 'Yes, enable live chat', 'disco' ) ); ?>',
298 errorSave: '<?php echo esc_js( __( 'Could not save preference. Please try again.', 'disco' ) ); ?>',
299 errorNetwork: '<?php echo esc_js( __( 'Network error. Please try again.', 'disco' ) ); ?>'
300 };
301
302 const bubble = document.getElementById('disco-chat-bubble');
303 const modalOverlay = document.getElementById('disco-chat-modal-overlay');
304 const btnEnable = document.getElementById('disco-chat-btn-enable');
305 const btnCancel = document.getElementById('disco-chat-btn-cancel');
306 const errorEl = document.getElementById('disco-chat-error');
307
308 function showError( msg ) {
309 errorEl.textContent = msg;
310 errorEl.style.display = 'block';
311 }
312
313 function hideError() {
314 errorEl.style.display = 'none';
315 errorEl.textContent = '';
316 }
317
318 function hasHashRoute() {
319 const hash = window.location.hash;
320 return hash && hash !== '#' && hash !== '#/' && hash.length > 2;
321 }
322
323 function showBubble() {
324 if ( currentPage === 'disco-create-discount' && hasHashRoute() ) {
325 return;
326 }
327 bubble.style.display = 'flex';
328 }
329
330 function hideBubble() {
331 bubble.style.display = 'none';
332 }
333
334 function openModal() {
335 modalOverlay.classList.add('disco-modal-open');
336 }
337
338 function closeModal() {
339 modalOverlay.classList.remove('disco-modal-open');
340 hideError();
341 }
342
343 // Hash change: hide/show bubble on campaign list page
344 function onHashChange() {
345 if ( currentPage !== 'disco-create-discount' ) {
346 return;
347 }
348 if ( hasHashRoute() ) {
349 hideBubble();
350 if ( consentGiven ) {
351 LiveChatWidget.call('hide');
352 }
353 } else {
354 if ( consentGiven ) {
355 LiveChatWidget.call('hide');
356 }
357 showBubble();
358 }
359 }
360
361 // Initialize LiveChat after consent (or on page load if already consented).
362 // Bootstrap runs here — never in page source — so tracking.js only loads post-consent.
363 function initLiveChat() {
364 window.__lc = window.__lc || {};
365 window.__lc.license = 7156251;
366 window.__lc.integration_name = "manual_channels";
367 window.__lc.product_name = "livechat";
368 window.__lc.asyncInit = false;
369
370 /* jshint ignore:start */
371 (function(n,t,c){function i(n){return e._h?e._h.apply(null,n):e._q.push(n)}var e={_q:[],_h:null,_v:"2.0",on:function(){i(["on",c.call(arguments)])},once:function(){i(["once",c.call(arguments)])},off:function(){i(["off",c.call(arguments)])},get:function(){if(!e._h)throw new Error("[LiveChatWidget] You can't use getters before load.");return i(["get",c.call(arguments)])},call:function(){i(["call",c.call(arguments)])},init:function(){var n=t.createElement("script");n.async=!0,n.type="text/javascript",n.src="https://cdn.livechatinc.com/tracking.js",t.head.appendChild(n)}};!n.__lc.asyncInit&&e.init(),n.LiveChatWidget=n.LiveChatWidget||e}(window,document,[].slice));
372 /* jshint ignore:end */
373
374 LiveChatWidget.on('ready', function() {
375 LiveChatWidget.call('hide');
376 showBubble();
377 });
378
379 LiveChatWidget.on('visibility_changed', function(data) {
380 if ( data.visibility !== 'maximized' ) {
381 LiveChatWidget.call('hide');
382 showBubble();
383 }
384 });
385 }
386
387 // Bubble click
388 bubble.addEventListener('click', function() {
389 if ( ! consentGiven ) {
390 hideBubble();
391 openModal();
392 return;
393 }
394 hideBubble();
395 LiveChatWidget.call('maximize');
396 });
397
398 // Modal: Cancel
399 btnCancel.addEventListener('click', function() {
400 closeModal();
401 showBubble();
402 });
403
404 // Modal: backdrop click
405 modalOverlay.addEventListener('click', function(e) {
406 if ( e.target === modalOverlay ) {
407 closeModal();
408 showBubble();
409 }
410 });
411
412 // Modal: Enable — save consent via settings API then load LiveChat
413 btnEnable.addEventListener('click', function() {
414 hideError();
415 btnEnable.disabled = true;
416 btnEnable.textContent = i18n.enabling;
417
418 const xhr = new XMLHttpRequest();
419 xhr.open('PUT', settingsUrl, true);
420 xhr.setRequestHeader('Content-Type', 'application/json');
421 xhr.setRequestHeader('X-WP-Nonce', restNonce);
422
423 xhr.onload = function() {
424 if ( xhr.status >= 200 && xhr.status < 300 ) {
425 consentGiven = true;
426 btnEnable.disabled = false;
427 btnEnable.textContent = i18n.enableBtn;
428 closeModal();
429 initLiveChat();
430 window.dispatchEvent( new CustomEvent( 'disco:livechat:changed', { detail: { enabled: true } } ) );
431 } else {
432 btnEnable.disabled = false;
433 btnEnable.textContent = i18n.enableBtn;
434 showError( i18n.errorSave );
435 }
436 };
437
438 xhr.onerror = function() {
439 btnEnable.disabled = false;
440 btnEnable.textContent = i18n.enableBtn;
441 showError( i18n.errorNetwork );
442 };
443
444 xhr.send(JSON.stringify({ live_chat_support: true }));
445 });
446
447 document.addEventListener('DOMContentLoaded', function() {
448 setTimeout(function() {
449 if ( currentPage === 'disco-create-discount' && hasHashRoute() ) {
450 return;
451 }
452
453 if ( consentGiven ) {
454 initLiveChat();
455 } else {
456 showBubble();
457 }
458
459 if ( currentPage === 'disco-create-discount' ) {
460 window.addEventListener('hashchange', onHashChange);
461 window.addEventListener('popstate', onHashChange);
462
463 const _origPush = history.pushState.bind( history );
464 const _origReplace = history.replaceState.bind( history );
465 history.pushState = function() {
466 _origPush.apply( history, arguments );
467 onHashChange();
468 };
469 history.replaceState = function() {
470 _origReplace.apply( history, arguments );
471 onHashChange();
472 };
473 }
474 }, 1000);
475 });
476
477 // Sync with React toggle on the same page.
478 window.addEventListener( 'disco:livechat:changed', function( e ) {
479 const isEnabled = !! e.detail.enabled;
480
481 if ( isEnabled && ! consentGiven ) {
482 consentGiven = true;
483 initLiveChat();
484 } else if ( ! isEnabled && consentGiven ) {
485 consentGiven = false;
486 if ( typeof LiveChatWidget !== 'undefined' ) {
487 LiveChatWidget.call('hide');
488 }
489 showBubble();
490 }
491 } );
492 })();
493 </script>
494 <!-- /Disco Plugin: LiveChat Widget -->
495 <?php
496 }
497
498 /**
499 * Inject LiveChat only on allowed Disco plugin admin pages.
500 */
501 function disco_livechat_admin() {
502 if ( ! current_user_can( 'manage_options' ) && ! current_user_can( 'manage_woocommerce' ) ) {
503 return;
504 }
505
506 $current_page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : '';
507
508 if ( ! in_array( $current_page, disco_livechat_allowed_pages(), true ) ) {
509 return;
510 }
511
512 disco_livechat_script();
513 }
514
515 add_action( 'admin_footer', 'disco_livechat_admin' );
516