Changeset 3447338
- Timestamp:
- 01/26/2026 07:49:52 PM (6 weeks ago)
- Location:
- workzen-connector/trunk
- Files:
-
- 1 added
- 15 edited
-
assets/admin.css (modified) (2 diffs)
-
assets/admin.js (modified) (12 diffs)
-
assets/booking.js (modified) (6 diffs)
-
assets/booking.min.js (modified) (1 diff)
-
assets/floating-buttons.js (modified) (1 diff)
-
assets/floating-buttons.min.js (modified) (1 diff)
-
includes/class-admin-pages.php (modified) (3 diffs)
-
includes/class-ajax-handlers.php (modified) (3 diffs)
-
includes/class-constants.php (modified) (1 diff)
-
includes/class-integrations-manager.php (modified) (1 diff)
-
includes/class-lead-sender.php (modified) (8 diffs)
-
includes/class-online-booking.php (modified) (3 diffs)
-
includes/class-shortcodes.php (modified) (2 diffs)
-
readme.txt (modified) (3 diffs)
-
uninstall.php (added)
-
workzen-connector.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
workzen-connector/trunk/assets/admin.css
r3436387 r3447338 1121 1121 transform: translateY(-100%); 1122 1122 opacity: 0; 1123 pointer-events: none; /* Don't block clicks when hidden */ 1123 1124 transition: transform 0.3s ease, opacity 0.3s ease; 1124 1125 } … … 1127 1128 transform: translateY(0); 1128 1129 opacity: 1; 1130 pointer-events: auto; /* Allow clicks when visible */ 1129 1131 } 1130 1132 -
workzen-connector/trunk/assets/admin.js
r3436387 r3447338 170 170 }); 171 171 172 // Update statistics 173 function updateStats() {172 // Update statistics (reserved for future use) 173 function _updateStats() { 174 174 const total = $('.wzc-integration-card').length; 175 175 const enabled = $('.wzc-integration-card.enabled').length; … … 372 372 373 373 function syncBookingData(silent) { 374 var$btn = $('#wzc-sync-booking-data');375 var$status = $('#wzc-sync-status');376 varbtnDefaultHtml = '<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'; 377 377 378 378 if (!silent && $btn.length) { … … 423 423 // ======================================================================== 424 424 425 varformHasChanges = false;426 varisSaving = false;427 var$unsavedBar = null;428 varoriginalButtonText = null; // Store original button text429 varresetButtonTimeout = null; // Track timeout for button reset425 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 430 430 431 431 // Get current tab from URL 432 432 function getCurrentTab() { 433 varurlParams = new URLSearchParams(window.location.search);433 const urlParams = new URLSearchParams(window.location.search); 434 434 return urlParams.get('tab') || 'configuration'; 435 435 } … … 437 437 // Update connection status indicator 438 438 function updateConnectionIndicator(status) { 439 var$indicator = $('.wzc-connection-indicator');439 let $indicator = $('.wzc-connection-indicator'); 440 440 if (!$indicator.length) { 441 441 // Create indicator if it doesn't exist 442 var$inputField = $('#wzconnector_integration_key');442 const $inputField = $('#wzconnector_integration_key'); 443 443 if ($inputField.length) { 444 444 $indicator = $('<span class="wzc-connection-indicator"></span>'); … … 481 481 $unsavedBar.on('click', '.wzc-save-now-btn', function(e) { 482 482 e.preventDefault(); 483 var$btn = $(this);483 const $btn = $(this); 484 484 if ($btn.prop('disabled')) return; 485 485 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(); 487 487 if ($form.length) { 488 varcurrentTab = getCurrentTab();488 const currentTab = getCurrentTab(); 489 489 saveTabSettings($form, currentTab); 490 490 } … … 521 521 // Update save button to show unsaved state 522 522 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"]'); 524 524 if (!$submitBtn.length) return; 525 525 … … 573 573 // Intercept form submission and use AJAX 574 574 $('.wzc-admin-wrapper form').on('submit', function(e) { 575 var$form = $(this);576 varcurrentTab = getCurrentTab();575 const $form = $(this); 576 const currentTab = getCurrentTab(); 577 577 578 578 // Skip AJAX for special forms (mode form, etc) … … 591 591 // Save tab settings via AJAX 592 592 function saveTabSettings($form, tab) { 593 var$submitBtn = $form.find('input[type="submit"], button[type="submit"]');594 varoriginalText = $submitBtn.val() || $submitBtn.text();595 varsettings = {};593 const $submitBtn = $form.find('input[type="submit"], button[type="submit"]'); 594 const originalText = $submitBtn.val() || $submitBtn.text(); 595 const settings = {}; 596 596 597 597 isSaving = true; … … 604 604 // Collect all form fields 605 605 $form.find('input, select, textarea').each(function() { 606 var$field = $(this);607 varname = $field.attr('name');606 const $field = $(this); 607 const name = $field.attr('name'); 608 608 609 609 if (!name) return; … … 673 673 674 674 // Reset button text after delay (use stored original text) 675 varbtnText = originalButtonText || originalText.replace(/^• /, '');675 const btnText = originalButtonText || originalText.replace(/^• /, ''); 676 676 resetButtonTimeout = setTimeout(function() { 677 677 if ($submitBtn.is('input')) { … … 686 686 } else { 687 687 alert('Error saving settings: ' + (response.data.message || 'Unknown error')); 688 var btnText = originalButtonText || originalText.replace(/^• /, '');688 const resetText = originalButtonText || originalText.replace(/^• /, ''); 689 689 if ($submitBtn.is('input')) { 690 $submitBtn.val( btnText);690 $submitBtn.val(resetText); 691 691 } else { 692 $submitBtn.text( btnText);692 $submitBtn.text(resetText); 693 693 } 694 694 // Reset bar button … … 702 702 $submitBtn.prop('disabled', false); 703 703 alert('Failed to save settings. Please try again.'); 704 varbtnText = originalButtonText || originalText.replace(/^• /, '');704 const btnText = originalButtonText || originalText.replace(/^• /, ''); 705 705 if ($submitBtn.is('input')) { 706 706 $submitBtn.val(btnText); -
workzen-connector/trunk/assets/booking.js
r3436487 r3447338 20 20 21 21 // DOM elements 22 let bookingModal, bookingOverlay, bookingCloseBtn, bookingForm;22 let _bookingModal, bookingOverlay, bookingCloseBtn, bookingForm; 23 23 let calendarDays, calendarMonth, calendarPrev, calendarNext; 24 24 let timeSlots, selectedDateDisplay, summaryJobName, summaryDateTime; … … 36 36 // Get DOM elements for modal 37 37 bookingOverlay = document.querySelector('.wzc-booking-modal-overlay'); 38 bookingModal = document.querySelector('.wzc-booking-modal');38 _bookingModal = document.querySelector('.wzc-booking-modal'); 39 39 bookingCloseBtn = document.querySelector('.wzc-booking-close'); 40 40 bookingForm = document.getElementById('wzc-booking-form'); … … 185 185 186 186 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'); 190 190 const containerCalendarPrev = container.querySelector('.wzc-calendar-prev'); 191 191 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'); 197 197 const containerBookingForm = container.querySelector('.wzc-embedded-booking-form'); 198 198 … … 325 325 // Get max date based on booking window 326 326 const maxDate = new Date(); 327 const dateRange = parseInt(wzcBooking.date_range, 10) || 14;327 const dateRange = parseInt(wzcBooking.date_range, 10) || 365; 328 328 maxDate.setDate(maxDate.getDate() + dateRange); 329 329 maxDate.setHours(0, 0, 0, 0); … … 804 804 // Get max date based on booking window (date range) 805 805 const maxDate = new Date(); 806 const dateRange = parseInt(wzcBooking.date_range, 10) || 14;806 const dateRange = parseInt(wzcBooking.date_range, 10) || 365; 807 807 maxDate.setDate(maxDate.getDate() + dateRange); 808 808 maxDate.setHours(0, 0, 0, 0); … … 957 957 958 958 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(':'); 962 962 startHour = parseInt(startHourStr); 963 963 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 203 203 204 204 // Use the form that triggered the event 205 varcurrentForm = e.target;206 varisEmbeddedForm = currentForm.classList.contains('wzc-embedded-lead-form');205 const currentForm = e.target; 206 const isEmbeddedForm = currentForm.classList.contains('wzc-embedded-lead-form'); 207 207 208 208 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 30 30 public function add_admin_menu() { 31 31 // 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 } 34 41 35 42 add_menu_page( … … 603 610 </div> 604 611 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 605 677 <?php elseif ( $current_tab === 'integrations' ) : ?> 606 678 <!-- Integrations Tab --> … … 1105 1177 <th scope="row"><label for="wzconnector_booking_date_range"><?php esc_html_e( 'Booking Window (Days)', 'workzen-connector' ); ?></label></th> 1106 1178 <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> 1109 1181 </td> 1110 1182 </tr> -
workzen-connector/trunk/includes/class-ajax-handlers.php
r3436387 r3447338 38 38 add_action( 'wp_ajax_wzconnector_sync_booking_data', array( $this, 'ajax_sync_booking_data' ) ); 39 39 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 } 40 45 } 41 46 … … 80 85 ) ); 81 86 } 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 ) ); 82 138 } 83 139 … … 452 508 wp_send_json_error( array( 453 509 '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.', 454 530 ) ); 455 531 } -
workzen-connector/trunk/includes/class-constants.php
r3436387 r3447338 82 82 return array( 'automatic', 'manual' ); 83 83 } 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 } 84 170 } -
workzen-connector/trunk/includes/class-integrations-manager.php
r3384405 r3447338 68 68 69 69 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 } 71 83 } 72 84 } -
workzen-connector/trunk/includes/class-lead-sender.php
r3436387 r3447338 57 57 } 58 58 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 ); 78 61 79 62 $args = array( … … 167 150 ); 168 151 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 } 171 162 } 172 163 } … … 188 179 $queue = get_option( WZC_Constants::OPTION_RETRY_QUEUE, array() ); 189 180 $new_queue = array(); 181 190 182 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 ); 214 201 215 202 $args = array( … … 235 222 236 223 // 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 ); 238 225 239 226 if ( ! $success ) { … … 246 233 } 247 234 } 235 248 236 update_option( WZC_Constants::OPTION_RETRY_QUEUE, $new_queue, false ); 237 249 238 if ( ! empty( $new_queue ) ) { 250 239 wp_schedule_single_event( time() + 300, 'wzconnector_process_queue' ); 251 240 } 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; 252 274 } 253 275 … … 334 356 $tracking_cookie = sanitize_text_field( wp_unslash( $tracking_cookie ) ); 335 357 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 ) ) { 337 361 $tracking_cookie = ''; 338 362 } … … 341 365 try { 342 366 // 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 } 348 377 } 349 378 } catch ( Exception $e ) { … … 389 418 return $tracking; 390 419 } 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 bool399 */400 420 } -
workzen-connector/trunk/includes/class-online-booking.php
r3436387 r3447338 95 95 96 96 // Cache the data for 12 hours (skip in dev) 97 if ( ! $is_dev) {97 if ( ! ( defined( 'WORKZEN_DEV' ) && WORKZEN_DEV ) ) { 98 98 set_transient( $cache_key, $booking_data, 12 * HOUR_IN_SECONDS ); 99 99 } … … 215 215 && ! empty( $booking_data['job_types'] ); 216 216 $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' ); 218 218 $time_interval = get_option( WZC_Constants::OPTION_BOOKING_TIME_INTERVAL, '60' ); 219 219 … … 294 294 <div class="wzc-job-types"> 295 295 <?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 ?> 296 303 <div class="wzc-job-type-card" data-job-guid="<?php echo esc_attr( $job_type['guid'] ); ?>"> 297 304 <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> 299 306 </div> 300 307 <?php endforeach; ?> -
workzen-connector/trunk/includes/class-shortcodes.php
r3436487 r3447338 111 111 <div class="wzc-job-types"> 112 112 <?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 ?> 113 120 <div class="wzc-job-type-card" data-job-guid="<?php echo esc_attr( $job_type['guid'] ); ?>"> 114 121 <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> 116 123 </div> 117 124 <?php endforeach; ?> … … 386 393 && ! empty( $booking_data['job_types'] ); 387 394 $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' ); 389 396 $time_interval = get_option( WZC_Constants::OPTION_BOOKING_TIME_INTERVAL, '60' ); 390 397 -
workzen-connector/trunk/readme.txt
r3436487 r3447338 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1.1 1.17 Stable tag: 1.12.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 102 102 103 103 == 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 104 117 105 118 = 1.10.0 = … … 214 227 == Upgrade Notice == 215 228 229 = 1.12.0 = 230 Security and reliability update! Fixed race conditions, improved error handling, added input validation, and extended booking date range. Update recommended. 231 216 232 = 1.10.0 = 217 233 Major 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 3 3 * Plugin Name: WorkZen Connector 4 4 * 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.1 1.15 * Version: 1.12.0 6 6 * Author: Ika Balzam 7 7 * Author URI: https://workzen.io … … 20 20 21 21 // Define plugin constants 22 define( 'WZC_VERSION', '1.1 1.1' );22 define( 'WZC_VERSION', '1.12.0' ); 23 23 define( 'WZC_PLUGIN_FILE', __FILE__ ); 24 24 define( 'WZC_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
Note: See TracChangeset
for help on using the changeset viewer.