PluginProbe
Booking Calendar / 11.9
Booking Calendar v11.9
11.9 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 All 205 releases
booking / includes / page-form-builder / field-packs / static-text / field-static-text.php

field-static-text.php in Booking Calendar 11.9, at includes/page-form-builder/field-packs/static-text/field-static-text.php

452 lines 16.3 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: Static Text (label / paragraph / heading) — Schema-driven, Factory Inspector
4 *
5 * Purpose:
6 * - Non-interactive text block for headings, labels, paragraphs, etc.
7 * - One pack type "static_text" with props: text, links, tag, align, bold, italic, html_allowed, nl2br, cssclass_extra, html_id, help.
8 * - Inspector is Factory-driven (no WP templates). Preview is rendered by a pure JS renderer class.
9 * - Link tokens such as {terms} are replaced with sanitized link definitions.
10 * - Exporter (JS) emits escaped HTML (optionally wrapped with id/class) + optional help.
11 *
12 * Contracts Used:
13 * - Filter: wpbc_bfb_register_field_packs
14 * - Action: wpbc_enqueue_js_field_pack
15 * - Action: wpbc_bfb_palette_register_items (optional palette registration)
16 *
17 * Files:
18 * ../includes/page-form-builder/field-packs/static-text/field-static-text.php (this file)
19 * ../includes/page-form-builder/field-packs/static-text/_out/static-text-links.js (link editor + token renderer)
20 * ../includes/page-form-builder/field-packs/static-text/_out/static-text-links.css (link editor layout)
21 * ../includes/page-form-builder/field-packs/static-text/_out/static-text.js (renderer + exporter glue)
22 *
23 * @package Booking Calendar
24 * @author wpdevelop
25 * @since 11.0.0
26 * @modified 2026-09-25
27 * @version 1.1.0
28 */
29
30 if ( ! defined( 'ABSPATH' ) ) {
31 exit;
32 }
33
34 /**
35 * Register the "static_text" field pack (Schema-driven Inspector).
36 *
37 * Props stored in schema:
38 * - text : string — content to show
39 * - links : array — optional token-to-link definitions
40 * - tag : string — allowed: p,label,span,small,div,h1,h2,h3,h4,h5,h6
41 * - align : string — left|center|right
42 * - bold : int|bool — 0|1
43 * - italic : int|bool — 0|1
44 * - html_allowed : int|bool — 0|1
45 * - nl2br : int|bool — 0|1 (applies only when html_allowed=0)
46 * - cssclass_extra : string — optional extra classes for wrapper element
47 * - html_id : string — optional id
48 * - help : string — optional help text (Inspector-only, exported below content)
49 * - name : string — hidden from Inspector, stored for consistency
50 *
51 * @param array $packs Accumulated packs.
52 * @return array Modified packs with "static_text" field included.
53 */
54 function wpbc_bfb_register_field_packs__field_static_text_schema( $packs ) {
55
56 $tag_options = array(
57 'p' => __( 'Paragraph', 'booking' ),
58 'label' => __( 'Label', 'booking' ),
59 'span' => __( 'Span', 'booking' ),
60 'small' => __( 'Small', 'booking' ),
61 'div' => __( 'Div', 'booking' ),
62 'h1' => 'H1',
63 'h2' => 'H2',
64 'h3' => 'H3',
65 'h4' => 'H4',
66 'h5' => 'H5',
67 'h6' => 'H6',
68 );
69
70 $align_options = array(
71 'left' => __( 'Left', 'booking' ),
72 'center' => __( 'Center', 'booking' ),
73 'right' => __( 'Right', 'booking' ),
74 );
75
76 $packs['static_text'] = array(
77 'kind' => 'field',
78 'type' => 'static_text',
79 'label' => __( 'Static Text', 'booking' ),
80 'icon' => 'text',
81 'usage_key' => 'static_text',
82
83 // === Schema: defaults + coercion handled by Inspector/Factory ===
84 'schema' => array(
85 'props' => array(
86 'text' => array( 'type' => 'string', 'default' => __( 'Add your message here…', 'booking' ) ),
87 'links' => array( 'type' => 'array', 'default' => array() ),
88 'tag' => array( 'type' => 'string', 'enum' => array_keys( $tag_options ), 'default' => 'p' ),
89 'align' => array( 'type' => 'string', 'enum' => array_keys( $align_options ), 'default' => 'left' ),
90 'bold' => array( 'type' => 'int', 'min' => 0, 'max' => 1, 'default' => 0 ),
91 'italic' => array( 'type' => 'int', 'min' => 0, 'max' => 1, 'default' => 0 ),
92 'html_allowed' => array( 'type' => 'int', 'min' => 0, 'max' => 1, 'default' => 0 ),
93 'nl2br' => array( 'type' => 'int', 'min' => 0, 'max' => 1, 'default' => 1 ),
94 'cssclass_extra' => array( 'type' => 'string', 'default' => '' ),
95 'html_id' => array( 'type' => 'string', 'default' => '' ),
96 'help' => array( 'type' => 'string', 'default' => '' ),
97 // "name" is supported in storage but hidden in Inspector.
98 'name' => array( 'type' => 'string', 'default' => '' ),
99 ),
100 ),
101
102 // === Inspector UI (Factory-driven) ===
103 'inspector_ui' => array(
104 'title' => __( 'Static Text', 'booking' ),
105 'description' => __( 'Non-interactive text block (paragraph, label, or heading).', 'booking' ),
106 'header_variant' => 'toolbar',
107 'header_actions' => array( 'deselect', 'scrollto', 'move-up', 'move-down', 'duplicate', 'delete' ),
108 'groups' => array(
109 array(
110 'key' => 'content',
111 'label' => __( 'Content', 'booking' ),
112 'open' => true,
113 'controls' => array(
114 array(
115 'type' => 'textarea',
116 'key' => 'text',
117 'label' => __( 'Text', 'booking' ),
118 'rows' => 3,
119 'placeholder' => __( 'Enter text', 'booking' ) . '...',
120 ),
121 array(
122 'type' => 'select',
123 'key' => 'tag',
124 'label' => __( 'HTML Tag', 'booking' ),
125 'options' => $tag_options,
126 ),
127 array(
128 'type' => 'select',
129 'key' => 'align',
130 'label' => __( 'Alignment', 'booking' ),
131 'options' => $align_options,
132 ),
133 array( 'type' => 'checkbox', 'key' => 'bold', 'label' => __( 'Bold', 'booking' ) ),
134 array( 'type' => 'checkbox', 'key' => 'italic', 'label' => __( 'Italic', 'booking' ) ),
135 // array( 'type' => 'checkbox', 'key' => 'html_allowed', 'label' => __( 'Allow basic HTML', 'booking' ) ),
136 array( 'type' => 'checkbox', 'key' => 'nl2br', 'label' => __( 'Convert newlines to <br> tag', 'booking' ) ),
137 array(
138 'type' => 'slot',
139 'key' => 'links',
140 'label' => __( 'Link definitions', 'booking' ),
141 'slot' => 'static_text_links',
142 ),
143 ),
144 ),
145 array(
146 'key' => 'advanced',
147 'label' => __( 'Advanced', 'booking' ),
148 'controls' => array(
149 array( 'type' => 'text', 'key' => 'cssclass_extra', 'label' => __( 'Extra CSS classes', 'booking' ) ),
150 // "name" intentionally omitted from Inspector
151 array( 'type' => 'text', 'key' => 'html_id', 'label' => __( 'HTML ID', 'booking' ) ),
152 // array( 'type' => 'textarea', 'key' => 'help', 'label' => __( 'Help text', 'booking' ), 'rows' => 3 ),
153 ),
154 ),
155 ),
156 ),
157 );
158
159 return $packs;
160 }
161 add_filter( 'wpbc_bfb_register_field_packs', 'wpbc_bfb_register_field_packs__field_static_text_schema' );
162
163 /**
164 * Enqueue the Static Text field pack assets for the Builder page.
165 *
166 * @param string $page Current admin page slug (unused, provided for parity with other packs).
167 * @return void
168 */
169 function wpbc_bfb_enqueue__field_static_text_assets( $page ) {
170
171 wp_enqueue_style(
172 'wpbc-bfb_field_static_text_links',
173 wpbc_plugin_url( '/includes/page-form-builder/field-packs/static-text/_out/static-text-links.css' ),
174 array(),
175 WP_BK_VERSION_NUM
176 );
177
178 wp_enqueue_script(
179 'wpbc-bfb_field_static_text_links',
180 wpbc_plugin_url( '/includes/page-form-builder/field-packs/static-text/_out/static-text-links.js' ),
181 array( 'wpbc-bfb' ),
182 WP_BK_VERSION_NUM,
183 true
184 );
185
186 wp_localize_script(
187 'wpbc-bfb_field_static_text_links',
188 'WPBC_BFB_Static_Text_Links_Boot',
189 array(
190 'add_link' => __( 'Add link', 'booking' ),
191 'anchor' => __( 'Anchor', 'booking' ),
192 'css_class' => __( 'CSS class', 'booking' ),
193 'destination' => __( 'Destination', 'booking' ),
194 'duplicate' => __( 'Duplicate', 'booking' ),
195 'link_type' => __( 'Type', 'booking' ),
196 'links_help' => __( 'Use tokens inside braces, for example: {terms} or {privacy_policy}. Each token can link to a URL or an anchor on the same page.', 'booking' ),
197 'missing_definitions' => __( 'Add a link definition for: %s', 'booking' ),
198 'no_links' => __( 'Add a link definition, then insert its token into the text.', 'booking' ),
199 'remove' => __( 'Remove', 'booking' ),
200 'target' => __( 'Target', 'booking' ),
201 'token_key' => __( 'Token Key', 'booking' ),
202 'url' => __( 'URL', 'booking' ),
203 'visible_text' => __( 'Text', 'booking' ),
204 ),
205 );
206
207 wp_enqueue_script(
208 'wpbc-bfb_field_static_text',
209 wpbc_plugin_url( '/includes/page-form-builder/field-packs/static-text/_out/static-text.js' ),
210 array( 'wpbc-bfb', 'wpbc-bfb_field_static_text_links' ),
211 WP_BK_VERSION_NUM,
212 true
213 );
214 }
215 add_action( 'wpbc_enqueue_js_field_pack', 'wpbc_bfb_enqueue__field_static_text_assets', 10, 1 );
216
217 /**
218 * Limit a scalar link-definition value without requiring mbstring.
219 *
220 * @param mixed $raw_value Candidate scalar value.
221 * @param int $max_length Maximum character count.
222 *
223 * @return string Bounded string, or an empty string for non-scalar input.
224 */
225 function wpbc_bfb_static_text_links_limit_string( $raw_value, $max_length ) {
226
227 if ( ! is_scalar( $raw_value ) ) {
228 return '';
229 }
230
231 $raw_value = (string) $raw_value;
232 $max_length = max( 0, (int) $max_length );
233
234 if ( function_exists( 'mb_substr' ) ) {
235 return mb_substr( $raw_value, 0, $max_length );
236 }
237
238 return substr( $raw_value, 0, $max_length );
239 }
240
241 /**
242 * Normalize a Static Text link token key to the Builder brace-token grammar.
243 *
244 * @param mixed $raw_key Candidate token key.
245 *
246 * @return string Lowercase token key containing only letters, numbers, and underscores.
247 */
248 function wpbc_bfb_static_text_links_sanitize_token_key( $raw_key ) {
249
250 $token_key = strtolower( wpbc_bfb_static_text_links_limit_string( $raw_key, 64 ) );
251 $token_key = preg_replace( '/[\s\-]+/', '_', $token_key );
252 $token_key = preg_replace( '/[^a-z0-9_]/', '', $token_key );
253 $token_key = preg_replace( '/_+/', '_', $token_key );
254
255 return trim( (string) $token_key, '_' );
256 }
257
258 /**
259 * Normalize a space-delimited CSS class list from one link definition.
260 *
261 * @param mixed $raw_classes Candidate class list.
262 *
263 * @return string Sanitized, de-duplicated class list.
264 */
265 function wpbc_bfb_static_text_links_sanitize_css_classes( $raw_classes ) {
266
267 $raw_classes = wpbc_bfb_static_text_links_limit_string( $raw_classes, 200 );
268 $raw_class_names = preg_split( '/\s+/', trim( $raw_classes ) );
269 $safe_class_names = array();
270
271 foreach ( (array) $raw_class_names as $raw_class_name ) {
272 $safe_class_name = sanitize_html_class( $raw_class_name );
273 if ( '' !== $safe_class_name ) {
274 $safe_class_names[ $safe_class_name ] = $safe_class_name;
275 }
276 }
277
278 return implode( ' ', array_values( $safe_class_names ) );
279 }
280
281 /**
282 * Normalize one link destination according to its allow-listed link type.
283 *
284 * URL values use WordPress protocol filtering. Anchor and popup identifiers
285 * are restricted to characters that are safe in an HTML identifier.
286 *
287 * @param mixed $raw_destination Candidate destination.
288 * @param string $link_type Normalized link type.
289 *
290 * @return string Sanitized destination.
291 */
292 function wpbc_bfb_static_text_links_sanitize_destination( $raw_destination, $link_type ) {
293
294 $destination = trim( wpbc_bfb_static_text_links_limit_string( $raw_destination, 2048 ) );
295
296 if ( 'anchor' === $link_type || 'popup' === $link_type ) {
297 $destination = ltrim( $destination, '#' );
298 return (string) preg_replace( '/[^A-Za-z0-9_:\-.]/', '', $destination );
299 }
300
301 return esc_url_raw( $destination );
302 }
303
304 /**
305 * Normalize executable-free link definitions for Static Text and Accept Terms.
306 *
307 * The collection is bounded to avoid allowing a crafted Builder request to
308 * persist an unreasonably large set of link records. Duplicate token keys are
309 * made deterministic by adding a numeric suffix.
310 *
311 * @param mixed $raw_links Candidate array or JSON-encoded link definitions.
312 *
313 * @return array Sanitized ordered link definitions.
314 */
315 function wpbc_bfb_static_text_links_normalize_definitions( $raw_links ) {
316
317 if ( is_string( $raw_links ) ) {
318 $decoded_links = json_decode( $raw_links, true );
319 $raw_links = is_array( $decoded_links ) ? $decoded_links : array();
320 }
321
322 if ( ! is_array( $raw_links ) ) {
323 return array();
324 }
325
326 $normalized_links = array();
327 $used_keys = array();
328 $link_index = 0;
329
330 foreach ( array_slice( array_values( $raw_links ), 0, 20 ) as $raw_link ) {
331 if ( ! is_array( $raw_link ) ) {
332 continue;
333 }
334
335 $link_index++;
336 $token_key = wpbc_bfb_static_text_links_sanitize_token_key( isset( $raw_link['key'] ) ? $raw_link['key'] : '' );
337
338 if ( '' === $token_key ) {
339 $token_key = 'link_' . $link_index;
340 }
341
342 $base_key = $token_key;
343 $key_suffix = 2;
344 while ( isset( $used_keys[ $token_key ] ) ) {
345 $token_key = substr( $base_key, 0, 60 ) . '_' . $key_suffix;
346 $key_suffix++;
347 }
348 $used_keys[ $token_key ] = true;
349
350 $link_type = isset( $raw_link['link_type'] ) ? sanitize_key( $raw_link['link_type'] ) : 'url';
351 if ( ! in_array( $link_type, array( 'url', 'anchor', 'popup' ), true ) ) {
352 $link_type = 'url';
353 }
354
355 $visible_text = isset( $raw_link['text'] ) ? sanitize_text_field( wpbc_bfb_static_text_links_limit_string( $raw_link['text'], 300 ) ) : '';
356 if ( '' === $visible_text ) {
357 $visible_text = str_replace( '_', ' ', $token_key );
358 }
359
360 $normalized_links[] = array(
361 'key' => $token_key,
362 'text' => $visible_text,
363 'link_type' => $link_type,
364 'destination' => wpbc_bfb_static_text_links_sanitize_destination(
365 isset( $raw_link['destination'] ) ? $raw_link['destination'] : '',
366 $link_type
367 ),
368 'target' => isset( $raw_link['target'] ) && '_self' === $raw_link['target'] ? '_self' : '_blank',
369 'cssclass' => wpbc_bfb_static_text_links_sanitize_css_classes( isset( $raw_link['cssclass'] ) ? $raw_link['cssclass'] : '' ),
370 );
371 }
372
373 return $normalized_links;
374 }
375
376 /**
377 * Sanitize stored link definitions throughout a decoded Builder structure.
378 *
379 * Missing `links` properties are intentionally left missing so existing
380 * Static Text fields retain their exact storage shape and Accept Terms can
381 * continue applying its established defaults. Explicit empty collections
382 * remain empty.
383 *
384 * @param array $structure_branch Decoded Builder structure or nested branch.
385 *
386 * @return array Structure with field-owned link definitions normalized.
387 */
388 function wpbc_bfb_sanitize_structure__static_text_links( $structure_branch ) {
389
390 if ( ! is_array( $structure_branch ) ) {
391 return array();
392 }
393
394 if (
395 isset( $structure_branch['type'], $structure_branch['data'] )
396 && 'field' === $structure_branch['type']
397 && is_array( $structure_branch['data'] )
398 ) {
399 $field_type = isset( $structure_branch['data']['type'] ) ? sanitize_key( $structure_branch['data']['type'] ) : '';
400
401 if (
402 in_array( $field_type, array( 'static_text', 'accept_terms' ), true )
403 && array_key_exists( 'links', $structure_branch['data'] )
404 ) {
405 $structure_branch['data']['links'] = wpbc_bfb_static_text_links_normalize_definitions( $structure_branch['data']['links'] );
406 }
407 }
408
409 foreach ( $structure_branch as $branch_key => $branch_value ) {
410 if ( ! is_array( $branch_value ) ) {
411 continue;
412 }
413
414 $structure_branch[ $branch_key ] = wpbc_bfb_sanitize_structure__static_text_links( $branch_value );
415 }
416
417 return $structure_branch;
418 }
419 add_filter( 'wpbc_bfb_sanitize_structure_before_save', 'wpbc_bfb_sanitize_structure__static_text_links', 20, 1 );
420
421 /**
422 * (Optional) Register a palette item.
423 *
424 * @param string $group Palette group (e.g., 'general', 'structure').
425 * @param string $position Position: 'top' | 'bottom'.
426 * @return void
427 */
428 function wpbc_bfb_palette_register_items__static_text_schema( $group, $position ) {
429
430 if ( 'essentials' !== $group || 'top' !== $position ) {
431 return;
432 }
433 ?>
434 <li class="wpbc_bfb__field"
435 data-id="static_text"
436 data-type="static_text"
437 data-usage_key="static_text"
438 data-text="<?php echo esc_attr( __( 'Add your message here…', 'booking' ) ); ?>"
439 data-tag="p"
440 data-align="left"
441 data-bold="0"
442 data-italic="0"
443 data-html_allowed="0"
444 data-nl2br="1">
445 <i class="menu_icon icon-1x wpbc-bi-fonts"></i>
446 <span class="wpbc_bfb__field-label"><?php echo esc_html( __( 'Static Text', 'booking' ) ); ?></span>
447 <span class="wpbc_bfb__field-type">static_text</span>
448 </li>
449 <?php
450 }
451 add_action( 'wpbc_bfb_palette_register_items', 'wpbc_bfb_palette_register_items__static_text_schema', 10, 2 );
452