Plugin Directory

Changeset 3411134


Ignore:
Timestamp:
12/04/2025 03:28:49 PM (3 months ago)
Author:
madnesscode1
Message:

chore: release 1.0.7 - GDPR modal styling and bug fixes

  • Added customizable styling options for GDPR modal
  • Fixed undefined array key warnings
  • SEO optimization: changed tag widget to floating
Location:
madnesschat-button/tags/1.0.2
Files:
1 added
5 edited

Legend:

Unmodified
Added
Removed
  • madnesschat-button/tags/1.0.2/assets/js/mcnb-frontend-simple.js

    r3403447 r3411134  
    1 (function(){
    2     function onReady(fn){
    3         if(document.readyState!=='loading'){
    4             fn();
    5         } else {
    6             document.addEventListener('DOMContentLoaded', fn);
    7         }
    8     }
    9 
    10     function createSvgIcon(iconType){
    11         var svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
    12         svg.setAttribute('viewBox','0 0 24 24');
    13         svg.setAttribute('width','1em');
    14         svg.setAttribute('height','1em');
    15         svg.className = 'mcnb-icon';
    16         svg.setAttribute('aria-hidden','true');
    17         svg.setAttribute('focusable','false');
    18         svg.style.display = 'inline-block';
    19         svg.style.fill = 'currentColor';
    20         svg.style.verticalAlign = 'middle';
    21        
    22         var path = document.createElementNS('http://www.w3.org/2000/svg','path');
    23         path.setAttribute('fill','currentColor');
    24        
    25         // Diferentes íconos según el tipo
    26         switch(iconType) {
    27             case 'whatsapp':
    28                 path.setAttribute('d','M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.890-5.335 11.893-11.893A11.821 11.821 0 0020.051 3.488');
    29                 break;
    30             case 'phone':
    31                 path.setAttribute('d','M20.01 15.38c-1.23 0-2.42-.2-3.53-.56-.35-.12-.74-.03-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z');
    32                 break;
    33             case 'chat':
    34                 path.setAttribute('d','M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z');
    35                 break;
    36             case 'message':
    37                 path.setAttribute('d','M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10h5v-2h-5c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8v1.43c0 .79-.71 1.57-1.5 1.57s-1.5-.78-1.5-1.57V12c0-2.76-2.24-5-5-5s-5 2.24-5 5 2.24 5 5 5c1.38 0 2.64-.56 3.54-1.47.65.89 1.77 1.47 2.96 1.47 1.97 0 3.5-1.53 3.5-3.5V12c0-5.52-4.48-10-10-10zm0 13c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z');
    38                 break;
    39             default:
    40                 // Fallback al ícono de WhatsApp
    41                 path.setAttribute('d','M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.890-5.335 11.893-11.893A11.821 11.821 0 0020.051 3.488');
    42         }
    43        
    44         svg.appendChild(path);
    45         return svg;
    46     }
    47    
     1(function () {
     2    function onReady(fn) {
     3        if (document.readyState !== 'loading') {
     4            fn();
     5        } else {
     6            document.addEventListener('DOMContentLoaded', fn);
     7        }
     8    }
     9
     10    function createSvgIcon(iconType) {
     11        try {
     12            // Check for SVG support
     13            if (!document.createElementNS) {
     14                return createFallbackIcon();
     15            }
     16
     17            var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
     18            if (!svg) {
     19                return createFallbackIcon();
     20            }
     21
     22            svg.setAttribute('viewBox', '0 0 24 24');
     23            svg.setAttribute('width', '1em');
     24            svg.setAttribute('height', '1em');
     25            svg.className = 'mcnb-icon';
     26            svg.setAttribute('aria-hidden', 'true');
     27            svg.setAttribute('focusable', 'false');
     28            svg.style.display = 'inline-block';
     29            svg.style.fill = 'currentColor';
     30            svg.style.verticalAlign = 'middle';
     31
     32            var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
     33            if (!path) {
     34                return createFallbackIcon();
     35            }
     36
     37            path.setAttribute('fill', 'currentColor');
     38
     39            // Diferentes íconos según el tipo
     40            switch (iconType) {
     41                case 'whatsapp':
     42                    path.setAttribute('d', 'M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.890-5.335 11.893-11.893A11.821 11.821 0 0020.051 3.488');
     43                    break;
     44                case 'phone':
     45                    path.setAttribute('d', 'M20.01 15.38c-1.23 0-2.42-.2-3.53-.56-.35-.12-.74-.03-1.01.24l-1.57 1.97c-2.83-1.35-5.48-3.9-6.89-6.83l1.95-1.66c.27-.28.35-.67.24-1.02-.37-1.11-.56-2.3-.56-3.53 0-.54-.45-.99-.99-.99H4.19C3.65 3 3 3.24 3 3.99 3 13.28 10.73 21 20.01 21c.71 0 .99-.63.99-1.18v-3.45c0-.54-.45-.99-.99-.99z');
     46                    break;
     47                case 'chat':
     48                    path.setAttribute('d', 'M20 2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4l4 4 4-4h4c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z');
     49                    break;
     50                case 'message':
     51                    path.setAttribute('d', 'M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10h5v-2h-5c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8v1.43c0 .79-.71 1.57-1.5 1.57s-1.5-.78-1.5-1.57V12c0-2.76-2.24-5-5-5s-5 2.24-5 5 2.24 5 5 5c1.38 0 2.64-.56 3.54-1.47.65.89 1.77 1.47 2.96 1.47 1.97 0 3.5-1.53 3.5-3.5V12c0-5.52-4.48-10-10-10zm0 13c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3z');
     52                    break;
     53                default:
     54                    // Fallback al ícono de WhatsApp
     55                    path.setAttribute('d', 'M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.890-5.335 11.893-11.893A11.821 11.821 0 0020.051 3.488');
     56            }
     57
     58            svg.appendChild(path);
     59            return svg;
     60        } catch (e) {
     61            // If any error occurs during SVG creation, return fallback
     62            return createFallbackIcon();
     63        }
     64    }
     65
    4866    function createCustomIcon(customIcon) {
    49         if (customIcon.match(/\.(svg|png|jpg|jpeg|gif)$/i)) {
    50             // Es una URL de imagen
    51             var img = document.createElement('img');
    52             img.src = customIcon;
    53             img.className = 'mcnb-icon';
    54             img.style.width = '1em';
    55             img.style.height = '1em';
    56             img.style.display = 'inline-block';
    57             img.style.verticalAlign = 'middle';
    58             return img;
    59         } else {
    60             // Es código HTML personalizado
    61             var wrapper = document.createElement('span');
    62             wrapper.className = 'mcnb-icon';
    63             wrapper.innerHTML = customIcon;
    64             wrapper.style.display = 'inline-block';
    65             wrapper.style.fontSize = '1em';
    66             wrapper.style.lineHeight = '1';
    67             return wrapper;
    68         }
    69     }
    70    
    71     function createFallbackIcon(){
     67        try {
     68            if (!customIcon) {
     69                return createFallbackIcon();
     70            }
     71
     72            if (customIcon.match(/\.(svg|png|jpg|jpeg|gif)$/i)) {
     73                // Es una URL de imagen
     74                var img = document.createElement('img');
     75                img.src = customIcon;
     76                img.className = 'mcnb-icon';
     77                img.alt = 'WhatsApp';
     78                img.style.width = '1em';
     79                img.style.height = '1em';
     80                img.style.display = 'inline-block';
     81                img.style.verticalAlign = 'middle';
     82
     83                // Handle image load error
     84                img.onerror = function () {
     85                    var fallback = createFallbackIcon();
     86                    if (img.parentNode) {
     87                        img.parentNode.replaceChild(fallback, img);
     88                    }
     89                };
     90
     91                return img;
     92            } else {
     93                // Es código HTML personalizado
     94                var wrapper = document.createElement('span');
     95                wrapper.className = 'mcnb-icon';
     96                wrapper.innerHTML = customIcon;
     97                wrapper.style.display = 'inline-block';
     98                wrapper.style.fontSize = '1em';
     99                wrapper.style.lineHeight = '1';
     100                return wrapper;
     101            }
     102        } catch (e) {
     103            return createFallbackIcon();
     104        }
     105    }
     106
     107    function createFallbackIcon() {
    72108        // Ícono de fallback usando texto
    73109        var icon = document.createElement('span');
     
    81117
    82118
    83     function trackClick(){
    84         if(!window.mcnbData){ return; }
    85        
     119    function trackClick() {
     120        if (!window.mcnbData) { return; }
     121
    86122        // Verificar consentimiento GDPR antes de hacer tracking (Directriz 7)
    87123        // Solo hacer tracking si:
     
    89125        // 2. (GDPR está deshabilitado O se ha dado consentimiento)
    90126        var cfg = window.mcnbData;
    91         if(cfg.analyticsEnabled){
     127        if (cfg.analyticsEnabled) {
    92128            // Si GDPR está habilitado, verificar consentimiento
    93             if(cfg.gdprEnabled && !hasGDPRConsent()){
     129            if (cfg.gdprEnabled && !hasGDPRConsent()) {
    94130                // No hacer tracking sin consentimiento
    95131                return;
     
    99135            return;
    100136        }
    101        
     137
    102138        var trackingData = {
    103139            action: 'mcnb_track_click',
     
    109145
    110146        var ajaxUrl = getAjaxUrl();
    111         if(!ajaxUrl){
     147        if (!ajaxUrl) {
    112148            // No se puede hacer tracking sin URL de Ajax válida
    113149            return;
     
    115151
    116152        // Usar jQuery AJAX si está disponible (de WordPress), sino fetch
    117         if(window.jQuery && window.jQuery.ajax){
     153        if (window.jQuery && window.jQuery.ajax) {
    118154            window.jQuery.ajax({
    119155                url: ajaxUrl,
    120156                method: 'POST',
    121157                data: trackingData,
    122                 success: function(response){
     158                success: function (response) {
    123159                    // Click tracked successfully
    124160                },
    125                 error: function(xhr, status, error){
     161                error: function (xhr, status, error) {
    126162                    // Failed to track click
    127163                }
    128164            });
    129         } else if(window.fetch){
     165        } else if (window.fetch) {
    130166            fetch(ajaxUrl, {
    131167                method: 'POST',
     
    134170                },
    135171                body: new URLSearchParams(trackingData)
    136             }).then(function(response){
     172            }).then(function (response) {
    137173                return response.json();
    138             }).then(function(data){
     174            }).then(function (data) {
    139175                // Click tracked successfully
    140             }).catch(function(error){
     176            }).catch(function (error) {
    141177                // Failed to track click
    142178            });
     
    144180    }
    145181
    146     function getAjaxUrl(){
    147         if(window.mcnbData && window.mcnbData.ajaxUrl){ return window.mcnbData.ajaxUrl; }
    148         if(window.ajaxurl){ return window.ajaxurl; }
     182    function getAjaxUrl() {
     183        if (window.mcnbData && window.mcnbData.ajaxUrl) { return window.mcnbData.ajaxUrl; }
     184        if (window.ajaxurl) { return window.ajaxurl; }
    149185        // No usar fallback estático - debe ser proporcionado desde PHP via wp_localize_scri
    150186        return null;
     
    153189    function getCurrentDeviceConfig() {
    154190        var cfg = window.mcnbData;
    155        
     191
    156192        // Si responsive no está habilitado, usar configuración básica
    157193        if (!cfg.responsiveEnabled || !cfg.responsiveConfig) {
     
    164200            };
    165201        }
    166        
     202
    167203        // Determinar dispositivo actual según breakpoints
    168204        var screenWidth = window.innerWidth;
    169205        var deviceType = 'desktop'; // Por defecto
    170        
     206
    171207        if (screenWidth <= 425) {
    172208            deviceType = 'mobile';
     
    178214            deviceType = 'desktop';
    179215        }
    180        
     216
    181217        // Dispositivo detectado según breakpoint
    182        
     218
    183219        // Obtener configuración para el dispositivo
    184220        var deviceConfig = cfg.responsiveConfig[deviceType];
     
    195231            };
    196232        }
    197        
     233
    198234        return {
    199235            size: parseInt(deviceConfig.size || '56', 10),
     
    207243    }
    208244
    209     onReady(function(){
    210         if(!window.mcnbData){
    211             return; 
    212         }
    213        
     245    onReady(function () {
     246        if (!window.mcnbData) {
     247            return;
     248        }
     249
    214250        var cfg = window.mcnbData;
    215        
     251
    216252        // A/B Testing no disponible en versión gratuita - siempre usar configuración original
    217253        function getABVariant() {
     
    228264        }
    229265
    230         function mount(){
     266        function mount() {
    231267            var root = document.getElementById('mcnb-root');
    232             if(!root){
    233                 return; 
    234             }
    235            
     268            if (!root) {
     269                return;
     270            }
     271
    236272            // Limpiar contenido previo
    237273            root.innerHTML = '';
    238            
     274
    239275            // A/B Testing no disponible en versión gratuita - usar configuración original
    240276            var abVariant = getABVariant(); // Siempre 'A'
    241277            var variantConfig = getVariantConfig(abVariant); // Siempre configuración original
    242            
     278
    243279            // Obtener configuración según responsive
    244280            var currentConfig = getCurrentDeviceConfig();
    245            
     281
    246282            // Posicionamiento
    247283            root.style.position = 'fixed';
    248284            root.style.zIndex = '999999';
    249            
     285
    250286            // Aplicar posicionamiento según la variante A/B
    251287            if (currentConfig.position === 'bottom-right') {
     
    269305
    270306            var btn = document.createElement('a');
    271             btn.href = cfg.link || 'https://wa.me/'; 
    272             btn.target = '_blank'; 
     307            btn.href = cfg.link || 'https://wa.me/';
     308            btn.target = '_blank';
    273309            btn.rel = 'noopener noreferrer';
    274310            btn.className = 'mcnb-button';
    275            
     311
    276312            // Aplicar estilos básicos
    277313            var size = currentConfig.size;
    278             btn.style.width = size + 'px'; 
    279             btn.style.height = size + 'px'; 
    280             btn.style.background = currentConfig.brandColor || '#25D366'; 
     314            btn.style.width = size + 'px';
     315            btn.style.height = size + 'px';
     316            btn.style.background = currentConfig.brandColor || '#25D366';
    281317            btn.style.color = cfg.textColor || '#ffffff';
    282318            btn.style.borderRadius = '50%';
     
    287323            btn.style.fontSize = Math.round(size * 0.6) + 'px';
    288324            btn.style.transition = 'all 0.15s ease';
    289            
     325
    290326            // Aplicar estilo de botón si está configurado
    291             if(cfg.buttonStyle === 'square'){
     327            if (cfg.buttonStyle === 'square') {
    292328                btn.style.borderRadius = '8px';
    293             } else if(cfg.buttonStyle === 'pill'){
     329            } else if (cfg.buttonStyle === 'pill') {
    294330                btn.style.borderRadius = '999px';
    295331                btn.style.padding = '0 16px';
    296             } else if(cfg.buttonStyle === 'text_side'){
     332            } else if (cfg.buttonStyle === 'text_side') {
    297333                btn.style.borderRadius = '999px';
    298334                btn.style.padding = '12px 20px';
    299335                btn.style.gap = '8px';
    300336            }
    301            
     337
    302338            // Aplicar sombra si está habilitada (más visible por defecto)
    303             if(cfg.shadowEnabled){
     339            if (cfg.shadowEnabled) {
    304340                // Sombra más visible y pronunciada por defecto
    305341                var shadowValue = cfg.shadowColor || 'rgba(0,0,0,0.3)';
     
    309345                btn.style.boxShadow = 'none';
    310346            }
    311            
     347
    312348            // Aplicar borde si está habilitado
    313             if(cfg.borderEnabled){
     349            if (cfg.borderEnabled) {
    314350                btn.style.border = (cfg.borderWidth || 2) + 'px solid ' + (cfg.borderColor || '#ffffff');
    315351            }
    316            
     352
    317353            // Aplicar animación
    318             if(cfg.animation && cfg.animation !== 'none'){
     354            if (cfg.animation && cfg.animation !== 'none') {
    319355                btn.style.animation = 'mcnb-' + cfg.animation + ' 2s infinite';
    320                 if(cfg.animationDelay){
     356                if (cfg.animationDelay) {
    321357                    btn.style.animationDelay = cfg.animationDelay + 'ms';
    322358                }
    323359            }
    324            
     360
    325361            // Aplicar efecto hover usando clases CSS (más eficiente y soporta todos los efectos)
    326             if(cfg.hoverEffect && cfg.hoverEffect !== 'none'){
     362            if (cfg.hoverEffect && cfg.hoverEffect !== 'none') {
    327363                btn.classList.add('hover-' + cfg.hoverEffect);
    328364            }
    329365
    330         // Agregar ícono según configuración
    331         try {
     366            // Agregar ícono según configuración
     367            try {
    332368                var icon;
    333369                if (cfg.iconType === 'custom' && cfg.customIcon) {
     
    336372                    icon = createSvgIcon(cfg.iconType || 'chat');
    337373                }
    338                
     374
    339375                if (icon && (icon.appendChild || icon.tagName)) {
    340376                    btn.appendChild(icon);
     
    342378                    throw new Error('Icon creation failed');
    343379                }
    344             } catch(e) {
     380            } catch (e) {
    345381                btn.appendChild(createFallbackIcon());
    346382            }
    347383
    348384            // Añadir tracking de clicks
    349             btn.addEventListener('click', function(e) {
     385            btn.addEventListener('click', function (e) {
    350386                e.preventDefault();
    351                
     387
    352388                // Verificar GDPR si está habilitado
    353389                if (cfg.gdprEnabled && !hasGDPRConsent()) {
     
    355391                    return;
    356392                }
    357                
    358             // Si ya tiene consentimiento o GDPR está deshabilitado, continuar
    359            
    360             // Tracking interno
    361             trackClick();
    362                
     393
     394                // Si ya tiene consentimiento o GDPR está deshabilitado, continuar
     395
     396                // Tracking interno
     397                trackClick();
     398
    363399                // Abrir WhatsApp
    364400                openWhatsApp();
    365401            });
    366            
     402
    367403            // Si es estilo text_side, agregar texto
    368             if(cfg.buttonStyle === 'text_side' && cfg.labelText){
     404            if (cfg.buttonStyle === 'text_side' && cfg.labelText) {
    369405                var textSpan = document.createElement('span');
    370406                textSpan.textContent = cfg.labelText;
     
    375411            // Etiqueta externa (solo si no es text_side y está habilitada para este dispositivo)
    376412            var label = null;
    377             if(currentConfig.showLabel && cfg.buttonStyle !== 'text_side' && cfg.labelText){
     413            if (currentConfig.showLabel && cfg.buttonStyle !== 'text_side' && cfg.labelText) {
    378414                label = document.createElement('span');
    379415                label.className = 'mcnb-label';
    380416                label.textContent = cfg.labelText || 'Chat';
    381                 label.style.background = cfg.brandColor || '#25D366'; 
     417                label.style.background = cfg.brandColor || '#25D366';
    382418                label.style.color = cfg.textColor || '#ffffff';
    383419                label.style.padding = '10px 14px';
    384420                label.style.borderRadius = '999px';
    385421                label.style.whiteSpace = 'nowrap';
    386                
     422
    387423                // Aplicar posicionamiento según labelPosition
    388424                var labelPosition = cfg.labelPosition || 'left';
    389                 switch(labelPosition) {
     425                switch (labelPosition) {
    390426                    case 'left':
    391427                        label.style.marginRight = '10px';
     
    402438                }
    403439            }
    404            
     440
    405441            // Configurar wrapper y añadir elementos en el orden correcto
    406442            wrapper.style.display = 'flex';
    407443            wrapper.style.alignItems = 'center';
    408            
    409             if(label) {
     444
     445            if (label) {
    410446                var labelPosition = cfg.labelPosition || 'left';
    411                
    412                 if(labelPosition === 'left') {
     447
     448                if (labelPosition === 'left') {
    413449                    wrapper.style.flexDirection = 'row';
    414450                    wrapper.appendChild(label);
    415451                    wrapper.appendChild(btn);
    416                 } else if(labelPosition === 'right') {
     452                } else if (labelPosition === 'right') {
    417453                    wrapper.style.flexDirection = 'row';
    418454                    wrapper.appendChild(btn);
    419455                    wrapper.appendChild(label);
    420                 } else if(labelPosition === 'top') {
     456                } else if (labelPosition === 'top') {
    421457                    wrapper.style.flexDirection = 'column';
    422458                    wrapper.appendChild(label);
    423459                    wrapper.appendChild(btn);
    424                 } else if(labelPosition === 'bottom') {
     460                } else if (labelPosition === 'bottom') {
    425461                    wrapper.style.flexDirection = 'column';
    426462                    wrapper.appendChild(btn);
     
    447483        function checkAndMount() {
    448484            if (buttonMounted) return;
    449            
     485
    450486            // Verificar si todas las condiciones necesarias están cumplidas
    451487            var delayReady = (cfg.delayMs || 0) === 0 || triggers.delay;
    452488            var scrollReady = (cfg.scrollPercent || 0) === 0 || triggers.scroll;
    453489            var timeReady = (cfg.timeOnPage || 0) === 0 || triggers.time;
    454            
     490
    455491            if (delayReady && scrollReady && timeReady) {
    456492                // Montar el botón sin verificar GDPR (eso se hace en el click)
     
    463499            // 1. Configurar delay
    464500            if (cfg.delayMs && cfg.delayMs > 0) {
    465                 setTimeout(function() {
     501                setTimeout(function () {
    466502                    triggers.delay = true;
    467503                    checkAndMount();
     
    470506                triggers.delay = true;
    471507            }
    472            
     508
    473509            // 2. Configurar scroll
    474510            if (cfg.scrollPercent && cfg.scrollPercent > 0) {
    475511                // Verificar scroll inicial
    476512                var currentScroll = getScrollPercent();
    477                
     513
    478514                if (currentScroll >= cfg.scrollPercent) {
    479515                    triggers.scroll = true;
     
    493529                triggers.scroll = true;
    494530            }
    495            
     531
    496532            // 3. Configurar tiempo en página
    497533            if (cfg.timeOnPage && cfg.timeOnPage > 0) {
    498                 setTimeout(function() {
     534                setTimeout(function () {
    499535                    triggers.time = true;
    500536                    checkAndMount();
     
    503539                triggers.time = true;
    504540            }
    505            
     541
    506542            // Verificación inicial
    507543            checkAndMount();
     
    521557        // Iniciar configuración de disparadores
    522558        setupTriggers();
    523        
     559
    524560        // Reposicionar en cambio de tamaño de ventana (responsive)
    525         if(cfg.responsiveEnabled) {
     561        if (cfg.responsiveEnabled) {
    526562            var resizeTimeout;
    527             window.addEventListener('resize', function() {
     563            window.addEventListener('resize', function () {
    528564                clearTimeout(resizeTimeout);
    529                 resizeTimeout = setTimeout(function() {
     565                resizeTimeout = setTimeout(function () {
    530566                    mount(); // Volver a montar con nueva configuración
    531567                }, 250);
     
    533569        }
    534570    });
    535    
     571
    536572    // Funciones de GDPR
    537573    function hasGDPRConsent() {
    538574        return localStorage.getItem('mcnb_gdpr_consent') === 'true';
    539575    }
    540    
     576
    541577    function setGDPRConsent(consent) {
    542578        localStorage.setItem('mcnb_gdpr_consent', consent ? 'true' : 'false');
    543579    }
    544    
     580
    545581    function showGDPRModal() {
    546582        if (!window.mcnbData) return;
    547        
     583
    548584        // Evitar que se abra múltiples veces
    549585        if (document.querySelector('.mcnb-gdpr-modal')) return;
    550        
     586
    551587        var cfg = window.mcnbData;
     588
     589        // Get customizable styles from config (con valores por defecto)
     590        var modalBgColor = cfg.gdprModalBgColor || 'rgba(0,0,0,0.7)';
     591        var modalContentBg = cfg.gdprModalContentBg || '#ffffff';
     592        var modalBorderRadius = cfg.gdprModalBorderRadius || '12px';
     593        var modalTitleColor = cfg.gdprModalTitleColor || '#2c3e50';
     594        var acceptBtnBg = cfg.gdprAcceptBtnBg || cfg.brandColor || '#25D366';
     595        var acceptBtnColor = cfg.gdprAcceptBtnColor || '#ffffff';
     596        var acceptBtnHoverBg = cfg.gdprAcceptBtnHoverBg || '#22c55e';
     597        var declineBtnBg = cfg.gdprDeclineBtnBg || '#e74c3c';
     598        var declineBtnColor = cfg.gdprDeclineBtnColor || '#ffffff';
     599        var declineBtnHoverBg = cfg.gdprDeclineBtnHoverBg || '#dc2626';
     600        var btnBorderRadius = cfg.gdprBtnBorderRadius || '6px';
     601
    552602        var modal = document.createElement('div');
    553603        modal.className = 'mcnb-gdpr-modal';
     604        modal.setAttribute('role', 'dialog');
     605        modal.setAttribute('aria-modal', 'true');
     606        modal.setAttribute('aria-labelledby', 'mcnb-gdpr-title');
     607        modal.setAttribute('tabindex', '-1');
    554608        modal.style.cssText = `
    555609            position: fixed;
     
    558612            width: 100%;
    559613            height: 100%;
    560             background: rgba(0,0,0,0.7);
     614            background: ${modalBgColor};
    561615            display: flex;
    562616            align-items: center;
     
    564618            z-index: 999999;
    565619            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
     620            animation: mcnb-fade-in 0.25s ease;
    566621        `;
    567        
     622
    568623        var content = document.createElement('div');
     624        content.className = 'mcnb-gdpr-content';
    569625        content.style.cssText = `
    570             background: white;
     626            background: ${modalContentBg};
    571627            padding: 30px;
    572             border-radius: 12px;
     628            border-radius: ${modalBorderRadius};
    573629            max-width: 400px;
    574630            width: 90%;
    575631            text-align: center;
    576632            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
     633            animation: mcnb-slide-up 0.25s ease;
    577634        `;
    578        
     635
    579636        content.innerHTML = `
    580             <h3 style="margin: 0 0 20px 0; color: #2c3e50; font-size: 18px;">${cfg.gdprMessage || 'Usamos chat para atenderte. ¿Aceptas continuar?'}</h3>
     637            <h3 id="mcnb-gdpr-title" style="margin: 0 0 20px 0; color: ${modalTitleColor}; font-size: 18px;">${cfg.gdprMessage || 'Usamos chat para atenderte. ¿Aceptas continuar?'}</h3>
    581638            <div style="display: flex; gap: 12px; justify-content: center; margin-top: 24px;">
    582639                <button class="mcnb-gdpr-accept" style="
    583                     background: #25D366;
    584                     color: white;
     640                    background: ${acceptBtnBg};
     641                    color: ${acceptBtnColor};
    585642                    border: none;
    586643                    padding: 12px 24px;
    587                     border-radius: 6px;
     644                    border-radius: ${btnBorderRadius};
    588645                    font-weight: 600;
    589646                    cursor: pointer;
    590647                    font-size: 14px;
     648                    transition: all 0.2s ease;
    591649                ">${cfg.gdprButtonLabel || 'Aceptar y continuar'}</button>
    592650                <button class="mcnb-gdpr-decline" style="
    593                     background: #e74c3c;
    594                     color: white;
     651                    background: ${declineBtnBg};
     652                    color: ${declineBtnColor};
    595653                    border: none;
    596654                    padding: 12px 24px;
    597                     border-radius: 6px;
     655                    border-radius: ${btnBorderRadius};
    598656                    font-weight: 600;
    599657                    cursor: pointer;
    600658                    font-size: 14px;
     659                    transition: all 0.2s ease;
    601660                ">Cancelar</button>
    602661            </div>
    603662        `;
    604        
     663
    605664        modal.appendChild(content);
    606665        document.body.appendChild(modal);
    607        
    608         // Event listeners
    609         content.querySelector('.mcnb-gdpr-accept').addEventListener('click', function() {
    610         setGDPRConsent(true);
    611         document.body.removeChild(modal);
    612        
    613         // Abrir WhatsApp después de aceptar
    614        
    615         // Tracking interno
    616         trackClick();
    617            
     666
     667        // Focus trap elements
     668        var acceptBtn = content.querySelector('.mcnb-gdpr-accept');
     669        var declineBtn = content.querySelector('.mcnb-gdpr-decline');
     670        var focusableElements = [acceptBtn, declineBtn];
     671        var firstFocusable = focusableElements[0];
     672        var lastFocusable = focusableElements[focusableElements.length - 1];
     673
     674        // Set initial focus
     675        setTimeout(function () {
     676            firstFocusable.focus();
     677        }, 100);
     678
     679        // Close modal function (ARREGLADO: sin parpadeo)
     680        function closeModal() {
     681            if (modal.parentNode) {
     682                modal.style.pointerEvents = 'none';
     683                content.style.animation = 'mcnb-slide-down 0.3s ease forwards';
     684                modal.style.animation = 'mcnb-fade-out 0.3s ease forwards';
     685                setTimeout(function () {
     686                    if (modal.parentNode) {
     687                        document.body.removeChild(modal);
     688                    }
     689                }, 300);
     690            }
     691        }
     692
     693        // Accept button
     694        acceptBtn.addEventListener('click', function () {
     695            setGDPRConsent(true);
     696            closeModal();
     697
     698            // Tracking interno
     699            trackClick();
     700
    618701            // Abrir WhatsApp
    619702            openWhatsApp();
    620703        });
    621        
    622         content.querySelector('.mcnb-gdpr-decline').addEventListener('click', function() {
    623             document.body.removeChild(modal);
     704
     705        // Decline button
     706        declineBtn.addEventListener('click', function () {
     707            closeModal();
    624708        });
    625        
    626         // Cerrar con ESC
    627         modal.addEventListener('keydown', function(e) {
    628             if (e.key === 'Escape') {
    629                 document.body.removeChild(modal);
     709
     710        // Close on backdrop click (click on modal, not content)
     711        modal.addEventListener('click', function (e) {
     712            if (e.target === modal) {
     713                closeModal();
    630714            }
    631715        });
    632         modal.focus();
    633     }
    634    
     716
     717        // Close with ESC key
     718        function handleEscape(e) {
     719            if (e.key === 'Escape' || e.keyCode === 27) {
     720                closeModal();
     721                document.removeEventListener('keydown', handleEscape);
     722            }
     723        }
     724        document.addEventListener('keydown', handleEscape);
     725
     726        // Focus trap
     727        modal.addEventListener('keydown', function (e) {
     728            if (e.key === 'Tab' || e.keyCode === 9) {
     729                if (e.shiftKey) {
     730                    // Shift + Tab
     731                    if (document.activeElement === firstFocusable) {
     732                        e.preventDefault();
     733                        lastFocusable.focus();
     734                    }
     735                } else {
     736                    // Tab
     737                    if (document.activeElement === lastFocusable) {
     738                        e.preventDefault();
     739                        firstFocusable.focus();
     740                    }
     741                }
     742            }
     743        });
     744
     745        // Add CSS animations if not already present
     746        if (!document.getElementById('mcnb-modal-styles')) {
     747            var style = document.createElement('style');
     748            style.id = 'mcnb-modal-styles';
     749            style.textContent = `
     750                @keyframes mcnb-fade-in {
     751                    from { opacity: 0; }
     752                    to { opacity: 1; }
     753                }
     754                @keyframes mcnb-fade-out {
     755                    from { opacity: 1; }
     756                    to { opacity: 0; }
     757                }
     758                @keyframes mcnb-slide-up {
     759                    from { transform: translateY(20px); opacity: 0; }
     760                    to { transform: translateY(0); opacity: 1; }
     761                }
     762                @keyframes mcnb-slide-down {
     763                    from { transform: translateY(0); opacity: 1; }
     764                    to { transform: translateY(20px); opacity: 0; }
     765                }
     766                .mcnb-gdpr-accept:hover {
     767                    background: ${acceptBtnHoverBg} !important;
     768                    transform: translateY(-1px);
     769                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
     770                }
     771                .mcnb-gdpr-decline:hover {
     772                    background: ${declineBtnHoverBg} !important;
     773                    transform: translateY(-1px);
     774                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
     775                }
     776                .mcnb-gdpr-accept:focus,
     777                .mcnb-gdpr-decline:focus {
     778                    outline: 2px solid #3b82f6;
     779                    outline-offset: 2px;
     780                }
     781                .mcnb-gdpr-accept:active,
     782                .mcnb-gdpr-decline:active {
     783                    transform: translateY(0);
     784                }
     785            `;
     786            document.head.appendChild(style);
     787        }
     788    }
     789
    635790    // Función para abrir WhatsApp con UTM
    636791    function openWhatsApp() {
    637792        if (!window.mcnbData) return;
    638        
     793
    639794        var cfg = window.mcnbData;
    640795        var url = cfg.link;
    641        
     796
    642797        // Versión gratuita - siempre usar mensaje por defecto
    643798        var message = cfg.defaultMessage;
    644        
     799
    645800        // Si hay mensaje personalizado, agregarlo a la URL
    646801        if (message && message.trim()) {
     
    648803            url += separator + 'text=' + encodeURIComponent(message);
    649804        }
    650        
     805
    651806        // Agregar parámetros UTM si están habilitados
    652807        if (cfg.utmEnabled) {
     
    655810            utmParams.append('utm_medium', cfg.utmMedium || 'cta');
    656811            utmParams.append('utm_campaign', cfg.utmCampaign || 'support');
    657            
     812
    658813            // Agregar UTM al mensaje
    659814            var separator = url.includes('?') ? '&' : '?';
    660815            url += separator + utmParams.toString();
    661816        }
    662        
     817
    663818        window.open(url, '_blank');
    664819    }
  • madnesschat-button/tags/1.0.2/frontend/class-mcnb-frontend.php

    r3403447 r3411134  
    88        add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
    99        add_action( 'wp_footer', array( $this, 'render_markup' ) );
     10    }
     11
     12    /**
     13     * Normalize time format to HH:MM for consistent comparison
     14     *
     15     * @param string $time Time string in various formats (H:i, H:i:s, etc.)
     16     * @return string Normalized time in HH:MM format or empty string if invalid
     17     */
     18    private function normalize_time_format( $time ) {
     19        if ( empty( $time ) ) {
     20            return '';
     21        }
     22       
     23        // Remove any whitespace
     24        $time = trim( $time );
     25       
     26        // Try to parse the time
     27        $timestamp = strtotime( $time );
     28        if ( false === $timestamp ) {
     29            return '';
     30        }
     31       
     32        // Return in HH:MM format (24-hour)
     33        return date( 'H:i', $timestamp );
    1034    }
    1135
     
    3054            // Obtener hora local usando wp_date que respeta la zona horaria configurada en WordPress
    3155            $current_time = wp_date( 'H:i' );
    32             $start_time = $options['schedule_start'];
    33             $end_time = $options['schedule_end'];
     56            $start_time = isset( $options['schedule_start'] ) ? $options['schedule_start'] : '';
     57            $end_time = isset( $options['schedule_end'] ) ? $options['schedule_end'] : '';
     58           
     59            // Normalizar formato de tiempo a HH:MM para comparación consistente
     60            $current_time = $this->normalize_time_format( $current_time );
     61            $start_time = $this->normalize_time_format( $start_time );
     62            $end_time = $this->normalize_time_format( $end_time );
     63           
     64            // Validar que los tiempos sean válidos
     65            if ( empty( $start_time ) || empty( $end_time ) ) {
     66                return true; // Si no hay horario válido, mostrar el botón
     67            }
    3468           
    3569            // Comparar horarios como strings en formato HH:MM
     
    100134        $link = add_query_arg( array_map( 'rawurlencode', $query ), $link );
    101135
    102         // Preparar payload sanitizado para JavaScript
    103         $payload = array(
    104             'link' => esc_url_raw( $link ),
    105             'position' => sanitize_key( $options['position'] ),
    106             'offsetX' => max( 0, min( 500, intval( $options['offset_x'] ) ) ),
    107             'offsetY' => max( 0, min( 500, intval( $options['offset_y'] ) ) ),
    108             'size' => sanitize_key( $options['size'] ),
    109             'buttonStyle' => sanitize_key( $options['button_style'] ?? 'circle' ),
    110             'showLabel' => ! empty( $options['show_label'] ),
    111             'labelText' => wp_strip_all_tags( $options['label_text'] ),
    112             'labelPosition' => sanitize_key( $options['label_position'] ?? 'left' ),
    113             'brandColor' => sanitize_hex_color( $options['brand_color'] ) ?: '#25D366',
    114             'textColor' => sanitize_hex_color( $options['text_color'] ) ?: '#ffffff',
    115             'shadowEnabled' => ! empty( $options['shadow_enabled'] ),
    116             'shadowColor' => sanitize_text_field( $options['shadow_color'] ?? 'rgba(0,0,0,0.3)' ),
    117             'borderEnabled' => ! empty( $options['border_enabled'] ),
    118             'borderColor' => sanitize_hex_color( $options['border_color'] ) ?: '#ffffff',
    119             'borderWidth' => max( 1, min( 10, intval( $options['border_width'] ?? 2 ) ) ),
    120             'animation' => sanitize_key( $options['animation'] ?? 'none' ),
    121             'animationDelay' => max( 1000, min( 10000, intval( $options['animation_delay'] ?? 3000 ) ) ),
    122             'hoverEffect' => sanitize_key( $options['hover_effect'] ?? 'lift' ),
    123             'iconType' => sanitize_key( $options['icon_type'] ?? 'whatsapp' ),
    124             'customIcon' => esc_url( $options['custom_icon'] ?? '' ),
    125             'delayMs' => max( 0, min( 60000, intval( $options['delay_ms'] ) ) ),
    126             'scrollPercent' => max( 0, min( 100, intval( $options['scroll_percent'] ) ) ),
    127             'timeOnPage' => max( 0, min( 600, intval( $options['time_on_page'] ) ) ),
    128             'gdprEnabled' => ! empty( $options['gdpr_enabled'] ),
    129             'gdprMessage' => wp_strip_all_tags( $options['gdpr_message'] ),
    130             'gdprButtonLabel' => wp_strip_all_tags( $options['gdpr_button_label'] ),
    131             'analyticsEnabled' => ! empty( $options['analytics_enabled'] ),
    132             'utmEnabled' => ! empty( $options['utm_enabled'] ),
    133             'utmSource' => sanitize_key( $options['utm_source'] ?? 'whatsapp_button' ),
    134             'utmMedium' => sanitize_key( $options['utm_medium'] ?? 'cta' ),
    135             'utmCampaign' => sanitize_key( $options['utm_campaign'] ?? 'support' ),
    136             // A/B Testing deshabilitado en versión gratuita
    137             'abTestingEnabled' => false,
    138             'ajaxUrl' => admin_url( 'admin-ajax.php' ),
    139             'trackingNonce' => wp_create_nonce( 'mcnb_track_click' ),
    140             'responsiveEnabled' => ! empty( $options['responsive_enabled'] ),
    141             'responsiveConfig' => $options['responsive_config'] ?? array(),
    142         );
     136        // Validar y normalizar configuración responsive
     137    $responsive_config = array();
     138    if ( ! empty( $options['responsive_enabled'] ) && is_array( $options['responsive_config'] ?? null ) ) {
     139        $devices = array( 'mobile', 'tablet', 'laptop', 'desktop' );
     140        foreach ( $devices as $device ) {
     141            if ( isset( $options['responsive_config'][ $device ] ) && is_array( $options['responsive_config'][ $device ] ) ) {
     142                $device_config = $options['responsive_config'][ $device ];
     143                $responsive_config[ $device ] = array(
     144                    'size' => sanitize_text_field( $device_config['size'] ?? '56' ),
     145                    'position' => sanitize_key( $device_config['position'] ?? 'bottom-right' ),
     146                    'offset_x' => max( 0, min( 500, intval( $device_config['offset_x'] ?? 20 ) ) ),
     147                    'offset_y' => max( 0, min( 500, intval( $device_config['offset_y'] ?? 20 ) ) ),
     148                    'show_label' => ! empty( $device_config['show_label'] ),
     149                );
     150            }
     151        }
     152    }
     153
     154    // Preparar payload sanitizado para JavaScript
     155    $payload = array(
     156        'link' => esc_url_raw( $link ),
     157        'position' => sanitize_key( $options['position'] ),
     158        'offsetX' => max( 0, min( 500, intval( $options['offset_x'] ) ) ),
     159        'offsetY' => max( 0, min( 500, intval( $options['offset_y'] ) ) ),
     160        'size' => sanitize_key( $options['size'] ),
     161        'buttonStyle' => sanitize_key( $options['button_style'] ?? 'circle' ),
     162        'showLabel' => ! empty( $options['show_label'] ),
     163        'labelText' => wp_strip_all_tags( $options['label_text'] ?? '' ),
     164        'labelPosition' => sanitize_key( $options['label_position'] ?? 'left' ),
     165        'brandColor' => sanitize_hex_color( $options['brand_color'] ) ?: '#25D366',
     166        'textColor' => sanitize_hex_color( $options['text_color'] ) ?: '#ffffff',
     167        'shadowEnabled' => ! empty( $options['shadow_enabled'] ),
     168        'shadowColor' => sanitize_text_field( $options['shadow_color'] ?? 'rgba(0,0,0,0.3)' ),
     169        'borderEnabled' => ! empty( $options['border_enabled'] ),
     170        'borderColor' => sanitize_hex_color( $options['border_color'] ) ?: '#ffffff',
     171        'borderWidth' => max( 1, min( 10, intval( $options['border_width'] ?? 2 ) ) ),
     172        'animation' => sanitize_key( $options['animation'] ?? 'none' ),
     173        'animationDelay' => max( 1000, min( 10000, intval( $options['animation_delay'] ?? 3000 ) ) ),
     174        'hoverEffect' => sanitize_key( $options['hover_effect'] ?? 'lift' ),
     175        'iconType' => sanitize_key( $options['icon_type'] ?? 'whatsapp' ),
     176        'customIcon' => esc_url( $options['custom_icon'] ?? '' ),
     177        'delayMs' => max( 0, min( 60000, intval( $options['delay_ms'] ?? 0 ) ) ),
     178        'scrollPercent' => max( 0, min( 100, intval( $options['scroll_percent'] ?? 0 ) ) ),
     179        'timeOnPage' => max( 0, min( 600, intval( $options['time_on_page'] ?? 0 ) ) ),
     180        'gdprEnabled' => ! empty( $options['gdpr_enabled'] ),
     181        'gdprMessage' => wp_strip_all_tags( $options['gdpr_message'] ?? '' ),
     182        'gdprButtonLabel' => wp_strip_all_tags( $options['gdpr_button_label'] ?? '' ),
     183        // GDPR Modal Styling Options
     184        'gdprModalBgColor' => sanitize_text_field( $options['gdpr_modal_bg_color'] ?? 'rgba(0,0,0,0.7)' ),
     185        'gdprModalContentBg' => sanitize_hex_color( $options['gdpr_modal_content_bg'] ?? '' ) ?: '#ffffff',
     186        'gdprModalBorderRadius' => sanitize_text_field( $options['gdpr_modal_border_radius'] ?? '12px' ),
     187        'gdprModalTitleColor' => sanitize_hex_color( $options['gdpr_modal_title_color'] ?? '' ) ?: '#2c3e50',
     188        'gdprAcceptBtnBg' => sanitize_hex_color( $options['gdpr_accept_btn_bg'] ?? '' ) ?: '',
     189        'gdprAcceptBtnColor' => sanitize_hex_color( $options['gdpr_accept_btn_color'] ?? '' ) ?: '#ffffff',
     190        'gdprAcceptBtnHoverBg' => sanitize_hex_color( $options['gdpr_accept_btn_hover_bg'] ?? '' ) ?: '#22c55e',
     191        'gdprDeclineBtnBg' => sanitize_hex_color( $options['gdpr_decline_btn_bg'] ?? '' ) ?: '#e74c3c',
     192        'gdprDeclineBtnColor' => sanitize_hex_color( $options['gdpr_decline_btn_color'] ?? '' ) ?: '#ffffff',
     193        'gdprDeclineBtnHoverBg' => sanitize_hex_color( $options['gdpr_decline_btn_hover_bg'] ?? '' ) ?: '#dc2626',
     194        'gdprBtnBorderRadius' => sanitize_text_field( $options['gdpr_btn_border_radius'] ?? '6px' ),
     195        'analyticsEnabled' => ! empty( $options['analytics_enabled'] ),
     196        'utmEnabled' => ! empty( $options['utm_enabled'] ),
     197        'utmSource' => sanitize_key( $options['utm_source'] ?? 'whatsapp_button' ),
     198        'utmMedium' => sanitize_key( $options['utm_medium'] ?? 'cta' ),
     199        'utmCampaign' => sanitize_key( $options['utm_campaign'] ?? 'support' ),
     200        // A/B Testing deshabilitado en versión gratuita
     201        'abTestingEnabled' => false,
     202        'ajaxUrl' => admin_url( 'admin-ajax.php' ),
     203        'trackingNonce' => wp_create_nonce( 'mcnb_track_click' ),
     204        'responsiveEnabled' => ! empty( $options['responsive_enabled'] ),
     205        'responsiveConfig' => $responsive_config,
     206    );
    143207
    144208        wp_localize_script( 'mcnb-frontend', 'mcnbData', $payload );
  • madnesschat-button/tags/1.0.2/includes/class-mcnb-settings.php

    r3403447 r3411134  
    184184        $is_privacy_tab = array_key_exists( 'gdpr_message', $input ) ||
    185185                          array_key_exists( 'gdpr_button_label', $input ) ||
     186                          array_key_exists( 'gdpr_modal_bg_color', $input ) ||
     187                          array_key_exists( 'gdpr_modal_content_bg', $input ) ||
    186188                          array_key_exists( 'utm_source', $input ) ||
    187189                          array_key_exists( 'utm_medium', $input ) ||
     
    206208            $gdpr_btn = sanitize_text_field( (string) $input['gdpr_button_label'] );
    207209            $sanitized['gdpr_button_label'] = mb_substr( $gdpr_btn, 0, 50, 'UTF-8' );
     210        }
     211       
     212        // Sanitizar estilos del modal GDPR
     213        if ( array_key_exists( 'gdpr_modal_bg_color', $input ) ) {
     214            $modal_bg = sanitize_text_field( (string) $input['gdpr_modal_bg_color'] );
     215            // Validar formato de color CSS (hex, rgb, rgba, hsl)
     216            if ( preg_match( '/^(#[0-9a-fA-F]{3,6}|rgba?\([0-9,\s\.]+\)|hsla?\([0-9,\s\.%]+\))$/', $modal_bg ) ) {
     217                $sanitized['gdpr_modal_bg_color'] = $modal_bg;
     218            } else {
     219                $sanitized['gdpr_modal_bg_color'] = 'rgba(0,0,0,0.7)';
     220            }
     221        }
     222        if ( array_key_exists( 'gdpr_modal_content_bg', $input ) ) {
     223            $content_bg = sanitize_hex_color( (string) $input['gdpr_modal_content_bg'] );
     224            $sanitized['gdpr_modal_content_bg'] = $content_bg ? $content_bg : '#ffffff';
     225        }
     226        if ( array_key_exists( 'gdpr_modal_border_radius', $input ) ) {
     227            $border_radius = sanitize_text_field( (string) $input['gdpr_modal_border_radius'] );
     228            // Validar formato CSS (px, em, rem, %)
     229            if ( preg_match( '/^\d+(\.\d+)?(px|em|rem|%)$/', $border_radius ) ) {
     230                $sanitized['gdpr_modal_border_radius'] = $border_radius;
     231            } else {
     232                $sanitized['gdpr_modal_border_radius'] = '12px';
     233            }
     234        }
     235        if ( array_key_exists( 'gdpr_modal_title_color', $input ) ) {
     236            $title_color = sanitize_hex_color( (string) $input['gdpr_modal_title_color'] );
     237            $sanitized['gdpr_modal_title_color'] = $title_color ? $title_color : '#2c3e50';
     238        }
     239        if ( array_key_exists( 'gdpr_accept_btn_bg', $input ) ) {
     240            $accept_bg = sanitize_hex_color( (string) $input['gdpr_accept_btn_bg'] );
     241            $sanitized['gdpr_accept_btn_bg'] = $accept_bg ? $accept_bg : '';
     242        }
     243        if ( array_key_exists( 'gdpr_accept_btn_color', $input ) ) {
     244            $accept_color = sanitize_hex_color( (string) $input['gdpr_accept_btn_color'] );
     245            $sanitized['gdpr_accept_btn_color'] = $accept_color ? $accept_color : '#ffffff';
     246        }
     247        if ( array_key_exists( 'gdpr_accept_btn_hover_bg', $input ) ) {
     248            $accept_hover = sanitize_hex_color( (string) $input['gdpr_accept_btn_hover_bg'] );
     249            $sanitized['gdpr_accept_btn_hover_bg'] = $accept_hover ? $accept_hover : '#22c55e';
     250        }
     251        if ( array_key_exists( 'gdpr_decline_btn_bg', $input ) ) {
     252            $decline_bg = sanitize_hex_color( (string) $input['gdpr_decline_btn_bg'] );
     253            $sanitized['gdpr_decline_btn_bg'] = $decline_bg ? $decline_bg : '#e74c3c';
     254        }
     255        if ( array_key_exists( 'gdpr_decline_btn_color', $input ) ) {
     256            $decline_color = sanitize_hex_color( (string) $input['gdpr_decline_btn_color'] );
     257            $sanitized['gdpr_decline_btn_color'] = $decline_color ? $decline_color : '#ffffff';
     258        }
     259        if ( array_key_exists( 'gdpr_decline_btn_hover_bg', $input ) ) {
     260            $decline_hover = sanitize_hex_color( (string) $input['gdpr_decline_btn_hover_bg'] );
     261            $sanitized['gdpr_decline_btn_hover_bg'] = $decline_hover ? $decline_hover : '#dc2626';
     262        }
     263        if ( array_key_exists( 'gdpr_btn_border_radius', $input ) ) {
     264            $btn_border_radius = sanitize_text_field( (string) $input['gdpr_btn_border_radius'] );
     265            // Validar formato CSS (px, em, rem, %)
     266            if ( preg_match( '/^\d+(\.\d+)?(px|em|rem|%)$/', $btn_border_radius ) ) {
     267                $sanitized['gdpr_btn_border_radius'] = $btn_border_radius;
     268            } else {
     269                $sanitized['gdpr_btn_border_radius'] = '6px';
     270            }
    208271        }
    209272       
     
    829892            echo '</div>';
    830893            echo '</div>';
     894           
     895            // Estilos del modal GDPR
     896            echo '<div class="mcnb-field-row">';
     897            echo '<div class="mcnb-field-col-full">';
     898            echo '<h4 style="margin: 20px 0 10px 0; color: #333; border-top: 1px solid #ddd; padding-top: 15px;">' . esc_html__( 'Estilos del modal', 'madnesschat-button' ) . '</h4>';
     899            echo '</div>';
     900            echo '</div>';
     901           
     902            // Color de fondo del modal (overlay)
     903            echo '<div class="mcnb-field-row">';
     904            echo '<div class="mcnb-field-col">';
     905            echo '<label class="mcnb-field-label">' . esc_html__( 'Color de fondo del modal', 'madnesschat-button' ) . '</label>';
     906            echo '<input type="text" name="mcnb_basic_options[gdpr_modal_bg_color]" value="' . esc_attr( $options['gdpr_modal_bg_color'] ?? 'rgba(0,0,0,0.7)' ) . '" class="mcnb-color" />';
     907            echo '<p class="mcnb-field-hint">' . esc_html__( 'Color del overlay de fondo', 'madnesschat-button' ) . '</p>';
     908            echo '</div>';
     909            // Color de fondo de la card
     910            echo '<div class="mcnb-field-col">';
     911            echo '<label class="mcnb-field-label">' . esc_html__( 'Color de fondo de la card', 'madnesschat-button' ) . '</label>';
     912            echo '<input type="text" name="mcnb_basic_options[gdpr_modal_content_bg]" value="' . esc_attr( $options['gdpr_modal_content_bg'] ?? '#ffffff' ) . '" class="mcnb-color" />';
     913            echo '<p class="mcnb-field-hint">' . esc_html__( 'Color de fondo del contenido', 'madnesschat-button' ) . '</p>';
     914            echo '</div>';
     915            echo '</div>';
     916           
     917            // Color del título y border radius
     918            echo '<div class="mcnb-field-row">';
     919            echo '<div class="mcnb-field-col">';
     920            echo '<label class="mcnb-field-label">' . esc_html__( 'Color del título', 'madnesschat-button' ) . '</label>';
     921            echo '<input type="text" name="mcnb_basic_options[gdpr_modal_title_color]" value="' . esc_attr( $options['gdpr_modal_title_color'] ?? '#2c3e50' ) . '" class="mcnb-color" />';
     922            echo '</div>';
     923            echo '<div class="mcnb-field-col">';
     924            echo '<label class="mcnb-field-label">' . esc_html__( 'Border radius del modal', 'madnesschat-button' ) . '</label>';
     925            echo '<input type="text" name="mcnb_basic_options[gdpr_modal_border_radius]" value="' . esc_attr( $options['gdpr_modal_border_radius'] ?? '12px' ) . '" class="regular-text" placeholder="12px" />';
     926            echo '</div>';
     927            echo '</div>';
     928           
     929            // Botón Aceptar
     930            echo '<div class="mcnb-field-row">';
     931            echo '<div class="mcnb-field-col-full">';
     932            echo '<h5 style="margin: 15px 0 10px 0; color: #555;">' . esc_html__( 'Botón Aceptar', 'madnesschat-button' ) . '</h5>';
     933            echo '</div>';
     934            echo '</div>';
     935            echo '<div class="mcnb-field-row">';
     936            echo '<div class="mcnb-field-col">';
     937            echo '<label class="mcnb-field-label">' . esc_html__( 'Color de fondo', 'madnesschat-button' ) . '</label>';
     938            echo '<input type="text" name="mcnb_basic_options[gdpr_accept_btn_bg]" value="' . esc_attr( $options['gdpr_accept_btn_bg'] ?? '' ) . '" class="mcnb-color" />';
     939            echo '<p class="mcnb-field-hint">' . esc_html__( 'Dejar vacío para usar el color del botón principal', 'madnesschat-button' ) . '</p>';
     940            echo '</div>';
     941            echo '<div class="mcnb-field-col">';
     942            echo '<label class="mcnb-field-label">' . esc_html__( 'Color del texto', 'madnesschat-button' ) . '</label>';
     943            echo '<input type="text" name="mcnb_basic_options[gdpr_accept_btn_color]" value="' . esc_attr( $options['gdpr_accept_btn_color'] ?? '#ffffff' ) . '" class="mcnb-color" />';
     944            echo '</div>';
     945            echo '<div class="mcnb-field-col">';
     946            echo '<label class="mcnb-field-label">' . esc_html__( 'Color hover', 'madnesschat-button' ) . '</label>';
     947            echo '<input type="text" name="mcnb_basic_options[gdpr_accept_btn_hover_bg]" value="' . esc_attr( $options['gdpr_accept_btn_hover_bg'] ?? '#22c55e' ) . '" class="mcnb-color" />';
     948            echo '</div>';
     949            echo '</div>';
     950           
     951            // Botón Cancelar/Declinar
     952            echo '<div class="mcnb-field-row">';
     953            echo '<div class="mcnb-field-col-full">';
     954            echo '<h5 style="margin: 15px 0 10px 0; color: #555;">' . esc_html__( 'Botón Cancelar', 'madnesschat-button' ) . '</h5>';
     955            echo '</div>';
     956            echo '</div>';
     957            echo '<div class="mcnb-field-row">';
     958            echo '<div class="mcnb-field-col">';
     959            echo '<label class="mcnb-field-label">' . esc_html__( 'Color de fondo', 'madnesschat-button' ) . '</label>';
     960            echo '<input type="text" name="mcnb_basic_options[gdpr_decline_btn_bg]" value="' . esc_attr( $options['gdpr_decline_btn_bg'] ?? '#e74c3c' ) . '" class="mcnb-color" />';
     961            echo '</div>';
     962            echo '<div class="mcnb-field-col">';
     963            echo '<label class="mcnb-field-label">' . esc_html__( 'Color del texto', 'madnesschat-button' ) . '</label>';
     964            echo '<input type="text" name="mcnb_basic_options[gdpr_decline_btn_color]" value="' . esc_attr( $options['gdpr_decline_btn_color'] ?? '#ffffff' ) . '" class="mcnb-color" />';
     965            echo '</div>';
     966            echo '<div class="mcnb-field-col">';
     967            echo '<label class="mcnb-field-label">' . esc_html__( 'Color hover', 'madnesschat-button' ) . '</label>';
     968            echo '<input type="text" name="mcnb_basic_options[gdpr_decline_btn_hover_bg]" value="' . esc_attr( $options['gdpr_decline_btn_hover_bg'] ?? '#dc2626' ) . '" class="mcnb-color" />';
     969            echo '</div>';
     970            echo '</div>';
     971           
     972            // Border radius de botones
     973            echo '<div class="mcnb-field-row">';
     974            echo '<div class="mcnb-field-col">';
     975            echo '<label class="mcnb-field-label">' . esc_html__( 'Border radius de botones', 'madnesschat-button' ) . '</label>';
     976            echo '<input type="text" name="mcnb_basic_options[gdpr_btn_border_radius]" value="' . esc_attr( $options['gdpr_btn_border_radius'] ?? '6px' ) . '" class="regular-text" placeholder="6px" />';
     977            echo '</div>';
     978            echo '</div>';
     979           
    831980            echo '</div>';
    832981    }, 'mcnb_section_privacy', 'mcnb_privacy' );
  • madnesschat-button/tags/1.0.2/madnesschat-button.php

    r3406405 r3411134  
    33 * Plugin Name: Floating Contact Button
    44 * Description: Easy-to-configure floating WhatsApp button with GDPR compliance, basic analytics, and smart triggers.
    5  * Version: 1.0.6
     5 * Version: 1.0.7
    66 * Author: madnesscode1
    77 * Requires at least: 5.6
     
    3939}
    4040
    41 define( 'MCNB_VERSION', '1.0.6' );
     41define( 'MCNB_VERSION', '1.0.7' );
    4242define( 'MCNB_SLUG', 'madnesschat-button' );
    4343define( 'MCNB_FILE', __FILE__ );
     
    9191        'gdpr_message' => __( 'Usamos chat para atenderte. ¿Aceptas continuar?', 'madnesschat-button' ),
    9292        'gdpr_button_label' => __( 'Aceptar y continuar', 'madnesschat-button' ),
     93        'gdpr_modal_bg_color' => 'rgba(0,0,0,0.7)',
     94        'gdpr_modal_content_bg' => '#ffffff',
     95        'gdpr_modal_border_radius' => '12px',
     96        'gdpr_modal_title_color' => '#2c3e50',
     97        'gdpr_accept_btn_bg' => '',
     98        'gdpr_accept_btn_color' => '#ffffff',
     99        'gdpr_accept_btn_hover_bg' => '#22c55e',
     100        'gdpr_decline_btn_bg' => '#e74c3c',
     101        'gdpr_decline_btn_color' => '#ffffff',
     102        'gdpr_decline_btn_hover_bg' => '#dc2626',
     103        'gdpr_btn_border_radius' => '6px',
    93104        'analytics_enabled' => true,
    94105        'utm_enabled' => true,
     
    120131require_once MCNB_DIR . 'includes/class-mcnb-settings.php';
    121132require_once MCNB_DIR . 'includes/class-mcnb-security.php';
     133require_once MCNB_DIR . 'includes/class-mcnb-seo.php';
    122134// require_once MCNB_DIR . 'includes/class-mcnb-license.php'; // Removido - venta directa sin licencias
    123135require_once MCNB_DIR . 'includes/class-mcnb-analytics.php';
     
    156168    // Frontend siempre se carga
    157169    new MCNB_Frontend();
     170   
     171    // SEO features (schema markup, OG tags)
     172    new MCNB_SEO();
    158173} );
    159174
  • madnesschat-button/tags/1.0.2/readme.txt

    r3406409 r3411134  
    55Tested up to: 6.9
    66Requires PHP: 7.3
    7 Stable tag: 1.0.6
     7Stable tag: 1.0.7
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    167167
    168168== Changelog ==
     169
     170= 1.0.7 =
     171* Added customizable styling options for GDPR modal (background colors, button colors, border radius).
     172* Fixed undefined array key warnings in frontend class.
     173* SEO optimization: Changed tag from "widget" to "floating" for better discoverability.
    169174
    170175= 1.0.6 =
     
    212217== Upgrade Notice ==
    213218
     219= 1.0.7 =
     220New GDPR modal styling options: customize colors, buttons, and border radius for the consent modal. Fixed warnings and improved SEO. Recommended update for all users.
     221
    214222= 1.0.6 =
    215223Plugin rebranding and visual improvements: renamed to "Floating Contact Button" for WordPress.org compliance, enhanced dashboard visibility, and added frontend screenshots. Recommended update for all users.
Note: See TracChangeset for help on using the changeset viewer.