Plugin Directory

Changeset 3428053


Ignore:
Timestamp:
12/27/2025 02:13:54 AM (8 weeks ago)
Author:
chrsinteractive
Message:

Release version 2.0.0 - Dual popup system, color customization, tabbed admin interface

Location:
simple-exit-notifier
Files:
22 added
1 deleted
11 edited

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;}
    323}
     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 */
     53input[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 */
    107
    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;}
    1910
    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;}
    3013
    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;}
    3916
    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;}
    4320
    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;}
    4832.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;}
    6240
    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 */
     48body.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');
    47220        } 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">&#x2197;</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  
    11<?php
    2 
    3 if (!defined('ABSPATH')) {
    4     exit;
     2/**
     3 * Admin settings class.
     4 *
     5 * @package SimpleExitNotifier
     6 */
     7
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
    510}
    611
     12/**
     13 * Class CHRSSEN_Admin
     14 *
     15 * Handles admin settings page and plugin configuration.
     16 *
     17 * @since 1.0.0
     18 */
    719class CHRSSEN_Admin {
    820
    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&#10;twitter.com&#10;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    }
    154913}
  • simple-exit-notifier/trunk/includes/class-simple-exit-notifier-frontend.php

    r3213943 r3428053  
    11<?php
    2 
    3 if (!defined('ABSPATH')) {
    4     exit;
     2/**
     3 * Frontend functionality class.
     4 *
     5 * @package SimpleExitNotifier
     6 */
     7
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
    510}
    611
     12/**
     13 * Class CHRSSEN_Frontend
     14 *
     15 * Handles frontend modal display and script loading.
     16 *
     17 * @since 1.0.0
     18 */
    719class CHRSSEN_Frontend {
    820
    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    }
    70334}
  • simple-exit-notifier/trunk/includes/class-simple-exit-notifier-init.php

    r3209994 r3428053  
    11<?php
     2/**
     3 * Plugin initialization class.
     4 *
     5 * @package SimpleExitNotifier
     6 */
    27
    3 if (!defined('ABSPATH')) {
    4     exit;
     8if ( ! defined( 'ABSPATH' ) ) {
     9    exit;
    510}
    611
     12/**
     13 * Class CHRSSEN_Init
     14 *
     15 * Handles plugin initialization and bootstrapping.
     16 *
     17 * @since 1.0.0
     18 */
    719class CHRSSEN_Init {
    8     /**
    9      * Initialize the plugin.
    10      */
    11     public static function init() {
    12         self::load_dependencies();
    13         self::initialize_classes();
    14     }
    1520
    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    }
    2232
    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    }
    3143
    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    }
    3660}
  • simple-exit-notifier/trunk/readme.txt

    r3213943 r3428053  
    44Tags: exit popup, external links, link notifier, external link warning
    55Requires at least: 6.0.0
    6 Tested up to: 6.7.1
    7 Requires PHP: 8.1
    8 Stable tag: 1.1.0
     6Tested up to: 6.9
     7Requires PHP: 8.4
     8Stable tag: 2.0.0
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    1717
    1818### 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.
    2536
    2637### Use Cases:
    2738- Warn users before they leave your site.
    2839- 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.
    3042
    3143== Support ==
     
    4759== Frequently Asked Questions ==
    4860
    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? =
     62Go 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? =
     65Go 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? =
     68Use 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? =
     71Yes! 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? =
     74In 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? =
     77Enable "Delay Timer" in the Behavior tab and set the number of seconds to wait before the popup appears.
    5178
    5279= 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.
     80In 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? =
     83In 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? =
     86In the Exclusions tab, use the "Excluded Pages" dropdown to select pages where the popup should not appear.
    5487
    5588= Does the plugin work with both `http` and `https` links? =
     
    5992No, the popup is triggered only for external links.
    6093
     94= Can users opt to not see the popup again? =
     95Yes! Enable "Remember User Choice" in the Behavior tab. Users will see a "Don't show again" checkbox.
     96
    6197= Does the plugin support translations? =
    6298Yes, the plugin is translation-ready. Use `.pot` files to add translations.
     
    64100== Screenshots ==
    65101
    66 1. **Admin Settings Panel**: Customize popup texts and configure settings.
    67 2. **Popup Example**: A notification alert when clicking an external link.
     1021. **Content 1 Tab**: Default popup settings with enable/disable toggle, trigger class, and customizable texts.
     1032. **Content 2 Tab**: Custom popup settings for targeted links with separate content options.
     1043. **Appearance Tab**: Color pickers for header, body, and buttons, plus overlay and animation settings.
     1054. **Behavior Tab**: Delay timer, remember choice, and external link icon settings.
     1065. **Exclusions Tab**: Exception classes, whitelisted domains, and page exclusions.
    68107
    69108== 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.
    70147
    71148= 1.1.0 =
     
    86163== Upgrade Notice ==
    87164
     165= 2.0.0 =
     166IMPORTANT: 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 =
     169Code modernization update with PHP 8.4 and WordPress 6.9 compatibility. Improved accessibility and security.
     170
    88171= 1.0.0 =
    89172Initial release. Upgrade to stay up-to-date with future improvements.
  • simple-exit-notifier/trunk/simple-exit-notifier.php

    r3213943 r3428053  
    11<?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 */
    1417
    15 if (!defined('ABSPATH')) {
    16     exit; // Exit if accessed directly
     18if ( ! defined( 'ABSPATH' ) ) {
     19    exit;
    1720}
    1821
    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.
     23define( 'CHRSSEN_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
     24define( 'CHRSSEN_PLUGIN_PATH', plugin_dir_path( __FILE__ ) );
     25define( 'CHRSSEN_VERSION', '2.0.0' );
    2326
    24 // Include necessary files
     27// Include necessary files.
    2528require_once CHRSSEN_PLUGIN_PATH . 'includes/class-simple-exit-notifier-init.php';
    2629require_once CHRSSEN_PLUGIN_PATH . 'includes/class-simple-exit-notifier-admin.php';
    2730require_once CHRSSEN_PLUGIN_PATH . 'includes/class-simple-exit-notifier-frontend.php';
    2831
    29 // Initialize the plugin
    30 add_action('plugins_loaded', ['CHRSSEN_Init', 'init']);
     32// Initialize the plugin.
     33add_action( 'plugins_loaded', [ 'CHRSSEN_Init', 'init' ] );
Note: See TracChangeset for help on using the changeset viewer.