Plugin Directory

Changeset 3447338


Ignore:
Timestamp:
01/26/2026 07:49:52 PM (6 weeks ago)
Author:
codejitsu
Message:

Update to version 1.12.0 - Security, reliability, and UX improvements

Location:
workzen-connector/trunk
Files:
1 added
15 edited

Legend:

Unmodified
Added
Removed
  • workzen-connector/trunk/assets/admin.css

    r3436387 r3447338  
    11211121    transform: translateY(-100%);
    11221122    opacity: 0;
     1123    pointer-events: none; /* Don't block clicks when hidden */
    11231124    transition: transform 0.3s ease, opacity 0.3s ease;
    11241125}
     
    11271128    transform: translateY(0);
    11281129    opacity: 1;
     1130    pointer-events: auto; /* Allow clicks when visible */
    11291131}
    11301132
  • workzen-connector/trunk/assets/admin.js

    r3436387 r3447338  
    170170    });
    171171
    172     // Update statistics
    173     function updateStats() {
     172    // Update statistics (reserved for future use)
     173    function _updateStats() {
    174174        const total = $('.wzc-integration-card').length;
    175175        const enabled = $('.wzc-integration-card.enabled').length;
     
    372372
    373373    function syncBookingData(silent) {
    374         var $btn = $('#wzc-sync-booking-data');
    375         var $status = $('#wzc-sync-status');
    376         var btnDefaultHtml = '<span class="dashicons dashicons-update" style="margin-top: 3px;"></span> Sync Changes from Workzen.io';
     374        const $btn = $('#wzc-sync-booking-data');
     375        const $status = $('#wzc-sync-status');
     376        const btnDefaultHtml = '<span class="dashicons dashicons-update" style="margin-top: 3px;"></span> Sync Changes from Workzen.io';
    377377
    378378        if (!silent && $btn.length) {
     
    423423    // ========================================================================
    424424
    425     var formHasChanges = false;
    426     var isSaving = false;
    427     var $unsavedBar = null;
    428     var originalButtonText = null; // Store original button text
    429     var resetButtonTimeout = null; // Track timeout for button reset
     425    let formHasChanges = false;
     426    let isSaving = false;
     427    let $unsavedBar = null;
     428    let originalButtonText = null; // Store original button text
     429    let resetButtonTimeout = null; // Track timeout for button reset
    430430
    431431    // Get current tab from URL
    432432    function getCurrentTab() {
    433         var urlParams = new URLSearchParams(window.location.search);
     433        const urlParams = new URLSearchParams(window.location.search);
    434434        return urlParams.get('tab') || 'configuration';
    435435    }
     
    437437    // Update connection status indicator
    438438    function updateConnectionIndicator(status) {
    439         var $indicator = $('.wzc-connection-indicator');
     439        let $indicator = $('.wzc-connection-indicator');
    440440        if (!$indicator.length) {
    441441            // Create indicator if it doesn't exist
    442             var $inputField = $('#wzconnector_integration_key');
     442            const $inputField = $('#wzconnector_integration_key');
    443443            if ($inputField.length) {
    444444                $indicator = $('<span class="wzc-connection-indicator"></span>');
     
    481481        $unsavedBar.on('click', '.wzc-save-now-btn', function(e) {
    482482            e.preventDefault();
    483             var $btn = $(this);
     483            const $btn = $(this);
    484484            if ($btn.prop('disabled')) return;
    485485
    486             var $form = $('.wzc-admin-wrapper form[action*="options.php"]').not('#wzc-mode-form').first();
     486            const $form = $('.wzc-admin-wrapper form[action*="options.php"]').not('#wzc-mode-form').first();
    487487            if ($form.length) {
    488                 var currentTab = getCurrentTab();
     488                const currentTab = getCurrentTab();
    489489                saveTabSettings($form, currentTab);
    490490            }
     
    521521    // Update save button to show unsaved state
    522522    function updateSaveButtonState() {
    523         var $submitBtn = $('.wzc-admin-wrapper form:not(#wzc-mode-form) input[type="submit"], .wzc-admin-wrapper form:not(#wzc-mode-form) button[type="submit"]');
     523        const $submitBtn = $('.wzc-admin-wrapper form:not(#wzc-mode-form) input[type="submit"], .wzc-admin-wrapper form:not(#wzc-mode-form) button[type="submit"]');
    524524        if (!$submitBtn.length) return;
    525525
     
    573573    // Intercept form submission and use AJAX
    574574    $('.wzc-admin-wrapper form').on('submit', function(e) {
    575         var $form = $(this);
    576         var currentTab = getCurrentTab();
     575        const $form = $(this);
     576        const currentTab = getCurrentTab();
    577577
    578578        // Skip AJAX for special forms (mode form, etc)
     
    591591    // Save tab settings via AJAX
    592592    function saveTabSettings($form, tab) {
    593         var $submitBtn = $form.find('input[type="submit"], button[type="submit"]');
    594         var originalText = $submitBtn.val() || $submitBtn.text();
    595         var settings = {};
     593        const $submitBtn = $form.find('input[type="submit"], button[type="submit"]');
     594        const originalText = $submitBtn.val() || $submitBtn.text();
     595        const settings = {};
    596596
    597597        isSaving = true;
     
    604604        // Collect all form fields
    605605        $form.find('input, select, textarea').each(function() {
    606             var $field = $(this);
    607             var name = $field.attr('name');
     606            const $field = $(this);
     607            const name = $field.attr('name');
    608608
    609609            if (!name) return;
     
    673673
    674674                    // Reset button text after delay (use stored original text)
    675                     var btnText = originalButtonText || originalText.replace(/^• /, '');
     675                    const btnText = originalButtonText || originalText.replace(/^• /, '');
    676676                    resetButtonTimeout = setTimeout(function() {
    677677                        if ($submitBtn.is('input')) {
     
    686686                } else {
    687687                    alert('Error saving settings: ' + (response.data.message || 'Unknown error'));
    688                     var btnText = originalButtonText || originalText.replace(/^• /, '');
     688                    const resetText = originalButtonText || originalText.replace(/^• /, '');
    689689                    if ($submitBtn.is('input')) {
    690                         $submitBtn.val(btnText);
     690                        $submitBtn.val(resetText);
    691691                    } else {
    692                         $submitBtn.text(btnText);
     692                        $submitBtn.text(resetText);
    693693                    }
    694694                    // Reset bar button
     
    702702                $submitBtn.prop('disabled', false);
    703703                alert('Failed to save settings. Please try again.');
    704                 var btnText = originalButtonText || originalText.replace(/^• /, '');
     704                const btnText = originalButtonText || originalText.replace(/^• /, '');
    705705                if ($submitBtn.is('input')) {
    706706                    $submitBtn.val(btnText);
  • workzen-connector/trunk/assets/booking.js

    r3436487 r3447338  
    2020
    2121    // DOM elements
    22     let bookingModal, bookingOverlay, bookingCloseBtn, bookingForm;
     22    let _bookingModal, bookingOverlay, bookingCloseBtn, bookingForm;
    2323    let calendarDays, calendarMonth, calendarPrev, calendarNext;
    2424    let timeSlots, selectedDateDisplay, summaryJobName, summaryDateTime;
     
    3636        // Get DOM elements for modal
    3737        bookingOverlay = document.querySelector('.wzc-booking-modal-overlay');
    38         bookingModal = document.querySelector('.wzc-booking-modal');
     38        _bookingModal = document.querySelector('.wzc-booking-modal');
    3939        bookingCloseBtn = document.querySelector('.wzc-booking-close');
    4040        bookingForm = document.getElementById('wzc-booking-form');
     
    185185
    186186    function initEmbeddedScheduler(container) {
    187         // Get container-specific DOM elements
    188         const containerCalendarDays = container.querySelector('.wzc-calendar-days');
    189         const containerCalendarMonth = container.querySelector('.wzc-calendar-month');
     187        // Get container-specific DOM elements (some prefixed with _ as they're re-queried where needed)
     188        const _containerCalendarDays = container.querySelector('.wzc-calendar-days');
     189        const _containerCalendarMonth = container.querySelector('.wzc-calendar-month');
    190190        const containerCalendarPrev = container.querySelector('.wzc-calendar-prev');
    191191        const containerCalendarNext = container.querySelector('.wzc-calendar-next');
    192         const containerTimeSlots = container.querySelector('.wzc-time-slots');
    193         const containerSelectedDateDisplay = container.querySelector('.wzc-selected-date');
    194         const containerSummaryJobName = container.querySelector('.wzc-summary-job-name');
    195         const containerSummaryDateTime = container.querySelector('.wzc-summary-datetime-value');
    196         const containerBookingSteps = container.querySelectorAll('.wzc-booking-step');
     192        const _containerTimeSlots = container.querySelector('.wzc-time-slots');
     193        const _containerSelectedDateDisplay = container.querySelector('.wzc-selected-date');
     194        const _containerSummaryJobName = container.querySelector('.wzc-summary-job-name');
     195        const _containerSummaryDateTime = container.querySelector('.wzc-summary-datetime-value');
     196        const _containerBookingSteps = container.querySelectorAll('.wzc-booking-step');
    197197        const containerBookingForm = container.querySelector('.wzc-embedded-booking-form');
    198198
     
    325325        // Get max date based on booking window
    326326        const maxDate = new Date();
    327         const dateRange = parseInt(wzcBooking.date_range, 10) || 14;
     327        const dateRange = parseInt(wzcBooking.date_range, 10) || 365;
    328328        maxDate.setDate(maxDate.getDate() + dateRange);
    329329        maxDate.setHours(0, 0, 0, 0);
     
    804804        // Get max date based on booking window (date range)
    805805        const maxDate = new Date();
    806         const dateRange = parseInt(wzcBooking.date_range, 10) || 14;
     806        const dateRange = parseInt(wzcBooking.date_range, 10) || 365;
    807807        maxDate.setDate(maxDate.getDate() + dateRange);
    808808        maxDate.setHours(0, 0, 0, 0);
     
    957957
    958958        if (wzcBooking.respect_work_hours && dayHours && dayHours !== 'closed') {
    959             // Parse work hours
    960             const [startHourStr, startMinute] = dayHours.start.split(':');
    961             const [endHourStr, endMinute] = dayHours.end.split(':');
     959            // Parse work hours (minutes extracted for future granular support)
     960            const [startHourStr, _startMinute] = dayHours.start.split(':');
     961            const [endHourStr, _endMinute] = dayHours.end.split(':');
    962962            startHour = parseInt(startHourStr);
    963963            endHour = parseInt(endHourStr);
  • workzen-connector/trunk/assets/booking.min.js

    r3436487 r3447338  
    1 !function(){"use strict";const e={selectedJobGuid:null,selectedJobName:null,selectedDate:null,selectedTime:null,selectedTime24h:null,currentMonth:(new Date).getMonth(),currentYear:(new Date).getFullYear()};let t,o,n,c,r,s,a,l,i,d,u,m,w,g=[];function y(e,t){e.querySelectorAll(".wzc-booking-step").forEach((e=>{e.getAttribute("data-step")===t?e.classList.add("active"):e.classList.remove("active")}))}function p(e,t){const o=e.querySelector(".wzc-calendar-days"),n=e.querySelector(".wzc-calendar-month"),c=e.querySelector(".wzc-calendar-prev"),r=e.querySelector(".wzc-calendar-next");if(!o||!n)return;const s=t.currentYear,a=t.currentMonth;n.textContent=`${["January","February","March","April","May","June","July","August","September","October","November","December"][a]} ${s}`,o.innerHTML="";const l=new Date(s,a,1).getDay(),i=new Date(s,a+1,0).getDate(),d=new Date;d.setHours(0,0,0,0);const u=new Date,m=parseInt(wzcBooking.date_range,10)||14;u.setDate(u.getDate()+m),u.setHours(0,0,0,0),function(e,t,o,n,c,r){if(!e||!t)return;const s=new Date(o,n,1),a=new Date(c.getFullYear(),c.getMonth(),1),l=new Date(r.getFullYear(),r.getMonth(),1);s<=a?(e.disabled=!0,e.style.opacity="0.5",e.style.cursor="not-allowed"):(e.disabled=!1,e.style.opacity="1",e.style.cursor="pointer");s>=l?(t.disabled=!0,t.style.opacity="0.5",t.style.cursor="not-allowed"):(t.disabled=!1,t.style.opacity="1",t.style.cursor="pointer")}(c,r,s,a,d,u);for(let e=0;e<l;e++){const e=document.createElement("div");e.className="wzc-calendar-day empty",o.appendChild(e)}for(let n=1;n<=i;n++){const c=new Date(s,a,n);c.setHours(0,0,0,0);const r=document.createElement("div");r.className="wzc-calendar-day",r.textContent=n,c<d||c>u||wzcBooking.respect_work_hours&&!S(c)?r.classList.add("disabled"):r.addEventListener("click",(function(){v(e,t,c,r)})),c.getTime()===d.getTime()&&r.classList.add("today"),o.appendChild(r)}}function h(e,t,o){const n=e.querySelector(".wzc-calendar-prev"),c=e.querySelector(".wzc-calendar-next");o<0&&n&&n.disabled||o>0&&c&&c.disabled||(t.currentMonth+=o,t.currentMonth>11?(t.currentMonth=0,t.currentYear++):t.currentMonth<0&&(t.currentMonth=11,t.currentYear--),p(e,t))}function v(e,t,o,n){e.querySelectorAll(".wzc-calendar-day.selected").forEach((e=>{e.classList.remove("selected")})),n.classList.add("selected"),t.selectedDate=o,function(e,t,o){const n=e.querySelector(".wzc-time-slots"),c=e.querySelector(".wzc-selected-date");if(!n||!c)return;const r={weekday:"long",year:"numeric",month:"long",day:"numeric"};c.textContent=o.toLocaleDateString("en-US",r),n.innerHTML="";const s=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"][o.getDay()],a=wzcBooking.work_hours[s];let l,i;if(wzcBooking.respect_work_hours&&a&&"closed"!==a){const[e]=a.start.split(":"),[t]=a.end.split(":");l=parseInt(e),i=parseInt(t)}else{if(wzcBooking.respect_work_hours)return void(n.innerHTML='<p class="wzc-no-slots">No time slots available for this day.</p>');l=0,i=24}const d=wzcBooking.time_interval,u=60*(i-l)/d;for(let o=0;o<u;o++){const c=60*l+o*d,r=Math.floor(c/60),s=c%60,a=C(r,s),i=`${r.toString().padStart(2,"0")}:${s.toString().padStart(2,"0")}`,u=document.createElement("div");u.className="wzc-time-slot",u.textContent=a,u.setAttribute("data-time",a),u.setAttribute("data-time-24h",i),u.addEventListener("click",(function(){b(e,t,a,i,u)})),n.appendChild(u)}}(e,t,o),setTimeout((()=>{y(e,"time")}),300)}function b(e,t,o,n,c){e.querySelectorAll(".wzc-time-slot.selected").forEach((e=>{e.classList.remove("selected")})),c.classList.add("selected"),t.selectedTime=o,t.selectedTime24h=n,setTimeout((()=>{!function(e,t){const o=e.querySelector(".wzc-summary-job"),n=e.querySelector(".wzc-summary-job-name"),c=e.querySelector(".wzc-summary-datetime-value");t.selectedJobName&&n&&o?(n.textContent=t.selectedJobName,o.style.display="block"):o&&(o.style.display="none");if(t.selectedDate&&t.selectedTime&&c){const e={weekday:"short",month:"short",day:"numeric",year:"numeric"},o=t.selectedDate.toLocaleDateString("en-US",e);c.textContent=`${o} at ${t.selectedTime}`}}(e,t),y(e,"contact")}),300)}function k(e,t,o,n){const c=e.querySelector(".wzc-embedded-scheduler-body");if(!c)return;const r=c.querySelector(".wzc-message");r&&r.remove();const s=document.createElement("div");if(s.className=`wzc-message ${o}`,n){const e=document.createElement("h3");e.textContent=n,s.appendChild(e)}const a=document.createElement("p");a.textContent=t,s.appendChild(a),c.insertBefore(s,c.firstChild),"error"===o&&setTimeout((()=>{s.remove()}),5e3)}function f(){if(o){o.classList.remove("active"),document.body.style.overflow="",function(){e.selectedJobGuid=null,e.selectedJobName=null,e.selectedDate=null,e.selectedTime=null,e.selectedTime24h=null,c&&c.reset();const t=document.querySelector(".wzc-booking-modal .wzc-message");t&&t.remove();document.querySelectorAll(".wzc-booking-step").forEach((e=>{e.style.display=""})),document.querySelectorAll(".wzc-job-type-card.selected").forEach((e=>{e.classList.remove("selected")})),document.querySelectorAll(".wzc-calendar-day.selected").forEach((e=>{e.classList.remove("selected")})),document.querySelectorAll(".wzc-time-slot.selected").forEach((e=>{e.classList.remove("selected")}))}();const t=document.querySelector(".wzc-floating-container"),n=document.querySelector(".wzc-backdrop"),r=document.querySelector(".wzc-main-toggle");if(t&&t.classList.remove("expanded"),r){r.classList.remove("active");const e=r.querySelector(".wzc-toggle-icon"),t=r.getAttribute("data-icon")||"plus";if(e){const o={plus:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',menu:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',"dots-vertical":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>',"dots-horizontal":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>',phone:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>',message:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',chat:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>',help:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',star:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>',heart:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',settings:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>',"chevron-up":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',"chat-dots":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/></svg>'};o[t]&&(e.innerHTML=o[t])}}n&&n.classList.remove("active")}}function z(e){w.forEach((t=>{t.getAttribute("data-step")===e?t.classList.add("active"):t.classList.remove("active")}))}function x(){if(!r||!s)return;const t=e.currentYear,o=e.currentMonth;s.textContent=`${["January","February","March","April","May","June","July","August","September","October","November","December"][o]} ${t}`,r.innerHTML="";const n=new Date(t,o,1).getDay(),c=new Date(t,o+1,0).getDate(),i=new Date;i.setHours(0,0,0,0);const d=new Date,u=parseInt(wzcBooking.date_range,10)||14;d.setDate(d.getDate()+u),d.setHours(0,0,0,0),function(e,t,o,n){if(!a||!l)return;const c=new Date(e,t,1),r=new Date(o.getFullYear(),o.getMonth(),1),s=new Date(n.getFullYear(),n.getMonth(),1);c<=r?(a.disabled=!0,a.style.opacity="0.5",a.style.cursor="not-allowed"):(a.disabled=!1,a.style.opacity="1",a.style.cursor="pointer");c>=s?(l.disabled=!0,l.style.opacity="0.5",l.style.cursor="not-allowed"):(l.disabled=!1,l.style.opacity="1",l.style.cursor="pointer")}(t,o,i,d);for(let e=0;e<n;e++){const e=document.createElement("div");e.className="wzc-calendar-day empty",r.appendChild(e)}for(let e=1;e<=c;e++){const n=new Date(t,o,e);n.setHours(0,0,0,0);const c=document.createElement("div");c.className="wzc-calendar-day",c.textContent=e,n<i||n>d||wzcBooking.respect_work_hours&&!S(n)?c.classList.add("disabled"):c.addEventListener("click",(function(){q(n,c)})),n.getTime()===i.getTime()&&c.classList.add("today"),r.appendChild(c)}}function S(e){const t=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"][e.getDay()],o=wzcBooking.work_hours[t];return o&&"closed"!==o}function L(t){t<0&&a&&a.disabled||t>0&&l&&l.disabled||(e.currentMonth+=t,e.currentMonth>11?(e.currentMonth=0,e.currentYear++):e.currentMonth<0&&(e.currentMonth=11,e.currentYear--),x())}function q(t,o){document.querySelectorAll(".wzc-calendar-day.selected").forEach((e=>{e.classList.remove("selected")})),o.classList.add("selected"),e.selectedDate=t,function(e){if(!i||!d)return;const t={weekday:"long",year:"numeric",month:"long",day:"numeric"};d.textContent=e.toLocaleDateString("en-US",t),i.innerHTML="";const o=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"][e.getDay()],n=wzcBooking.work_hours[o];let c,r;if(wzcBooking.respect_work_hours&&n&&"closed"!==n){const[e,t]=n.start.split(":"),[o,s]=n.end.split(":");c=parseInt(e),r=parseInt(o)}else{if(wzcBooking.respect_work_hours)return void(i.innerHTML='<p class="wzc-no-slots">No time slots available for this day.</p>');c=0,r=24}const s=wzcBooking.time_interval,a=60*(r-c)/s;for(let e=0;e<a;e++){const t=60*c+e*s,o=Math.floor(t/60),n=t%60,r=C(o,n),a=`${o.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`,l=document.createElement("div");l.className="wzc-time-slot",l.textContent=r,l.setAttribute("data-time",r),l.setAttribute("data-time-24h",a),l.addEventListener("click",(function(){D(r,a,l)})),i.appendChild(l)}}(t),setTimeout((()=>{z("time")}),300)}function C(e,t){const o=e>=12?"PM":"AM";return`${0===e?12:e>12?e-12:e}:${t.toString().padStart(2,"0")} ${o}`}function D(t,o,n){document.querySelectorAll(".wzc-time-slot.selected").forEach((e=>{e.classList.remove("selected")})),n.classList.add("selected"),e.selectedTime=t,e.selectedTime24h=o,setTimeout((()=>{!function(){e.selectedJobName?(u.textContent=e.selectedJobName,document.querySelector(".wzc-summary-job").style.display="block"):document.querySelector(".wzc-summary-job").style.display="none";if(e.selectedDate&&e.selectedTime){const t={weekday:"short",month:"short",day:"numeric",year:"numeric"},o=e.selectedDate.toLocaleDateString("en-US",t);m.textContent=`${o} at ${e.selectedTime}`}}(),z("contact")}),300)}function E(t){t.preventDefault();const o=c.querySelector(".wzc-submit-btn"),n=new FormData(c),r=n.get("name"),s=n.get("email"),a=n.get("phone");if(!r||!s||!a)return void M("Please fill in all required fields.","error");if(!e.selectedDate||!e.selectedTime)return void M("Please select a date and time.","error");o.disabled=!0,o.classList.add("loading"),o.textContent="Submitting...";const l=new FormData;l.append("action","wzconnector_submit_booking"),l.append("nonce",wzcBooking.nonce),l.append("name",r),l.append("email",s),l.append("phone",a),l.append("address",n.get("address")||""),l.append("city",n.get("city")||""),l.append("zip",n.get("zip")||""),l.append("notes",n.get("notes")||"");const i=e.selectedDate,d=i.getFullYear()+"-"+String(i.getMonth()+1).padStart(2,"0")+"-"+String(i.getDate()).padStart(2,"0");l.append("booking_date",d),l.append("booking_time",e.selectedTime24h),e.selectedJobGuid&&(l.append("job_guid",e.selectedJobGuid),l.append("job_name",e.selectedJobName)),fetch(wzcBooking.ajax_url,{method:"POST",body:l}).then((e=>e.json())).then((e=>{o.disabled=!1,o.classList.remove("loading"),o.textContent="Confirm Booking",e.success?(M(wzcBooking.success_message,"success",wzcBooking.success_title),document.querySelectorAll(".wzc-booking-step").forEach((e=>{e.style.display="none"})),c.reset(),setTimeout((()=>{f()}),3e3)):M(e.data.message||"An error occurred. Please try again.","error")})).catch((e=>{console.error("Booking error:",e),o.disabled=!1,o.classList.remove("loading"),o.textContent="Confirm Booking",M("An error occurred. Please try again.","error")}))}function M(e,t,o){const n=document.querySelector(".wzc-booking-modal .wzc-modal-body");if(!n)return;const c=n.querySelector(".wzc-message");c&&c.remove();const r=document.createElement("div");if(r.className=`wzc-message ${t}`,o){const e=document.createElement("h3");e.textContent=o,r.appendChild(e)}const s=document.createElement("p");s.textContent=e,r.appendChild(s),n.insertBefore(r,n.firstChild),"error"===t&&setTimeout((()=>{r.remove()}),5e3)}document.addEventListener("DOMContentLoaded",(function(){!function(){o=document.querySelector(".wzc-booking-modal-overlay"),t=document.querySelector(".wzc-booking-modal"),n=document.querySelector(".wzc-booking-close"),c=document.getElementById("wzc-booking-form"),g=document.querySelectorAll(".wzc-embedded-scheduler-container");const v=o&&c,b=g.length>0;if(!v&&!b)return;v&&function(){r=document.querySelector(".wzc-booking-modal .wzc-calendar-days"),s=document.querySelector(".wzc-booking-modal .wzc-calendar-month"),a=document.querySelector(".wzc-booking-modal .wzc-calendar-prev"),l=document.querySelector(".wzc-booking-modal .wzc-calendar-next"),i=document.querySelector(".wzc-booking-modal .wzc-time-slots"),d=document.querySelector(".wzc-booking-modal .wzc-selected-date"),u=document.querySelector(".wzc-booking-modal .wzc-summary-job-name"),m=document.querySelector(".wzc-booking-modal .wzc-summary-datetime-value"),w=document.querySelectorAll(".wzc-booking-modal .wzc-booking-step");const t=document.querySelector(".wzc-action-btn.booking");t&&t.addEventListener("click",(function(e){e.preventDefault();const t=document.querySelector(".wzc-floating-container"),n=document.querySelector(".wzc-backdrop"),c=document.querySelector(".wzc-main-toggle");if(t&&t.classList.remove("expanded"),c){c.classList.remove("active");const e=c.querySelector(".wzc-toggle-icon"),t=c.getAttribute("data-icon")||"plus";if(e){const o={plus:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',menu:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',"dots-vertical":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>',"dots-horizontal":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>',"chevron-up":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',"chat-dots":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/></svg>'};setTimeout((function(){o[t]&&(e.innerHTML=o[t])}),150)}}n&&n.classList.remove("active"),function(){if(o){o.classList.add("active"),document.body.style.overflow="hidden";z(wzcBooking.show_job_types?"job":"date")}}()}));n&&n.addEventListener("click",f);o&&o.addEventListener("click",(function(e){e.target===o&&f()}));document.querySelectorAll(".wzc-job-type-card").forEach((t=>{t.addEventListener("click",(function(){!function(t){document.querySelectorAll(".wzc-job-type-card.selected").forEach((e=>{e.classList.remove("selected")})),t.classList.add("selected"),e.selectedJobGuid=t.getAttribute("data-job-guid"),e.selectedJobName=t.querySelector("h4").textContent,setTimeout((()=>{z("date")}),300)}(this)}))}));document.querySelectorAll(".wzc-back-btn").forEach((e=>{e.addEventListener("click",(function(){!function(e){switch(e){case"back-to-job":z("job");break;case"back-to-date":case"skip-job":z("date");break;case"back-to-time":z("time")}}(this.getAttribute("data-action"))}))}));const g=document.querySelector(".wzc-skip-btn");g&&g.addEventListener("click",(function(){z("date")}));a&&a.addEventListener("click",(function(){L(-1)}));l&&l.addEventListener("click",(function(){L(1)}));c&&c.addEventListener("submit",E);x()}();b&&g.forEach((function(e){!function(e){e.querySelector(".wzc-calendar-days"),e.querySelector(".wzc-calendar-month");const t=e.querySelector(".wzc-calendar-prev"),o=e.querySelector(".wzc-calendar-next"),n=(e.querySelector(".wzc-time-slots"),e.querySelector(".wzc-selected-date"),e.querySelector(".wzc-summary-job-name"),e.querySelector(".wzc-summary-datetime-value"),e.querySelectorAll(".wzc-booking-step"),e.querySelector(".wzc-embedded-booking-form")),c={selectedJobGuid:null,selectedJobName:null,selectedDate:null,selectedTime:null,selectedTime24h:null,currentMonth:(new Date).getMonth(),currentYear:(new Date).getFullYear()},r=e.querySelectorAll(".wzc-job-type-card");r.forEach((t=>{t.addEventListener("click",(function(){e.querySelectorAll(".wzc-job-type-card.selected").forEach((e=>{e.classList.remove("selected")})),t.classList.add("selected"),c.selectedJobGuid=t.getAttribute("data-job-guid"),c.selectedJobName=t.querySelector("h4").textContent,setTimeout((()=>{y(e,"date")}),300)}))}));const s=e.querySelectorAll(".wzc-back-btn");s.forEach((t=>{t.addEventListener("click",(function(){const t=this.getAttribute("data-action");!function(e,t){switch(t){case"back-to-job":y(e,"job");break;case"back-to-date":case"skip-job":y(e,"date");break;case"back-to-time":y(e,"time")}}(e,t)}))}));const a=e.querySelector(".wzc-skip-btn");a&&a.addEventListener("click",(function(){y(e,"date")})),t&&t.addEventListener("click",(function(){h(e,c,-1)})),o&&o.addEventListener("click",(function(){h(e,c,1)})),n&&n.addEventListener("submit",(function(t){!function(e,t,o){e.preventDefault();const n=e.target,c=n.querySelector(".wzc-submit-btn"),r=new FormData(n),s=r.get("name"),a=r.get("email"),l=r.get("phone");if(!s||!a||!l)return void k(t,"Please fill in all required fields.","error");if(!o.selectedDate||!o.selectedTime)return void k(t,"Please select a date and time.","error");c.disabled=!0,c.classList.add("loading");const i=c.textContent;c.textContent="Submitting...";const d=new FormData;d.append("action","wzconnector_submit_booking"),d.append("nonce",wzcBooking.nonce),d.append("name",s),d.append("email",a),d.append("phone",l),d.append("address",r.get("address")||""),d.append("city",r.get("city")||""),d.append("zip",r.get("zip")||""),d.append("notes",r.get("notes")||"");const u=o.selectedDate,m=u.getFullYear()+"-"+String(u.getMonth()+1).padStart(2,"0")+"-"+String(u.getDate()).padStart(2,"0");d.append("booking_date",m),d.append("booking_time",o.selectedTime24h),o.selectedJobGuid&&(d.append("job_guid",o.selectedJobGuid),d.append("job_name",o.selectedJobName)),fetch(wzcBooking.ajax_url,{method:"POST",body:d}).then((e=>e.json())).then((e=>{c.disabled=!1,c.classList.remove("loading"),c.textContent=i,e.success?(k(t,wzcBooking.success_message,"success",wzcBooking.success_title),t.querySelectorAll(".wzc-booking-step").forEach((e=>{e.style.display="none"})),n.reset()):k(t,e.data.message||"An error occurred. Please try again.","error")})).catch((e=>{console.error("Booking error:",e),c.disabled=!1,c.classList.remove("loading"),c.textContent=i,k(t,"An error occurred. Please try again.","error")}))}(t,e,c)})),p(e,c)}(e)}))}()}))}();
     1!function(){"use strict";const e={selectedJobGuid:null,selectedJobName:null,selectedDate:null,selectedTime:null,selectedTime24h:null,currentMonth:(new Date).getMonth(),currentYear:(new Date).getFullYear()};let t,o,n,c,r,s,a,l,i,d,u,m,w,g=[];function y(e,t){e.querySelectorAll(".wzc-booking-step").forEach((e=>{e.getAttribute("data-step")===t?e.classList.add("active"):e.classList.remove("active")}))}function p(e,t){const o=e.querySelector(".wzc-calendar-days"),n=e.querySelector(".wzc-calendar-month"),c=e.querySelector(".wzc-calendar-prev"),r=e.querySelector(".wzc-calendar-next");if(!o||!n)return;const s=t.currentYear,a=t.currentMonth;n.textContent=`${["January","February","March","April","May","June","July","August","September","October","November","December"][a]} ${s}`,o.innerHTML="";const l=new Date(s,a,1).getDay(),i=new Date(s,a+1,0).getDate(),d=new Date;d.setHours(0,0,0,0);const u=new Date,m=parseInt(wzcBooking.date_range,10)||365;u.setDate(u.getDate()+m),u.setHours(0,0,0,0),function(e,t,o,n,c,r){if(!e||!t)return;const s=new Date(o,n,1),a=new Date(c.getFullYear(),c.getMonth(),1),l=new Date(r.getFullYear(),r.getMonth(),1);s<=a?(e.disabled=!0,e.style.opacity="0.5",e.style.cursor="not-allowed"):(e.disabled=!1,e.style.opacity="1",e.style.cursor="pointer");s>=l?(t.disabled=!0,t.style.opacity="0.5",t.style.cursor="not-allowed"):(t.disabled=!1,t.style.opacity="1",t.style.cursor="pointer")}(c,r,s,a,d,u);for(let e=0;e<l;e++){const e=document.createElement("div");e.className="wzc-calendar-day empty",o.appendChild(e)}for(let n=1;n<=i;n++){const c=new Date(s,a,n);c.setHours(0,0,0,0);const r=document.createElement("div");r.className="wzc-calendar-day",r.textContent=n,c<d||c>u||wzcBooking.respect_work_hours&&!S(c)?r.classList.add("disabled"):r.addEventListener("click",(function(){v(e,t,c,r)})),c.getTime()===d.getTime()&&r.classList.add("today"),o.appendChild(r)}}function h(e,t,o){const n=e.querySelector(".wzc-calendar-prev"),c=e.querySelector(".wzc-calendar-next");o<0&&n&&n.disabled||o>0&&c&&c.disabled||(t.currentMonth+=o,t.currentMonth>11?(t.currentMonth=0,t.currentYear++):t.currentMonth<0&&(t.currentMonth=11,t.currentYear--),p(e,t))}function v(e,t,o,n){e.querySelectorAll(".wzc-calendar-day.selected").forEach((e=>{e.classList.remove("selected")})),n.classList.add("selected"),t.selectedDate=o,function(e,t,o){const n=e.querySelector(".wzc-time-slots"),c=e.querySelector(".wzc-selected-date");if(!n||!c)return;const r={weekday:"long",year:"numeric",month:"long",day:"numeric"};c.textContent=o.toLocaleDateString("en-US",r),n.innerHTML="";const s=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"][o.getDay()],a=wzcBooking.work_hours[s];let l,i;if(wzcBooking.respect_work_hours&&a&&"closed"!==a){const[e]=a.start.split(":"),[t]=a.end.split(":");l=parseInt(e),i=parseInt(t)}else{if(wzcBooking.respect_work_hours)return void(n.innerHTML='<p class="wzc-no-slots">No time slots available for this day.</p>');l=0,i=24}const d=wzcBooking.time_interval,u=60*(i-l)/d;for(let o=0;o<u;o++){const c=60*l+o*d,r=Math.floor(c/60),s=c%60,a=C(r,s),i=`${r.toString().padStart(2,"0")}:${s.toString().padStart(2,"0")}`,u=document.createElement("div");u.className="wzc-time-slot",u.textContent=a,u.setAttribute("data-time",a),u.setAttribute("data-time-24h",i),u.addEventListener("click",(function(){b(e,t,a,i,u)})),n.appendChild(u)}}(e,t,o),setTimeout((()=>{y(e,"time")}),300)}function b(e,t,o,n,c){e.querySelectorAll(".wzc-time-slot.selected").forEach((e=>{e.classList.remove("selected")})),c.classList.add("selected"),t.selectedTime=o,t.selectedTime24h=n,setTimeout((()=>{!function(e,t){const o=e.querySelector(".wzc-summary-job"),n=e.querySelector(".wzc-summary-job-name"),c=e.querySelector(".wzc-summary-datetime-value");t.selectedJobName&&n&&o?(n.textContent=t.selectedJobName,o.style.display="block"):o&&(o.style.display="none");if(t.selectedDate&&t.selectedTime&&c){const e={weekday:"short",month:"short",day:"numeric",year:"numeric"},o=t.selectedDate.toLocaleDateString("en-US",e);c.textContent=`${o} at ${t.selectedTime}`}}(e,t),y(e,"contact")}),300)}function k(e,t,o,n){const c=e.querySelector(".wzc-embedded-scheduler-body");if(!c)return;const r=c.querySelector(".wzc-message");r&&r.remove();const s=document.createElement("div");if(s.className=`wzc-message ${o}`,n){const e=document.createElement("h3");e.textContent=n,s.appendChild(e)}const a=document.createElement("p");a.textContent=t,s.appendChild(a),c.insertBefore(s,c.firstChild),"error"===o&&setTimeout((()=>{s.remove()}),5e3)}function f(){if(o){o.classList.remove("active"),document.body.style.overflow="",function(){e.selectedJobGuid=null,e.selectedJobName=null,e.selectedDate=null,e.selectedTime=null,e.selectedTime24h=null,c&&c.reset();const t=document.querySelector(".wzc-booking-modal .wzc-message");t&&t.remove();document.querySelectorAll(".wzc-booking-step").forEach((e=>{e.style.display=""})),document.querySelectorAll(".wzc-job-type-card.selected").forEach((e=>{e.classList.remove("selected")})),document.querySelectorAll(".wzc-calendar-day.selected").forEach((e=>{e.classList.remove("selected")})),document.querySelectorAll(".wzc-time-slot.selected").forEach((e=>{e.classList.remove("selected")}))}();const t=document.querySelector(".wzc-floating-container"),n=document.querySelector(".wzc-backdrop"),r=document.querySelector(".wzc-main-toggle");if(t&&t.classList.remove("expanded"),r){r.classList.remove("active");const e=r.querySelector(".wzc-toggle-icon"),t=r.getAttribute("data-icon")||"plus";if(e){const o={plus:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',menu:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',"dots-vertical":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>',"dots-horizontal":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>',phone:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>',message:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',chat:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>',help:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',star:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>',heart:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',settings:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>',"chevron-up":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',"chat-dots":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/></svg>'};o[t]&&(e.innerHTML=o[t])}}n&&n.classList.remove("active")}}function z(e){w.forEach((t=>{t.getAttribute("data-step")===e?t.classList.add("active"):t.classList.remove("active")}))}function x(){if(!r||!s)return;const t=e.currentYear,o=e.currentMonth;s.textContent=`${["January","February","March","April","May","June","July","August","September","October","November","December"][o]} ${t}`,r.innerHTML="";const n=new Date(t,o,1).getDay(),c=new Date(t,o+1,0).getDate(),i=new Date;i.setHours(0,0,0,0);const d=new Date,u=parseInt(wzcBooking.date_range,10)||365;d.setDate(d.getDate()+u),d.setHours(0,0,0,0),function(e,t,o,n){if(!a||!l)return;const c=new Date(e,t,1),r=new Date(o.getFullYear(),o.getMonth(),1),s=new Date(n.getFullYear(),n.getMonth(),1);c<=r?(a.disabled=!0,a.style.opacity="0.5",a.style.cursor="not-allowed"):(a.disabled=!1,a.style.opacity="1",a.style.cursor="pointer");c>=s?(l.disabled=!0,l.style.opacity="0.5",l.style.cursor="not-allowed"):(l.disabled=!1,l.style.opacity="1",l.style.cursor="pointer")}(t,o,i,d);for(let e=0;e<n;e++){const e=document.createElement("div");e.className="wzc-calendar-day empty",r.appendChild(e)}for(let e=1;e<=c;e++){const n=new Date(t,o,e);n.setHours(0,0,0,0);const c=document.createElement("div");c.className="wzc-calendar-day",c.textContent=e,n<i||n>d||wzcBooking.respect_work_hours&&!S(n)?c.classList.add("disabled"):c.addEventListener("click",(function(){q(n,c)})),n.getTime()===i.getTime()&&c.classList.add("today"),r.appendChild(c)}}function S(e){const t=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"][e.getDay()],o=wzcBooking.work_hours[t];return o&&"closed"!==o}function L(t){t<0&&a&&a.disabled||t>0&&l&&l.disabled||(e.currentMonth+=t,e.currentMonth>11?(e.currentMonth=0,e.currentYear++):e.currentMonth<0&&(e.currentMonth=11,e.currentYear--),x())}function q(t,o){document.querySelectorAll(".wzc-calendar-day.selected").forEach((e=>{e.classList.remove("selected")})),o.classList.add("selected"),e.selectedDate=t,function(e){if(!i||!d)return;const t={weekday:"long",year:"numeric",month:"long",day:"numeric"};d.textContent=e.toLocaleDateString("en-US",t),i.innerHTML="";const o=["sunday","monday","tuesday","wednesday","thursday","friday","saturday"][e.getDay()],n=wzcBooking.work_hours[o];let c,r;if(wzcBooking.respect_work_hours&&n&&"closed"!==n){const[e,t]=n.start.split(":"),[o,s]=n.end.split(":");c=parseInt(e),r=parseInt(o)}else{if(wzcBooking.respect_work_hours)return void(i.innerHTML='<p class="wzc-no-slots">No time slots available for this day.</p>');c=0,r=24}const s=wzcBooking.time_interval,a=60*(r-c)/s;for(let e=0;e<a;e++){const t=60*c+e*s,o=Math.floor(t/60),n=t%60,r=C(o,n),a=`${o.toString().padStart(2,"0")}:${n.toString().padStart(2,"0")}`,l=document.createElement("div");l.className="wzc-time-slot",l.textContent=r,l.setAttribute("data-time",r),l.setAttribute("data-time-24h",a),l.addEventListener("click",(function(){D(r,a,l)})),i.appendChild(l)}}(t),setTimeout((()=>{z("time")}),300)}function C(e,t){const o=e>=12?"PM":"AM";return`${0===e?12:e>12?e-12:e}:${t.toString().padStart(2,"0")} ${o}`}function D(t,o,n){document.querySelectorAll(".wzc-time-slot.selected").forEach((e=>{e.classList.remove("selected")})),n.classList.add("selected"),e.selectedTime=t,e.selectedTime24h=o,setTimeout((()=>{!function(){e.selectedJobName?(u.textContent=e.selectedJobName,document.querySelector(".wzc-summary-job").style.display="block"):document.querySelector(".wzc-summary-job").style.display="none";if(e.selectedDate&&e.selectedTime){const t={weekday:"short",month:"short",day:"numeric",year:"numeric"},o=e.selectedDate.toLocaleDateString("en-US",t);m.textContent=`${o} at ${e.selectedTime}`}}(),z("contact")}),300)}function E(t){t.preventDefault();const o=c.querySelector(".wzc-submit-btn"),n=new FormData(c),r=n.get("name"),s=n.get("email"),a=n.get("phone");if(!r||!s||!a)return void M("Please fill in all required fields.","error");if(!e.selectedDate||!e.selectedTime)return void M("Please select a date and time.","error");o.disabled=!0,o.classList.add("loading"),o.textContent="Submitting...";const l=new FormData;l.append("action","wzconnector_submit_booking"),l.append("nonce",wzcBooking.nonce),l.append("name",r),l.append("email",s),l.append("phone",a),l.append("address",n.get("address")||""),l.append("city",n.get("city")||""),l.append("zip",n.get("zip")||""),l.append("notes",n.get("notes")||"");const i=e.selectedDate,d=i.getFullYear()+"-"+String(i.getMonth()+1).padStart(2,"0")+"-"+String(i.getDate()).padStart(2,"0");l.append("booking_date",d),l.append("booking_time",e.selectedTime24h),e.selectedJobGuid&&(l.append("job_guid",e.selectedJobGuid),l.append("job_name",e.selectedJobName)),fetch(wzcBooking.ajax_url,{method:"POST",body:l}).then((e=>e.json())).then((e=>{o.disabled=!1,o.classList.remove("loading"),o.textContent="Confirm Booking",e.success?(M(wzcBooking.success_message,"success",wzcBooking.success_title),document.querySelectorAll(".wzc-booking-step").forEach((e=>{e.style.display="none"})),c.reset(),setTimeout((()=>{f()}),3e3)):M(e.data.message||"An error occurred. Please try again.","error")})).catch((e=>{console.error("Booking error:",e),o.disabled=!1,o.classList.remove("loading"),o.textContent="Confirm Booking",M("An error occurred. Please try again.","error")}))}function M(e,t,o){const n=document.querySelector(".wzc-booking-modal .wzc-modal-body");if(!n)return;const c=n.querySelector(".wzc-message");c&&c.remove();const r=document.createElement("div");if(r.className=`wzc-message ${t}`,o){const e=document.createElement("h3");e.textContent=o,r.appendChild(e)}const s=document.createElement("p");s.textContent=e,r.appendChild(s),n.insertBefore(r,n.firstChild),"error"===t&&setTimeout((()=>{r.remove()}),5e3)}document.addEventListener("DOMContentLoaded",(function(){!function(){o=document.querySelector(".wzc-booking-modal-overlay"),t=document.querySelector(".wzc-booking-modal"),n=document.querySelector(".wzc-booking-close"),c=document.getElementById("wzc-booking-form"),g=document.querySelectorAll(".wzc-embedded-scheduler-container");const v=o&&c,b=g.length>0;if(!v&&!b)return;v&&function(){r=document.querySelector(".wzc-booking-modal .wzc-calendar-days"),s=document.querySelector(".wzc-booking-modal .wzc-calendar-month"),a=document.querySelector(".wzc-booking-modal .wzc-calendar-prev"),l=document.querySelector(".wzc-booking-modal .wzc-calendar-next"),i=document.querySelector(".wzc-booking-modal .wzc-time-slots"),d=document.querySelector(".wzc-booking-modal .wzc-selected-date"),u=document.querySelector(".wzc-booking-modal .wzc-summary-job-name"),m=document.querySelector(".wzc-booking-modal .wzc-summary-datetime-value"),w=document.querySelectorAll(".wzc-booking-modal .wzc-booking-step");const t=document.querySelector(".wzc-action-btn.booking");t&&t.addEventListener("click",(function(e){e.preventDefault();const t=document.querySelector(".wzc-floating-container"),n=document.querySelector(".wzc-backdrop"),c=document.querySelector(".wzc-main-toggle");if(t&&t.classList.remove("expanded"),c){c.classList.remove("active");const e=c.querySelector(".wzc-toggle-icon"),t=c.getAttribute("data-icon")||"plus";if(e){const o={plus:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',menu:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',"dots-vertical":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>',"dots-horizontal":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>',"chevron-up":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',"chat-dots":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/></svg>'};setTimeout((function(){o[t]&&(e.innerHTML=o[t])}),150)}}n&&n.classList.remove("active"),function(){if(o){o.classList.add("active"),document.body.style.overflow="hidden";z(wzcBooking.show_job_types?"job":"date")}}()}));n&&n.addEventListener("click",f);o&&o.addEventListener("click",(function(e){e.target===o&&f()}));document.querySelectorAll(".wzc-job-type-card").forEach((t=>{t.addEventListener("click",(function(){!function(t){document.querySelectorAll(".wzc-job-type-card.selected").forEach((e=>{e.classList.remove("selected")})),t.classList.add("selected"),e.selectedJobGuid=t.getAttribute("data-job-guid"),e.selectedJobName=t.querySelector("h4").textContent,setTimeout((()=>{z("date")}),300)}(this)}))}));document.querySelectorAll(".wzc-back-btn").forEach((e=>{e.addEventListener("click",(function(){!function(e){switch(e){case"back-to-job":z("job");break;case"back-to-date":case"skip-job":z("date");break;case"back-to-time":z("time")}}(this.getAttribute("data-action"))}))}));const g=document.querySelector(".wzc-skip-btn");g&&g.addEventListener("click",(function(){z("date")}));a&&a.addEventListener("click",(function(){L(-1)}));l&&l.addEventListener("click",(function(){L(1)}));c&&c.addEventListener("submit",E);x()}();b&&g.forEach((function(e){!function(e){e.querySelector(".wzc-calendar-days"),e.querySelector(".wzc-calendar-month");const t=e.querySelector(".wzc-calendar-prev"),o=e.querySelector(".wzc-calendar-next"),n=(e.querySelector(".wzc-time-slots"),e.querySelector(".wzc-selected-date"),e.querySelector(".wzc-summary-job-name"),e.querySelector(".wzc-summary-datetime-value"),e.querySelectorAll(".wzc-booking-step"),e.querySelector(".wzc-embedded-booking-form")),c={selectedJobGuid:null,selectedJobName:null,selectedDate:null,selectedTime:null,selectedTime24h:null,currentMonth:(new Date).getMonth(),currentYear:(new Date).getFullYear()},r=e.querySelectorAll(".wzc-job-type-card");r.forEach((t=>{t.addEventListener("click",(function(){e.querySelectorAll(".wzc-job-type-card.selected").forEach((e=>{e.classList.remove("selected")})),t.classList.add("selected"),c.selectedJobGuid=t.getAttribute("data-job-guid"),c.selectedJobName=t.querySelector("h4").textContent,setTimeout((()=>{y(e,"date")}),300)}))}));const s=e.querySelectorAll(".wzc-back-btn");s.forEach((t=>{t.addEventListener("click",(function(){const t=this.getAttribute("data-action");!function(e,t){switch(t){case"back-to-job":y(e,"job");break;case"back-to-date":case"skip-job":y(e,"date");break;case"back-to-time":y(e,"time")}}(e,t)}))}));const a=e.querySelector(".wzc-skip-btn");a&&a.addEventListener("click",(function(){y(e,"date")})),t&&t.addEventListener("click",(function(){h(e,c,-1)})),o&&o.addEventListener("click",(function(){h(e,c,1)})),n&&n.addEventListener("submit",(function(t){!function(e,t,o){e.preventDefault();const n=e.target,c=n.querySelector(".wzc-submit-btn"),r=new FormData(n),s=r.get("name"),a=r.get("email"),l=r.get("phone");if(!s||!a||!l)return void k(t,"Please fill in all required fields.","error");if(!o.selectedDate||!o.selectedTime)return void k(t,"Please select a date and time.","error");c.disabled=!0,c.classList.add("loading");const i=c.textContent;c.textContent="Submitting...";const d=new FormData;d.append("action","wzconnector_submit_booking"),d.append("nonce",wzcBooking.nonce),d.append("name",s),d.append("email",a),d.append("phone",l),d.append("address",r.get("address")||""),d.append("city",r.get("city")||""),d.append("zip",r.get("zip")||""),d.append("notes",r.get("notes")||"");const u=o.selectedDate,m=u.getFullYear()+"-"+String(u.getMonth()+1).padStart(2,"0")+"-"+String(u.getDate()).padStart(2,"0");d.append("booking_date",m),d.append("booking_time",o.selectedTime24h),o.selectedJobGuid&&(d.append("job_guid",o.selectedJobGuid),d.append("job_name",o.selectedJobName)),fetch(wzcBooking.ajax_url,{method:"POST",body:d}).then((e=>e.json())).then((e=>{c.disabled=!1,c.classList.remove("loading"),c.textContent=i,e.success?(k(t,wzcBooking.success_message,"success",wzcBooking.success_title),t.querySelectorAll(".wzc-booking-step").forEach((e=>{e.style.display="none"})),n.reset()):k(t,e.data.message||"An error occurred. Please try again.","error")})).catch((e=>{console.error("Booking error:",e),c.disabled=!1,c.classList.remove("loading"),c.textContent=i,k(t,"An error occurred. Please try again.","error")}))}(t,e,c)})),p(e,c)}(e)}))}()}))}();
  • workzen-connector/trunk/assets/floating-buttons.js

    r3436387 r3447338  
    203203
    204204            // Use the form that triggered the event
    205             var currentForm = e.target;
    206             var isEmbeddedForm = currentForm.classList.contains('wzc-embedded-lead-form');
     205            const currentForm = e.target;
     206            const isEmbeddedForm = currentForm.classList.contains('wzc-embedded-lead-form');
    207207
    208208            const submitBtn = currentForm.querySelector('.wzc-submit-btn');
  • workzen-connector/trunk/assets/floating-buttons.min.js

    r3436387 r3447338  
    1 !function(){"use strict";function e(){const e=document.querySelector(".wzc-floating-container"),t=document.querySelector(".wzc-main-toggle"),n=document.querySelector(".wzc-backdrop"),o=document.querySelector(".wzc-action-btn.contact"),r=document.querySelector(".wzc-contact-modal-overlay"),i=document.querySelector(".wzc-contact-modal-overlay .wzc-modal-close"),s=document.getElementById("wzc-floating-form"),c=document.querySelectorAll(".wzc-embedded-lead-form");if(!e&&!t&&0===c.length)return;const l={plus:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',menu:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',"dots-vertical":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>',"dots-horizontal":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>',phone:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>',message:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',chat:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>',help:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',star:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>',heart:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',settings:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>',"chevron-up":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',"chat-dots":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/></svg>',x:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'},a=t&&t.getAttribute("data-icon")||"plus";function d(){e&&t&&(e.classList.remove("expanded"),t.classList.remove("active"),n&&n.classList.remove("active"))}function u(){if(r&&(r.classList.remove("active"),document.body.style.overflow="",s)){const e=s.querySelector(".wzc-form-message");e&&e.remove()}}function w(e){e.preventDefault();var t=e.target,n=t.classList.contains("wzc-embedded-lead-form");const o=t.querySelector(".wzc-submit-btn"),r=new FormData(t),i=t.querySelector(".wzc-form-message");i&&i.remove();const s=r.get("name"),c=r.get("email"),l=r.get("phone");if(!s||!c||!l)return void g(t,"Please fill in all required fields.","error");if(!function(e){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}(c))return void g(t,"Please enter a valid email address.","error");o.disabled=!0,o.classList.add("loading");const a=o.textContent;o.textContent="Sending...";const d=new FormData;d.append("action","wzconnector_submit_floating_form"),d.append("nonce",wzcFloating.nonce),d.append("name",r.get("name")),d.append("email",r.get("email")),d.append("phone",r.get("phone")),d.append("subject",r.get("subject")||""),d.append("message",r.get("message")||""),fetch(wzcFloating.ajax_url,{method:"POST",body:d}).then((function(e){return e.json()})).then((function(e){e.success?(g(t,wzcFloating.thankyou_message,"success"),t.reset(),n||setTimeout((function(){u()}),2e3)):g(t,e.data.message||"Something went wrong. Please try again.","error")})).catch((function(e){console.error("Form submission error:",e),g(t,"Something went wrong. Please try again.","error")})).finally((function(){o.disabled=!1,o.classList.remove("loading"),o.textContent=a}))}function g(e,t,n){const o=document.createElement("div");o.className="wzc-form-message "+n;const r="success"===n?'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>':'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';o.innerHTML=r+"<span>"+t+"</span>",e.insertBefore(o,e.firstChild)}e&&t&&(t.addEventListener("click",(function(){const o=e.classList.contains("expanded"),r=t.querySelector(".wzc-toggle-icon");o?(r&&l[a]&&setTimeout((function(){r.innerHTML=l[a]}),150),d()):(r&&l.x&&setTimeout((function(){r.innerHTML=l.x}),150),function(){if(!e||!t)return;e.classList.add("expanded"),t.classList.add("active"),n&&n.classList.add("active")}())})),n&&n.addEventListener("click",(function(){const e=t.querySelector(".wzc-toggle-icon");e&&l[a]&&setTimeout((function(){e.innerHTML=l[a]}),150),d()})),o&&o.addEventListener("click",(function(e){e.preventDefault();const n=t.querySelector(".wzc-toggle-icon");n&&l[a]&&setTimeout((function(){n.innerHTML=l[a]}),150),r&&(r.classList.add("active"),document.body.style.overflow="hidden",setTimeout((function(){if(s){const e=s.querySelector('input[type="text"]');e&&e.focus()}}),300)),d()}))),i&&i.addEventListener("click",u),r&&r.addEventListener("click",(function(e){e.target===r&&u()})),s&&s.addEventListener("submit",w),c.forEach((function(e){e.addEventListener("submit",w)})),document.addEventListener("keydown",(function(n){if("Escape"===n.key)if(r&&r.classList.contains("active"))u();else if(e&&e.classList.contains("expanded")){if(t){const e=t.querySelector(".wzc-toggle-icon");e&&l[a]&&setTimeout((function(){e.innerHTML=l[a]}),150)}d()}})),function(){if(!wzcFloating.animation_enabled||"0"===wzcFloating.animation_enabled)return;if(!t)return;const n=wzcFloating.animation_type||"pulse",o=1e3*parseInt(wzcFloating.animation_delay)||5e3,r=wzcFloating.animation_speed||"normal",i=wzcFloating.animation_repeat||"periodic",s="1"===wzcFloating.animation_stop_on_interact;t.style.setProperty("--wzc-animation-duration",{slow:"1.5s",normal:"1s",fast:"0.6s"}[r]),setTimeout((function(){!function(n,o){if(!t)return;const r="wzc-animate-"+n;function i(){e&&e.classList.contains("expanded")||(t.classList.add(r),setTimeout((function(){t.classList.remove(r)}),1500))}m=r,i(),"periodic"===o?v=setInterval((function(){i()}),1e4):"continuous"===o&&(t.style.setProperty("animation-iteration-count","infinite"),t.classList.add(r))}(n,i)}),o),s&&t&&t.addEventListener("click",p,{once:!0})}();let v=null,m=null;function p(){v&&(clearInterval(v),v=null),t&&m&&(t.classList.remove(m),t.style.removeProperty("animation-iteration-count"),m=null)}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}();
     1!function(){"use strict";function e(){const e=document.querySelector(".wzc-floating-container"),t=document.querySelector(".wzc-main-toggle"),n=document.querySelector(".wzc-backdrop"),o=document.querySelector(".wzc-action-btn.contact"),r=document.querySelector(".wzc-contact-modal-overlay"),i=document.querySelector(".wzc-contact-modal-overlay .wzc-modal-close"),s=document.getElementById("wzc-floating-form"),c=document.querySelectorAll(".wzc-embedded-lead-form");if(!e&&!t&&0===c.length)return;const l={plus:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>',menu:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>',"dots-vertical":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/></svg>',"dots-horizontal":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><circle cx="5" cy="12" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="19" cy="12" r="2"/></svg>',phone:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg>',message:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>',chat:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/></svg>',help:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',star:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>',heart:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>',settings:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z"/><circle cx="12" cy="12" r="3"/></svg>',"chevron-up":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="18 15 12 9 6 15"/></svg>',"chat-dots":'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z"/></svg>',x:'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>'},a=t&&t.getAttribute("data-icon")||"plus";function d(){e&&t&&(e.classList.remove("expanded"),t.classList.remove("active"),n&&n.classList.remove("active"))}function u(){if(r&&(r.classList.remove("active"),document.body.style.overflow="",s)){const e=s.querySelector(".wzc-form-message");e&&e.remove()}}function w(e){e.preventDefault();const t=e.target,n=t.classList.contains("wzc-embedded-lead-form"),o=t.querySelector(".wzc-submit-btn"),r=new FormData(t),i=t.querySelector(".wzc-form-message");i&&i.remove();const s=r.get("name"),c=r.get("email"),l=r.get("phone");if(!s||!c||!l)return void g(t,"Please fill in all required fields.","error");if(!function(e){return/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e)}(c))return void g(t,"Please enter a valid email address.","error");o.disabled=!0,o.classList.add("loading");const a=o.textContent;o.textContent="Sending...";const d=new FormData;d.append("action","wzconnector_submit_floating_form"),d.append("nonce",wzcFloating.nonce),d.append("name",r.get("name")),d.append("email",r.get("email")),d.append("phone",r.get("phone")),d.append("subject",r.get("subject")||""),d.append("message",r.get("message")||""),fetch(wzcFloating.ajax_url,{method:"POST",body:d}).then((function(e){return e.json()})).then((function(e){e.success?(g(t,wzcFloating.thankyou_message,"success"),t.reset(),n||setTimeout((function(){u()}),2e3)):g(t,e.data.message||"Something went wrong. Please try again.","error")})).catch((function(e){console.error("Form submission error:",e),g(t,"Something went wrong. Please try again.","error")})).finally((function(){o.disabled=!1,o.classList.remove("loading"),o.textContent=a}))}function g(e,t,n){const o=document.createElement("div");o.className="wzc-form-message "+n;const r="success"===n?'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>':'<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>';o.innerHTML=r+"<span>"+t+"</span>",e.insertBefore(o,e.firstChild)}e&&t&&(t.addEventListener("click",(function(){const o=e.classList.contains("expanded"),r=t.querySelector(".wzc-toggle-icon");o?(r&&l[a]&&setTimeout((function(){r.innerHTML=l[a]}),150),d()):(r&&l.x&&setTimeout((function(){r.innerHTML=l.x}),150),function(){if(!e||!t)return;e.classList.add("expanded"),t.classList.add("active"),n&&n.classList.add("active")}())})),n&&n.addEventListener("click",(function(){const e=t.querySelector(".wzc-toggle-icon");e&&l[a]&&setTimeout((function(){e.innerHTML=l[a]}),150),d()})),o&&o.addEventListener("click",(function(e){e.preventDefault();const n=t.querySelector(".wzc-toggle-icon");n&&l[a]&&setTimeout((function(){n.innerHTML=l[a]}),150),r&&(r.classList.add("active"),document.body.style.overflow="hidden",setTimeout((function(){if(s){const e=s.querySelector('input[type="text"]');e&&e.focus()}}),300)),d()}))),i&&i.addEventListener("click",u),r&&r.addEventListener("click",(function(e){e.target===r&&u()})),s&&s.addEventListener("submit",w),c.forEach((function(e){e.addEventListener("submit",w)})),document.addEventListener("keydown",(function(n){if("Escape"===n.key)if(r&&r.classList.contains("active"))u();else if(e&&e.classList.contains("expanded")){if(t){const e=t.querySelector(".wzc-toggle-icon");e&&l[a]&&setTimeout((function(){e.innerHTML=l[a]}),150)}d()}})),function(){if(!wzcFloating.animation_enabled||"0"===wzcFloating.animation_enabled)return;if(!t)return;const n=wzcFloating.animation_type||"pulse",o=1e3*parseInt(wzcFloating.animation_delay)||5e3,r=wzcFloating.animation_speed||"normal",i=wzcFloating.animation_repeat||"periodic",s="1"===wzcFloating.animation_stop_on_interact;t.style.setProperty("--wzc-animation-duration",{slow:"1.5s",normal:"1s",fast:"0.6s"}[r]),setTimeout((function(){!function(n,o){if(!t)return;const r="wzc-animate-"+n;function i(){e&&e.classList.contains("expanded")||(t.classList.add(r),setTimeout((function(){t.classList.remove(r)}),1500))}v=r,i(),"periodic"===o?m=setInterval((function(){i()}),1e4):"continuous"===o&&(t.style.setProperty("animation-iteration-count","infinite"),t.classList.add(r))}(n,i)}),o),s&&t&&t.addEventListener("click",p,{once:!0})}();let m=null,v=null;function p(){m&&(clearInterval(m),m=null),t&&v&&(t.classList.remove(v),t.style.removeProperty("animation-iteration-count"),v=null)}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e):e()}();
  • workzen-connector/trunk/includes/class-admin-pages.php

    r3436487 r3447338  
    3030    public function add_admin_menu() {
    3131        // Get the SVG icon as base64 data URI for WordPress menu
    32         $icon_svg = file_get_contents( plugin_dir_path( dirname( __FILE__ ) ) . 'assets/images/workzen-sloth-icon.svg' );
    33         $icon_data_uri = 'data:image/svg+xml;base64,' . base64_encode( $icon_svg );
     32        $icon_path = plugin_dir_path( dirname( __FILE__ ) ) . 'assets/images/workzen-sloth-icon.svg';
     33        $icon_data_uri = 'dashicons-admin-generic'; // Fallback to WordPress dashicon
     34
     35        if ( file_exists( $icon_path ) && is_readable( $icon_path ) ) {
     36            $icon_svg = file_get_contents( $icon_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
     37            if ( false !== $icon_svg && ! empty( $icon_svg ) ) {
     38                $icon_data_uri = 'data:image/svg+xml;base64,' . base64_encode( $icon_svg ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode
     39            }
     40        }
    3441
    3542        add_menu_page(
     
    603610                </div>
    604611
     612                <?php if ( defined( 'WORKZEN_DEV' ) && WORKZEN_DEV ) : ?>
     613                    <!-- Dev Tools Section (only visible in dev mode) -->
     614                    <div class="wzc-dev-tools" style="margin-top: 20px; padding: 20px; background: #fef2f2; border: 2px dashed #ef4444; border-radius: 8px;">
     615                        <h3 style="margin: 0 0 12px 0; color: #dc2626; display: flex; align-items: center; gap: 8px;">
     616                            <span style="font-size: 20px;">⚠️</span>
     617                            <?php esc_html_e( 'Developer Tools', 'workzen-connector' ); ?>
     618                            <span style="font-size: 12px; font-weight: normal; color: #9ca3af;">(DEV MODE ONLY)</span>
     619                        </h3>
     620                        <p style="margin: 0 0 16px 0; color: #7f1d1d;">
     621                            <?php esc_html_e( 'These tools are only visible because WORKZEN_DEV is enabled. They will not appear in production.', 'workzen-connector' ); ?>
     622                        </p>
     623                        <div style="display: flex; gap: 12px; align-items: center;">
     624                            <button type="button" id="wzc-reset-plugin-data" class="button" style="background: #dc2626; border-color: #dc2626; color: white;">
     625                                <?php esc_html_e( '🗑️ Reset All Plugin Data', 'workzen-connector' ); ?>
     626                            </button>
     627                            <span id="wzc-reset-status" style="color: #64748b; font-style: italic;"></span>
     628                        </div>
     629                        <p style="margin: 12px 0 0 0; font-size: 12px; color: #9ca3af;">
     630                            <?php esc_html_e( 'This will delete ALL plugin settings, the request log, retry queue, transients, and scheduled events. Like a fresh install.', 'workzen-connector' ); ?>
     631                        </p>
     632                    </div>
     633
     634                    <script>
     635                    (function($) {
     636                        $('#wzc-reset-plugin-data').on('click', function() {
     637                            if (!confirm('<?php echo esc_js( __( 'Are you ABSOLUTELY SURE you want to reset ALL plugin data?\n\nThis will delete:\n- All configuration settings\n- Integration key\n- Request log\n- Retry queue\n- All cached data\n\nThis action cannot be undone!', 'workzen-connector' ) ); ?>')) {
     638                                return;
     639                            }
     640
     641                            var $btn = $(this);
     642                            var $status = $('#wzc-reset-status');
     643
     644                            $btn.prop('disabled', true).text('<?php echo esc_js( __( 'Resetting...', 'workzen-connector' ) ); ?>');
     645                            $status.text('');
     646
     647                            $.ajax({
     648                                url: ajaxurl,
     649                                type: 'POST',
     650                                data: {
     651                                    action: 'wzconnector_reset_plugin_data',
     652                                    nonce: '<?php echo esc_js( wp_create_nonce( 'wzconnector_nonce' ) ); ?>'
     653                                },
     654                                success: function(response) {
     655                                    if (response.success) {
     656                                        $status.css('color', '#16a34a').text(response.data.message);
     657                                        $btn.text('<?php echo esc_js( __( '✓ Reset Complete', 'workzen-connector' ) ); ?>');
     658                                        // Reload page after 2 seconds to show fresh state
     659                                        setTimeout(function() {
     660                                            window.location.reload();
     661                                        }, 2000);
     662                                    } else {
     663                                        $status.css('color', '#dc2626').text(response.data.message || '<?php echo esc_js( __( 'Reset failed', 'workzen-connector' ) ); ?>');
     664                                        $btn.prop('disabled', false).text('<?php echo esc_js( __( '🗑️ Reset All Plugin Data', 'workzen-connector' ) ); ?>');
     665                                    }
     666                                },
     667                                error: function() {
     668                                    $status.css('color', '#dc2626').text('<?php echo esc_js( __( 'Network error. Please try again.', 'workzen-connector' ) ); ?>');
     669                                    $btn.prop('disabled', false).text('<?php echo esc_js( __( '🗑️ Reset All Plugin Data', 'workzen-connector' ) ); ?>');
     670                                }
     671                            });
     672                        });
     673                    })(jQuery);
     674                    </script>
     675                <?php endif; ?>
     676
    605677            <?php elseif ( $current_tab === 'integrations' ) : ?>
    606678                <!-- Integrations Tab -->
     
    11051177                                <th scope="row"><label for="wzconnector_booking_date_range"><?php esc_html_e( 'Booking Window (Days)', 'workzen-connector' ); ?></label></th>
    11061178                                <td>
    1107                                     <input name="<?php echo esc_attr( WZC_Constants::OPTION_BOOKING_DATE_RANGE ); ?>" type="number" id="wzconnector_booking_date_range" value="<?php echo esc_attr( get_option( WZC_Constants::OPTION_BOOKING_DATE_RANGE, '14' ) ); ?>" min="1" max="365" class="small-text" />
    1108                                     <p class="description"><?php esc_html_e( 'How many days ahead customers can book (1-365 days, default: 14)', 'workzen-connector' ); ?></p>
     1179                                    <input name="<?php echo esc_attr( WZC_Constants::OPTION_BOOKING_DATE_RANGE ); ?>" type="number" id="wzconnector_booking_date_range" value="<?php echo esc_attr( get_option( WZC_Constants::OPTION_BOOKING_DATE_RANGE, '365' ) ); ?>" min="1" max="365" class="small-text" />
     1180                                    <p class="description"><?php esc_html_e( 'How many days ahead customers can book (1-365 days, default: 365)', 'workzen-connector' ); ?></p>
    11091181                                </td>
    11101182                            </tr>
  • workzen-connector/trunk/includes/class-ajax-handlers.php

    r3436387 r3447338  
    3838        add_action( 'wp_ajax_wzconnector_sync_booking_data', array( $this, 'ajax_sync_booking_data' ) );
    3939        add_action( 'wp_ajax_wzconnector_save_tab_settings', array( $this, 'ajax_save_tab_settings' ) );
     40
     41        // Dev mode only: Reset plugin data
     42        if ( defined( 'WORKZEN_DEV' ) && WORKZEN_DEV ) {
     43            add_action( 'wp_ajax_wzconnector_reset_plugin_data', array( $this, 'ajax_reset_plugin_data' ) );
     44        }
    4045    }
    4146
     
    8085            ) );
    8186        }
     87    }
     88
     89    /**
     90     * AJAX: Reset all plugin data (DEV MODE ONLY)
     91     *
     92     * Deletes all plugin options and transients, simulating a fresh install.
     93     * This action is only available when WORKZEN_DEV is defined and true.
     94     */
     95    public function ajax_reset_plugin_data() {
     96        // Double-check dev mode (handler registration already checks, but be safe)
     97        if ( ! defined( 'WORKZEN_DEV' ) || ! WORKZEN_DEV ) {
     98            wp_send_json_error( array( 'message' => 'Reset is only available in development mode.' ) );
     99        }
     100
     101        check_ajax_referer( 'wzconnector_nonce', 'nonce' );
     102
     103        if ( ! current_user_can( 'manage_options' ) ) {
     104            wp_send_json_error( array( 'message' => __( 'Unauthorized', 'workzen-connector' ) ) );
     105        }
     106
     107        $deleted_options = 0;
     108        $deleted_transients = 0;
     109
     110        // Delete all plugin options
     111        foreach ( WZC_Constants::get_all_option_names() as $option_name ) {
     112            if ( delete_option( $option_name ) ) {
     113                $deleted_options++;
     114            }
     115        }
     116
     117        // Delete all plugin transients
     118        foreach ( WZC_Constants::get_all_transient_names() as $transient_name ) {
     119            if ( delete_transient( $transient_name ) ) {
     120                $deleted_transients++;
     121            }
     122        }
     123
     124        // Clear any scheduled events
     125        wp_clear_scheduled_hook( 'wzconnector_process_queue' );
     126        wp_clear_scheduled_hook( 'wzconnector_daily_heartbeat' );
     127
     128        wp_send_json_success( array(
     129            'message'            => sprintf(
     130                /* translators: %1$d: number of options deleted, %2$d: number of transients deleted */
     131                __( 'Plugin data reset! Deleted %1$d options and %2$d transients.', 'workzen-connector' ),
     132                $deleted_options,
     133                $deleted_transients
     134            ),
     135            'deleted_options'    => $deleted_options,
     136            'deleted_transients' => $deleted_transients,
     137        ) );
    82138    }
    83139
     
    452508            wp_send_json_error( array(
    453509                'message' => 'Please enter a valid email address.',
     510            ) );
     511        }
     512
     513        // Validate booking date format (YYYY-MM-DD) and ensure it's a real date
     514        if ( ! preg_match( '/^\d{4}-\d{2}-\d{2}$/', $booking_date ) ) {
     515            wp_send_json_error( array(
     516                'message' => 'Invalid date format. Please select a valid date.',
     517            ) );
     518        }
     519        $date_parts = explode( '-', $booking_date );
     520        if ( ! checkdate( (int) $date_parts[1], (int) $date_parts[2], (int) $date_parts[0] ) ) {
     521            wp_send_json_error( array(
     522                'message' => 'Invalid date. Please select a valid date.',
     523            ) );
     524        }
     525
     526        // Validate booking time format (HH:MM in 24h format)
     527        if ( ! preg_match( '/^([01]\d|2[0-3]):([0-5]\d)$/', $booking_time ) ) {
     528            wp_send_json_error( array(
     529                'message' => 'Invalid time format. Please select a valid time.',
    454530            ) );
    455531        }
  • workzen-connector/trunk/includes/class-constants.php

    r3436387 r3447338  
    8282        return array( 'automatic', 'manual' );
    8383    }
     84
     85    /**
     86     * Get all plugin option names for cleanup/reset
     87     *
     88     * @return array List of all option names used by the plugin
     89     */
     90    public static function get_all_option_names() {
     91        return array(
     92            // Core Options
     93            self::OPTION_INTEGRATION_KEY,
     94            self::OPTION_ENDPOINT,
     95            self::OPTION_WEBSITE_NAME,
     96            self::OPTION_ENABLED_INTEGRATIONS,
     97            self::OPTION_RETRY_QUEUE,
     98            self::OPTION_INTEGRATION_MODE,
     99
     100            // Floating Button Options
     101            self::OPTION_FLOATING_ENABLED,
     102            self::OPTION_FLOATING_CALL_ENABLED,
     103            self::OPTION_FLOATING_PHONE,
     104            self::OPTION_FLOATING_WHATSAPP_ENABLED,
     105            self::OPTION_FLOATING_WHATSAPP,
     106            self::OPTION_FLOATING_CONTACT_ENABLED,
     107            self::OPTION_FLOATING_FORM_TITLE,
     108            self::OPTION_FLOATING_FORM_DESCRIPTION,
     109            self::OPTION_FLOATING_POSITION,
     110            self::OPTION_FLOATING_SIZE,
     111            self::OPTION_FLOATING_ICON,
     112            self::OPTION_FLOATING_COLOR,
     113            self::OPTION_FLOATING_THANKYOU_TITLE,
     114            self::OPTION_FLOATING_THANKYOU_MESSAGE,
     115            self::OPTION_FLOATING_CALL_TOOLTIP,
     116            self::OPTION_FLOATING_WHATSAPP_TOOLTIP,
     117            self::OPTION_FLOATING_CONTACT_TOOLTIP,
     118
     119            // Animation Options
     120            self::OPTION_ANIMATION_ENABLED,
     121            self::OPTION_ANIMATION_TYPE,
     122            self::OPTION_ANIMATION_DELAY,
     123            self::OPTION_ANIMATION_SPEED,
     124            self::OPTION_ANIMATION_REPEAT,
     125            self::OPTION_ANIMATION_STOP_ON_INTERACT,
     126
     127            // Online Booking Options
     128            self::OPTION_BOOKING_ENABLED,
     129            self::OPTION_BOOKING_TOOLTIP,
     130            self::OPTION_BOOKING_SHOW_JOB_TYPES,
     131            self::OPTION_BOOKING_RESPECT_WORK_HOURS,
     132            self::OPTION_BOOKING_DATE_RANGE,
     133            self::OPTION_BOOKING_TIME_INTERVAL,
     134            self::OPTION_BOOKING_FORM_TITLE,
     135            self::OPTION_BOOKING_FORM_DESCRIPTION,
     136            self::OPTION_BOOKING_SUCCESS_TITLE,
     137            self::OPTION_BOOKING_SUCCESS_MESSAGE,
     138
     139            // Reviews Options
     140            self::OPTION_REVIEWS_GOOGLE,
     141            self::OPTION_REVIEWS_YELP,
     142            self::OPTION_REVIEWS_FACEBOOK,
     143            self::OPTION_REVIEWS_BING,
     144            self::OPTION_REVIEWS_TRUSTPILOT,
     145            self::OPTION_REVIEWS_BBB,
     146
     147            // Internal/Runtime Options (not in constants)
     148            'wzconnector_request_log',
     149            'wzc_installation_id',
     150            'wzc_connection_status',
     151            'wzc_connection_last_error',
     152            'wzc_connection_last_test',
     153            'wzc_heartbeat_failure_count',
     154            'wzc_daily_heartbeat',
     155            'wzc_pending_connection_test',
     156        );
     157    }
     158
     159    /**
     160     * Get all transient names for cleanup/reset
     161     *
     162     * @return array List of transient names used by the plugin
     163     */
     164    public static function get_all_transient_names() {
     165        return array(
     166            'wzc_booking_data',
     167            'wzc_queue_schedule_lock',
     168        );
     169    }
    84170}
  • workzen-connector/trunk/includes/class-integrations-manager.php

    r3384405 r3447338  
    6868
    6969            if ( $should_load && class_exists( $class ) ) {
    70                 $this->integrations[ $slug ] = new $class( $this );
     70                try {
     71                    $this->integrations[ $slug ] = new $class( $this );
     72                } catch ( \Exception $e ) {
     73                    // Log the error but don't break other integrations
     74                    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
     75                        error_log( '[WorkZen Connector] Failed to load integration ' . $slug . ': ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     76                    }
     77                } catch ( \Error $e ) {
     78                    // Catch PHP 7+ errors (TypeError, etc.) as well
     79                    if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
     80                        error_log( '[WorkZen Connector] Error loading integration ' . $slug . ': ' . $e->getMessage() ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
     81                    }
     82                }
    7183            }
    7284        }
  • workzen-connector/trunk/includes/class-lead-sender.php

    r3436387 r3447338  
    5757        }
    5858
    59         // Build payload with tracking data
    60         $body = array(
    61             'website_name'  => get_option( WZC_Constants::OPTION_WEBSITE_NAME ),
    62             'integration'   => $integration_slug,
    63             'fields'        => $fields,
    64             'meta'          => $meta,
    65             'tracking'      => $tracking_data, // Add tracking data to payload
    66         );
    67 
    68         // Add booking-specific fields at root level if provided
    69         if ( ! empty( $booking_data['job_type_guid'] ) ) {
    70             $body['job_type_guid'] = $booking_data['job_type_guid'];
    71         }
    72         if ( ! empty( $booking_data['visit_request_start_time'] ) ) {
    73             $body['visit_request_start_time'] = $booking_data['visit_request_start_time'];
    74         }
    75         if ( ! empty( $booking_data['visit_request_time_frame'] ) ) {
    76             $body['visit_request_time_frame'] = $booking_data['visit_request_time_frame'];
    77         }
     59        // Build payload with tracking data using shared method
     60        $body = $this->build_api_payload( $integration_slug, $fields, $meta, $tracking_data, $booking_data );
    7861
    7962        $args = array(
     
    167150        );
    168151        update_option( WZC_Constants::OPTION_RETRY_QUEUE, $queue, false );
    169         if ( ! wp_next_scheduled( 'wzconnector_process_queue' ) ) {
    170             wp_schedule_single_event( time() + 300, 'wzconnector_process_queue' );
     152
     153        // Use transient lock to prevent race condition when scheduling
     154        // Multiple concurrent requests could all see no scheduled event and try to schedule
     155        $lock_key = 'wzc_queue_schedule_lock';
     156        if ( false === get_transient( $lock_key ) ) {
     157            // Set a short lock (10 seconds) to prevent duplicate scheduling
     158            set_transient( $lock_key, '1', 10 );
     159            if ( ! wp_next_scheduled( 'wzconnector_process_queue' ) ) {
     160                wp_schedule_single_event( time() + 300, 'wzconnector_process_queue' );
     161            }
    171162        }
    172163    }
     
    188179        $queue = get_option( WZC_Constants::OPTION_RETRY_QUEUE, array() );
    189180        $new_queue = array();
     181
    190182        foreach ( $queue as $item ) {
    191             // Extract tracking data and booking data if available
    192             $tracking_data = isset( $item['tracking'] ) ? $item['tracking'] : array();
    193             $booking_data = isset( $item['booking_data'] ) ? $item['booking_data'] : array();
    194 
    195             // Build payload
    196             $body = array(
    197                 'website_name' => get_option( WZC_Constants::OPTION_WEBSITE_NAME ),
    198                 'integration'   => $item['integration'],
    199                 'fields'        => $item['fields'],
    200                 'meta'          => isset( $item['meta'] ) ? $item['meta'] : array(),
    201                 'tracking'      => $tracking_data,
    202             );
    203 
    204             // Add booking-specific fields at root level if provided
    205             if ( ! empty( $booking_data['job_type_guid'] ) ) {
    206                 $body['job_type_guid'] = $booking_data['job_type_guid'];
    207             }
    208             if ( ! empty( $booking_data['visit_request_start_time'] ) ) {
    209                 $body['visit_request_start_time'] = $booking_data['visit_request_start_time'];
    210             }
    211             if ( ! empty( $booking_data['visit_request_time_frame'] ) ) {
    212                 $body['visit_request_time_frame'] = $booking_data['visit_request_time_frame'];
    213             }
     183            // Validate queue item structure - skip malformed entries
     184            if ( ! is_array( $item ) || ! isset( $item['integration'] ) || ! isset( $item['fields'] ) ) {
     185                $this->log_error( 'Skipping malformed queue item', $item );
     186                continue;
     187            }
     188
     189            // Ensure 'tries' exists and is numeric
     190            if ( ! isset( $item['tries'] ) || ! is_numeric( $item['tries'] ) ) {
     191                $item['tries'] = 0;
     192            }
     193
     194            // Extract data with safe defaults
     195            $tracking_data = isset( $item['tracking'] ) && is_array( $item['tracking'] ) ? $item['tracking'] : array();
     196            $booking_data = isset( $item['booking_data'] ) && is_array( $item['booking_data'] ) ? $item['booking_data'] : array();
     197            $meta = isset( $item['meta'] ) && is_array( $item['meta'] ) ? $item['meta'] : array();
     198
     199            // Build payload using shared method
     200            $body = $this->build_api_payload( $item['integration'], $item['fields'], $meta, $tracking_data, $booking_data );
    214201
    215202            $args = array(
     
    235222
    236223            // Log retry attempt
    237             $this->log_request( $item['integration'], $item['fields'], $success, $response_body, $item['tracking'] ?? array(), $item['booking_data'] ?? array() );
     224            $this->log_request( $item['integration'], $item['fields'], $success, $response_body, $tracking_data, $booking_data );
    238225
    239226            if ( ! $success ) {
     
    246233            }
    247234        }
     235
    248236        update_option( WZC_Constants::OPTION_RETRY_QUEUE, $new_queue, false );
     237
    249238        if ( ! empty( $new_queue ) ) {
    250239            wp_schedule_single_event( time() + 300, 'wzconnector_process_queue' );
    251240        }
     241    }
     242
     243    /**
     244     * Build the API payload for sending leads
     245     *
     246     * @param string $integration_slug Integration identifier
     247     * @param array  $fields           Contact fields
     248     * @param array  $meta             Metadata
     249     * @param array  $tracking_data    Tracking information
     250     * @param array  $booking_data     Booking-specific data
     251     * @return array The payload array
     252     */
     253    private function build_api_payload( $integration_slug, $fields, $meta, $tracking_data, $booking_data ) {
     254        $body = array(
     255            'website_name' => get_option( WZC_Constants::OPTION_WEBSITE_NAME ),
     256            'integration'  => $integration_slug,
     257            'fields'       => $fields,
     258            'meta'         => $meta,
     259            'tracking'     => $tracking_data,
     260        );
     261
     262        // Add booking-specific fields at root level if provided
     263        if ( ! empty( $booking_data['job_type_guid'] ) ) {
     264            $body['job_type_guid'] = $booking_data['job_type_guid'];
     265        }
     266        if ( ! empty( $booking_data['visit_request_start_time'] ) ) {
     267            $body['visit_request_start_time'] = $booking_data['visit_request_start_time'];
     268        }
     269        if ( ! empty( $booking_data['visit_request_time_frame'] ) ) {
     270            $body['visit_request_time_frame'] = $booking_data['visit_request_time_frame'];
     271        }
     272
     273        return $body;
    252274    }
    253275
     
    334356            $tracking_cookie = sanitize_text_field( wp_unslash( $tracking_cookie ) );
    335357
    336             if ( $tracking_cookie === '' || ! preg_match( '/^[A-Za-z0-9%+=\/_-]+$/', $tracking_cookie ) ) {
     358            // Validate base64 characters only (A-Za-z0-9+/=) plus URL-safe variants (-_)
     359            // Note: % is NOT a valid base64 character - it's used for URL encoding
     360            if ( $tracking_cookie === '' || ! preg_match( '/^[A-Za-z0-9+=\/_-]+$/', $tracking_cookie ) ) {
    337361                $tracking_cookie = '';
    338362            }
     
    341365                try {
    342366                    // UTF-8 safe base64 decoding (reverse of JS encoding)
    343                     $decoded = base64_decode( $tracking_cookie ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
    344                     $json_string = urldecode( $decoded );
    345                     $cookie_data = json_decode( $json_string, true );
    346                     if ( is_array( $cookie_data ) ) {
    347                         $tracking = $cookie_data;
     367                    // Use strict mode (second param = true) to reject invalid base64 input
     368                    $decoded = base64_decode( $tracking_cookie, true ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
     369                    if ( $decoded === false ) {
     370                        $this->log_error( 'Invalid base64 in tracking cookie' );
     371                    } else {
     372                        $json_string = urldecode( $decoded );
     373                        $cookie_data = json_decode( $json_string, true );
     374                        if ( json_last_error() === JSON_ERROR_NONE && is_array( $cookie_data ) ) {
     375                            $tracking = $cookie_data;
     376                        }
    348377                    }
    349378                } catch ( Exception $e ) {
     
    389418        return $tracking;
    390419    }
    391 
    392     /**
    393      * Polyfill for PHP 8's str_contains to maintain backwards compatibility.
    394      *
    395      * @param string $haystack String to search within.
    396      * @param string $needle   Substring to look for.
    397      *
    398      * @return bool
    399      */
    400420}
  • workzen-connector/trunk/includes/class-online-booking.php

    r3436387 r3447338  
    9595
    9696        // Cache the data for 12 hours (skip in dev)
    97         if ( ! $is_dev ) {
     97        if ( ! ( defined( 'WORKZEN_DEV' ) && WORKZEN_DEV ) ) {
    9898            set_transient( $cache_key, $booking_data, 12 * HOUR_IN_SECONDS );
    9999        }
     
    215215            && ! empty( $booking_data['job_types'] );
    216216        $respect_work_hours = get_option( WZC_Constants::OPTION_BOOKING_RESPECT_WORK_HOURS, '1' ) === '1';
    217         $date_range = get_option( WZC_Constants::OPTION_BOOKING_DATE_RANGE, '14' );
     217        $date_range = get_option( WZC_Constants::OPTION_BOOKING_DATE_RANGE, '365' );
    218218        $time_interval = get_option( WZC_Constants::OPTION_BOOKING_TIME_INTERVAL, '60' );
    219219
     
    294294                            <div class="wzc-job-types">
    295295                                <?php foreach ( $booking_data['job_types'] as $job_type ) : ?>
     296                                    <?php
     297                                    // Validate job_type has required fields before rendering
     298                                    if ( ! is_array( $job_type ) || ! isset( $job_type['guid'], $job_type['name'] ) ) {
     299                                        continue;
     300                                    }
     301                                    $job_description = isset( $job_type['description'] ) ? $job_type['description'] : '';
     302                                    ?>
    296303                                    <div class="wzc-job-type-card" data-job-guid="<?php echo esc_attr( $job_type['guid'] ); ?>">
    297304                                        <h4><?php echo esc_html( $job_type['name'] ); ?></h4>
    298                                         <p><?php echo esc_html( $job_type['description'] ); ?></p>
     305                                        <p><?php echo esc_html( $job_description ); ?></p>
    299306                                    </div>
    300307                                <?php endforeach; ?>
  • workzen-connector/trunk/includes/class-shortcodes.php

    r3436487 r3447338  
    111111                            <div class="wzc-job-types">
    112112                                <?php foreach ( $booking_data['job_types'] as $job_type ) : ?>
     113                                    <?php
     114                                    // Validate job_type has required fields before rendering
     115                                    if ( ! is_array( $job_type ) || ! isset( $job_type['guid'], $job_type['name'] ) ) {
     116                                        continue;
     117                                    }
     118                                    $job_description = isset( $job_type['description'] ) ? $job_type['description'] : '';
     119                                    ?>
    113120                                    <div class="wzc-job-type-card" data-job-guid="<?php echo esc_attr( $job_type['guid'] ); ?>">
    114121                                        <h4><?php echo esc_html( $job_type['name'] ); ?></h4>
    115                                         <p><?php echo esc_html( $job_type['description'] ); ?></p>
     122                                        <p><?php echo esc_html( $job_description ); ?></p>
    116123                                    </div>
    117124                                <?php endforeach; ?>
     
    386393            && ! empty( $booking_data['job_types'] );
    387394        $respect_work_hours = get_option( WZC_Constants::OPTION_BOOKING_RESPECT_WORK_HOURS, '1' ) === '1';
    388         $date_range = get_option( WZC_Constants::OPTION_BOOKING_DATE_RANGE, '14' );
     395        $date_range = get_option( WZC_Constants::OPTION_BOOKING_DATE_RANGE, '365' );
    389396        $time_interval = get_option( WZC_Constants::OPTION_BOOKING_TIME_INTERVAL, '60' );
    390397
  • workzen-connector/trunk/readme.txt

    r3436487 r3447338  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.11.1
     7Stable tag: 1.12.0
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    102102
    103103== Changelog ==
     104
     105= 1.12.0 =
     106* Security: Fixed potential race condition in queue scheduling with transient lock
     107* Security: Added strict mode to base64 decoding with JSON validation for tracking cookies
     108* Security: Added comprehensive booking date/time format validation
     109* Reliability: Added try-catch error handling for integration initialization
     110* Reliability: Added file existence checks for SVG assets with dashicon fallback
     111* Reliability: Added validation of queue item structure before processing
     112* Code Quality: Extracted duplicate payload building logic to shared method (DRY)
     113* Code Quality: Fixed undefined variable in online booking cache logic
     114* UX: Fixed invisible unsaved bar blocking WordPress admin bar clicks
     115* UX: Extended default booking date range from 14 to 365 days
     116* Dev: Added plugin data reset button for development environments
    104117
    105118= 1.10.0 =
     
    214227== Upgrade Notice ==
    215228
     229= 1.12.0 =
     230Security and reliability update! Fixed race conditions, improved error handling, added input validation, and extended booking date range. Update recommended.
     231
    216232= 1.10.0 =
    217233Major update! New installation tracking system, connection health monitoring with visual indicators, auto-test on save, and improved API reliability. Default button size changed to 64px for better visibility.
  • workzen-connector/trunk/workzen-connector.php

    r3436487 r3447338  
    33 * Plugin Name: WorkZen Connector
    44 * Description: Connects WordPress forms to WorkZen CRM. Captures leads from Contact Form 7, WPForms, Gravity Forms, and other popular form plugins, sending them securely to your WorkZen account via the WorkZen API (https://api.workzen.io). Includes floating buttons with online booking functionality.
    5  * Version: 1.11.1
     5 * Version: 1.12.0
    66 * Author: Ika Balzam
    77 * Author URI: https://workzen.io
     
    2020
    2121// Define plugin constants
    22 define( 'WZC_VERSION', '1.11.1' );
     22define( 'WZC_VERSION', '1.12.0' );
    2323define( 'WZC_PLUGIN_FILE', __FILE__ );
    2424define( 'WZC_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
Note: See TracChangeset for help on using the changeset viewer.