PluginProbe
Booking Calendar / 11.5
Booking Calendar v11.5
11.8.4 11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 All 204 releases
booking / includes / page-form-builder / field-packs / time-grid / time-grid.php

time-grid.php in Booking Calendar 11.5, at includes/page-form-builder/field-packs/time-grid/time-grid.php

443 lines 20.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * WPBC BFB Pack: Time Slots (WP-template–driven with grid editor & week-cycle profiles)
4 *
5 * Goal:
6 * - Ultra-easy creation of time slots via a visual grid: weekdays on X-axis, time on Y-axis.
7 * - Click or mouse-drag to add/remove slots.
8 * - Supports repeating week cycles (e.g., Week A/Week B/Week C …) so different weeks can have different slots.
9 *
10 * Overview:
11 * - Server (PHP) registers the pack, schema defaults, palette item, and prints wp.template() blocks.
12 * - Client (JS) renders preview and powers the Inspector’s interactive grid editor.
13 *
14 * File: ../includes/page-form-builder/field-packs/time-slots/field-time-slots-wptpl.php
15 *
16 * @package Booking Calendar
17 * @author wpdevelop
18 * @since 11.0.0
19 * @modified 2025-09-28
20 * @version 1.0.0
21 */
22
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 /**
28 * Register the "time_slots" field pack (WP-template–driven Inspector).
29 *
30 * Schema notes:
31 * - start_time / end_time in 'HH:MM' 24h format.
32 * - step_minutes granularity for grid rows.
33 * - week_cycle number of distinct weekly profiles (1..6). Profiles repeat in cycle order.
34 * - profiles: array of profile objects:
35 * array(
36 * array(
37 * 'key' => 'A',
38 * 'label' => 'Week A',
39 * 'slots' => array(
40 * // 1..7 (Mon..Sun). Each is array of ranges: array( array( 'from' => '10:00', 'to' => '12:00' ), ... )
41 * '1' => array( array( 'from' => '10:00', 'to' => '12:00' ) ),
42 * '2' => array(),
43 * ...
44 * '7' => array()
45 * ),
46 * ),
47 * ...
48 * )
49 *
50 * @param array $packs Accumulated packs.
51 *
52 * @return array Modified packs with "time_slots" field included.
53 */
54 function wpbc_bfb_register_field_packs__field_time_slots_wptpl( $packs ) {
55
56 $default_profile_slots = array(
57 '1' => array(), '2' => array(), '3' => array(), '4' => array(), '5' => array(), '6' => array(), '7' => array(),
58 );
59
60 $packs['time_slots'] = array(
61 'kind' => 'field',
62 'type' => 'time_slots',
63 'label' => __( 'Time Slots', 'booking' ),
64 'icon' => 'wpbc_icn_schedule',
65 'usage_key' => 'time_slots',
66
67 // Schema drives coercion/defaults/serialization.
68 'schema' => array(
69 'props' => array(
70 'label' => array( 'type' => 'string', 'default' => __( 'Time Slots', 'booking' ) ),
71 'name' => array( 'type' => 'string', 'default' => '' ),
72 'html_id' => array( 'type' => 'string', 'default' => '' ),
73 'required' => array( 'type' => 'boolean', 'default' => false ),
74 'cssclass' => array( 'type' => 'string', 'default' => '' ),
75 'help' => array( 'type' => 'string', 'default' => '' ),
76
77 // Grid configuration.
78 'start_time' => array( 'type' => 'string', 'default' => '08:00' ),
79 'end_time' => array( 'type' => 'string', 'default' => '20:00' ),
80 'step_minutes' => array( 'type' => 'number', 'default' => 30, 'min' => 5, 'max' => 180 ),
81
82 // Week cycle (profiles).
83 'week_cycle' => array( 'type' => 'number', 'default' => 1, 'min' => 1, 'max' => 6 ),
84 'profiles' => array(
85 'type' => 'array',
86 'default' => array(
87 array(
88 'key' => 'A',
89 'label' => 'Week A',
90 'slots' => $default_profile_slots,
91 ),
92 ),
93 ),
94
95 // Appearance.
96 'min_width' => array( 'type' => 'string', 'default' => '320px' ),
97 ),
98 ),
99
100 'templates_printer' => 'wpbc_bfb_field_time_slots_wptpl_print_templates',
101 );
102
103 return $packs;
104 }
105 add_filter( 'wpbc_bfb_register_field_packs', 'wpbc_bfb_register_field_packs__field_time_slots_wptpl' );
106
107 /**
108 * Enqueue the Time Slots (WP-template) pack JS for the Builder page.
109 *
110 * @param string $page Current admin page slug (from core).
111 *
112 * @return void
113 */
114 function wpbc_bfb_enqueue__field_time_slots_wptpl_js( $page ) {
115 wp_enqueue_script(
116 'wpbc-bfb_field_time_slots_wptpl',
117 wpbc_plugin_url( '/includes/page-form-builder/field-packs/time-grid/_out/time-grid-wptpl.js' ),
118 array( 'wp-util', 'wpbc-bfb' ),
119 WP_BK_VERSION_NUM,
120 true
121 );
122 }
123 add_action( 'wpbc_enqueue_js_field_pack', 'wpbc_bfb_enqueue__field_time_slots_wptpl_js', 10, 1 );
124
125 /**
126 * Print wp.template() blocks for the Time Slots field.
127 *
128 * Templates:
129 * - Preview: compact visualization of defined slots per week profile.
130 * - Inspector: full interactive grid editor (click/drag) + profile (week-cycle) control.
131 *
132 * @param string $page The current page slug (core passes the Builder slug here).
133 *
134 * @return void
135 */
136 function wpbc_bfb_field_time_slots_wptpl_print_templates( $page ) {
137
138 if ( WPBC_BFB_BUILDER_PAGE_SLUG !== $page ) {
139 return;
140 }
141 ?>
142 <script type="text/html" id="tmpl-wpbc-bfb-field-time-slots">
143 <span class="wpbc_bfb__noaction wpbc_bfb__no-drag-zone" inert="" style="display:block; min-width: {{ data.min_width || '320px' }};">
144 <# var is_req = ( true === data.required || 'true' === data.required || 1 === data.required || '1' === data.required ); #>
145 <# if ( data.label && data.label !== '' ) { #>
146 <label class="wpbc_bfb__field-label" <# if ( data.html_id ) { #> for="{{ data.html_id }}" <# } #>>
147 {{ data.label }} <# if ( is_req ) { #><span aria-hidden="true">*</span><# } #>
148 </label>
149 <# } #>
150
151 <div class="wpbc_bfb__time_preview {{ data.cssclass || '' }}" aria-disabled="true" tabindex="-1">
152 <#
153 var __profiles = Array.isArray( data.profiles ) ? data.profiles : [];
154 if ( ! __profiles.length ) {
155 __profiles = [ { key: 'A', label: 'Week A', slots: { '1':[], '2':[], '3':[], '4':[], '5':[], '6':[], '7':[] } } ];
156 }
157 var __days = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];
158 #>
159 <div class="wpbc_bfb__time_preview__weeks">
160 <# __profiles.forEach( function( p, idx ){ #>
161 <div class="wpbc_bfb__time_preview__week">
162 <div class="wpbc_bfb__time_preview__week_title">
163 <strong>{{ p && p.label ? p.label : ('Week ' + (idx+1)) }}</strong>
164 </div>
165 <div class="wpbc_bfb__time_preview__rows">
166 <# for ( var d = 1; d <= 7; d++ ) {
167 var day_lbl = __days[ d - 1 ];
168 var day_ranges = ( p && p.slots && p.slots[ String( d ) ] ) ? p.slots[ String( d ) ] : [];
169 #>
170 <div class="wpbc_bfb__time_preview__row">
171 <div class="wpbc_bfb__time_preview__day">{{ day_lbl }}</div>
172 <div class="wpbc_bfb__time_preview__slots">
173 <# if ( Array.isArray( day_ranges ) && day_ranges.length ) { #>
174 <# day_ranges.forEach( function(r){ #>
175 <span class="wpbc_bfb__time_badge">{{ (r.from || '') + '' + (r.to || '') }}</span>
176 <# }); #>
177 <# } else { #>
178 <span class="wpbc_bfb__time_badge wpbc_bfb__time_badge--empty"><?php echo esc_html__( 'No slots', 'booking' ); ?></span>
179 <# } #>
180 </div>
181 </div>
182 <# } #>
183 </div>
184 </div>
185 <# }); #>
186 </div>
187 <# if ( data.help ) { #>
188 <div class="wpbc_bfb__help">{{ data.help }}</div>
189 <# } #>
190 </div>
191 </span>
192 </script>
193
194
195
196 <script type="text/html" id="tmpl-wpbc-bfb-timegrid-row">
197 <div class="wpbc_bfb__timegrid_row" data-minute="{{ data.minute }}">
198 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--time">{{ data.label }}</div>
199 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--slot" data-day="1" data-minute="{{ data.minute }}"></div>
200 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--slot" data-day="2" data-minute="{{ data.minute }}"></div>
201 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--slot" data-day="3" data-minute="{{ data.minute }}"></div>
202 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--slot" data-day="4" data-minute="{{ data.minute }}"></div>
203 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--slot" data-day="5" data-minute="{{ data.minute }}"></div>
204 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--slot" data-day="6" data-minute="{{ data.minute }}"></div>
205 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--slot" data-day="7" data-minute="{{ data.minute }}"></div>
206 </div>
207 </script>
208
209 <?php
210 /**
211 * FIX 1 — Correct Inspector template ID to match the field type key "time_slots".
212 *
213 * The Builder looks up templates by: tmpl-wpbc-bfb-inspector-{type}
214 * So for type "time_slots" the ID must be "tmpl-wpbc-bfb-inspector-time_slots" (underscore).
215 *
216 * Replace the old <script> tag ID:
217 * id="tmpl-wpbc-bfb-inspector-time-slots"
218 * with the new one (underscore):
219 * id="tmpl-wpbc-bfb-inspector-time_slots"
220 *
221 * File: /includes/page-form-builder/field-packs/time-slots/field-time-slots-wptpl.php
222 */
223 ?>
224 <script type="text/html" id="tmpl-wpbc-bfb-inspector-time_slots">
225 <div class="wpbc_bfb__inspector__head">
226 <div class="header_container">
227 <div class="header_title_content">
228 <h3 class="title"><?php echo esc_html__( 'Time Slots', 'booking' ); ?></h3>
229 <div class="desc"><?php echo esc_html__( 'Click or drag on the grid to add time ranges. Use week cycle to set different slots for different weeks.', 'booking' ); ?></div>
230 </div>
231 <div class="actions wpbc_ajx_toolbar wpbc_no_borders">
232 <div class="ui_container ui_container_small">
233 <div class="ui_group">
234 <div class="ui_element">
235 <button type="button" class="button button-secondary wpbc_ui_control wpbc_ui_button" data-action="deselect" aria-label="<?php echo esc_attr__( 'Deselect', 'booking' ); ?>">
236 <i class="menu_icon icon-1x wpbc_icn_remove_done"></i>
237 </button>
238 <button type="button" class="button button-secondary wpbc_ui_control wpbc_ui_button" data-action="scrollto" aria-label="<?php echo esc_attr__( 'Scroll to field', 'booking' ); ?>">
239 <i class="menu_icon icon-1x wpbc_icn_ads_click filter_center_focus"></i>
240 </button>
241 </div>
242 <div class="ui_element">
243 <button type="button" class="button button-secondary wpbc_ui_control wpbc_ui_button" data-action="move-up" aria-label="<?php echo esc_attr__( 'Move up', 'booking' ); ?>">
244 <i class="menu_icon icon-1x wpbc_icn_arrow_upward"></i>
245 </button>
246 <button type="button" class="button button-secondary wpbc_ui_control wpbc_ui_button" data-action="move-down" aria-label="<?php echo esc_attr__( 'Move down', 'booking' ); ?>">
247 <i class="menu_icon icon-1x wpbc_icn_arrow_downward"></i>
248 </button>
249 </div>
250 <div class="ui_element">
251 <button data-action="duplicate" aria-label="<?php echo esc_attr__( 'Duplicate', 'booking' ); ?>" type="button" class="button button-secondary wpbc_ui_control wpbc_ui_button">
252 <i class="menu_icon icon-1x wpbc_icn_content_copy"></i>
253 </button>
254 <button data-action="delete" aria-label="<?php echo esc_attr__( 'Delete', 'booking' ); ?>" type="button" class="button button-secondary wpbc_ui_control wpbc_ui_button wpbc_ui_button_danger button-link-delete">
255 <span class="in-button-text"><?php echo esc_html__( 'Delete', 'booking' ); ?></span>&nbsp;&nbsp;<i class="menu_icon icon-1x wpbc_icn_delete_outline"></i>
256 </button>
257 </div>
258 </div><!-- .ui_group -->
259 </div><!-- .ui_container -->
260 </div><!-- .actions -->
261 </div><!-- .header_container -->
262 </div><!-- .wpbc_bfb__inspector__head -->
263
264 <div class="wpbc_bfb__inspector__body">
265
266 <section class="wpbc_bfb__inspector__group wpbc_ui__collapsible_group is-open" data-group="basic">
267 <button type="button" class="group__header">
268 <h3><?php echo esc_html__( 'Basic', 'booking' ); ?></h3>
269 <i class="wpbc_ui_el__vert_menu_root_section_icon menu_icon icon-1x wpbc-bi-chevron-right"></i>
270 </button>
271 <div class="group__fields">
272 <div class="inspector__row">
273 <label class="inspector__label"><?php echo esc_html__( 'Label', 'booking' ); ?></label>
274 <div class="inspector__control">
275 <input type="text" class="inspector__input" data-inspector-key="label" value="{{ data.label || '<?php echo esc_js( __( 'Time Slots', 'booking' ) ); ?>' }}">
276 </div>
277 </div>
278
279 <div class="inspector__row">
280 <label class="inspector__label"><?php echo esc_html__( 'Name', 'booking' ); ?></label>
281 <div class="inspector__control">
282 <input type="text" class="inspector__input" data-inspector-key="name" placeholder="<?php esc_attr_e( 'auto — from label', 'booking' ); ?>" value="{{ data.name || '' }}">
283 </div>
284 </div>
285
286 <div class="inspector__row">
287 <label class="inspector__label"><?php echo esc_html__( 'Required', 'booking' ); ?></label>
288 <div class="inspector__control">
289 <input type="checkbox" class="inspector__checkbox" data-inspector-key="required"
290 <# if ( true === data.required || 'true' === data.required || 1 === data.required || '1' === data.required ) { #> checked <# } #> >
291 </div>
292 </div>
293
294 <div class="inspector__row">
295 <label class="inspector__label"><?php echo esc_html__( 'Help text', 'booking' ); ?></label>
296 <div class="inspector__control">
297 <textarea class="inspector__textarea" rows="3" data-inspector-key="help">{{ data.help || '' }}</textarea>
298 </div>
299 </div>
300 </div>
301 </section>
302
303 <section class="wpbc_bfb__inspector__group wpbc_ui__collapsible_group is-open" data-group="grid">
304 <button type="button" class="group__header">
305 <h3><?php echo esc_html__( 'Time grid', 'booking' ); ?></h3>
306 <i class="wpbc_ui_el__vert_menu_root_section_icon menu_icon icon-1x wpbc-bi-chevron-right"></i>
307 </button>
308
309 <div class="group__fields">
310
311 <div class="inspector__row">
312 <label class="inspector__label"><?php echo esc_html__( 'From / To', 'booking' ); ?></label>
313 <div class="inspector__control wpbc_inline_inputs">
314 <input type="time" class="inspector__input inspector__w_45" data-inspector-key="start_time" value="{{ data.start_time || '08:00' }}">
315 <input type="time" class="inspector__input inspector__w_45" data-inspector-key="end_time" value="{{ data.end_time || '20:00' }}">
316 </div>
317 </div>
318
319 <div class="inspector__row">
320 <label class="inspector__label"><?php echo esc_html__( 'Step (minutes)', 'booking' ); ?></label>
321 <div class="inspector__control">
322 <div class="wpbc_len_group wpbc_inline_inputs" data-len-group="time-step">
323 <input data-len-range type="range" class="inspector__input" min="5" max="180" step="5" value="{{ data.step_minutes || 30 }}">
324 <input data-len-value type="number" class="inspector__input inspector__w_30" data-inspector-key="step_minutes" min="5" max="180" step="5" value="{{ data.step_minutes || 30 }}">
325 </div>
326 </div>
327 </div>
328
329 <div class="inspector__row">
330 <label class="inspector__label"><?php echo esc_html__( 'Week cycle', 'booking' ); ?></label>
331 <div class="inspector__control">
332 <select class="inspector__input js-week-cycle" data-inspector-key="week_cycle">
333 <# var wc = parseInt( data.week_cycle || 1, 10 ); if ( ! isFinite( wc ) || wc < 1 ) { wc = 1; } if ( wc > 6 ) { wc = 6; } #>
334 <# for ( var i = 1; i <= 6; i++ ) { #>
335 <option value="{{ i }}" <# if ( i === wc ) { #> selected <# } #> >
336 {{ i === 1 ? '<?php echo esc_js( __( 'Every week (1)', 'booking' ) ); ?>' : ('<?php echo esc_js( __( 'Repeat', 'booking' ) ); ?> ' + i) }}
337 </option>
338 <# } #>
339 </select>
340 <p class="wpbc_bfb__help" style="margin-top:6px;"><?php echo esc_html__( 'Choose how many different weekly profiles rotate in sequence (e.g., 2 = Week A / Week B / Week A / …).', 'booking' ); ?></p>
341 </div>
342 </div>
343
344 <div class="wpbc_bfb__timegrid_toolbar">
345 <div class="wpbc_bfb__timegrid_profiles">
346 <label><?php echo esc_html__( 'Profile', 'booking' ); ?>:</label>
347 <select class="js-profile-select"></select>
348 <button type="button" class="button button-secondary js-profile-rename"><?php echo esc_html__( 'Rename', 'booking' ); ?></button>
349 <button type="button" class="button button-secondary js-profile-duplicate"><?php echo esc_html__( 'Duplicate', 'booking' ); ?></button>
350 <button type="button" class="button js-profile-clear"><?php echo esc_html__( 'Clear all', 'booking' ); ?></button>
351 </div>
352 <div class="wpbc_bfb__timegrid_helpers">
353 <button type="button" class="button button-secondary js-row-fill"><?php echo esc_html__( 'Fill row…', 'booking' ); ?></button>
354 <button type="button" class="button button-secondary js-col-fill"><?php echo esc_html__( 'Fill column…', 'booking' ); ?></button>
355 <button type="button" class="button button-secondary js-copy-prev-day"><?php echo esc_html__( 'Copy ← day', 'booking' ); ?></button>
356 <button type="button" class="button button-secondary js-copy-prev-week"><?php echo esc_html__( 'Copy ← week', 'booking' ); ?></button>
357 </div>
358 </div>
359
360 <div class="wpbc_bfb__timegrid_root" data-grid>
361 <div class="wpbc_bfb__timegrid_head">
362 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--corner" aria-hidden="true"></div>
363 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--day" data-day="1"><?php echo esc_html__( 'Mon', 'booking' ); ?></div>
364 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--day" data-day="2"><?php echo esc_html__( 'Tue', 'booking' ); ?></div>
365 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--day" data-day="3"><?php echo esc_html__( 'Wed', 'booking' ); ?></div>
366 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--day" data-day="4"><?php echo esc_html__( 'Thu', 'booking' ); ?></div>
367 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--day" data-day="5"><?php echo esc_html__( 'Fri', 'booking' ); ?></div>
368 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--day" data-day="6"><?php echo esc_html__( 'Sat', 'booking' ); ?></div>
369 <div class="wpbc_bfb__timegrid_cell wpbc_bfb__timegrid_cell--day" data-day="7"><?php echo esc_html__( 'Sun', 'booking' ); ?></div>
370 </div>
371 <div class="wpbc_bfb__timegrid_body"></div>
372 </div>
373
374 <#
375 var __profiles_val = JSON.stringify( data.profiles || [] );
376 __profiles_val = __profiles_val.replace(/<\/textarea/gi, '<\\/textarea');
377 #>
378 <textarea class="wpbc_bfb__options_state inspector__input js-profiles-json" data-inspector-key="profiles" hidden>{{ __profiles_val }}</textarea>
379 </div>
380 </section>
381
382 <section class="wpbc_bfb__inspector__group wpbc_ui__collapsible_group" data-group="appearance">
383 <button type="button" class="group__header">
384 <h3><?php echo esc_html__( 'Appearance', 'booking' ); ?></h3>
385 <i class="wpbc_ui_el__vert_menu_root_section_icon menu_icon icon-1x wpbc-bi-chevron-right"></i>
386 </button>
387 <div class="group__fields">
388 <div class="inspector__row">
389 <label class="inspector__label"><?php echo esc_html__( 'CSS class', 'booking' ); ?></label>
390 <div class="inspector__control">
391 <input type="text" class="inspector__input" data-inspector-key="cssclass" value="{{ data.cssclass || '' }}">
392 </div>
393 </div>
394
395 <div class="inspector__row">
396 <label class="inspector__label"><?php echo esc_html__( 'HTML ID', 'booking' ); ?></label>
397 <div class="inspector__control">
398 <input type="text" class="inspector__input" data-inspector-key="html_id" value="{{ data.html_id || '' }}">
399 </div>
400 </div>
401
402 <div class="inspector__row">
403 <label class="inspector__label"><?php echo esc_html__( 'Min width', 'booking' ); ?></label>
404 <div class="inspector__control">
405 <input type="text" class="inspector__input" data-inspector-key="min_width" placeholder="320px" value="{{ data.min_width || '320px' }}">
406 </div>
407 </div>
408 </div>
409 </section>
410
411 </div><!-- .wpbc_bfb__inspector__body -->
412 </script>
413
414 <?php
415 }
416
417 /**
418 * (Optional) Register the "Time Slots" palette item in "general/bottom".
419 *
420 * @param string $group Palette group: 'general' | 'advanced' | ...
421 * @param string $position Position: 'top' | 'bottom'
422 *
423 * @return void
424 */
425 function wpbc_bfb_palette_register_items__time_slots_wptpl( $group, $position ) {
426
427 if ( 'times' !== $group || 'bottom' !== $position ) {
428 return;
429 }
430 ?>
431 <li class="wpbc_bfb__field"
432 data-id="time_slots"
433 data-type="time_slots"
434 data-usage_key="time_slots"
435 data-label="<?php echo esc_attr( __( 'Time Slots', 'booking' ) ); ?>">
436 <i class="menu_icon icon-1x wpbc_icn_access_time"></i>
437 <span class="wpbc_bfb__field-label"><?php echo esc_html( __( 'Time Slots', 'booking' ) ); ?> - Grid</span>
438 <span class="wpbc_bfb__field-type">time-slots</span>
439 </li>
440 <?php
441 }
442 add_action( 'wpbc_bfb_palette_register_items', 'wpbc_bfb_palette_register_items__time_slots_wptpl', 10, 2 );
443