Changeset 3431070
- Timestamp:
- 01/02/2026 01:48:09 PM (3 months ago)
- Location:
- creavi-booking-service
- Files:
-
- 27 added
- 9 edited
-
tags/1.1.0 (added)
-
tags/1.1.0/assets (added)
-
tags/1.1.0/assets/css (added)
-
tags/1.1.0/assets/css/admin.css (added)
-
tags/1.1.0/assets/css/style.css (added)
-
tags/1.1.0/assets/js (added)
-
tags/1.1.0/assets/js/admin.js (added)
-
tags/1.1.0/assets/js/booking.js (added)
-
tags/1.1.0/assets/vendor (added)
-
tags/1.1.0/assets/vendor/flatpickr (added)
-
tags/1.1.0/assets/vendor/flatpickr/flatpickr.min.css (added)
-
tags/1.1.0/assets/vendor/flatpickr/flatpickr.min.js (added)
-
tags/1.1.0/assets/vendor/luxon (added)
-
tags/1.1.0/assets/vendor/luxon/luxon.min.js (added)
-
tags/1.1.0/creavi-booking-service.php (added)
-
tags/1.1.0/includes (added)
-
tags/1.1.0/includes/admin.php (added)
-
tags/1.1.0/includes/ajax-handlers.php (added)
-
tags/1.1.0/includes/cbs-gcal-remote.php (added)
-
tags/1.1.0/includes/freebusy.php (added)
-
tags/1.1.0/includes/functions.php (added)
-
tags/1.1.0/includes/meta-boxes.php (added)
-
tags/1.1.0/includes/post-types.php (added)
-
tags/1.1.0/includes/render-booking-inline.php (added)
-
tags/1.1.0/includes/save-service.php (added)
-
tags/1.1.0/readme.txt (added)
-
trunk/assets/css/admin.css (modified) (1 diff)
-
trunk/assets/js/admin.js (modified) (1 diff)
-
trunk/assets/js/booking.js (modified) (2 diffs)
-
trunk/creavi-booking-service.php (modified) (1 diff)
-
trunk/includes/freebusy.php (added)
-
trunk/includes/functions.php (modified) (2 diffs)
-
trunk/includes/meta-boxes.php (modified) (2 diffs)
-
trunk/includes/render-booking-inline.php (modified) (2 diffs)
-
trunk/includes/save-service.php (modified) (2 diffs)
-
trunk/readme.txt (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
creavi-booking-service/trunk/assets/css/admin.css
r3342870 r3431070 267 267 fill: #007cba; 268 268 } 269 270 271 /* Tabs wrapper */ 272 .creavibc-tabs { 273 margin-top: 6px; 274 } 275 276 .creavibc-tabs-head { 277 display: flex; 278 gap: 16px; 279 align-items: center; 280 justify-content: space-between; 281 padding: 14px 14px; 282 background: #fff; 283 border-radius: 12px; 284 } 285 286 .creavibc-tabs-title { 287 font-size: 14px; 288 font-weight: 700; 289 color: #111827; 290 margin-bottom: 2px; 291 } 292 293 .creavibc-tabs-subtitle { 294 font-size: 12px; 295 color: #6b7280; 296 } 297 298 /* Tab buttons */ 299 .creavibc-tab-nav { 300 display: inline-flex; 301 background: #f3f4f6; 302 border-radius: 999px; 303 padding: 4px; 304 border: 1px solid #e5e7eb; 305 } 306 307 .creavibc-tab { 308 appearance: none; 309 border: 0; 310 background: transparent; 311 cursor: pointer; 312 padding: 8px 12px; 313 border-radius: 999px; 314 font-weight: 700; 315 font-size: 12px; 316 color: #374151; 317 display: inline-flex; 318 align-items: baseline; 319 gap: 8px; 320 transition: all .15s ease; 321 } 322 323 .creavibc-tab .creavibc-tab-hint { 324 font-weight: 600; 325 font-size: 11px; 326 color: #6b7280; 327 } 328 329 .creavibc-tab.is-active { 330 background: #fff; 331 box-shadow: 0 1px 0 rgba(17, 24, 39, .04), 0 1px 2px rgba(17, 24, 39, .08); 332 color: #111827; 333 } 334 335 .creavibc-tab.is-active .creavibc-tab-hint { 336 color: #374151; 337 } 338 339 /* Panels */ 340 .creavibc-tab-panels { 341 margin-top: 12px; 342 } 343 344 .creavibc-tab-panel { display: none; } 345 .creavibc-tab-panel.is-active { display: block; } 346 347 .creavibc-panel-card { 348 background: #fff; 349 border-radius: 12px; 350 padding: 14px; 351 } 352 353 .creavibc-panel-title { 354 font-size: 13px; 355 font-weight: 700; 356 color: #111827; 357 margin-bottom: 6px; 358 } 359 360 .creavibc-help { 361 font-size: 12px; 362 color: #6b7280; 363 margin: 0 0 12px 0; 364 } 365 366 .creavibc-field { 367 margin-bottom: 14px; 368 } 369 370 .creavibc-input, 371 .creavibc-select { 372 width: 50%; 373 max-width: 520px; 374 padding: 8px 10px; 375 border-radius: 10px; 376 border: 1px solid #d1d5db; 377 background: #fff; 378 } 379 380 .creavibc-input:focus, 381 .creavibc-select:focus { 382 outline: none; 383 border-color: #60a5fa; 384 box-shadow: 0 0 0 3px rgba(96, 165, 250, .25); 385 } -
creavi-booking-service/trunk/assets/js/admin.js
r3357718 r3431070 617 617 }); 618 618 }); 619 620 document.addEventListener('DOMContentLoaded', function () { 621 const panel = document.getElementById("creavibc-dynamic-days-settings"); 622 const radios = document.querySelectorAll('input[name="creavibc_availability_mode"]'); 623 624 if (!panel || !radios.length) return; 625 626 function sync() { 627 const checked = document.querySelector('input[name="creavibc_availability_mode"]:checked'); 628 const val = checked ? checked.value : "static"; 629 panel.style.display = (val === "dynamic") ? "block" : "none"; 630 } 631 632 radios.forEach(r => r.addEventListener("change", sync)); 633 sync(); 634 }); 635 636 637 document.addEventListener('DOMContentLoaded', function () { 638 const excludeField = document.getElementById('creavibc_excluded_booking_days'); 639 if (!excludeField || typeof flatpickr === 'undefined') return; 640 641 const defaultExcluded = excludeField.value 642 ? excludeField.value.split(',').map(s => s.trim()).filter(Boolean) 643 : []; 644 645 const fpExclude = flatpickr(excludeField, { 646 mode: 'multiple', 647 dateFormat: 'Y-m-d', 648 defaultDate: defaultExcluded, 649 closeOnSelect: false, 650 onChange: function (selectedDates, dateStr, instance) { 651 const formatted = selectedDates 652 .map(date => instance.formatDate(date, 'Y-m-d')) 653 .join(','); 654 excludeField.value = formatted; 655 excludeField.dispatchEvent(new Event('input', { bubbles: true })); 656 } 657 }); 658 }); 659 660 661 document.addEventListener('DOMContentLoaded', function () { 662 const radios = document.querySelectorAll('input[name="creavibc_availability_mode"]'); 663 const panel = document.getElementById('creavibc-dynamic-days-settings'); 664 if (!radios.length || !panel) return; 665 666 function sync() { 667 const val = document.querySelector('input[name="creavibc_availability_mode"]:checked')?.value || 'static'; 668 panel.style.display = (val === 'dynamic') ? 'block' : 'none'; 669 } 670 671 radios.forEach(r => r.addEventListener('change', sync)); 672 sync(); 673 }); 674 675 document.addEventListener('DOMContentLoaded', function () { 676 const root = document.querySelector('.creavibc-tabs'); 677 if (!root) return; 678 679 const hidden = document.getElementById('creavibc_availability_mode'); 680 const tabs = root.querySelectorAll('.creavibc-tab'); 681 const panels = root.querySelectorAll('.creavibc-tab-panel'); 682 683 function setActive(mode) { 684 if (!hidden) return; 685 hidden.value = mode; 686 687 tabs.forEach(btn => { 688 const isActive = btn.dataset.tab === mode; 689 btn.classList.toggle('is-active', isActive); 690 btn.setAttribute('aria-selected', isActive ? 'true' : 'false'); 691 }); 692 693 panels.forEach(panel => { 694 panel.classList.toggle('is-active', panel.dataset.panel === mode); 695 }); 696 } 697 698 tabs.forEach(btn => { 699 btn.addEventListener('click', function () { 700 setActive(btn.dataset.tab); 701 }); 702 }); 703 704 // init based on saved value 705 setActive(hidden?.value || root.dataset.default || 'static'); 706 }); 707 -
creavi-booking-service/trunk/assets/js/booking.js
r3347062 r3431070 233 233 } 234 234 235 // ----------------------------- 236 // Dynamic availability helpers 237 // ----------------------------- 238 function creavibc_normalizeCsvDates(csv) { 239 if (!csv) return []; 240 return String(csv).split(',') 241 .map(s => (s || '').trim()) 242 .filter(Boolean); 243 } 244 245 function creavibc_adminDateToUserDate(adminDateStr, adminTz, userTz) { 246 // Use noon to avoid DST edge issues when converting date-only values 247 return luxon.DateTime 248 .fromISO(adminDateStr, { zone: adminTz }) 249 .set({ hour: 12, minute: 0, second: 0, millisecond: 0 }) 250 .setZone(userTz) 251 .toISODate(); 252 } 253 254 function creavibc_generateDynamicAvailableDates({ monthsAhead, adminTz, tzMode, excludedCsv }) { 255 const userTz = window.CREAVIBC_USER_TIMEZONE || Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC'; 256 const safeMonths = Math.max(1, Math.min(12, parseInt(monthsAhead, 10) || 1)); 257 258 // Generate in admin tz (source of truth) 259 const startAdmin = luxon.DateTime.now().setZone(adminTz).startOf('day'); 260 const endAdmin = startAdmin.plus({ months: safeMonths }).startOf('day'); 261 262 // Excluded dates set (stored as admin dates) 263 const excludedAdmin = new Set(creavibc_normalizeCsvDates(excludedCsv)); 264 const excludedForPicker = new Set(); 265 266 if (tzMode === 'localized' && userTz !== adminTz) { 267 excludedAdmin.forEach(d => excludedForPicker.add(creavibc_adminDateToUserDate(d, adminTz, userTz))); 268 } else { 269 excludedAdmin.forEach(d => excludedForPicker.add(d)); 270 } 271 272 const out = []; 273 274 for (let cursor = startAdmin; cursor <= endAdmin; cursor = cursor.plus({ days: 1 })) { 275 const adminDate = cursor.toISODate(); 276 277 const dateForPicker = 278 (tzMode === 'localized' && userTz !== adminTz) 279 ? creavibc_adminDateToUserDate(adminDate, adminTz, userTz) 280 : adminDate; 281 282 if (!excludedForPicker.has(dateForPicker)) { 283 out.push(dateForPicker); 284 } 285 } 286 287 return out; 288 } 289 290 235 291 function initInlineCalendar(serviceId) { 236 292 const calendarEl = document.querySelector(`.creavibc-datepicker-inline[data-service-id="${serviceId}"]`); … … 244 300 245 301 // Sort AVAILABLE_DATES and prefer the first date >= today 246 const dates = (instance.AVAILABLE_DATES || []).slice().sort(); 302 //const dates = (instance.AVAILABLE_DATES || []).slice().sort(); 303 //const todayStr = new Date().toISOString().slice(0, 10); 304 //const firstEnabled = dates.find(d => d >= todayStr) || dates[0] || null; 305 306 // Build enabled dates: static (existing) OR dynamic (generated) 307 let dates = (instance.AVAILABLE_DATES || []).slice(); 308 309 if (instance.AVAILABILITY_MODE === 'dynamic' && window.luxon) { 310 dates = creavibc_generateDynamicAvailableDates({ 311 monthsAhead: instance.MONTHS_AHEAD || 1, 312 adminTz: instance.ADMIN_TIMEZONE || 'UTC', 313 tzMode: instance.TIMEZONE_MODE || 'localized', 314 excludedCsv: instance.EXCLUDED_DATES || '' 315 }); 316 } 317 318 dates = dates.slice().sort(); 319 247 320 const todayStr = new Date().toISOString().slice(0, 10); 248 321 const firstEnabled = dates.find(d => d >= todayStr) || dates[0] || null; 322 323 249 324 250 325 const fp = flatpickr(calendarEl, { -
creavi-booking-service/trunk/creavi-booking-service.php
r3358416 r3431070 24 24 require_once CREAVIBC_PLUGIN_DIR . 'includes/render-booking-inline.php'; 25 25 require_once CREAVIBC_PLUGIN_DIR . 'includes/admin.php'; 26 //require_once CREAVIBC_PLUGIN_DIR . 'includes/freebusy.php'; 26 27 require_once CREAVIBC_PLUGIN_DIR . 'includes/ajax-handlers.php'; 27 28 -
creavi-booking-service/trunk/includes/functions.php
r3342870 r3431070 68 68 $brand_color = get_post_meta($post->ID, '_creavibc_primary_color', true) ?: '#569FF7'; 69 69 70 $availability_mode = get_post_meta($post->ID, '_creavibc_availability_mode', true) ?: 'static'; 71 $months_ahead = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true); 72 if ($months_ahead < 1) $months_ahead = 1; 73 if ($months_ahead > 12) $months_ahead = 12; 74 75 $excluded = get_post_meta($post->ID, '_creavibc_excluded_booking_days', true) ?: ''; 76 77 70 78 71 79 global $creavibc_instances; … … 78 86 'ADMIN_TIMEZONE' => $tz_iana, 79 87 'TIMEZONE_MODE' => $tz_mode, 88 89 'AVAILABILITY_MODE' => $availability_mode, // static|dynamic 90 'MONTHS_AHEAD' => $months_ahead, // 1..12 91 'EXCLUDED_DATES' => $excluded, // "YYYY-MM-DD,YYYY-MM-DD" 80 92 ]; 81 93 -
creavi-booking-service/trunk/includes/meta-boxes.php
r3358416 r3431070 97 97 98 98 99 99 /* 100 100 function creavibc_render_available_days_box($post) { 101 101 $date_value = get_post_meta($post->ID, '_creavibc_available_booking_days', true); … … 119 119 echo '<input type="text" id="creavibc_available_booking_days" name="creavibc_available_booking_days" value="' . $dates_string . '" style="width:100%; padding: 10px; font-size: 16px;" autocomplete="off">'; 120 120 echo '<p class="description">Click inside the field to manually select or edit available dates. You can also use the range selector above to quickly generate a full list of future dates.</p>'; 121 }*/ 122 /* 123 function creavibc_render_available_days_box($post) { 124 125 // Existing saved static dates (keep as-is) 126 $date_value = get_post_meta($post->ID, '_creavibc_available_booking_days', true); 127 $dates_array = is_array($date_value) ? $date_value : explode(',', (string) $date_value); 128 $dates_string = esc_attr(implode(',', array_filter(array_map('trim', $dates_array)))); 129 130 // Availability mode 131 $mode = get_post_meta($post->ID, '_creavibc_availability_mode', true); 132 if ( ! in_array($mode, ['static', 'dynamic'], true) ) { 133 $mode = 'static'; 134 } 135 136 // Dynamic settings 137 $months_ahead = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true); 138 if ($months_ahead < 1) $months_ahead = 1; 139 if ($months_ahead > 12) $months_ahead = 12; 140 141 $exclude_value = get_post_meta($post->ID, '_creavibc_excluded_booking_days', true); 142 $exclude_array = is_array($exclude_value) ? $exclude_value : explode(',', (string) $exclude_value); 143 $exclude_string = esc_attr(implode(',', array_filter(array_map('trim', $exclude_array)))); 144 145 wp_nonce_field('creavibc_save_days', 'creavibc_days_nonce'); 146 147 // --- Mode switch 148 echo '<div style="margin-bottom:14px; padding:12px; border:1px solid #e5e5e5; border-radius:8px; background:#fff;">'; 149 echo '<p style="margin:0 0 8px 0;"><strong>How do you want to define available days?</strong></p>'; 150 151 echo '<label style="display:block; margin:0 0 8px 0;">'; 152 echo '<input type="radio" name="creavibc_availability_mode" value="static" ' . checked($mode, 'static', false) . ' /> '; 153 echo '<strong>Static</strong> — You manually pick exact bookable dates.'; 154 echo '</label>'; 155 156 echo '<label style="display:block; margin:0;">'; 157 echo '<input type="radio" name="creavibc_availability_mode" value="dynamic" ' . checked($mode, 'dynamic', false) . ' /> '; 158 echo '<strong>Dynamic</strong> — Customers can book in a rolling range from today (e.g. next 3 months), with optional exclusions.'; 159 echo '</label>'; 160 161 //echo '<p class="description" style="margin:8px 0 0 0;">Tip: Start with Dynamic for a SaaS-like experience. Use exclusions for vacations, holidays, blocked days.</p>'; 162 echo '</div>'; 163 164 // --- Dynamic panel 165 $dynamic_style = ($mode === 'dynamic') ? '' : 'display:none;'; 166 echo '<div id="creavibc-dynamic-days-settings" style="margin-bottom:14px; padding:12px; border:1px solid #e5e5e5; border-radius:8px; background:#fff; ' . esc_attr($dynamic_style) . '">'; 167 168 echo '<p style="margin:0 0 10px 0;"><strong>Dynamic availability settings</strong></p>'; 169 170 echo '<p style="margin:0 0 12px 0;">'; 171 echo '<label for="creavibc_dynamic_months_ahead"><strong>Booking window (months ahead)</strong></label><br>'; 172 echo '<span class="description">Example: “3” means customers can book from today until 3 months from now.</span><br>'; 173 echo '<select name="creavibc_dynamic_months_ahead" id="creavibc_dynamic_months_ahead" style="margin-top:6px;">'; 174 for ($i = 1; $i <= 12; $i++) { 175 $label = $i . ' month' . ($i > 1 ? 's' : ''); 176 echo '<option value="' . esc_attr($i) . '" ' . selected($months_ahead, $i, false) . '>' . esc_html($label) . '</option>'; 177 } 178 echo '</select>'; 179 echo '</p>'; 180 181 echo '<p style="margin:0;">'; 182 echo '<label for="creavibc_excluded_booking_days"><strong>Excluded dates (not bookable)</strong></label><br>'; 183 echo '<span class="description">Pick one or multiple dates to block (vacations, holidays, fully booked days). Stored as comma-separated YYYY-MM-DD.</span><br>'; 184 echo '<input type="text" id="creavibc_excluded_booking_days" name="creavibc_excluded_booking_days" value="' . $exclude_string . '" style="width:100%; padding:8px; margin-top:6px;" autocomplete="off">'; 185 echo '</p>'; 186 187 echo '</div>'; 188 189 // --- Static UI (keep behavior) 190 echo '<div style="padding:12px; border:1px solid #e5e5e5; border-radius:8px; background:#fff;">'; 191 192 echo '<p style="margin:0 0 8px 0;"><strong>Static availability (manual dates)</strong></p>'; 193 echo '<label for="creavibc_range_selector"><strong>Quick generate range</strong></label><br>'; 194 echo '<span class="description">This fills the static field below with all dates from today up to the chosen range.</span>'; 195 196 echo '<div style="margin-top:6px;">'; 197 echo '<select id="creavibc_range_selector">'; 198 echo '<option value="">— Select a Range —</option>'; 199 for ($i = 1; $i <= 12; $i++) { 200 $label = $i . ' month' . ($i > 1 ? 's' : ''); 201 echo '<option value="' . esc_attr($i) . '">' . esc_html($label . ' ahead') . '</option>'; 202 } 203 echo '</select>'; 204 echo '</div>'; 205 206 echo '<p style="margin:14px 0 6px 0;"><strong>Bookable dates</strong></p>'; 207 echo '<span class="description">Click the input to select multiple dates. Stored as comma-separated YYYY-MM-DD.</span><br>'; 208 echo '<input type="text" id="creavibc_available_booking_days" name="creavibc_available_booking_days" value="' . $dates_string . '" style="width:100%; padding:10px; font-size:16px; margin-top:6px;" autocomplete="off">'; 209 210 echo '</div>'; 211 } 212 */ 213 214 function creavibc_render_available_days_box($post) { 215 216 // Existing saved static dates (keep as-is) 217 $date_value = get_post_meta($post->ID, '_creavibc_available_booking_days', true); 218 $dates_array = is_array($date_value) ? $date_value : explode(',', (string) $date_value); 219 $dates_string = esc_attr(implode(',', array_filter(array_map('trim', $dates_array)))); 220 221 // Availability mode + dynamic settings 222 $mode = get_post_meta($post->ID, '_creavibc_availability_mode', true); 223 if ( ! in_array($mode, ['static', 'dynamic'], true) ) { 224 $mode = 'static'; 225 } 226 227 $months_ahead = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true); 228 $months_ahead = max(1, min(12, $months_ahead ?: 1)); 229 230 // ✅ Canonical excluded field name + meta key (do not change!) 231 $excluded_value = (string) get_post_meta($post->ID, '_creavibc_excluded_booking_days', true); 232 $excluded_array = array_filter(array_map('trim', explode(',', $excluded_value))); 233 $excluded_string = esc_attr(implode(',', $excluded_array)); 234 235 wp_nonce_field('creavibc_save_days', 'creavibc_days_nonce'); 236 237 $is_dynamic = ($mode === 'dynamic'); 238 ?> 239 240 <div class="creavibc-tabs" data-default="<?php echo esc_attr($mode); ?>"> 241 <!-- Hidden field: this is what gets saved --> 242 <input type="hidden" name="creavibc_availability_mode" id="creavibc_availability_mode" value="<?php echo esc_attr($mode); ?>"> 243 244 <!-- Header --> 245 <div class="creavibc-tabs-head"> 246 <div> 247 <div class="creavibc-tabs-title"><?php esc_html_e('Available Booking Days', 'creavi-booking-service'); ?></div> 248 <div class="creavibc-tabs-subtitle"> 249 <?php esc_html_e('Choose how customers can book: rolling window (dynamic) or manually selected dates (static).', 'creavi-booking-service'); ?> 250 </div> 251 </div> 252 253 <!-- Tab buttons --> 254 <div class="creavibc-tab-nav" role="tablist" aria-label="<?php esc_attr_e('Availability mode', 'creavi-booking-service'); ?>"> 255 <button 256 type="button" 257 class="creavibc-tab <?php echo $is_dynamic ? 'is-active' : ''; ?>" 258 data-tab="dynamic" 259 role="tab" 260 aria-selected="<?php echo $is_dynamic ? 'true' : 'false'; ?>" 261 > 262 <?php esc_html_e('Dynamic', 'creavi-booking-service'); ?> 263 <span class="creavibc-tab-hint"><?php esc_html_e('Rolling window', 'creavi-booking-service'); ?></span> 264 </button> 265 266 <button 267 type="button" 268 class="creavibc-tab <?php echo ! $is_dynamic ? 'is-active' : ''; ?>" 269 data-tab="static" 270 role="tab" 271 aria-selected="<?php echo ! $is_dynamic ? 'true' : 'false'; ?>" 272 > 273 <?php esc_html_e('Static', 'creavi-booking-service'); ?> 274 <span class="creavibc-tab-hint"><?php esc_html_e('Manual dates', 'creavi-booking-service'); ?></span> 275 </button> 276 </div> 277 </div> 278 279 <!-- Panels --> 280 <div class="creavibc-tab-panels"> 281 <!-- Dynamic panel --> 282 <div class="creavibc-tab-panel <?php echo $is_dynamic ? 'is-active' : ''; ?>" data-panel="dynamic" role="tabpanel"> 283 <div class="creavibc-panel-card"> 284 <div class="creavibc-panel-title"><?php esc_html_e('Dynamic availability settings', 'creavi-booking-service'); ?></div> 285 <p class="creavibc-help"> 286 <?php esc_html_e('Customers can book from today up to the selected window. You can also block specific dates (vacations, holidays, fully booked days).', 'creavi-booking-service'); ?> 287 </p> 288 289 <div class="creavibc-field"> 290 <label for="creavibc_dynamic_months_ahead"><strong><?php esc_html_e('Booking window (months ahead)', 'creavi-booking-service'); ?></strong></label> 291 <div class="creavibc-help"><?php esc_html_e('Example: “3” means customers can book from today until 3 months from now.', 'creavi-booking-service'); ?></div> 292 <select name="creavibc_dynamic_months_ahead" id="creavibc_dynamic_months_ahead" class="creavibc-select"> 293 <?php for ($i = 1; $i <= 12; $i++): ?> 294 <option value="<?php echo esc_attr($i); ?>" <?php selected($months_ahead, $i); ?>> 295 <?php echo esc_html($i . ' ' . __('month', 'creavi-booking-service') . ($i > 1 ? 's' : '')); ?> 296 </option> 297 <?php endfor; ?> 298 </select> 299 </div> 300 301 <div class="creavibc-field"> 302 <label for="creavibc_excluded_booking_days"><strong><?php esc_html_e('Excluded dates (not bookable)', 'creavi-booking-service'); ?></strong></label> 303 <div class="creavibc-help"><?php esc_html_e('Pick one or multiple dates to block. Stored as comma-separated YYYY-MM-DD.', 'creavi-booking-service'); ?></div> 304 305 <!-- ✅ Keep the SAME ID + NAME so flatpickr init & POST save match --> 306 <input 307 type="text" 308 id="creavibc_excluded_booking_days" 309 name="creavibc_excluded_booking_days" 310 value="<?php echo $excluded_string; ?>" 311 class="creavibc-input" 312 autocomplete="off" 313 placeholder="YYYY-MM-DD, YYYY-MM-DD" 314 > 315 </div> 316 317 <div class="creavibc-help" style="margin-top:10px;"> 318 <?php esc_html_e('Tip: Excluded dates are applied only when Dynamic is selected.', 'creavi-booking-service'); ?> 319 </div> 320 </div> 321 </div> 322 323 <!-- Static panel --> 324 <div class="creavibc-tab-panel <?php echo ! $is_dynamic ? 'is-active' : ''; ?>" data-panel="static" role="tabpanel"> 325 <div class="creavibc-panel-card"> 326 <div class="creavibc-panel-title"><?php esc_html_e('Static availability (manual dates)', 'creavi-booking-service'); ?></div> 327 <p class="creavibc-help"><?php esc_html_e('Select exact bookable dates. Use the quick range generator or pick manually.', 'creavi-booking-service'); ?></p> 328 329 <div class="creavibc-field"> 330 <label for="creavibc_range_selector"><strong><?php esc_html_e('Quick generate range', 'creavi-booking-service'); ?></strong></label> 331 <div class="creavibc-help"><?php esc_html_e('Fills the static field below with all dates from today up to the chosen range.', 'creavi-booking-service'); ?></div> 332 333 <select id="creavibc_range_selector" class="creavibc-select"> 334 <option value=""><?php esc_html_e('— Select a Range —', 'creavi-booking-service'); ?></option> 335 <?php for ($i = 1; $i <= 12; $i++): ?> 336 <option value="<?php echo esc_attr($i); ?>"> 337 <?php echo esc_html($i . ' Month' . ($i > 1 ? 's' : '') . ' Ahead'); ?> 338 </option> 339 <?php endfor; ?> 340 </select> 341 </div> 342 343 <div class="creavibc-field"> 344 <label for="creavibc_available_booking_days"><strong><?php esc_html_e('Available booking dates', 'creavi-booking-service'); ?></strong></label> 345 <div class="creavibc-help"><?php esc_html_e('Click inside to pick dates. Stored as comma-separated YYYY-MM-DD.', 'creavi-booking-service'); ?></div> 346 347 <input 348 type="text" 349 id="creavibc_available_booking_days" 350 name="creavibc_available_booking_days" 351 value="<?php echo $dates_string; ?>" 352 class="creavibc-input" 353 autocomplete="off" 354 placeholder="YYYY-MM-DD, YYYY-MM-DD" 355 > 356 </div> 357 </div> 358 </div> 359 </div> 360 361 362 </div> 363 364 <?php 121 365 } 122 366 -
creavi-booking-service/trunk/includes/render-booking-inline.php
r3347062 r3431070 50 50 $tz_mode = get_post_meta($post->ID, '_creavibc_timezone_mode', true) ?: 'localized'; 51 51 $brand_color = get_post_meta($post->ID, '_creavibc_primary_color', true) ?: '#569FF7'; 52 53 $availability_mode = get_post_meta($post->ID, '_creavibc_availability_mode', true) ?: 'static'; 54 $months_ahead = (int) get_post_meta($post->ID, '_creavibc_dynamic_months_ahead', true); 55 if ($months_ahead < 1) $months_ahead = 1; 56 if ($months_ahead > 12) $months_ahead = 12; 57 58 $excluded = get_post_meta($post->ID, '_creavibc_excluded_booking_days', true) ?: ''; 59 52 60 53 61 global $creavibc_instances; … … 59 67 'ADMIN_TIMEZONE' => $tz_iana, 60 68 'TIMEZONE_MODE' => $tz_mode, 69 70 'AVAILABILITY_MODE' => $availability_mode, // static|dynamic 71 'MONTHS_AHEAD' => $months_ahead, // 1..12 72 'EXCLUDED_DATES' => $excluded, // "YYYY-MM-DD,YYYY-MM-DD" 61 73 ]; 62 74 ?> -
creavi-booking-service/trunk/includes/save-service.php
r3352052 r3431070 8 8 // Save available days 9 9 10 /* 10 11 $days_nonce = isset($_POST['creavibc_days_nonce']) ? sanitize_text_field(wp_unslash($_POST['creavibc_days_nonce'])) : ''; 11 12 if ($days_nonce && wp_verify_nonce($days_nonce, 'creavibc_save_days')) { … … 16 17 17 18 } 18 } 19 }*/ 20 21 $days_nonce = isset($_POST['creavibc_days_nonce']) ? sanitize_text_field(wp_unslash($_POST['creavibc_days_nonce'])) : ''; 22 if ( $days_nonce && wp_verify_nonce( $days_nonce, 'creavibc_save_days' ) ) { 23 24 // 1) Static dates (existing) 25 if ( isset($_POST['creavibc_available_booking_days']) ) { 26 $raw = sanitize_text_field( wp_unslash( $_POST['creavibc_available_booking_days'] ) ); 27 // Normalize: remove spaces around commas 28 $raw = implode(',', array_filter(array_map('trim', explode(',', $raw)))); 29 update_post_meta( $post_id, '_creavibc_available_booking_days', $raw ); 30 } 31 32 // 2) Availability mode (new) 33 $mode = isset($_POST['creavibc_availability_mode']) 34 ? sanitize_text_field( wp_unslash($_POST['creavibc_availability_mode']) ) 35 : 'static'; 36 37 $mode = in_array($mode, ['static','dynamic'], true) ? $mode : 'static'; 38 update_post_meta( $post_id, '_creavibc_availability_mode', $mode ); 39 40 // 3) Months ahead (new) 41 $months = isset($_POST['creavibc_dynamic_months_ahead']) 42 ? (int) $_POST['creavibc_dynamic_months_ahead'] 43 : 1; 44 45 if ( $months < 1 ) $months = 1; 46 if ( $months > 12 ) $months = 12; 47 48 update_post_meta( $post_id, '_creavibc_dynamic_months_ahead', $months ); 49 50 // 4) Excluded dates (new) 51 if ( isset($_POST['creavibc_excluded_booking_days']) ) { 52 $exclude_raw = sanitize_text_field( wp_unslash($_POST['creavibc_excluded_booking_days']) ); 53 54 $exclude_dates = array_filter(array_map('trim', explode(',', $exclude_raw))); 55 // Keep only valid YYYY-MM-DD 56 $exclude_dates = array_values(array_filter($exclude_dates, function($d){ 57 return preg_match('/^\d{4}-\d{2}-\d{2}$/', $d); 58 })); 59 60 update_post_meta( $post_id, '_creavibc_excluded_booking_days', implode(',', $exclude_dates) ); 61 } else { 62 // If field missing, keep existing meta (don’t delete) 63 // (Do nothing) 64 } 65 } 66 19 67 20 68 // Save form fields -
creavi-booking-service/trunk/readme.txt
r3423763 r3431070 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1. 0.177 Stable tag: 1.1.0 8 8 License: GPLv2 or later 9 9 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 86 86 == Changelog == 87 87 88 = 1.1.0 = 89 * Added dynamic availability mode with rolling booking windows and support for excluded dates. 90 * Improved Available Booking Days UI with a tab-based switch between dynamic and static modes, fully backward compatible. 88 91 89 92 = 1.0.17 = … … 93 96 * Fixed admin timezone selector to correctly keep the saved value selected on post edit. 94 97 * Improved timezone dropdown population to show the full list while preserving the stored timezone. 95 96 98 97 99 = 1.0.15 = … … 105 107 * Added smooth animations when rendering or clearing time slots 106 108 * Improved booking slots container with height transition to prevent layout jumps 107 108 109 109 110 = 1.0.12 =
Note: See TracChangeset
for help on using the changeset viewer.