Changeset 3428053
- Timestamp:
- 12/27/2025 02:13:54 AM (8 weeks ago)
- Location:
- simple-exit-notifier
- Files:
-
- 22 added
- 1 deleted
- 11 edited
-
assets/banner-1544x500.jpg (modified) (previous)
-
assets/banner-772x250.jpg (modified) (previous)
-
assets/screenshot-1.png (added)
-
assets/screenshot-2.png (added)
-
assets/screenshot-3.png (added)
-
assets/screenshot-4.png (added)
-
assets/screenshot-5.png (added)
-
tags/2.0.0 (added)
-
tags/2.0.0/assets (added)
-
tags/2.0.0/assets/css (added)
-
tags/2.0.0/assets/css/admin.css (added)
-
tags/2.0.0/assets/css/frontend.css (added)
-
tags/2.0.0/assets/js (added)
-
tags/2.0.0/assets/js/frontend.js (added)
-
tags/2.0.0/assets/js/settings.js (added)
-
tags/2.0.0/includes (added)
-
tags/2.0.0/includes/class-simple-exit-notifier-admin.php (added)
-
tags/2.0.0/includes/class-simple-exit-notifier-frontend.php (added)
-
tags/2.0.0/includes/class-simple-exit-notifier-init.php (added)
-
tags/2.0.0/index.php (added)
-
tags/2.0.0/readme.txt (added)
-
tags/2.0.0/simple-exit-notifier.php (added)
-
tags/2.0.0/uninstall.php (added)
-
trunk/assets/css/admin.css (modified) (1 diff)
-
trunk/assets/css/frontend.css (modified) (1 diff)
-
trunk/assets/js/frontend.js (modified) (1 diff)
-
trunk/assets/js/settings.js (modified) (1 diff)
-
trunk/assets/screenshots (deleted)
-
trunk/includes/class-simple-exit-notifier-admin.php (modified) (1 diff)
-
trunk/includes/class-simple-exit-notifier-frontend.php (modified) (1 diff)
-
trunk/includes/class-simple-exit-notifier-init.php (modified) (1 diff)
-
trunk/readme.txt (modified) (6 diffs)
-
trunk/simple-exit-notifier.php (modified) (1 diff)
-
trunk/uninstall.php (added)
Legend:
- Unmodified
- Added
- Removed
-
simple-exit-notifier/trunk/assets/css/admin.css
r3213943 r3428053 1 @media (min-width:1000px) { 2 .chrssen-container.col-half {display: grid;grid-template-columns: repeat(auto-fill, 49%);justify-content: space-between;} 1 /** 2 * Simple Exit Notifier - Admin Styles 3 * 4 * @package SimpleExitNotifier 5 * @since 1.0.0 6 */ 7 8 /* Main wrapper */ 9 .chrssen-wrap {max-width: 1400px;} 10 11 /* Tab navigation */ 12 .chrssen-nav-tabs {margin-bottom: 20px;} 13 14 /* Settings container - two column layout */ 15 .chrssen-settings-container {display: flex; gap: 30px; align-items: flex-start;} 16 .chrssen-settings-main {flex: 1; min-width: 0; background: #fff; padding: 20px; border: 1px solid #ccd0d4; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);} 17 .chrssen-settings-preview {width: 380px; flex-shrink: 0; position: sticky; top: 32px;} 18 19 /* Responsive layout */ 20 @media (max-width: 1200px) { 21 .chrssen-settings-container {flex-direction: column;} 22 .chrssen-settings-preview {width: 100%; position: static;} 3 23 } 24 25 /* Tab content */ 26 .chrssen-tab-content h2 {margin-top: 30px; padding-bottom: 10px; border-bottom: 1px solid #eee;} 27 .chrssen-tab-content h2:first-child {margin-top: 0;} 28 29 /* Toggle switch */ 30 .chrssen-toggle {position: relative; display: inline-block; width: 50px; height: 26px;} 31 .chrssen-toggle input {opacity: 0; width: 0; height: 0;} 32 .chrssen-toggle-slider {position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; transition: 0.3s; border-radius: 26px;} 33 .chrssen-toggle-slider:before {position: absolute; content: ""; height: 20px; width: 20px; left: 3px; bottom: 3px; background-color: #fff; transition: 0.3s; border-radius: 50%;} 34 .chrssen-toggle input:checked + .chrssen-toggle-slider {background-color: #2271b1;} 35 .chrssen-toggle input:checked + .chrssen-toggle-slider:before {transform: translateX(24px);} 36 .chrssen-toggle input:focus + .chrssen-toggle-slider {box-shadow: 0 0 1px #2271b1;} 37 38 /* Preview box */ 39 .chrssen-preview-box {margin: 0;} 40 .chrssen-preview-box .hndle {cursor: default;} 41 .chrssen-preview-box .inside {padding: 15px;} 42 .chrssen-preview-note {margin: 15px 0 0; text-align: center; color: #666;} 43 44 /* Preview modal styling */ 45 #chrssen-preview-modal .simple-exit-notifier-content {border-radius: 4px; overflow: hidden;} 46 #chrssen-preview-modal .simple-exit-notifier-header h2 {margin: 0; padding: 0; font-size: 1.3em;} 47 48 /* Color picker adjustments */ 49 .wp-picker-container {display: inline-block;} 50 .wp-picker-container .wp-color-result.button {margin: 0;} 51 52 /* Range slider */ 53 input[type="range"] {vertical-align: middle; margin-right: 10px;} 54 #chrssen_overlay_opacity_value {font-weight: 600; min-width: 30px; display: inline-block;} 55 56 /* Form table adjustments */ 57 .chrssen-tab-content .form-table th {width: 200px; padding-left: 0;} 58 .chrssen-tab-content .form-table td {padding-left: 0;} 59 .chrssen-tab-content .description {color: #666; font-style: italic; margin-top: 5px;} 60 61 /* Code textarea */ 62 .chrssen-tab-content textarea.code {font-family: Consolas, Monaco, monospace; font-size: 13px;} 63 64 /* Multi-select pages */ 65 .chrssen-select-pages {min-height: 120px;} -
simple-exit-notifier/trunk/assets/css/frontend.css
r3209994 r3428053 1 #simple-exit-notifier-modal { 2 position: fixed; 3 top: 0; 4 left: 0; 5 width: 100%; 6 height: 100%; 7 z-index: 9999; 8 display: none; 9 } 1 /** 2 * Simple Exit Notifier - Frontend Styles 3 * 4 * @package SimpleExitNotifier 5 * @since 2.0.0 6 */ 10 7 11 .simple-exit-notifier-overlay { 12 position: absolute; 13 top: 0; 14 left: 0; 15 width: 100%; 16 height: 100%; 17 background: rgba(0, 0, 0, 0.7); 18 } 8 /* Modal container */ 9 #simple-exit-notifier-modal {position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 9999; display: none;} 19 10 20 .simple-exit-notifier-content { 21 position: absolute; 22 top: 50%; 23 left: 50%; 24 transform: translate(-50%, -50%); 25 background: #fff; 26 box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); 27 width:750px; 28 max-width:90%; 29 } 11 /* Overlay */ 12 .simple-exit-notifier-overlay {position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.7); cursor: pointer;} 30 13 31 .simple-exit-notifier-header { 32 padding:10px 20px; 33 background:#ccc; 34 } 35 .simple-exit-notifier-header h2 { 36 margin:0; 37 padding:0; 38 } 14 /* Modal content */ 15 .simple-exit-notifier-content {position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #fff; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); width: 750px; max-width: 90%; border-radius: 4px; overflow: hidden;} 39 16 40 .simple-exit-notifier-message { 41 padding:20px 20px 0; 42 }17 /* Header */ 18 .simple-exit-notifier-header {padding: 10px 20px; background: #ccc;} 19 .simple-exit-notifier-header h2 {margin: 0; padding: 0; font-size: 1.5em;} 43 20 44 .simple-exit-notifier-actions { 45 padding-bottom:20px; 46 text-align:center; 47 } 21 /* Message */ 22 .simple-exit-notifier-message {padding: 20px 20px 0;} 23 .simple-exit-notifier-message p {margin: 0 0 1em;} 24 25 /* Remember checkbox */ 26 .simple-exit-notifier-remember {padding: 0 20px 10px; text-align: center;} 27 .simple-exit-notifier-remember label {cursor: pointer; display: inline-flex; align-items: center; gap: 6px;} 28 .simple-exit-notifier-remember input[type="checkbox"] {width: 16px; height: 16px; margin: 0; cursor: pointer;} 29 30 /* Actions */ 31 .simple-exit-notifier-actions {padding: 10px 20px 20px; text-align: center;} 48 32 .simple-exit-notifier-content button, 49 .simple-exit-notifier-content a { 50 margin: 10px; 51 padding: 10px 20px; 52 border: none; 53 border-radius: 4px; 54 cursor: pointer; 55 text-decoration: none; 56 display: inline-block; 57 } 58 #simple-exit-notifier-cancel { 59 background: #f44336; 60 color: #fff; 61 } 33 .simple-exit-notifier-content a {margin: 10px; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; font-size: 1em; transition: opacity 0.2s ease;} 34 .simple-exit-notifier-content button:hover, 35 .simple-exit-notifier-content a:hover {opacity: 0.9;} 36 .simple-exit-notifier-content button:focus, 37 .simple-exit-notifier-content a:focus {outline: 2px solid #0073aa; outline-offset: 2px;} 38 .simple-exit-notifier-content button:disabled, 39 .simple-exit-notifier-content a:disabled {cursor: not-allowed;} 62 40 63 #simple-exit-notifier-proceed { 64 background: #4caf50; 65 color: #fff; 66 } 41 /* Cancel button */ 42 #simple-exit-notifier-cancel {background: #f44336; color: #fff;} 43 44 /* Proceed button */ 45 #simple-exit-notifier-proceed {background: #4caf50; color: #fff;} 46 47 /* Body class when modal is open - prevents scroll */ 48 body.simple-exit-notifier-modal-open {overflow: hidden;} 49 50 /* External link icon */ 51 .chrssen-external-icon {font-size: 0.8em; vertical-align: super; margin: 0 2px;} 52 53 /* Animation: Fade (default) */ 54 #simple-exit-notifier-modal.chrssen-animation-fade .simple-exit-notifier-content {opacity: 0; transition: opacity 0.3s ease;} 55 #simple-exit-notifier-modal.chrssen-animation-fade.chrssen-modal-visible .simple-exit-notifier-content {opacity: 1;} 56 57 /* Animation: Slide Up */ 58 #simple-exit-notifier-modal.chrssen-animation-slide-up .simple-exit-notifier-content {transform: translate(-50%, 100%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease;} 59 #simple-exit-notifier-modal.chrssen-animation-slide-up.chrssen-modal-visible .simple-exit-notifier-content {transform: translate(-50%, -50%); opacity: 1;} 60 61 /* Animation: Slide Down */ 62 #simple-exit-notifier-modal.chrssen-animation-slide-down .simple-exit-notifier-content {transform: translate(-50%, -200%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease;} 63 #simple-exit-notifier-modal.chrssen-animation-slide-down.chrssen-modal-visible .simple-exit-notifier-content {transform: translate(-50%, -50%); opacity: 1;} 64 65 /* Animation: None */ 66 #simple-exit-notifier-modal.chrssen-animation-none .simple-exit-notifier-content {opacity: 1;} -
simple-exit-notifier/trunk/assets/js/frontend.js
r3213943 r3428053 1 jQuery(document).ready(function ($) { 2 let externalLink = ''; 3 let openInNewTab = false; 4 5 // Get the exception class from a localized variable 6 const exceptionClass = simple_exit_notifier_data.exceptionClass || 'noexit'; // Default to 'noexit' if empty 7 8 // Detect clicks on links 9 $('a').on('click', function (e) { 10 const link = $(this).attr('href'); 11 const hasExceptionClass = exceptionClass && $(this).hasClass(exceptionClass); // Check only if exceptionClass is set 12 openInNewTab = $(this).attr('target') === '_blank'; // Check if the link should open in a new tab 13 14 // Check if the link is external and does not have the exception class 15 if (link && link.startsWith('http') && !link.includes(window.location.hostname) && !hasExceptionClass) { 16 e.preventDefault(); // Prevent the default action 17 18 // Save the external link 19 externalLink = link; 20 21 // Update the "Proceed" button with the link 22 $('#simple-exit-notifier-proceed').attr('href', externalLink); 23 24 // Update the external link display if enabled 25 const externalLinkElement = $('#simple-exit-notifier-external-link'); 26 if (externalLinkElement.length) { 27 externalLinkElement.text(`Visit: ${externalLink}`).show(); // Display the link 28 } 29 30 // Show the modal 31 $('#simple-exit-notifier-modal').fadeIn(); 32 } 33 }); 34 35 // Close the modal 36 $('#simple-exit-notifier-cancel').on('click', function () { 37 $('#simple-exit-notifier-modal').fadeOut(); 38 }); 39 40 // Handle the "Proceed" button 41 $('#simple-exit-notifier-proceed').on('click', function (e) { 42 e.preventDefault(); // Prevent default button action 43 44 // Open the link in the appropriate way 45 if (openInNewTab) { 46 window.open(externalLink, '_blank'); // Open in a new tab 1 /** 2 * Simple Exit Notifier - Frontend JavaScript 3 * 4 * Handles external link detection and modal display. 5 * 6 * @package SimpleExitNotifier 7 * @since 2.0.0 8 */ 9 (function($) { 10 'use strict'; 11 12 var externalLink = ''; 13 var openInNewTab = false; 14 var $activeModal = null; 15 var $lastFocusedElement = null; 16 var delayTimer = null; 17 var countdownInterval = null; 18 19 // Get settings from localized data. 20 var settings = (typeof simple_exit_notifier_data !== 'undefined') ? simple_exit_notifier_data : {}; 21 var exceptionClasses = settings.exceptionClasses || ['noexit']; 22 var whitelistedDomains = settings.whitelistedDomains || []; 23 var triggerClass = settings.triggerClass || ''; 24 var customPopupEnabled = settings.customPopupEnabled || false; 25 var customPopupClass = settings.customPopupClass || ''; 26 var delayEnabled = settings.delayEnabled || false; 27 var delaySeconds = settings.delaySeconds || 5; 28 var rememberEnabled = settings.rememberEnabled || false; 29 var rememberDuration = settings.rememberDuration || 'session'; 30 var linkIconEnabled = settings.linkIconEnabled || false; 31 var linkIconPosition = settings.linkIconPosition || 'after'; 32 var animationStyle = settings.animationStyle || 'fade'; 33 34 // Cache DOM elements. 35 var $defaultModal = null; 36 var $customModal = null; 37 38 // Storage key for remember choice. 39 var storageKey = 'chrssen_skip_popup'; 40 41 /** 42 * Check if user has opted to skip the popup. 43 * 44 * @return {boolean} True if should skip, false otherwise. 45 */ 46 function shouldSkipPopup() { 47 if (!rememberEnabled) { 48 return false; 49 } 50 51 // Check sessionStorage first. 52 if (rememberDuration === 'session') { 53 return sessionStorage.getItem(storageKey) === 'true'; 54 } 55 56 // Check localStorage for persistent storage. 57 var stored = localStorage.getItem(storageKey); 58 if (!stored) { 59 return false; 60 } 61 62 try { 63 var data = JSON.parse(stored); 64 if (data.forever) { 65 return true; 66 } 67 if (data.expires && new Date().getTime() < data.expires) { 68 return true; 69 } 70 // Expired, remove it. 71 localStorage.removeItem(storageKey); 72 return false; 73 } catch (e) { 74 return false; 75 } 76 } 77 78 /** 79 * Save user's remember choice. 80 */ 81 function saveRememberChoice() { 82 if (!rememberEnabled) { 83 return; 84 } 85 86 if (rememberDuration === 'session') { 87 sessionStorage.setItem(storageKey, 'true'); 88 return; 89 } 90 91 var data = {}; 92 var now = new Date().getTime(); 93 94 switch (rememberDuration) { 95 case '1day': 96 data.expires = now + (24 * 60 * 60 * 1000); 97 break; 98 case '7days': 99 data.expires = now + (7 * 24 * 60 * 60 * 1000); 100 break; 101 case '30days': 102 data.expires = now + (30 * 24 * 60 * 60 * 1000); 103 break; 104 case 'forever': 105 data.forever = true; 106 break; 107 default: 108 data.expires = now + (24 * 60 * 60 * 1000); 109 } 110 111 localStorage.setItem(storageKey, JSON.stringify(data)); 112 } 113 114 /** 115 * Check if a URL is external. 116 * 117 * @param {string} url The URL to check. 118 * @return {boolean} True if external, false otherwise. 119 */ 120 function isExternalLink(url) { 121 if (!url || typeof url !== 'string') { 122 return false; 123 } 124 125 // Must start with http:// or https://. 126 if (!url.match(/^https?:\/\//i)) { 127 return false; 128 } 129 130 try { 131 var linkUrl = new URL(url); 132 var linkHost = linkUrl.hostname; 133 134 // Not external if same host. 135 if (linkHost === window.location.hostname) { 136 return false; 137 } 138 139 // Check whitelisted domains. 140 if (whitelistedDomains.length > 0) { 141 for (var i = 0; i < whitelistedDomains.length; i++) { 142 var domain = whitelistedDomains[i].toLowerCase(); 143 // Match exact domain or subdomain. 144 if (linkHost === domain || linkHost.endsWith('.' + domain)) { 145 return false; 146 } 147 } 148 } 149 150 return true; 151 } catch (e) { 152 return false; 153 } 154 } 155 156 /** 157 * Check if link has any exception class. 158 * 159 * @param {jQuery} $link The link element. 160 * @return {boolean} True if has exception class, false otherwise. 161 */ 162 function hasExceptionClass($link) { 163 for (var i = 0; i < exceptionClasses.length; i++) { 164 if ($link.hasClass(exceptionClasses[i])) { 165 return true; 166 } 167 } 168 return false; 169 } 170 171 /** 172 * Determine which modal to show for a link. 173 * 174 * Logic: 175 * 1. If custom popup is enabled and link has custom popup class -> show custom modal 176 * 2. If trigger class is set and link has trigger class -> show default modal 177 * 3. If trigger class is empty -> show default modal for all external links 178 * 179 * @param {jQuery} $link The link element. 180 * @return {jQuery|null} The modal to show, or null if none. 181 */ 182 function getModalForLink($link) { 183 // Priority 1: Custom popup class (overrides all) 184 if (customPopupEnabled && customPopupClass && $link.hasClass(customPopupClass)) { 185 return $customModal && $customModal.length ? $customModal : null; 186 } 187 188 // Priority 2: Default popup 189 // If trigger class is set, only show for links with that class 190 if (triggerClass) { 191 if ($link.hasClass(triggerClass)) { 192 return $defaultModal && $defaultModal.length ? $defaultModal : null; 193 } 194 // Link doesn't have the trigger class, don't show any popup 195 return null; 196 } 197 198 // Priority 3: No trigger class set, show default popup for all external links 199 return $defaultModal && $defaultModal.length ? $defaultModal : null; 200 } 201 202 /** 203 * Show the modal dialog. 204 * 205 * @param {jQuery} $modal The modal to show. 206 */ 207 function showModal($modal) { 208 if (!$modal || !$modal.length) { 209 return; 210 } 211 212 $activeModal = $modal; 213 $lastFocusedElement = $(document.activeElement); 214 215 var $cancelBtn = $modal.find('.simple-exit-notifier-cancel'); 216 217 if (animationStyle === 'none') { 218 $modal.show(); 219 $cancelBtn.trigger('focus'); 47 220 } else { 48 window.location.href = externalLink; // Open in the same tab 49 } 50 51 // Close the modal (optional) 52 $('#simple-exit-notifier-modal').fadeOut(); 53 }); 54 }); 221 $modal.show(); 222 // Trigger reflow for animation. 223 $modal[0].offsetHeight; 224 $modal.addClass('chrssen-modal-visible'); 225 $cancelBtn.trigger('focus'); 226 } 227 228 $('body').addClass('simple-exit-notifier-modal-open'); 229 230 // Handle delay timer. 231 if (delayEnabled) { 232 startDelayTimer($modal); 233 } 234 } 235 236 /** 237 * Hide the active modal dialog. 238 */ 239 function hideModal() { 240 if (!$activeModal || !$activeModal.length) { 241 return; 242 } 243 244 var $modal = $activeModal; 245 246 // Clear any running timers. 247 if (delayTimer) { 248 clearTimeout(delayTimer); 249 delayTimer = null; 250 } 251 if (countdownInterval) { 252 clearInterval(countdownInterval); 253 countdownInterval = null; 254 } 255 256 // Reset proceed button. 257 var $proceedBtn = $modal.find('.simple-exit-notifier-proceed'); 258 $proceedBtn.prop('disabled', false).css('opacity', '1'); 259 $proceedBtn.text($proceedBtn.data('originalText') || $proceedBtn.text()); 260 261 if (animationStyle === 'none') { 262 $modal.hide(); 263 restoreFocus(); 264 } else { 265 $modal.removeClass('chrssen-modal-visible'); 266 setTimeout(function() { 267 $modal.hide(); 268 restoreFocus(); 269 }, 300); 270 } 271 272 $('body').removeClass('simple-exit-notifier-modal-open'); 273 274 // Reset remember checkbox. 275 var $rememberCheckbox = $modal.find('.simple-exit-notifier-remember-checkbox'); 276 if ($rememberCheckbox.length) { 277 $rememberCheckbox.prop('checked', false); 278 } 279 280 $activeModal = null; 281 } 282 283 /** 284 * Restore focus to last focused element. 285 */ 286 function restoreFocus() { 287 if ($lastFocusedElement && $lastFocusedElement.length) { 288 $lastFocusedElement.trigger('focus'); 289 } 290 } 291 292 /** 293 * Start the delay timer for proceed button. 294 * 295 * @param {jQuery} $modal The active modal. 296 */ 297 function startDelayTimer($modal) { 298 var remainingSeconds = delaySeconds; 299 var $proceedBtn = $modal.find('.simple-exit-notifier-proceed'); 300 301 // Store original text. 302 if (!$proceedBtn.data('originalText')) { 303 $proceedBtn.data('originalText', $proceedBtn.text()); 304 } 305 306 // Disable proceed button and show countdown. 307 $proceedBtn.prop('disabled', true).css('opacity', '0.5'); 308 $proceedBtn.text($proceedBtn.data('originalText') + ' (' + remainingSeconds + ')'); 309 310 countdownInterval = setInterval(function() { 311 remainingSeconds--; 312 if (remainingSeconds > 0) { 313 $proceedBtn.text($proceedBtn.data('originalText') + ' (' + remainingSeconds + ')'); 314 } else { 315 clearInterval(countdownInterval); 316 countdownInterval = null; 317 $proceedBtn.prop('disabled', false).css('opacity', '1'); 318 $proceedBtn.text($proceedBtn.data('originalText')); 319 } 320 }, 1000); 321 } 322 323 /** 324 * Handle keyboard navigation within modal. 325 * 326 * @param {Event} e The keydown event. 327 */ 328 function handleKeydown(e) { 329 if (!$activeModal || !$activeModal.is(':visible')) { 330 return; 331 } 332 333 // Escape key closes modal. 334 if (e.key === 'Escape' || e.keyCode === 27) { 335 e.preventDefault(); 336 hideModal(); 337 return; 338 } 339 340 // Tab key traps focus within modal. 341 if (e.key === 'Tab' || e.keyCode === 9) { 342 var $focusableElements = $activeModal.find('a, button, input, [tabindex]:not([tabindex="-1"])').filter(':visible'); 343 var $firstElement = $focusableElements.first(); 344 var $lastElement = $focusableElements.last(); 345 346 if (e.shiftKey) { 347 if (document.activeElement === $firstElement[0]) { 348 e.preventDefault(); 349 $lastElement.trigger('focus'); 350 } 351 } else { 352 if (document.activeElement === $lastElement[0]) { 353 e.preventDefault(); 354 $firstElement.trigger('focus'); 355 } 356 } 357 } 358 } 359 360 /** 361 * Add external link icons to links. 362 */ 363 function addLinkIcons() { 364 if (!linkIconEnabled) { 365 return; 366 } 367 368 var iconHtml = '<span class="chrssen-external-icon" aria-hidden="true">↗</span>'; 369 370 $('a').each(function() { 371 var $link = $(this); 372 var href = $link.attr('href'); 373 374 // Skip if has exception class or not external. 375 if (hasExceptionClass($link) || !isExternalLink(href)) { 376 return; 377 } 378 379 // Skip if already has icon. 380 if ($link.find('.chrssen-external-icon').length) { 381 return; 382 } 383 384 if (linkIconPosition === 'before') { 385 $link.prepend(iconHtml + ' '); 386 } else { 387 $link.append(' ' + iconHtml); 388 } 389 }); 390 } 391 392 /** 393 * Initialize the plugin. 394 */ 395 function init() { 396 // Cache DOM elements. 397 $defaultModal = $('#simple-exit-notifier-modal'); 398 $customModal = $('#simple-exit-notifier-custom-modal'); 399 400 // Exit if no modals exist. 401 if (!$defaultModal.length && !$customModal.length) { 402 return; 403 } 404 405 // Add external link icons. 406 addLinkIcons(); 407 408 // Use event delegation for better performance. 409 $(document).on('click', 'a', function(e) { 410 var $link = $(this); 411 var href = $link.attr('href'); 412 413 // Skip if has exception class. 414 if (hasExceptionClass($link)) { 415 return; 416 } 417 418 // Skip if not external. 419 if (!isExternalLink(href)) { 420 return; 421 } 422 423 // Skip if user opted to remember choice. 424 if (shouldSkipPopup()) { 425 return; 426 } 427 428 // Determine which modal to show. 429 var $modal = getModalForLink($link); 430 431 // No modal to show (e.g., trigger class doesn't match). 432 if (!$modal) { 433 return; 434 } 435 436 e.preventDefault(); 437 438 externalLink = href; 439 openInNewTab = $link.attr('target') === '_blank'; 440 441 // Update proceed button href. 442 var $proceedBtn = $modal.find('.simple-exit-notifier-proceed'); 443 $proceedBtn.attr('href', externalLink); 444 445 // Update external link display if enabled. 446 var $externalLinkDisplay = $modal.find('.simple-exit-notifier-external-link'); 447 if ($externalLinkDisplay.length) { 448 $externalLinkDisplay.text('Visit: ' + externalLink).show(); 449 } 450 451 showModal($modal); 452 }); 453 454 // Close modal on cancel click (event delegation for both modals). 455 $(document).on('click', '.simple-exit-notifier-cancel', function(e) { 456 e.preventDefault(); 457 hideModal(); 458 }); 459 460 // Close modal on overlay click (event delegation for both modals). 461 $(document).on('click', '.simple-exit-notifier-overlay', function() { 462 hideModal(); 463 }); 464 465 // Handle proceed button (event delegation for both modals). 466 $(document).on('click', '.simple-exit-notifier-proceed', function(e) { 467 e.preventDefault(); 468 469 var $proceedBtn = $(this); 470 471 // Don't proceed if button is disabled (delay timer). 472 if ($proceedBtn.prop('disabled')) { 473 return; 474 } 475 476 if (!externalLink) { 477 return; 478 } 479 480 // Save remember choice if checked. 481 if ($activeModal) { 482 var $rememberCheckbox = $activeModal.find('.simple-exit-notifier-remember-checkbox'); 483 if ($rememberCheckbox.length && $rememberCheckbox.is(':checked')) { 484 saveRememberChoice(); 485 } 486 } 487 488 if (openInNewTab) { 489 window.open(externalLink, '_blank', 'noopener,noreferrer'); 490 } else { 491 window.location.href = externalLink; 492 } 493 494 hideModal(); 495 }); 496 497 // Keyboard accessibility. 498 $(document).on('keydown', handleKeydown); 499 } 500 501 // Initialize on DOM ready. 502 $(init); 503 504 })(jQuery); -
simple-exit-notifier/trunk/assets/js/settings.js
r3213943 r3428053 1 document.addEventListener('DOMContentLoaded', () => { 2 const headingInput = document.getElementById('chrssen_heading'); 3 const messageInput = document.getElementById('chrssen_message'); 4 const proceedInput = document.getElementById('chrssen_proceed_text'); 5 const cancelInput = document.getElementById('chrssen_cancel_text'); 6 const displayExternalLink = document.getElementById('chrssen_display_external_link'); 7 const previewHeading = document.getElementById('chrssen-preview-heading'); 8 const previewMessage = document.getElementById('chrssen-preview-message'); 9 const previewProceed = document.getElementById('chrssen-preview-proceed'); 10 const previewCancel = document.getElementById('chrssen-preview-cancel'); 11 const previewExternalLink = document.getElementById('chrssen-preview-external-link'); 12 13 const updatePreview = () => { 14 previewHeading.textContent = headingInput.value || 'Leaving Our Site'; 15 previewMessage.textContent = messageInput.value || 'You are about to leave our website. Do you want to proceed?'; 16 previewProceed.textContent = proceedInput.value || 'Proceed'; 17 previewCancel.textContent = cancelInput.value || 'Cancel'; 18 previewExternalLink.style.display = displayExternalLink.checked ? 'block' : 'none'; 19 }; 20 21 // Initialize preview on page load 22 updatePreview(); 23 24 // Add event listeners to update preview dynamically 25 headingInput.addEventListener('input', updatePreview); 26 messageInput.addEventListener('input', updatePreview); 27 proceedInput.addEventListener('input', updatePreview); 28 cancelInput.addEventListener('input', updatePreview); 29 displayExternalLink.addEventListener('change', updatePreview); 30 }); 1 /** 2 * Simple Exit Notifier - Admin Settings JavaScript 3 * 4 * Handles live preview updates, color pickers, and toggle visibility. 5 * 6 * @package SimpleExitNotifier 7 * @since 2.0.0 8 */ 9 (function($) { 10 'use strict'; 11 12 /** 13 * Initialize the settings functionality. 14 */ 15 function init() { 16 initColorPickers(); 17 initToggleVisibility(); 18 initPreviewUpdates(); 19 initOpacitySlider(); 20 } 21 22 /** 23 * Initialize WordPress color pickers. 24 */ 25 function initColorPickers() { 26 if (typeof $.fn.wpColorPicker === 'undefined') { 27 return; 28 } 29 30 $('.chrssen-color-picker').wpColorPicker({ 31 change: function(event, ui) { 32 updatePreviewColors(); 33 }, 34 clear: function() { 35 updatePreviewColors(); 36 } 37 }); 38 } 39 40 /** 41 * Initialize toggle visibility for conditional fields. 42 */ 43 function initToggleVisibility() { 44 // Delay timer toggle. 45 var delayEnabled = $('#chrssen_delay_enabled'); 46 if (delayEnabled.length) { 47 delayEnabled.on('change', function() { 48 $('.chrssen-delay-row').toggle(this.checked); 49 }); 50 } 51 52 // Remember choice toggle. 53 var rememberEnabled = $('#chrssen_remember_enabled'); 54 if (rememberEnabled.length) { 55 rememberEnabled.on('change', function() { 56 $('.chrssen-remember-row').toggle(this.checked); 57 }); 58 } 59 60 // Link icon toggle. 61 var iconEnabled = $('#chrssen_link_icon_enabled'); 62 if (iconEnabled.length) { 63 iconEnabled.on('change', function() { 64 $('.chrssen-icon-row').toggle(this.checked); 65 }); 66 } 67 68 // Custom popup toggle. 69 var customPopupEnabled = $('#chrssen_custom_popup_enabled'); 70 if (customPopupEnabled.length) { 71 customPopupEnabled.on('change', function() { 72 $('.chrssen-custom-popup-row').toggle(this.checked); 73 }); 74 } 75 } 76 77 /** 78 * Initialize live preview updates for text fields. 79 */ 80 function initPreviewUpdates() { 81 var headingInput = document.getElementById('chrssen_heading'); 82 var messageInput = document.getElementById('chrssen_message'); 83 var proceedInput = document.getElementById('chrssen_proceed_text'); 84 var cancelInput = document.getElementById('chrssen_cancel_text'); 85 var displayExternalLink = document.getElementById('chrssen_display_external_link'); 86 var previewHeading = document.getElementById('chrssen-preview-heading'); 87 var previewMessage = document.getElementById('chrssen-preview-message'); 88 var previewProceed = document.getElementById('chrssen-preview-proceed'); 89 var previewCancel = document.getElementById('chrssen-preview-cancel'); 90 var previewExternalLink = document.getElementById('chrssen-preview-external-link'); 91 92 /** 93 * Update the preview with current input values. 94 */ 95 function updatePreview() { 96 if (headingInput && previewHeading) { 97 previewHeading.textContent = headingInput.value || 'Leaving Our Site'; 98 } 99 if (messageInput && previewMessage) { 100 previewMessage.textContent = messageInput.value || 'You are about to leave our website. Do you want to proceed?'; 101 } 102 if (proceedInput && previewProceed) { 103 previewProceed.textContent = proceedInput.value || 'Proceed'; 104 } 105 if (cancelInput && previewCancel) { 106 previewCancel.textContent = cancelInput.value || 'Cancel'; 107 } 108 if (displayExternalLink && previewExternalLink) { 109 previewExternalLink.style.display = displayExternalLink.checked ? 'block' : 'none'; 110 } 111 } 112 113 // Initialize preview on page load. 114 updatePreview(); 115 116 // Add event listeners. 117 if (headingInput) { 118 headingInput.addEventListener('input', updatePreview); 119 } 120 if (messageInput) { 121 messageInput.addEventListener('input', updatePreview); 122 } 123 if (proceedInput) { 124 proceedInput.addEventListener('input', updatePreview); 125 } 126 if (cancelInput) { 127 cancelInput.addEventListener('input', updatePreview); 128 } 129 if (displayExternalLink) { 130 displayExternalLink.addEventListener('change', updatePreview); 131 } 132 } 133 134 /** 135 * Update preview colors from color pickers. 136 */ 137 function updatePreviewColors() { 138 // Use setTimeout to ensure color picker has updated. 139 setTimeout(function() { 140 var headerBg = $('#chrssen_header_bg_color').val() || '#cccccc'; 141 var headerText = $('#chrssen_header_text_color').val() || '#000000'; 142 var bodyBg = $('#chrssen_body_bg_color').val() || '#ffffff'; 143 var bodyText = $('#chrssen_body_text_color').val() || '#000000'; 144 var proceedBg = $('#chrssen_proceed_bg_color').val() || '#4caf50'; 145 var proceedText = $('#chrssen_proceed_text_color').val() || '#ffffff'; 146 var cancelBg = $('#chrssen_cancel_bg_color').val() || '#f44336'; 147 var cancelText = $('#chrssen_cancel_text_color').val() || '#ffffff'; 148 149 // Update preview elements. 150 var previewContent = $('#chrssen-preview-content'); 151 var previewHeader = $('#chrssen-preview-header'); 152 var previewHeading = $('#chrssen-preview-heading'); 153 var previewMessage = $('#chrssen-preview-message'); 154 var previewProceed = $('#chrssen-preview-proceed'); 155 var previewCancel = $('#chrssen-preview-cancel'); 156 157 if (previewContent.length) { 158 previewContent.css({ 159 'background': bodyBg, 160 'color': bodyText 161 }); 162 } 163 if (previewHeader.length) { 164 previewHeader.css({ 165 'background': headerBg, 166 'color': headerText 167 }); 168 } 169 if (previewHeading.length) { 170 previewHeading.css('color', headerText); 171 } 172 if (previewMessage.length) { 173 previewMessage.parent().css('color', bodyText); 174 } 175 if (previewProceed.length) { 176 previewProceed.css({ 177 'background': proceedBg, 178 'color': proceedText 179 }); 180 } 181 if (previewCancel.length) { 182 previewCancel.css({ 183 'background': cancelBg, 184 'color': cancelText 185 }); 186 } 187 }, 10); 188 } 189 190 /** 191 * Initialize opacity slider value display. 192 */ 193 function initOpacitySlider() { 194 var opacitySlider = document.getElementById('chrssen_overlay_opacity'); 195 var opacityValue = document.getElementById('chrssen_overlay_opacity_value'); 196 197 if (opacitySlider && opacityValue) { 198 opacitySlider.addEventListener('input', function() { 199 opacityValue.textContent = this.value; 200 }); 201 } 202 } 203 204 // Initialize on DOM ready. 205 $(document).ready(init); 206 207 })(jQuery); -
simple-exit-notifier/trunk/includes/class-simple-exit-notifier-admin.php
r3213943 r3428053 1 1 <?php 2 3 if (!defined('ABSPATH')) { 4 exit; 2 /** 3 * Admin settings class. 4 * 5 * @package SimpleExitNotifier 6 */ 7 8 if ( ! defined( 'ABSPATH' ) ) { 9 exit; 5 10 } 6 11 12 /** 13 * Class CHRSSEN_Admin 14 * 15 * Handles admin settings page and plugin configuration. 16 * 17 * @since 1.0.0 18 */ 7 19 class CHRSSEN_Admin { 8 20 9 public function __construct() { 10 add_action('admin_init', [$this, 'chrssen_register_settings']); 11 add_action('admin_menu', [$this, 'chrssen_add_admin_menu']); 12 add_action('admin_enqueue_scripts', [$this, 'chrssen_enqueue_admin_scripts']); 13 add_filter('plugin_action_links_' . plugin_basename(CHRSSEN_PLUGIN_PATH . 'simple-exit-notifier.php'), [$this, 'chrssen_add_settings_link']); 14 } 15 16 /** 17 * Enqueue admin scripts for the settings page. 18 * 19 * @param string $hook The current admin page. 20 */ 21 public function chrssen_enqueue_admin_scripts($hook) { 22 if ($hook === 'settings_page_simple-exit-notifier') { 23 // Enqueue admin-specific CSS 24 wp_enqueue_style('chrssen-admin-css', CHRSSEN_PLUGIN_URL . 'assets/css/admin.css', [], CHRSSEN_VERSION); 25 26 // Enqueue admin-specific JS 27 wp_enqueue_script('chrssen-settings-js', CHRSSEN_PLUGIN_URL . 'assets/js/settings.js', [], CHRSSEN_VERSION, true); 28 } 29 } 30 31 /** 32 * Add a "Settings" link to the plugin on the plugins page. 33 * 34 * @param array $links Existing plugin action links. 35 * @return array Modified plugin action links. 36 */ 37 public function chrssen_add_settings_link($links) { 38 $settings_link = '<a href="' . admin_url('options-general.php?page=simple-exit-notifier') . '">' . __('Settings', 'simple-exit-notifier') . '</a>'; 39 array_unshift($links, $settings_link); 40 return $links; 41 } 42 43 44 /** 45 * Register plugin settings. 46 */ 47 public function chrssen_register_settings() { 48 register_setting('chrssen_settings_group', 'chrssen_heading', ['sanitize_callback' => 'sanitize_text_field']); 49 register_setting('chrssen_settings_group', 'chrssen_message', ['sanitize_callback' => 'wp_kses_post']); 50 register_setting('chrssen_settings_group', 'chrssen_proceed_text', ['sanitize_callback' => 'sanitize_text_field']); 51 register_setting('chrssen_settings_group', 'chrssen_cancel_text', ['sanitize_callback' => 'sanitize_text_field']); 52 register_setting('chrssen_settings_group', 'chrssen_exception_class', ['sanitize_callback' => 'sanitize_text_field']); 53 register_setting('chrssen_settings_group', 'chrssen_display_external_link', ['sanitize_callback' => 'sanitize_text_field']); 54 } 55 56 /** 57 * Add plugin settings page to the admin menu. 58 */ 59 public function chrssen_add_admin_menu() { 60 add_options_page( 61 __('Simple Exit Notifier', 'simple-exit-notifier'), 62 __('Exit Notifier', 'simple-exit-notifier'), 63 'manage_options', 64 'simple-exit-notifier', 65 [$this, 'chrssen_render_settings_page'] 66 ); 67 } 68 69 /** 70 * Render the settings page. 71 */ 72 public function chrssen_render_settings_page() { 73 if (!current_user_can('manage_options')) { 74 return; 75 } 76 ?> 77 <div class="wrap"> 78 <h1><?php esc_html_e('Simple Exit Notifier', 'simple-exit-notifier'); ?></h1> 79 80 <div class="chrssen-container col-half"> 81 <div class="chrssen-col postbox"> 82 <div class="inside"> 83 <h2><?php esc_html_e('Settings', 'simple-exit-notifier'); ?></h2> 84 <form method="post" action="options.php"> 85 <?php 86 settings_fields('chrssen_settings_group'); 87 do_settings_sections('chrssen_settings_group'); 88 ?> 89 <table class="form-table"> 90 <tr> 91 <th scope="row"><label for="chrssen_heading"><?php esc_html_e('Popup Heading', 'simple-exit-notifier'); ?></label></th> 92 <td><input type="text" id="chrssen_heading" name="chrssen_heading" value="<?php echo esc_attr(get_option('chrssen_heading', 'Leaving Our Site')); ?>" class="regular-text" /></td> 93 </tr> 94 <tr> 95 <th scope="row"><label for="chrssen_message"><?php esc_html_e('Popup Message', 'simple-exit-notifier'); ?></label></th> 96 <td><textarea id="chrssen_message" name="chrssen_message" rows="5" class="large-text"><?php echo esc_textarea(get_option('chrssen_message', 'You are about to leave our website. Do you want to proceed?')); ?></textarea></td> 97 </tr> 98 <tr> 99 <th scope="row"><label for="chrssen_proceed_text"><?php esc_html_e('Proceed Button Text', 'simple-exit-notifier'); ?></label></th> 100 <td><input type="text" id="chrssen_proceed_text" name="chrssen_proceed_text" value="<?php echo esc_attr(get_option('chrssen_proceed_text', 'Proceed')); ?>" class="regular-text" /></td> 101 </tr> 102 <tr> 103 <th scope="row"><label for="chrssen_cancel_text"><?php esc_html_e('Cancel Button Text', 'simple-exit-notifier'); ?></label></th> 104 <td><input type="text" id="chrssen_cancel_text" name="chrssen_cancel_text" value="<?php echo esc_attr(get_option('chrssen_cancel_text', 'Cancel')); ?>" class="regular-text" /></td> 105 </tr> 106 <tr> 107 <th scope="row"><label for="chrssen_display_external_link"><?php esc_html_e('Display External Link in Popup', 'simple-exit-notifier'); ?></label></th> 108 <td> 109 <input type="checkbox" id="chrssen_display_external_link" name="chrssen_display_external_link" value="1" <?php checked(1, get_option('chrssen_display_external_link', 0)); ?> /> 110 <p class="description"><?php esc_html_e('If enabled, the external link (href) will be displayed in the popup.', 'simple-exit-notifier'); ?></p> 111 <p class="description"><?php esc_html_e('(Visit: https://www.xyz.com)', 'simple-exit-notifier'); ?></p> 112 </td> 113 </tr> 114 <tr> 115 <th scope="row"><label for="chrssen_exception_class"><?php esc_html_e('Exception Class', 'simple-exit-notifier'); ?></label></th> 116 <td> 117 <input type="text" id="chrssen_exception_class" name="chrssen_exception_class" value="<?php echo esc_attr(get_option('chrssen_exception_class', 'noexit')); ?>" class="regular-text" /> 118 <p class="description"><?php esc_html_e('Add a CSS class to exclude certain links from showing the exit popup (default: "noexit").', 'simple-exit-notifier'); ?></p> 119 </td> 120 </tr> 121 </table> 122 <?php submit_button(); ?> 123 </form> 124 </div> 125 </div> 126 <div class="chrssen-col postbox"> 127 <div class="inside"> 128 <h2><?php esc_html_e('Preview', 'simple-exit-notifier'); ?></h2> 129 130 <div id="chrssen-preview-modal"> 131 <div class="simple-exit-notifier-content" style="background: #fff;box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);margin-top:20px;"> 132 <div class="simple-exit-notifier-header" style="padding: 10px 20px;background: #ccc;"> 133 <h2 id="chrssen-preview-heading"><?php echo esc_html(get_option('chrssen_heading', 'Leaving Our Site')); ?></h2> 134 </div> 135 <div class="simple-exit-notifier-message" style="padding: 20px 20px 0;"> 136 <p id="chrssen-preview-message"><?php echo wp_kses_post(get_option('chrssen_message', 'You are about to leave our website. Do you want to proceed?')); ?></p> 137 <p id="chrssen-preview-external-link" style="text-align: center; <?php echo get_option('chrssen_display_external_link', 0) ? '' : 'display: none;'; ?>"> 138 <?php esc_html_e('Visit: https://www.xyz.com', 'simple-exit-notifier'); ?> 139 </p> 140 </div> 141 <div class="simple-exit-notifier-actions" style="padding-bottom: 20px;text-align: center;"> 142 <a id="chrssen-preview-proceed" href="#" style="margin: 10px;background: #4caf50; color: #fff; padding: 10px 20px; text-decoration: none; border-radius: 4px;"><?php echo esc_html(get_option('chrssen_proceed_text', 'Proceed')); ?></a> 143 <button id="chrssen-preview-cancel" style="margin: 10px;background: #f44336; color: #fff; padding: 10px 20px; border: none; border-radius: 4px;"><?php echo esc_html(get_option('chrssen_cancel_text', 'Cancel')); ?></button> 144 </div> 145 </div> 146 </div> 147 </div> 148 </div> 149 </div> 150 151 </div> 152 <?php 153 } 21 /** 22 * Default settings values. 23 * 24 * @since 2.0.0 25 * 26 * @var array<string, mixed> 27 */ 28 private array $defaults = [ 29 // General. 30 'enabled' => 1, 31 'trigger_class' => '', 32 'heading' => '', 33 'message' => '', 34 'proceed_text' => '', 35 'cancel_text' => '', 36 'display_external_link' => 0, 37 // Custom Popup. 38 'custom_popup_enabled' => 0, 39 'custom_popup_class' => '', 40 'custom_popup_heading' => '', 41 'custom_popup_message' => '', 42 'custom_popup_proceed' => '', 43 'custom_popup_cancel' => '', 44 // Appearance. 45 'header_bg_color' => '#cccccc', 46 'header_text_color' => '#000000', 47 'body_bg_color' => '#ffffff', 48 'body_text_color' => '#000000', 49 'proceed_bg_color' => '#4caf50', 50 'proceed_text_color' => '#ffffff', 51 'cancel_bg_color' => '#f44336', 52 'cancel_text_color' => '#ffffff', 53 'overlay_opacity' => '0.7', 54 'modal_width' => '600', 55 'animation_style' => 'fade', 56 'custom_css' => '', 57 // Behavior. 58 'delay_enabled' => 0, 59 'delay_seconds' => 5, 60 'remember_enabled' => 0, 61 'remember_duration' => 'session', 62 'link_icon_enabled' => 0, 63 'link_icon_position' => 'after', 64 // Exclusions. 65 'exception_classes' => 'noexit', 66 'whitelisted_domains' => '', 67 'excluded_pages' => [], 68 ]; 69 70 /** 71 * Constructor. 72 * 73 * @since 1.0.0 74 */ 75 public function __construct() { 76 add_action( 'admin_init', [ $this, 'chrssen_register_settings' ] ); 77 add_action( 'admin_menu', [ $this, 'chrssen_add_admin_menu' ] ); 78 add_action( 'admin_enqueue_scripts', [ $this, 'chrssen_enqueue_admin_scripts' ] ); 79 add_filter( 'plugin_action_links_' . plugin_basename( CHRSSEN_PLUGIN_PATH . 'simple-exit-notifier.php' ), [ $this, 'chrssen_add_settings_link' ] ); 80 } 81 82 /** 83 * Get option value with default fallback. 84 * 85 * @since 2.0.0 86 * 87 * @param string $key Option key without prefix. 88 * 89 * @return mixed Option value. 90 */ 91 private function get_option( string $key ): mixed { 92 $default = $this->defaults[ $key ] ?? ''; 93 return get_option( 'chrssen_' . $key, $default ); 94 } 95 96 /** 97 * Enqueue admin scripts for the settings page. 98 * 99 * @since 1.0.0 100 * 101 * @param string $hook The current admin page hook. 102 * 103 * @return void 104 */ 105 public function chrssen_enqueue_admin_scripts( string $hook ): void { 106 if ( 'settings_page_simple-exit-notifier' !== $hook ) { 107 return; 108 } 109 110 // WordPress color picker. 111 wp_enqueue_style( 'wp-color-picker' ); 112 wp_enqueue_script( 'wp-color-picker' ); 113 114 wp_enqueue_style( 115 'chrssen-admin-css', 116 CHRSSEN_PLUGIN_URL . 'assets/css/admin.css', 117 [ 'wp-color-picker' ], 118 CHRSSEN_VERSION 119 ); 120 121 wp_enqueue_script( 122 'chrssen-settings-js', 123 CHRSSEN_PLUGIN_URL . 'assets/js/settings.js', 124 [ 'jquery', 'wp-color-picker' ], 125 CHRSSEN_VERSION, 126 true 127 ); 128 } 129 130 /** 131 * Add a "Settings" link to the plugin on the plugins page. 132 * 133 * @since 1.1.0 134 * 135 * @param array<string> $links Existing plugin action links. 136 * 137 * @return array<string> Modified plugin action links. 138 */ 139 public function chrssen_add_settings_link( array $links ): array { 140 $settings_link = '<a href="' . esc_url( admin_url( 'options-general.php?page=simple-exit-notifier' ) ) . '">' . esc_html__( 'Settings', 'simple-exit-notifier' ) . '</a>'; 141 array_unshift( $links, $settings_link ); 142 return $links; 143 } 144 145 /** 146 * Register plugin settings. 147 * 148 * Uses separate settings groups per tab to prevent data loss when saving. 149 * 150 * @since 1.0.0 151 * 152 * @return void 153 */ 154 public function chrssen_register_settings(): void { 155 // General settings - separate group. 156 register_setting( 'chrssen_general_group', 'chrssen_enabled', [ 'sanitize_callback' => 'absint' ] ); 157 register_setting( 'chrssen_general_group', 'chrssen_trigger_class', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 158 register_setting( 'chrssen_general_group', 'chrssen_heading', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 159 register_setting( 'chrssen_general_group', 'chrssen_message', [ 'sanitize_callback' => 'wp_kses_post' ] ); 160 register_setting( 'chrssen_general_group', 'chrssen_proceed_text', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 161 register_setting( 'chrssen_general_group', 'chrssen_cancel_text', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 162 register_setting( 'chrssen_general_group', 'chrssen_display_external_link', [ 'sanitize_callback' => 'absint' ] ); 163 164 // Custom Popup settings - separate group. 165 register_setting( 'chrssen_custom_popup_group', 'chrssen_custom_popup_enabled', [ 'sanitize_callback' => 'absint' ] ); 166 register_setting( 'chrssen_custom_popup_group', 'chrssen_custom_popup_class', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 167 register_setting( 'chrssen_custom_popup_group', 'chrssen_custom_popup_heading', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 168 register_setting( 'chrssen_custom_popup_group', 'chrssen_custom_popup_message', [ 'sanitize_callback' => 'wp_kses_post' ] ); 169 register_setting( 'chrssen_custom_popup_group', 'chrssen_custom_popup_proceed', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 170 register_setting( 'chrssen_custom_popup_group', 'chrssen_custom_popup_cancel', [ 'sanitize_callback' => 'sanitize_text_field' ] ); 171 172 // Appearance settings - separate group. 173 register_setting( 'chrssen_appearance_group', 'chrssen_header_bg_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 174 register_setting( 'chrssen_appearance_group', 'chrssen_header_text_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 175 register_setting( 'chrssen_appearance_group', 'chrssen_body_bg_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 176 register_setting( 'chrssen_appearance_group', 'chrssen_body_text_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 177 register_setting( 'chrssen_appearance_group', 'chrssen_proceed_bg_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 178 register_setting( 'chrssen_appearance_group', 'chrssen_proceed_text_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 179 register_setting( 'chrssen_appearance_group', 'chrssen_cancel_bg_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 180 register_setting( 'chrssen_appearance_group', 'chrssen_cancel_text_color', [ 'sanitize_callback' => 'sanitize_hex_color' ] ); 181 register_setting( 'chrssen_appearance_group', 'chrssen_overlay_opacity', [ 'sanitize_callback' => [ $this, 'sanitize_opacity' ] ] ); 182 register_setting( 'chrssen_appearance_group', 'chrssen_modal_width', [ 'sanitize_callback' => [ $this, 'sanitize_modal_width' ] ] ); 183 register_setting( 'chrssen_appearance_group', 'chrssen_animation_style', [ 'sanitize_callback' => [ $this, 'sanitize_animation' ] ] ); 184 register_setting( 'chrssen_appearance_group', 'chrssen_custom_css', [ 'sanitize_callback' => [ $this, 'sanitize_css' ] ] ); 185 186 // Behavior settings - separate group. 187 register_setting( 'chrssen_behavior_group', 'chrssen_delay_enabled', [ 'sanitize_callback' => 'absint' ] ); 188 register_setting( 'chrssen_behavior_group', 'chrssen_delay_seconds', [ 'sanitize_callback' => [ $this, 'sanitize_delay_seconds' ] ] ); 189 register_setting( 'chrssen_behavior_group', 'chrssen_remember_enabled', [ 'sanitize_callback' => 'absint' ] ); 190 register_setting( 'chrssen_behavior_group', 'chrssen_remember_duration', [ 'sanitize_callback' => [ $this, 'sanitize_remember_duration' ] ] ); 191 register_setting( 'chrssen_behavior_group', 'chrssen_link_icon_enabled', [ 'sanitize_callback' => 'absint' ] ); 192 register_setting( 'chrssen_behavior_group', 'chrssen_link_icon_position', [ 'sanitize_callback' => [ $this, 'sanitize_icon_position' ] ] ); 193 194 // Exclusion settings - separate group. 195 register_setting( 'chrssen_exclusions_group', 'chrssen_exception_classes', [ 'sanitize_callback' => 'sanitize_textarea_field' ] ); 196 register_setting( 'chrssen_exclusions_group', 'chrssen_whitelisted_domains', [ 'sanitize_callback' => 'sanitize_textarea_field' ] ); 197 register_setting( 'chrssen_exclusions_group', 'chrssen_excluded_pages', [ 'sanitize_callback' => [ $this, 'sanitize_excluded_pages' ] ] ); 198 } 199 200 /** 201 * Sanitize opacity value. 202 * 203 * @since 2.0.0 204 * 205 * @param mixed $value The opacity value. 206 * 207 * @return string Sanitized opacity. 208 */ 209 public function sanitize_opacity( mixed $value ): string { 210 if ( null === $value || '' === $value ) { 211 return '0.7'; 212 } 213 $opacity = (float) $value; 214 $opacity = max( 0.1, min( 1.0, $opacity ) ); 215 return (string) $opacity; 216 } 217 218 /** 219 * Sanitize modal width value. 220 * 221 * @since 2.0.0 222 * 223 * @param mixed $value The width value. 224 * 225 * @return int Sanitized width. 226 */ 227 public function sanitize_modal_width( mixed $value ): int { 228 if ( null === $value || '' === $value ) { 229 return 600; 230 } 231 $width = absint( $value ); 232 return max( 300, min( 1200, $width ) ); 233 } 234 235 /** 236 * Sanitize delay seconds value. 237 * 238 * @since 2.0.0 239 * 240 * @param mixed $value The delay value. 241 * 242 * @return int Sanitized delay. 243 */ 244 public function sanitize_delay_seconds( mixed $value ): int { 245 if ( null === $value || '' === $value ) { 246 return 5; 247 } 248 $seconds = absint( $value ); 249 return max( 1, min( 30, $seconds ) ); 250 } 251 252 /** 253 * Sanitize animation style. 254 * 255 * @since 2.0.0 256 * 257 * @param mixed $value The animation value. 258 * 259 * @return string Sanitized animation. 260 */ 261 public function sanitize_animation( mixed $value ): string { 262 if ( null === $value || '' === $value ) { 263 return 'fade'; 264 } 265 $allowed = [ 'fade', 'slide-up', 'slide-down', 'none' ]; 266 return in_array( $value, $allowed, true ) ? $value : 'fade'; 267 } 268 269 /** 270 * Sanitize remember duration. 271 * 272 * @since 2.0.0 273 * 274 * @param mixed $value The duration value. 275 * 276 * @return string Sanitized duration. 277 */ 278 public function sanitize_remember_duration( mixed $value ): string { 279 if ( null === $value || '' === $value ) { 280 return 'session'; 281 } 282 $allowed = [ 'session', '1day', '7days', '30days', 'forever' ]; 283 return in_array( $value, $allowed, true ) ? $value : 'session'; 284 } 285 286 /** 287 * Sanitize icon position. 288 * 289 * @since 2.0.0 290 * 291 * @param mixed $value The position value. 292 * 293 * @return string Sanitized position. 294 */ 295 public function sanitize_icon_position( mixed $value ): string { 296 if ( null === $value || '' === $value ) { 297 return 'after'; 298 } 299 $allowed = [ 'before', 'after' ]; 300 return in_array( $value, $allowed, true ) ? $value : 'after'; 301 } 302 303 /** 304 * Sanitize custom CSS. 305 * 306 * @since 2.0.0 307 * 308 * @param mixed $value The CSS value. 309 * 310 * @return string Sanitized CSS. 311 */ 312 public function sanitize_css( mixed $value ): string { 313 if ( null === $value || '' === $value ) { 314 return ''; 315 } 316 return wp_strip_all_tags( $value ); 317 } 318 319 /** 320 * Sanitize excluded pages. 321 * 322 * @since 2.0.0 323 * 324 * @param mixed $value The pages value. 325 * 326 * @return array<int> Sanitized page IDs. 327 */ 328 public function sanitize_excluded_pages( mixed $value ): array { 329 if ( ! is_array( $value ) ) { 330 return []; 331 } 332 return array_map( 'absint', $value ); 333 } 334 335 /** 336 * Add plugin settings page to the admin menu. 337 * 338 * @since 1.0.0 339 * 340 * @return void 341 */ 342 public function chrssen_add_admin_menu(): void { 343 add_options_page( 344 __( 'Simple Exit Notifier', 'simple-exit-notifier' ), 345 __( 'Exit Notifier', 'simple-exit-notifier' ), 346 'manage_options', 347 'simple-exit-notifier', 348 [ $this, 'chrssen_render_settings_page' ] 349 ); 350 } 351 352 /** 353 * Render the settings page. 354 * 355 * @since 1.0.0 356 * 357 * @return void 358 */ 359 public function chrssen_render_settings_page(): void { 360 if ( ! current_user_can( 'manage_options' ) ) { 361 return; 362 } 363 364 // Get current tab. 365 // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Tab navigation does not require nonce verification. 366 $current_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'general'; 367 $tabs = [ 368 'general' => __( 'Content 1', 'simple-exit-notifier' ), 369 'custom_popup' => __( 'Content 2', 'simple-exit-notifier' ), 370 'appearance' => __( 'Appearance', 'simple-exit-notifier' ), 371 'behavior' => __( 'Behavior', 'simple-exit-notifier' ), 372 'exclusions' => __( 'Exclusions', 'simple-exit-notifier' ), 373 ]; 374 375 // Map tabs to their settings groups. 376 $settings_groups = [ 377 'general' => 'chrssen_general_group', 378 'custom_popup' => 'chrssen_custom_popup_group', 379 'appearance' => 'chrssen_appearance_group', 380 'behavior' => 'chrssen_behavior_group', 381 'exclusions' => 'chrssen_exclusions_group', 382 ]; 383 $current_group = $settings_groups[ $current_tab ] ?? 'chrssen_general_group'; 384 ?> 385 <div class="wrap chrssen-wrap"> 386 <h1><?php esc_html_e( 'Simple Exit Notifier', 'simple-exit-notifier' ); ?></h1> 387 388 <nav class="nav-tab-wrapper chrssen-nav-tabs"> 389 <?php foreach ( $tabs as $tab_key => $tab_label ) : ?> 390 <a href="<?php echo esc_url( add_query_arg( 'tab', $tab_key, admin_url( 'options-general.php?page=simple-exit-notifier' ) ) ); ?>" class="nav-tab <?php echo $current_tab === $tab_key ? 'nav-tab-active' : ''; ?>"> 391 <?php echo esc_html( $tab_label ); ?> 392 </a> 393 <?php endforeach; ?> 394 </nav> 395 396 <div class="chrssen-settings-container"> 397 <div class="chrssen-settings-main"> 398 <form method="post" action="options.php"> 399 <?php settings_fields( $current_group ); ?> 400 <input type="hidden" name="chrssen_active_tab" value="<?php echo esc_attr( $current_tab ); ?>" /> 401 402 <?php 403 switch ( $current_tab ) { 404 case 'custom_popup': 405 $this->render_custom_popup_tab(); 406 break; 407 case 'appearance': 408 $this->render_appearance_tab(); 409 break; 410 case 'behavior': 411 $this->render_behavior_tab(); 412 break; 413 case 'exclusions': 414 $this->render_exclusions_tab(); 415 break; 416 default: 417 $this->render_general_tab(); 418 break; 419 } 420 ?> 421 422 <?php submit_button(); ?> 423 </form> 424 </div> 425 426 <div class="chrssen-settings-preview"> 427 <?php $this->render_preview(); ?> 428 </div> 429 </div> 430 </div> 431 <?php 432 } 433 434 /** 435 * Render the General tab. 436 * 437 * @since 2.0.0 438 * 439 * @return void 440 */ 441 private function render_general_tab(): void { 442 $enabled = $this->get_option( 'enabled' ); 443 $trigger_class = $this->get_option( 'trigger_class' ); 444 $heading = $this->get_option( 'heading' ); 445 $message = $this->get_option( 'message' ); 446 $proceed_text = $this->get_option( 'proceed_text' ); 447 $cancel_text = $this->get_option( 'cancel_text' ); 448 $display_external_link = $this->get_option( 'display_external_link' ); 449 450 // Use translatable defaults for display. 451 $heading = ! empty( $heading ) ? $heading : __( 'Leaving Our Site', 'simple-exit-notifier' ); 452 $message = ! empty( $message ) ? $message : __( 'You are about to leave our website. Do you want to proceed?', 'simple-exit-notifier' ); 453 $proceed_text = ! empty( $proceed_text ) ? $proceed_text : __( 'Proceed', 'simple-exit-notifier' ); 454 $cancel_text = ! empty( $cancel_text ) ? $cancel_text : __( 'Cancel', 'simple-exit-notifier' ); 455 ?> 456 <div class="chrssen-tab-content" id="chrssen-tab-general"> 457 <table class="form-table"> 458 <tr> 459 <th scope="row"><?php esc_html_e( 'Enable Exit Notifier', 'simple-exit-notifier' ); ?></th> 460 <td> 461 <label class="chrssen-toggle"> 462 <input type="checkbox" id="chrssen_enabled" name="chrssen_enabled" value="1" <?php checked( 1, $enabled ); ?> /> 463 <span class="chrssen-toggle-slider"></span> 464 </label> 465 <p class="description"><?php esc_html_e( 'Enable or disable the exit notification popup globally.', 'simple-exit-notifier' ); ?></p> 466 </td> 467 </tr> 468 <tr> 469 <th scope="row"><label for="chrssen_trigger_class"><?php esc_html_e( 'Trigger Class (Optional)', 'simple-exit-notifier' ); ?></label></th> 470 <td> 471 <input type="text" id="chrssen_trigger_class" name="chrssen_trigger_class" value="<?php echo esc_attr( $trigger_class ); ?>" class="regular-text" placeholder="e.g., external-link" /> 472 <p class="description"><?php esc_html_e( 'If set, this popup will only apply to external links with this CSS class. Leave empty to apply to all external links.', 'simple-exit-notifier' ); ?></p> 473 </td> 474 </tr> 475 <tr> 476 <th scope="row"><label for="chrssen_heading"><?php esc_html_e( 'Popup Heading', 'simple-exit-notifier' ); ?></label></th> 477 <td> 478 <input type="text" id="chrssen_heading" name="chrssen_heading" value="<?php echo esc_attr( $heading ); ?>" class="regular-text" /> 479 </td> 480 </tr> 481 <tr> 482 <th scope="row"><label for="chrssen_message"><?php esc_html_e( 'Popup Message', 'simple-exit-notifier' ); ?></label></th> 483 <td> 484 <textarea id="chrssen_message" name="chrssen_message" rows="4" class="large-text"><?php echo esc_textarea( $message ); ?></textarea> 485 <p class="description"><?php esc_html_e( 'Basic HTML is allowed (bold, italic, links).', 'simple-exit-notifier' ); ?></p> 486 </td> 487 </tr> 488 <tr> 489 <th scope="row"><label for="chrssen_proceed_text"><?php esc_html_e( 'Proceed Button Text', 'simple-exit-notifier' ); ?></label></th> 490 <td> 491 <input type="text" id="chrssen_proceed_text" name="chrssen_proceed_text" value="<?php echo esc_attr( $proceed_text ); ?>" class="regular-text" /> 492 </td> 493 </tr> 494 <tr> 495 <th scope="row"><label for="chrssen_cancel_text"><?php esc_html_e( 'Cancel Button Text', 'simple-exit-notifier' ); ?></label></th> 496 <td> 497 <input type="text" id="chrssen_cancel_text" name="chrssen_cancel_text" value="<?php echo esc_attr( $cancel_text ); ?>" class="regular-text" /> 498 </td> 499 </tr> 500 <tr> 501 <th scope="row"><?php esc_html_e( 'Show External URL', 'simple-exit-notifier' ); ?></th> 502 <td> 503 <label> 504 <input type="checkbox" id="chrssen_display_external_link" name="chrssen_display_external_link" value="1" <?php checked( 1, $display_external_link ); ?> /> 505 <?php esc_html_e( 'Display the external link URL in the popup', 'simple-exit-notifier' ); ?> 506 </label> 507 <p class="description"><?php esc_html_e( 'Example: "Visit: https://example.com"', 'simple-exit-notifier' ); ?></p> 508 </td> 509 </tr> 510 </table> 511 </div> 512 <?php 513 } 514 515 /** 516 * Render the Custom Popup tab. 517 * 518 * @since 2.1.0 519 * 520 * @return void 521 */ 522 private function render_custom_popup_tab(): void { 523 $custom_popup_enabled = $this->get_option( 'custom_popup_enabled' ); 524 $custom_popup_class = $this->get_option( 'custom_popup_class' ); 525 $custom_popup_heading = $this->get_option( 'custom_popup_heading' ); 526 $custom_popup_message = $this->get_option( 'custom_popup_message' ); 527 $custom_popup_proceed = $this->get_option( 'custom_popup_proceed' ); 528 $custom_popup_cancel = $this->get_option( 'custom_popup_cancel' ); 529 530 // Use translatable defaults for display. 531 $custom_popup_heading = ! empty( $custom_popup_heading ) ? $custom_popup_heading : ''; 532 $custom_popup_message = ! empty( $custom_popup_message ) ? $custom_popup_message : ''; 533 $custom_popup_proceed = ! empty( $custom_popup_proceed ) ? $custom_popup_proceed : __( 'Proceed', 'simple-exit-notifier' ); 534 $custom_popup_cancel = ! empty( $custom_popup_cancel ) ? $custom_popup_cancel : __( 'Cancel', 'simple-exit-notifier' ); 535 ?> 536 <div class="chrssen-tab-content" id="chrssen-tab-custom-popup"> 537 <p class="chrssen-tab-description"><?php esc_html_e( 'Configure a secondary popup that will be shown for external links with a specific CSS class. When enabled, this popup overrides the default popup for matching links.', 'simple-exit-notifier' ); ?></p> 538 539 <table class="form-table"> 540 <tr> 541 <th scope="row"><?php esc_html_e( 'Enable Custom Popup', 'simple-exit-notifier' ); ?></th> 542 <td> 543 <label class="chrssen-toggle"> 544 <input type="checkbox" id="chrssen_custom_popup_enabled" name="chrssen_custom_popup_enabled" value="1" <?php checked( 1, $custom_popup_enabled ); ?> /> 545 <span class="chrssen-toggle-slider"></span> 546 </label> 547 <p class="description"><?php esc_html_e( 'Enable this secondary popup for specific external links.', 'simple-exit-notifier' ); ?></p> 548 </td> 549 </tr> 550 <tr class="chrssen-custom-popup-row" <?php echo ! $custom_popup_enabled ? 'style="display:none;"' : ''; ?>> 551 <th scope="row"><label for="chrssen_custom_popup_class"><?php esc_html_e( 'Trigger Class', 'simple-exit-notifier' ); ?></label></th> 552 <td> 553 <input type="text" id="chrssen_custom_popup_class" name="chrssen_custom_popup_class" value="<?php echo esc_attr( $custom_popup_class ); ?>" class="regular-text" placeholder="e.g., sponsored-link" /> 554 <p class="description"><?php esc_html_e( 'CSS class that triggers this popup. Links with this class will show this popup instead of the default one.', 'simple-exit-notifier' ); ?></p> 555 </td> 556 </tr> 557 <tr class="chrssen-custom-popup-row" <?php echo ! $custom_popup_enabled ? 'style="display:none;"' : ''; ?>> 558 <th scope="row"><label for="chrssen_custom_popup_heading"><?php esc_html_e( 'Popup Heading', 'simple-exit-notifier' ); ?></label></th> 559 <td> 560 <input type="text" id="chrssen_custom_popup_heading" name="chrssen_custom_popup_heading" value="<?php echo esc_attr( $custom_popup_heading ); ?>" class="regular-text" placeholder="<?php esc_attr_e( 'Leaving Our Site', 'simple-exit-notifier' ); ?>" /> 561 </td> 562 </tr> 563 <tr class="chrssen-custom-popup-row" <?php echo ! $custom_popup_enabled ? 'style="display:none;"' : ''; ?>> 564 <th scope="row"><label for="chrssen_custom_popup_message"><?php esc_html_e( 'Popup Message', 'simple-exit-notifier' ); ?></label></th> 565 <td> 566 <textarea id="chrssen_custom_popup_message" name="chrssen_custom_popup_message" rows="4" class="large-text" placeholder="<?php esc_attr_e( 'You are about to leave our website. Do you want to proceed?', 'simple-exit-notifier' ); ?>"><?php echo esc_textarea( $custom_popup_message ); ?></textarea> 567 <p class="description"><?php esc_html_e( 'Basic HTML is allowed (bold, italic, links).', 'simple-exit-notifier' ); ?></p> 568 </td> 569 </tr> 570 <tr class="chrssen-custom-popup-row" <?php echo ! $custom_popup_enabled ? 'style="display:none;"' : ''; ?>> 571 <th scope="row"><label for="chrssen_custom_popup_proceed"><?php esc_html_e( 'Proceed Button Text', 'simple-exit-notifier' ); ?></label></th> 572 <td> 573 <input type="text" id="chrssen_custom_popup_proceed" name="chrssen_custom_popup_proceed" value="<?php echo esc_attr( $custom_popup_proceed ); ?>" class="regular-text" /> 574 </td> 575 </tr> 576 <tr class="chrssen-custom-popup-row" <?php echo ! $custom_popup_enabled ? 'style="display:none;"' : ''; ?>> 577 <th scope="row"><label for="chrssen_custom_popup_cancel"><?php esc_html_e( 'Cancel Button Text', 'simple-exit-notifier' ); ?></label></th> 578 <td> 579 <input type="text" id="chrssen_custom_popup_cancel" name="chrssen_custom_popup_cancel" value="<?php echo esc_attr( $custom_popup_cancel ); ?>" class="regular-text" /> 580 </td> 581 </tr> 582 </table> 583 </div> 584 <?php 585 } 586 587 /** 588 * Render the Appearance tab. 589 * 590 * @since 2.0.0 591 * 592 * @return void 593 */ 594 private function render_appearance_tab(): void { 595 // Use defaults if values are empty. 596 $header_bg_color = $this->get_option( 'header_bg_color' ) ?: $this->defaults['header_bg_color']; 597 $header_text_color = $this->get_option( 'header_text_color' ) ?: $this->defaults['header_text_color']; 598 $body_bg_color = $this->get_option( 'body_bg_color' ) ?: $this->defaults['body_bg_color']; 599 $body_text_color = $this->get_option( 'body_text_color' ) ?: $this->defaults['body_text_color']; 600 $proceed_bg_color = $this->get_option( 'proceed_bg_color' ) ?: $this->defaults['proceed_bg_color']; 601 $proceed_text_color = $this->get_option( 'proceed_text_color' ) ?: $this->defaults['proceed_text_color']; 602 $cancel_bg_color = $this->get_option( 'cancel_bg_color' ) ?: $this->defaults['cancel_bg_color']; 603 $cancel_text_color = $this->get_option( 'cancel_text_color' ) ?: $this->defaults['cancel_text_color']; 604 $overlay_opacity = $this->get_option( 'overlay_opacity' ) ?: $this->defaults['overlay_opacity']; 605 $modal_width = $this->get_option( 'modal_width' ) ?: $this->defaults['modal_width']; 606 $animation_style = $this->get_option( 'animation_style' ) ?: $this->defaults['animation_style']; 607 $custom_css = $this->get_option( 'custom_css' ); 608 ?> 609 <div class="chrssen-tab-content" id="chrssen-tab-appearance"> 610 <h2><?php esc_html_e( 'Colors', 'simple-exit-notifier' ); ?></h2> 611 <table class="form-table"> 612 <tr> 613 <th scope="row"><?php esc_html_e( 'Header Background', 'simple-exit-notifier' ); ?></th> 614 <td> 615 <input type="text" id="chrssen_header_bg_color" name="chrssen_header_bg_color" value="<?php echo esc_attr( $header_bg_color ); ?>" class="chrssen-color-picker" data-default-color="#cccccc" /> 616 </td> 617 </tr> 618 <tr> 619 <th scope="row"><?php esc_html_e( 'Header Text', 'simple-exit-notifier' ); ?></th> 620 <td> 621 <input type="text" id="chrssen_header_text_color" name="chrssen_header_text_color" value="<?php echo esc_attr( $header_text_color ); ?>" class="chrssen-color-picker" data-default-color="#000000" /> 622 </td> 623 </tr> 624 <tr> 625 <th scope="row"><?php esc_html_e( 'Body Background', 'simple-exit-notifier' ); ?></th> 626 <td> 627 <input type="text" id="chrssen_body_bg_color" name="chrssen_body_bg_color" value="<?php echo esc_attr( $body_bg_color ); ?>" class="chrssen-color-picker" data-default-color="#ffffff" /> 628 </td> 629 </tr> 630 <tr> 631 <th scope="row"><?php esc_html_e( 'Body Text', 'simple-exit-notifier' ); ?></th> 632 <td> 633 <input type="text" id="chrssen_body_text_color" name="chrssen_body_text_color" value="<?php echo esc_attr( $body_text_color ); ?>" class="chrssen-color-picker" data-default-color="#000000" /> 634 </td> 635 </tr> 636 <tr> 637 <th scope="row"><?php esc_html_e( 'Proceed Button Background', 'simple-exit-notifier' ); ?></th> 638 <td> 639 <input type="text" id="chrssen_proceed_bg_color" name="chrssen_proceed_bg_color" value="<?php echo esc_attr( $proceed_bg_color ); ?>" class="chrssen-color-picker" data-default-color="#4caf50" /> 640 </td> 641 </tr> 642 <tr> 643 <th scope="row"><?php esc_html_e( 'Proceed Button Text', 'simple-exit-notifier' ); ?></th> 644 <td> 645 <input type="text" id="chrssen_proceed_text_color" name="chrssen_proceed_text_color" value="<?php echo esc_attr( $proceed_text_color ); ?>" class="chrssen-color-picker" data-default-color="#ffffff" /> 646 </td> 647 </tr> 648 <tr> 649 <th scope="row"><?php esc_html_e( 'Cancel Button Background', 'simple-exit-notifier' ); ?></th> 650 <td> 651 <input type="text" id="chrssen_cancel_bg_color" name="chrssen_cancel_bg_color" value="<?php echo esc_attr( $cancel_bg_color ); ?>" class="chrssen-color-picker" data-default-color="#f44336" /> 652 </td> 653 </tr> 654 <tr> 655 <th scope="row"><?php esc_html_e( 'Cancel Button Text', 'simple-exit-notifier' ); ?></th> 656 <td> 657 <input type="text" id="chrssen_cancel_text_color" name="chrssen_cancel_text_color" value="<?php echo esc_attr( $cancel_text_color ); ?>" class="chrssen-color-picker" data-default-color="#ffffff" /> 658 </td> 659 </tr> 660 </table> 661 662 <h2><?php esc_html_e( 'Layout', 'simple-exit-notifier' ); ?></h2> 663 <table class="form-table"> 664 <tr> 665 <th scope="row"><label for="chrssen_overlay_opacity"><?php esc_html_e( 'Overlay Opacity', 'simple-exit-notifier' ); ?></label></th> 666 <td> 667 <input type="range" id="chrssen_overlay_opacity" name="chrssen_overlay_opacity" min="0.1" max="1" step="0.1" value="<?php echo esc_attr( $overlay_opacity ); ?>" /> 668 <span id="chrssen_overlay_opacity_value"><?php echo esc_html( $overlay_opacity ); ?></span> 669 </td> 670 </tr> 671 <tr> 672 <th scope="row"><label for="chrssen_modal_width"><?php esc_html_e( 'Modal Width (px)', 'simple-exit-notifier' ); ?></label></th> 673 <td> 674 <input type="number" id="chrssen_modal_width" name="chrssen_modal_width" value="<?php echo esc_attr( $modal_width ); ?>" min="300" max="1200" step="10" class="small-text" /> 675 <p class="description"><?php esc_html_e( 'Width in pixels (300-1200). Will be responsive on smaller screens.', 'simple-exit-notifier' ); ?></p> 676 </td> 677 </tr> 678 <tr> 679 <th scope="row"><label for="chrssen_animation_style"><?php esc_html_e( 'Animation Style', 'simple-exit-notifier' ); ?></label></th> 680 <td> 681 <select id="chrssen_animation_style" name="chrssen_animation_style"> 682 <option value="fade" <?php selected( $animation_style, 'fade' ); ?>><?php esc_html_e( 'Fade', 'simple-exit-notifier' ); ?></option> 683 <option value="slide-up" <?php selected( $animation_style, 'slide-up' ); ?>><?php esc_html_e( 'Slide Up', 'simple-exit-notifier' ); ?></option> 684 <option value="slide-down" <?php selected( $animation_style, 'slide-down' ); ?>><?php esc_html_e( 'Slide Down', 'simple-exit-notifier' ); ?></option> 685 <option value="none" <?php selected( $animation_style, 'none' ); ?>><?php esc_html_e( 'None', 'simple-exit-notifier' ); ?></option> 686 </select> 687 </td> 688 </tr> 689 </table> 690 691 <h2><?php esc_html_e( 'Custom CSS', 'simple-exit-notifier' ); ?></h2> 692 <table class="form-table"> 693 <tr> 694 <th scope="row"><label for="chrssen_custom_css"><?php esc_html_e( 'Additional CSS', 'simple-exit-notifier' ); ?></label></th> 695 <td> 696 <textarea id="chrssen_custom_css" name="chrssen_custom_css" rows="8" class="large-text code"><?php echo esc_textarea( $custom_css ); ?></textarea> 697 <p class="description"><?php esc_html_e( 'Add custom CSS to further customize the popup appearance.', 'simple-exit-notifier' ); ?></p> 698 </td> 699 </tr> 700 </table> 701 </div> 702 <?php 703 } 704 705 /** 706 * Render the Behavior tab. 707 * 708 * @since 2.0.0 709 * 710 * @return void 711 */ 712 private function render_behavior_tab(): void { 713 $delay_enabled = $this->get_option( 'delay_enabled' ); 714 $delay_seconds = $this->get_option( 'delay_seconds' ); 715 $remember_enabled = $this->get_option( 'remember_enabled' ); 716 $remember_duration = $this->get_option( 'remember_duration' ); 717 $link_icon_enabled = $this->get_option( 'link_icon_enabled' ); 718 $link_icon_position = $this->get_option( 'link_icon_position' ); 719 ?> 720 <div class="chrssen-tab-content" id="chrssen-tab-behavior"> 721 <h2><?php esc_html_e( 'Delay Timer', 'simple-exit-notifier' ); ?></h2> 722 <table class="form-table"> 723 <tr> 724 <th scope="row"><?php esc_html_e( 'Enable Delay Timer', 'simple-exit-notifier' ); ?></th> 725 <td> 726 <label> 727 <input type="checkbox" id="chrssen_delay_enabled" name="chrssen_delay_enabled" value="1" <?php checked( 1, $delay_enabled ); ?> /> 728 <?php esc_html_e( 'Show a countdown before allowing users to proceed', 'simple-exit-notifier' ); ?> 729 </label> 730 </td> 731 </tr> 732 <tr class="chrssen-delay-row" <?php echo ! $delay_enabled ? 'style="display:none;"' : ''; ?>> 733 <th scope="row"><label for="chrssen_delay_seconds"><?php esc_html_e( 'Delay Duration', 'simple-exit-notifier' ); ?></label></th> 734 <td> 735 <input type="number" id="chrssen_delay_seconds" name="chrssen_delay_seconds" value="<?php echo esc_attr( $delay_seconds ); ?>" min="1" max="30" class="small-text" /> 736 <span><?php esc_html_e( 'seconds', 'simple-exit-notifier' ); ?></span> 737 </td> 738 </tr> 739 </table> 740 741 <h2><?php esc_html_e( 'Remember User Choice', 'simple-exit-notifier' ); ?></h2> 742 <table class="form-table"> 743 <tr> 744 <th scope="row"><?php esc_html_e( 'Enable Remember Choice', 'simple-exit-notifier' ); ?></th> 745 <td> 746 <label> 747 <input type="checkbox" id="chrssen_remember_enabled" name="chrssen_remember_enabled" value="1" <?php checked( 1, $remember_enabled ); ?> /> 748 <?php esc_html_e( 'Allow users to skip the popup for future visits', 'simple-exit-notifier' ); ?> 749 </label> 750 <p class="description"><?php esc_html_e( 'A "Don\'t show again" checkbox will appear in the popup.', 'simple-exit-notifier' ); ?></p> 751 </td> 752 </tr> 753 <tr class="chrssen-remember-row" <?php echo ! $remember_enabled ? 'style="display:none;"' : ''; ?>> 754 <th scope="row"><label for="chrssen_remember_duration"><?php esc_html_e( 'Remember Duration', 'simple-exit-notifier' ); ?></label></th> 755 <td> 756 <select id="chrssen_remember_duration" name="chrssen_remember_duration"> 757 <option value="session" <?php selected( $remember_duration, 'session' ); ?>><?php esc_html_e( 'This session only', 'simple-exit-notifier' ); ?></option> 758 <option value="1day" <?php selected( $remember_duration, '1day' ); ?>><?php esc_html_e( '1 Day', 'simple-exit-notifier' ); ?></option> 759 <option value="7days" <?php selected( $remember_duration, '7days' ); ?>><?php esc_html_e( '7 Days', 'simple-exit-notifier' ); ?></option> 760 <option value="30days" <?php selected( $remember_duration, '30days' ); ?>><?php esc_html_e( '30 Days', 'simple-exit-notifier' ); ?></option> 761 <option value="forever" <?php selected( $remember_duration, 'forever' ); ?>><?php esc_html_e( 'Forever', 'simple-exit-notifier' ); ?></option> 762 </select> 763 </td> 764 </tr> 765 </table> 766 767 <h2><?php esc_html_e( 'External Link Indicator', 'simple-exit-notifier' ); ?></h2> 768 <table class="form-table"> 769 <tr> 770 <th scope="row"><?php esc_html_e( 'Show Link Icon', 'simple-exit-notifier' ); ?></th> 771 <td> 772 <label> 773 <input type="checkbox" id="chrssen_link_icon_enabled" name="chrssen_link_icon_enabled" value="1" <?php checked( 1, $link_icon_enabled ); ?> /> 774 <?php esc_html_e( 'Display an icon next to external links', 'simple-exit-notifier' ); ?> 775 </label> 776 <p class="description"><?php esc_html_e( 'Helps users identify which links will trigger the popup.', 'simple-exit-notifier' ); ?></p> 777 </td> 778 </tr> 779 <tr class="chrssen-icon-row" <?php echo ! $link_icon_enabled ? 'style="display:none;"' : ''; ?>> 780 <th scope="row"><label for="chrssen_link_icon_position"><?php esc_html_e( 'Icon Position', 'simple-exit-notifier' ); ?></label></th> 781 <td> 782 <select id="chrssen_link_icon_position" name="chrssen_link_icon_position"> 783 <option value="before" <?php selected( $link_icon_position, 'before' ); ?>><?php esc_html_e( 'Before link text', 'simple-exit-notifier' ); ?></option> 784 <option value="after" <?php selected( $link_icon_position, 'after' ); ?>><?php esc_html_e( 'After link text', 'simple-exit-notifier' ); ?></option> 785 </select> 786 </td> 787 </tr> 788 </table> 789 </div> 790 <?php 791 } 792 793 /** 794 * Render the Exclusions tab. 795 * 796 * @since 2.0.0 797 * 798 * @return void 799 */ 800 private function render_exclusions_tab(): void { 801 $exception_classes = $this->get_option( 'exception_classes' ); 802 $whitelisted_domains = $this->get_option( 'whitelisted_domains' ); 803 $excluded_pages = $this->get_option( 'excluded_pages' ); 804 805 // Migrate old single class option if exists. 806 $old_class = get_option( 'chrssen_exception_class', '' ); 807 if ( ! empty( $old_class ) && empty( $exception_classes ) ) { 808 $exception_classes = $old_class; 809 } 810 ?> 811 <div class="chrssen-tab-content" id="chrssen-tab-exclusions"> 812 <h2><?php esc_html_e( 'CSS Class Exceptions', 'simple-exit-notifier' ); ?></h2> 813 <table class="form-table"> 814 <tr> 815 <th scope="row"><label for="chrssen_exception_classes"><?php esc_html_e( 'Exception Classes', 'simple-exit-notifier' ); ?></label></th> 816 <td> 817 <textarea id="chrssen_exception_classes" name="chrssen_exception_classes" rows="3" class="large-text"><?php echo esc_textarea( $exception_classes ); ?></textarea> 818 <p class="description"><?php esc_html_e( 'Enter CSS class names (one per line or comma-separated) to exclude from the popup. Default: noexit', 'simple-exit-notifier' ); ?></p> 819 </td> 820 </tr> 821 </table> 822 823 <h2><?php esc_html_e( 'Whitelisted Domains', 'simple-exit-notifier' ); ?></h2> 824 <table class="form-table"> 825 <tr> 826 <th scope="row"><label for="chrssen_whitelisted_domains"><?php esc_html_e( 'Trusted Domains', 'simple-exit-notifier' ); ?></label></th> 827 <td> 828 <textarea id="chrssen_whitelisted_domains" name="chrssen_whitelisted_domains" rows="5" class="large-text" placeholder="facebook.com twitter.com linkedin.com"><?php echo esc_textarea( $whitelisted_domains ); ?></textarea> 829 <p class="description"><?php esc_html_e( 'Enter domain names (one per line) that should not trigger the popup. Do not include http:// or https://', 'simple-exit-notifier' ); ?></p> 830 </td> 831 </tr> 832 </table> 833 834 <h2><?php esc_html_e( 'Page Exclusions', 'simple-exit-notifier' ); ?></h2> 835 <table class="form-table"> 836 <tr> 837 <th scope="row"><label for="chrssen_excluded_pages"><?php esc_html_e( 'Excluded Pages', 'simple-exit-notifier' ); ?></label></th> 838 <td> 839 <select id="chrssen_excluded_pages" name="chrssen_excluded_pages[]" multiple class="chrssen-select-pages" style="width: 100%; max-width: 400px; min-height: 150px;"> 840 <?php 841 $pages = get_pages( [ 'post_status' => 'publish' ] ); 842 foreach ( $pages as $page ) { 843 $selected = in_array( $page->ID, (array) $excluded_pages, true ) ? 'selected' : ''; 844 printf( 845 '<option value="%d" %s>%s</option>', 846 esc_attr( $page->ID ), 847 esc_attr( $selected ), 848 esc_html( $page->post_title ) 849 ); 850 } 851 ?> 852 </select> 853 <p class="description"><?php esc_html_e( 'Select pages where the popup should not appear. Hold Ctrl/Cmd to select multiple.', 'simple-exit-notifier' ); ?></p> 854 </td> 855 </tr> 856 </table> 857 </div> 858 <?php 859 } 860 861 /** 862 * Render the preview panel. 863 * 864 * @since 2.0.0 865 * 866 * @return void 867 */ 868 private function render_preview(): void { 869 $heading = $this->get_option( 'heading' ); 870 $message = $this->get_option( 'message' ); 871 $proceed_text = $this->get_option( 'proceed_text' ); 872 $cancel_text = $this->get_option( 'cancel_text' ); 873 $header_bg_color = $this->get_option( 'header_bg_color' ); 874 $header_text_color = $this->get_option( 'header_text_color' ); 875 $body_bg_color = $this->get_option( 'body_bg_color' ); 876 $body_text_color = $this->get_option( 'body_text_color' ); 877 $proceed_bg_color = $this->get_option( 'proceed_bg_color' ); 878 $proceed_text_color = $this->get_option( 'proceed_text_color' ); 879 $cancel_bg_color = $this->get_option( 'cancel_bg_color' ); 880 $cancel_text_color = $this->get_option( 'cancel_text_color' ); 881 882 // Use translatable defaults for display. 883 $heading = ! empty( $heading ) ? $heading : __( 'Leaving Our Site', 'simple-exit-notifier' ); 884 $message = ! empty( $message ) ? $message : __( 'You are about to leave our website. Do you want to proceed?', 'simple-exit-notifier' ); 885 $proceed_text = ! empty( $proceed_text ) ? $proceed_text : __( 'Proceed', 'simple-exit-notifier' ); 886 $cancel_text = ! empty( $cancel_text ) ? $cancel_text : __( 'Cancel', 'simple-exit-notifier' ); 887 ?> 888 <div class="chrssen-preview-box postbox"> 889 <h2 class="hndle" style="text-align: center;"><?php esc_html_e( 'Preview', 'simple-exit-notifier' ); ?></h2> 890 <div class="inside"> 891 <div id="chrssen-preview-modal"> 892 <div class="simple-exit-notifier-content" id="chrssen-preview-content" style="background: <?php echo esc_attr( $body_bg_color ); ?>; color: <?php echo esc_attr( $body_text_color ); ?>; box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);"> 893 <div class="simple-exit-notifier-header" id="chrssen-preview-header" style="padding: 10px 20px; background: <?php echo esc_attr( $header_bg_color ); ?>; color: <?php echo esc_attr( $header_text_color ); ?>;"> 894 <h2 id="chrssen-preview-heading" style="color: <?php echo esc_attr( $header_text_color ); ?>;"><?php echo esc_html( $heading ); ?></h2> 895 </div> 896 <div class="simple-exit-notifier-message" style="padding: 20px 20px 0; color: <?php echo esc_attr( $body_text_color ); ?>;"> 897 <p id="chrssen-preview-message"><?php echo wp_kses_post( $message ); ?></p> 898 <p id="chrssen-preview-external-link" style="text-align: center; display: none;"> 899 <?php esc_html_e( 'Visit: https://example.com', 'simple-exit-notifier' ); ?> 900 </p> 901 </div> 902 <div class="simple-exit-notifier-actions" style="padding: 10px 20px 20px; text-align: center;"> 903 <a id="chrssen-preview-proceed" href="#" style="margin: 10px; background: <?php echo esc_attr( $proceed_bg_color ); ?>; color: <?php echo esc_attr( $proceed_text_color ); ?>; padding: 10px 20px; text-decoration: none; border-radius: 4px; display: inline-block;"><?php echo esc_html( $proceed_text ); ?></a> 904 <button id="chrssen-preview-cancel" type="button" style="margin: 10px; background: <?php echo esc_attr( $cancel_bg_color ); ?>; color: <?php echo esc_attr( $cancel_text_color ); ?>; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer;"><?php echo esc_html( $cancel_text ); ?></button> 905 </div> 906 </div> 907 </div> 908 <p class="chrssen-preview-note"><em><?php esc_html_e( 'This is a live preview. Changes update as you type.', 'simple-exit-notifier' ); ?></em></p> 909 </div> 910 </div> 911 <?php 912 } 154 913 } -
simple-exit-notifier/trunk/includes/class-simple-exit-notifier-frontend.php
r3213943 r3428053 1 1 <?php 2 3 if (!defined('ABSPATH')) { 4 exit; 2 /** 3 * Frontend functionality class. 4 * 5 * @package SimpleExitNotifier 6 */ 7 8 if ( ! defined( 'ABSPATH' ) ) { 9 exit; 5 10 } 6 11 12 /** 13 * Class CHRSSEN_Frontend 14 * 15 * Handles frontend modal display and script loading. 16 * 17 * @since 1.0.0 18 */ 7 19 class CHRSSEN_Frontend { 8 20 9 public function __construct() { 10 add_action('wp_enqueue_scripts', [$this, 'chrssen_enqueue_scripts']); 11 add_action('wp_footer', [$this, 'chrssen_add_modal_html']); 12 } 13 14 /** 15 * Enqueue frontend scripts and styles. 16 */ 17 public function chrssen_enqueue_scripts() { 18 if (!is_admin()) { 19 wp_enqueue_script('chrssen-frontend', CHRSSEN_PLUGIN_URL . 'assets/js/frontend.js', ['jquery'], CHRSSEN_VERSION, true); 20 wp_enqueue_style('chrssen-frontend', CHRSSEN_PLUGIN_URL . 'assets/css/frontend.css', [], CHRSSEN_VERSION); 21 22 wp_localize_script('chrssen-frontend', 'simple_exit_notifier_data', [ 23 'exceptionClass' => esc_js(get_option('chrssen_exception_class', 'noexit')), 24 ]); 25 } 26 } 27 28 /** 29 * Add modal HTML to the footer. 30 */ 31 public function chrssen_add_modal_html() { 32 // Retrieve saved options 33 $heading = get_option('chrssen_heading'); 34 $message = get_option('chrssen_message'); 35 $proceed_text = get_option('chrssen_proceed_text'); 36 $cancel_text = get_option('chrssen_cancel_text'); 37 $display_external_link = get_option('chrssen_display_external_link', 0); 38 39 40 // Set defaults if the options are empty 41 $heading = !empty($heading) ? esc_html($heading) : 'Leaving Our Site'; 42 $message = !empty($message) ? wp_kses_post($message) : 'You are about to leave our website. Do you want to proceed?'; 43 $proceed_text = !empty($proceed_text) ? esc_html($proceed_text) : 'Proceed'; 44 $cancel_text = !empty($cancel_text) ? esc_html($cancel_text) : 'Cancel'; 45 46 ?> 47 <div id="simple-exit-notifier-modal" style="display: none;"> 48 <div class="simple-exit-notifier-overlay"></div> 49 <div class="simple-exit-notifier-content"> 50 <div class="simple-exit-notifier-header"> 51 <h2><?php echo esc_html($heading); ?></h2> 52 </div> 53 <div class="simple-exit-notifier-message"> 54 <p><?php echo wp_kses_post($message); ?></p> 55 56 <?php if ($display_external_link): ?> 57 <p id="simple-exit-notifier-external-link" style="text-align: center;"></p> 58 <?php endif; ?> 59 60 </div> 61 <div class="simple-exit-notifier-actions"> 62 <a id="simple-exit-notifier-proceed" href="#" target="_blank"><?php echo esc_html($proceed_text); ?></a> 63 <button id="simple-exit-notifier-cancel"><?php echo esc_html($cancel_text); ?></button> 64 </div> 65 </div> 66 </div> 67 <?php 68 } 69 21 /** 22 * Default settings values. 23 * 24 * @since 2.0.0 25 * 26 * @var array<string, mixed> 27 */ 28 private array $defaults = [ 29 // General. 30 'enabled' => 1, 31 'trigger_class' => '', 32 'heading' => '', 33 'message' => '', 34 'proceed_text' => '', 35 'cancel_text' => '', 36 'display_external_link' => 0, 37 // Custom Popup. 38 'custom_popup_enabled' => 0, 39 'custom_popup_class' => '', 40 'custom_popup_heading' => '', 41 'custom_popup_message' => '', 42 'custom_popup_proceed' => '', 43 'custom_popup_cancel' => '', 44 // Appearance. 45 'header_bg_color' => '#cccccc', 46 'header_text_color' => '#000000', 47 'body_bg_color' => '#ffffff', 48 'body_text_color' => '#000000', 49 'proceed_bg_color' => '#4caf50', 50 'proceed_text_color' => '#ffffff', 51 'cancel_bg_color' => '#f44336', 52 'cancel_text_color' => '#ffffff', 53 'overlay_opacity' => '0.7', 54 'modal_width' => '600', 55 'animation_style' => 'fade', 56 'custom_css' => '', 57 // Behavior. 58 'delay_enabled' => 0, 59 'delay_seconds' => 5, 60 'remember_enabled' => 0, 61 'remember_duration' => 'session', 62 'link_icon_enabled' => 0, 63 'link_icon_position' => 'after', 64 // Exclusions. 65 'exception_classes' => 'noexit', 66 'whitelisted_domains' => '', 67 'excluded_pages' => [], 68 ]; 69 70 /** 71 * Constructor. 72 * 73 * @since 1.0.0 74 */ 75 public function __construct() { 76 add_action( 'wp_enqueue_scripts', [ $this, 'chrssen_enqueue_scripts' ] ); 77 add_action( 'wp_footer', [ $this, 'chrssen_add_modal_html' ] ); 78 } 79 80 /** 81 * Get option value with default fallback. 82 * 83 * @since 2.0.0 84 * 85 * @param string $key Option key without prefix. 86 * 87 * @return mixed Option value. 88 */ 89 private function get_option( string $key ): mixed { 90 $default = $this->defaults[ $key ] ?? ''; 91 return get_option( 'chrssen_' . $key, $default ); 92 } 93 94 /** 95 * Check if the notifier should be disabled on this page. 96 * 97 * @since 2.0.0 98 * 99 * @return bool True if disabled, false otherwise. 100 */ 101 private function is_disabled(): bool { 102 // Check global enable setting. 103 // If option doesn't exist (false), treat as enabled (default behavior). 104 // Only disable if explicitly set to 0. 105 $enabled = get_option( 'chrssen_enabled', 1 ); 106 if ( 0 === (int) $enabled ) { 107 return true; 108 } 109 110 // Check excluded pages. 111 $excluded_pages = $this->get_option( 'excluded_pages' ); 112 if ( ! empty( $excluded_pages ) && is_array( $excluded_pages ) ) { 113 $current_page_id = get_queried_object_id(); 114 if ( in_array( $current_page_id, $excluded_pages, true ) ) { 115 return true; 116 } 117 } 118 119 return false; 120 } 121 122 /** 123 * Enqueue frontend scripts and styles. 124 * 125 * @since 1.0.0 126 * 127 * @return void 128 */ 129 public function chrssen_enqueue_scripts(): void { 130 if ( is_admin() || $this->is_disabled() ) { 131 return; 132 } 133 134 wp_enqueue_script( 135 'chrssen-frontend', 136 CHRSSEN_PLUGIN_URL . 'assets/js/frontend.js', 137 [ 'jquery' ], 138 CHRSSEN_VERSION, 139 true 140 ); 141 142 wp_enqueue_style( 143 'chrssen-frontend', 144 CHRSSEN_PLUGIN_URL . 'assets/css/frontend.css', 145 [], 146 CHRSSEN_VERSION 147 ); 148 149 // Get exception classes - support both old and new options for backward compatibility. 150 $exception_classes = $this->get_option( 'exception_classes' ); 151 if ( empty( $exception_classes ) ) { 152 $exception_classes = get_option( 'chrssen_exception_class', 'noexit' ); 153 } 154 155 // Parse exception classes (comma or newline separated). 156 $classes_array = preg_split( '/[\s,]+/', $exception_classes, -1, PREG_SPLIT_NO_EMPTY ); 157 $classes_array = array_map( 'trim', $classes_array ); 158 $classes_array = array_filter( $classes_array ); 159 if ( empty( $classes_array ) ) { 160 $classes_array = [ 'noexit' ]; 161 } 162 163 // Parse whitelisted domains. 164 $whitelisted_domains = $this->get_option( 'whitelisted_domains' ); 165 $domains_array = preg_split( '/[\s,]+/', $whitelisted_domains, -1, PREG_SPLIT_NO_EMPTY ); 166 $domains_array = array_map( 'trim', $domains_array ); 167 $domains_array = array_filter( $domains_array ); 168 169 // Get trigger class for default popup. 170 $trigger_class = trim( $this->get_option( 'trigger_class' ) ); 171 172 // Get custom popup settings. 173 $custom_popup_enabled = (bool) $this->get_option( 'custom_popup_enabled' ); 174 $custom_popup_class = trim( $this->get_option( 'custom_popup_class' ) ); 175 176 wp_localize_script( 177 'chrssen-frontend', 178 'simple_exit_notifier_data', 179 [ 180 'exceptionClasses' => $classes_array, 181 'whitelistedDomains' => $domains_array, 182 'triggerClass' => $trigger_class, 183 'customPopupEnabled' => $custom_popup_enabled, 184 'customPopupClass' => $custom_popup_class, 185 'delayEnabled' => (bool) $this->get_option( 'delay_enabled' ), 186 'delaySeconds' => (int) $this->get_option( 'delay_seconds' ), 187 'rememberEnabled' => (bool) $this->get_option( 'remember_enabled' ), 188 'rememberDuration' => $this->get_option( 'remember_duration' ), 189 'linkIconEnabled' => (bool) $this->get_option( 'link_icon_enabled' ), 190 'linkIconPosition' => $this->get_option( 'link_icon_position' ), 191 'animationStyle' => $this->get_option( 'animation_style' ), 192 ] 193 ); 194 195 // Output custom CSS if set. 196 $custom_css = $this->get_option( 'custom_css' ); 197 if ( ! empty( $custom_css ) ) { 198 wp_add_inline_style( 'chrssen-frontend', $custom_css ); 199 } 200 } 201 202 /** 203 * Add modal HTML to the footer. 204 * 205 * @since 1.0.0 206 * 207 * @return void 208 */ 209 public function chrssen_add_modal_html(): void { 210 if ( $this->is_disabled() ) { 211 return; 212 } 213 214 // Default popup settings. 215 $heading = $this->get_option( 'heading' ); 216 $message = $this->get_option( 'message' ); 217 $proceed_text = $this->get_option( 'proceed_text' ); 218 $cancel_text = $this->get_option( 'cancel_text' ); 219 $display_external_link = $this->get_option( 'display_external_link' ); 220 $remember_enabled = $this->get_option( 'remember_enabled' ); 221 222 // Custom popup settings. 223 $custom_popup_enabled = (bool) $this->get_option( 'custom_popup_enabled' ); 224 $custom_popup_class = trim( $this->get_option( 'custom_popup_class' ) ); 225 $custom_popup_heading = $this->get_option( 'custom_popup_heading' ); 226 $custom_popup_message = $this->get_option( 'custom_popup_message' ); 227 $custom_popup_proceed = $this->get_option( 'custom_popup_proceed' ); 228 $custom_popup_cancel = $this->get_option( 'custom_popup_cancel' ); 229 230 // Appearance settings - use defaults if empty (shared by both popups). 231 $header_bg_color = $this->get_option( 'header_bg_color' ) ?: $this->defaults['header_bg_color']; 232 $header_text_color = $this->get_option( 'header_text_color' ) ?: $this->defaults['header_text_color']; 233 $body_bg_color = $this->get_option( 'body_bg_color' ) ?: $this->defaults['body_bg_color']; 234 $body_text_color = $this->get_option( 'body_text_color' ) ?: $this->defaults['body_text_color']; 235 $proceed_bg_color = $this->get_option( 'proceed_bg_color' ) ?: $this->defaults['proceed_bg_color']; 236 $proceed_text_color = $this->get_option( 'proceed_text_color' ) ?: $this->defaults['proceed_text_color']; 237 $cancel_bg_color = $this->get_option( 'cancel_bg_color' ) ?: $this->defaults['cancel_bg_color']; 238 $cancel_text_color = $this->get_option( 'cancel_text_color' ) ?: $this->defaults['cancel_text_color']; 239 $overlay_opacity = $this->get_option( 'overlay_opacity' ) ?: $this->defaults['overlay_opacity']; 240 $modal_width = $this->get_option( 'modal_width' ) ?: $this->defaults['modal_width']; 241 $animation_style = $this->get_option( 'animation_style' ) ?: $this->defaults['animation_style']; 242 243 // Set defaults if the options are empty. 244 $heading = ! empty( $heading ) ? $heading : __( 'Leaving Our Site', 'simple-exit-notifier' ); 245 $message = ! empty( $message ) ? $message : __( 'You are about to leave our website. Do you want to proceed?', 'simple-exit-notifier' ); 246 $proceed_text = ! empty( $proceed_text ) ? $proceed_text : __( 'Proceed', 'simple-exit-notifier' ); 247 $cancel_text = ! empty( $cancel_text ) ? $cancel_text : __( 'Cancel', 'simple-exit-notifier' ); 248 249 // Custom popup defaults. 250 $custom_popup_heading = ! empty( $custom_popup_heading ) ? $custom_popup_heading : __( 'Leaving Our Site', 'simple-exit-notifier' ); 251 $custom_popup_message = ! empty( $custom_popup_message ) ? $custom_popup_message : __( 'You are about to leave our website. Do you want to proceed?', 'simple-exit-notifier' ); 252 $custom_popup_proceed = ! empty( $custom_popup_proceed ) ? $custom_popup_proceed : __( 'Proceed', 'simple-exit-notifier' ); 253 $custom_popup_cancel = ! empty( $custom_popup_cancel ) ? $custom_popup_cancel : __( 'Cancel', 'simple-exit-notifier' ); 254 ?> 255 <style> 256 .simple-exit-notifier-overlay {background: rgba(0, 0, 0, <?php echo esc_attr( $overlay_opacity ); ?>);} 257 .simple-exit-notifier-content {background: <?php echo esc_attr( $body_bg_color ); ?>; color: <?php echo esc_attr( $body_text_color ); ?>; width: <?php echo esc_attr( $modal_width ); ?>px;} 258 .simple-exit-notifier-header {background: <?php echo esc_attr( $header_bg_color ); ?>; color: <?php echo esc_attr( $header_text_color ); ?>;} 259 .simple-exit-notifier-header h2 {color: <?php echo esc_attr( $header_text_color ); ?>;} 260 .simple-exit-notifier-message {color: <?php echo esc_attr( $body_text_color ); ?>;} 261 .simple-exit-notifier-modal .simple-exit-notifier-proceed {background: <?php echo esc_attr( $proceed_bg_color ); ?>; color: <?php echo esc_attr( $proceed_text_color ); ?>;} 262 .simple-exit-notifier-modal .simple-exit-notifier-cancel {background: <?php echo esc_attr( $cancel_bg_color ); ?>; color: <?php echo esc_attr( $cancel_text_color ); ?>;} 263 <?php if ( 'slide-up' === $animation_style ) : ?> 264 .simple-exit-notifier-modal.chrssen-animation-slide-up .simple-exit-notifier-content {transform: translate(-50%, 100%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease;} 265 .simple-exit-notifier-modal.chrssen-animation-slide-up.chrssen-modal-visible .simple-exit-notifier-content {transform: translate(-50%, -50%); opacity: 1;} 266 <?php elseif ( 'slide-down' === $animation_style ) : ?> 267 .simple-exit-notifier-modal.chrssen-animation-slide-down .simple-exit-notifier-content {transform: translate(-50%, -200%); opacity: 0; transition: transform 0.3s ease, opacity 0.3s ease;} 268 .simple-exit-notifier-modal.chrssen-animation-slide-down.chrssen-modal-visible .simple-exit-notifier-content {transform: translate(-50%, -50%); opacity: 1;} 269 <?php elseif ( 'fade' === $animation_style ) : ?> 270 .simple-exit-notifier-modal.chrssen-animation-fade .simple-exit-notifier-content {opacity: 0; transition: opacity 0.3s ease;} 271 .simple-exit-notifier-modal.chrssen-animation-fade.chrssen-modal-visible .simple-exit-notifier-content {opacity: 1;} 272 <?php endif; ?> 273 </style> 274 275 <!-- Default Popup Modal --> 276 <div id="simple-exit-notifier-modal" class="simple-exit-notifier-modal chrssen-animation-<?php echo esc_attr( $animation_style ); ?>" style="display: none;"> 277 <div class="simple-exit-notifier-overlay"></div> 278 <div class="simple-exit-notifier-content" role="dialog" aria-modal="true" aria-labelledby="simple-exit-notifier-title"> 279 <div class="simple-exit-notifier-header"> 280 <h2 id="simple-exit-notifier-title"><?php echo esc_html( $heading ); ?></h2> 281 </div> 282 <div class="simple-exit-notifier-message"> 283 <p><?php echo wp_kses_post( $message ); ?></p> 284 <?php if ( $display_external_link ) : ?> 285 <p class="simple-exit-notifier-external-link" style="text-align: center;"></p> 286 <?php endif; ?> 287 </div> 288 <?php if ( $remember_enabled ) : ?> 289 <div class="simple-exit-notifier-remember" style="padding: 0 20px 10px; text-align: center;"> 290 <label> 291 <input type="checkbox" class="simple-exit-notifier-remember-checkbox" /> 292 <?php esc_html_e( "Don't show this again", 'simple-exit-notifier' ); ?> 293 </label> 294 </div> 295 <?php endif; ?> 296 <div class="simple-exit-notifier-actions"> 297 <a class="simple-exit-notifier-proceed" href="#" target="_blank" rel="noopener noreferrer"><?php echo esc_html( $proceed_text ); ?></a> 298 <button class="simple-exit-notifier-cancel" type="button"><?php echo esc_html( $cancel_text ); ?></button> 299 </div> 300 </div> 301 </div> 302 303 <?php if ( $custom_popup_enabled && ! empty( $custom_popup_class ) ) : ?> 304 <!-- Custom Popup Modal --> 305 <div id="simple-exit-notifier-custom-modal" class="simple-exit-notifier-modal chrssen-animation-<?php echo esc_attr( $animation_style ); ?>" style="display: none;"> 306 <div class="simple-exit-notifier-overlay"></div> 307 <div class="simple-exit-notifier-content" role="dialog" aria-modal="true" aria-labelledby="simple-exit-notifier-custom-title"> 308 <div class="simple-exit-notifier-header"> 309 <h2 id="simple-exit-notifier-custom-title"><?php echo esc_html( $custom_popup_heading ); ?></h2> 310 </div> 311 <div class="simple-exit-notifier-message"> 312 <p><?php echo wp_kses_post( $custom_popup_message ); ?></p> 313 <?php if ( $display_external_link ) : ?> 314 <p class="simple-exit-notifier-external-link" style="text-align: center;"></p> 315 <?php endif; ?> 316 </div> 317 <?php if ( $remember_enabled ) : ?> 318 <div class="simple-exit-notifier-remember" style="padding: 0 20px 10px; text-align: center;"> 319 <label> 320 <input type="checkbox" class="simple-exit-notifier-remember-checkbox" /> 321 <?php esc_html_e( "Don't show this again", 'simple-exit-notifier' ); ?> 322 </label> 323 </div> 324 <?php endif; ?> 325 <div class="simple-exit-notifier-actions"> 326 <a class="simple-exit-notifier-proceed" href="#" target="_blank" rel="noopener noreferrer"><?php echo esc_html( $custom_popup_proceed ); ?></a> 327 <button class="simple-exit-notifier-cancel" type="button"><?php echo esc_html( $custom_popup_cancel ); ?></button> 328 </div> 329 </div> 330 </div> 331 <?php endif; ?> 332 <?php 333 } 70 334 } -
simple-exit-notifier/trunk/includes/class-simple-exit-notifier-init.php
r3209994 r3428053 1 1 <?php 2 /** 3 * Plugin initialization class. 4 * 5 * @package SimpleExitNotifier 6 */ 2 7 3 if ( !defined('ABSPATH')) {4 exit;8 if ( ! defined( 'ABSPATH' ) ) { 9 exit; 5 10 } 6 11 12 /** 13 * Class CHRSSEN_Init 14 * 15 * Handles plugin initialization and bootstrapping. 16 * 17 * @since 1.0.0 18 */ 7 19 class CHRSSEN_Init { 8 /**9 * Initialize the plugin.10 */11 public static function init() {12 self::load_dependencies();13 self::initialize_classes();14 }15 20 16 /** 17 * Load required dependencies. 18 */ 19 private static function load_dependencies() { 20 // Files already included in the main plugin file, no additional includes needed here. 21 } 21 /** 22 * Initialize the plugin. 23 * 24 * @since 1.0.0 25 * 26 * @return void 27 */ 28 public static function init(): void { 29 self::load_dependencies(); 30 self::initialize_classes(); 31 } 22 32 23 /** 24 * Initialize core classes. 25 */ 26 private static function initialize_classes() { 27 // Ensure classes are only initialized if they exist 28 if (class_exists('CHRSSEN_Admin')) { 29 new CHRSSEN_Admin(); 30 } 33 /** 34 * Load required dependencies. 35 * 36 * @since 1.0.0 37 * 38 * @return void 39 */ 40 private static function load_dependencies(): void { 41 // Files already included in the main plugin file, no additional includes needed here. 42 } 31 43 32 if (class_exists('CHRSSEN_Frontend')) { 33 new CHRSSEN_Frontend(); 34 } 35 } 44 /** 45 * Initialize core classes. 46 * 47 * @since 1.0.0 48 * 49 * @return void 50 */ 51 private static function initialize_classes(): void { 52 if ( class_exists( 'CHRSSEN_Admin' ) ) { 53 new CHRSSEN_Admin(); 54 } 55 56 if ( class_exists( 'CHRSSEN_Frontend' ) ) { 57 new CHRSSEN_Frontend(); 58 } 59 } 36 60 } -
simple-exit-notifier/trunk/readme.txt
r3213943 r3428053 4 4 Tags: exit popup, external links, link notifier, external link warning 5 5 Requires at least: 6.0.0 6 Tested up to: 6. 7.17 Requires PHP: 8. 18 Stable tag: 1.1.06 Tested up to: 6.9 7 Requires PHP: 8.4 8 Stable tag: 2.0.0 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 17 17 18 18 ### Features: 19 - Customizable popup heading, message, and button texts. 20 - Exclude specific links by adding an exception CSS class. 21 - Compatible with both `http` and `https` links. 22 - Works with links that open in the same or a new tab. 23 - Preview popup in admin panel 24 - Lightweight and easy to configure via the WordPress admin panel. 19 - **Enable/Disable Toggle**: Easily turn the notifier on or off globally. 20 - **Customizable Popup**: Change heading, message, and button texts. 21 - **Full Color Customization**: 8 color pickers for header, body, and button colors - perfect for matching your site's theme or dark mode. 22 - **Overlay Opacity**: Adjust the background overlay darkness. 23 - **Modal Width**: Set custom width for the popup. 24 - **Animation Styles**: Choose from fade, slide up, slide down, or none. 25 - **Custom CSS**: Add your own CSS for advanced styling. 26 - **Delay Timer**: Optional countdown before users can proceed. 27 - **Remember User Choice**: Allow users to skip the popup for future visits. 28 - **External Link Icon**: Display an icon next to external links. 29 - **Multiple Exception Classes**: Exclude links using multiple CSS classes. 30 - **Domain Whitelist**: Specify trusted domains that won't trigger the popup. 31 - **Page Exclusions**: Disable the popup on specific pages. 32 - **Keyboard Accessible**: Escape to close, Tab to navigate, focus trapping. 33 - **Click Overlay to Close**: Users can click outside the modal to dismiss. 34 - **Clean Uninstall**: Removes all plugin data when deleted. 35 - **Lightweight and Fast**: Minimal impact on page load times. 25 36 26 37 ### Use Cases: 27 38 - Warn users before they leave your site. 28 39 - Highlight external links with customizable notifications. 29 - Add exception rules for specific links using a CSS class. 40 - Comply with regulations requiring external link warnings. 41 - Match popup appearance to your site's dark mode theme. 30 42 31 43 == Support == … … 47 59 == Frequently Asked Questions == 48 60 49 = How do I customize the popup? = 50 Go to `Settings > Exit Notifier`. You can customize the popup heading, message, and button text directly from the admin panel. 61 = How do I enable or disable the popup? = 62 Go to `Settings > Exit Notifier` and use the "Enable Exit Notifier" toggle in the Content 1 tab. 63 64 = How do I customize the popup appearance? = 65 Go to `Settings > Exit Notifier` and click the "Appearance" tab. You can customize all colors, opacity, modal width, animation style, and add custom CSS. 66 67 = How do I match the popup to my dark mode theme? = 68 Use the color pickers in the Appearance tab to set dark background colors and light text colors for both the header and body of the popup. 69 70 = Can I show different popups for different links? = 71 Yes! Use the Content 1 and Content 2 tabs to create two different popups. Add a CSS class to specific links and set the corresponding "Trigger Class" to control which popup appears. 72 73 = How do I show the popup only for specific links? = 74 In the Content 1 tab, enter a CSS class in the "Trigger Class" field. The popup will only appear for external links with that class. Leave empty to show for all external links. 75 76 = How do I add a delay before showing the popup? = 77 Enable "Delay Timer" in the Behavior tab and set the number of seconds to wait before the popup appears. 51 78 52 79 = How do I exclude certain external links from showing the popup? = 53 In the settings, define an exception CSS class (default is `noexit`). Add this class to any link you want to exclude. 80 In the Exclusions tab, enter CSS class names (one per line or comma-separated). Add any of these classes to links you want to exclude. Default: `noexit` 81 82 = How do I whitelist certain domains? = 83 In the Exclusions tab, enter domain names (one per line) that should not trigger the popup. Example: `facebook.com` 84 85 = How do I exclude specific pages from showing the popup? = 86 In the Exclusions tab, use the "Excluded Pages" dropdown to select pages where the popup should not appear. 54 87 55 88 = Does the plugin work with both `http` and `https` links? = … … 59 92 No, the popup is triggered only for external links. 60 93 94 = Can users opt to not see the popup again? = 95 Yes! Enable "Remember User Choice" in the Behavior tab. Users will see a "Don't show again" checkbox. 96 61 97 = Does the plugin support translations? = 62 98 Yes, the plugin is translation-ready. Use `.pot` files to add translations. … … 64 100 == Screenshots == 65 101 66 1. **Admin Settings Panel**: Customize popup texts and configure settings. 67 2. **Popup Example**: A notification alert when clicking an external link. 102 1. **Content 1 Tab**: Default popup settings with enable/disable toggle, trigger class, and customizable texts. 103 2. **Content 2 Tab**: Custom popup settings for targeted links with separate content options. 104 3. **Appearance Tab**: Color pickers for header, body, and buttons, plus overlay and animation settings. 105 4. **Behavior Tab**: Delay timer, remember choice, and external link icon settings. 106 5. **Exclusions Tab**: Exception classes, whitelisted domains, and page exclusions. 68 107 69 108 == Changelog == 109 110 = 2.0.0 = 111 * Major Feature Update: Complete redesign of admin interface with tabbed navigation. 112 * Added: Global enable/disable toggle for the exit notifier. 113 * Added: 8 color pickers for full popup customization (header, body, and button colors). 114 * Added: Overlay opacity slider. 115 * Added: Modal width setting. 116 * Added: Animation style options (fade, slide up, slide down, none). 117 * Added: Custom CSS field for advanced styling. 118 * Added: Delay timer with countdown before users can proceed. 119 * Added: "Remember User Choice" feature with configurable duration. 120 * Added: External link icon indicator with position options. 121 * Added: Support for multiple exception CSS classes. 122 * Added: Domain whitelist to skip popup for trusted external domains. 123 * Added: Page exclusions to disable popup on specific pages. 124 * Added: Live preview panel in admin settings. 125 * Improved: Backward compatibility with existing settings from version 1.x. 126 * Improved: Better organized settings in logical tabs. 127 128 = 1.2.0 = 129 * Updated: WordPress 6.9 compatibility. 130 * Updated: PHP 8.4 compatibility. 131 * Updated: Added type hints to all function parameters and return types. 132 * Updated: Added proper DocBlocks to all classes and methods. 133 * Updated: Applied WordPress Coding Standards (WPCS) throughout. 134 * Updated: Maintained backward compatibility with existing settings. 135 * Updated: Improved checkbox sanitization using absint(). 136 * Updated: Improved external link detection using URL API. 137 * Enhancement: Added ARIA attributes for improved accessibility. 138 * Enhancement: Added rel="noopener noreferrer" to external links for security. 139 * Enhancement: Added explicit button type attributes. 140 * Enhancement: Added keyboard accessibility (Escape to close, focus trapping). 141 * Enhancement: Added click-to-close on overlay. 142 * Enhancement: Added focus management (returns focus after modal closes). 143 * Enhancement: Added body scroll lock when modal is open. 144 * Enhancement: Added visible focus states for keyboard navigation. 145 * Enhancement: Added hover states for buttons. 146 * Added: Uninstall script to clean up options on plugin deletion. 70 147 71 148 = 1.1.0 = … … 86 163 == Upgrade Notice == 87 164 165 = 2.0.0 = 166 IMPORTANT: Please backup your site before updating. Major feature update with full color customization, tabbed admin interface, delay timer, remember choice, domain whitelist, and more. All existing settings are preserved. 167 168 = 1.2.0 = 169 Code modernization update with PHP 8.4 and WordPress 6.9 compatibility. Improved accessibility and security. 170 88 171 = 1.0.0 = 89 172 Initial release. Upgrade to stay up-to-date with future improvements. -
simple-exit-notifier/trunk/simple-exit-notifier.php
r3213943 r3428053 1 1 <?php 2 /* 3 Plugin Name: Simple Exit Notifier 4 Plugin URI: https://wordpress.org/plugins/simple-exit-notifier/ 5 Description: Display a notification when a user clicks on an external link. 6 Version: 1.1.0 7 Requires at least: 6.0.0 8 Tested up to: 6.7.1 9 Author: CHRS Interactive 10 Author URI: https://www.chrsinteractive.com/ 11 Text Domain: simple-exit-notifier 12 License: GPLv2 or later 13 */ 2 /** 3 * Plugin Name: Simple Exit Notifier 4 * Plugin URI: https://wordpress.org/plugins/simple-exit-notifier/ 5 * Description: Display a notification when a user clicks on an external link. 6 * Version: 2.0.0 7 * Requires at least: 6.0.0 8 * Tested up to: 6.9 9 * Requires PHP: 8.4 10 * Author: CHRS Interactive 11 * Author URI: https://www.chrsinteractive.com/ 12 * Text Domain: simple-exit-notifier 13 * License: GPLv2 or later 14 * 15 * @package SimpleExitNotifier 16 */ 14 17 15 if ( !defined('ABSPATH')) {16 exit; // Exit if accessed directly 18 if ( ! defined( 'ABSPATH' ) ) { 19 exit; 17 20 } 18 21 19 // Define constants 20 define( 'CHRSSEN_PLUGIN_URL', plugin_dir_url(__FILE__));21 define( 'CHRSSEN_PLUGIN_PATH', plugin_dir_path(__FILE__));22 define( 'CHRSSEN_VERSION', '1.1.0');22 // Define constants. 23 define( 'CHRSSEN_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 24 define( 'CHRSSEN_PLUGIN_PATH', plugin_dir_path( __FILE__ ) ); 25 define( 'CHRSSEN_VERSION', '2.0.0' ); 23 26 24 // Include necessary files 27 // Include necessary files. 25 28 require_once CHRSSEN_PLUGIN_PATH . 'includes/class-simple-exit-notifier-init.php'; 26 29 require_once CHRSSEN_PLUGIN_PATH . 'includes/class-simple-exit-notifier-admin.php'; 27 30 require_once CHRSSEN_PLUGIN_PATH . 'includes/class-simple-exit-notifier-frontend.php'; 28 31 29 // Initialize the plugin 30 add_action( 'plugins_loaded', ['CHRSSEN_Init', 'init']);32 // Initialize the plugin. 33 add_action( 'plugins_loaded', [ 'CHRSSEN_Init', 'init' ] );
Note: See TracChangeset
for help on using the changeset viewer.