booking
/
includes
/
page-form-builder
/
field-packs
/
custom-shortcode
/
field-custom-shortcode.php
field-custom-shortcode.php in Booking Calendar 11.8.4, at includes/page-form-builder/field-packs/custom-shortcode/field-custom-shortcode.php
| 1 | <?php |
| 2 | /** |
| 3 | * WPBC BFB Pack: Custom Shortcode. |
| 4 | * |
| 5 | * Adds one validated shortcode token to the Advanced Booking Form. The token |
| 6 | * may include Booking Calendar options and quoted values, such as |
| 7 | * `[coupon discount ""]`, while the field pack remains independent of the |
| 8 | * runtime service that interprets the selected shortcode. |
| 9 | * |
| 10 | * @package Booking Calendar |
| 11 | * @since 11.8.2 |
| 12 | */ |
| 13 | |
| 14 | if ( ! defined( 'ABSPATH' ) ) { |
| 15 | exit; |
| 16 | } |
| 17 | |
| 18 | /** |
| 19 | * Normalize one Custom Shortcode value for persistence. |
| 20 | * |
| 21 | * This mirrors the browser-side syntax boundary so a direct or modified save |
| 22 | * request cannot persist nested tokens, HTML delimiters, control characters, |
| 23 | * unbalanced quotes, or an excessively long shortcode through this field pack. |
| 24 | * Runtime shortcode support remains the responsibility of the public form |
| 25 | * parser and edition-specific token producers. |
| 26 | * |
| 27 | * @since 11.8.4 |
| 28 | * |
| 29 | * @param mixed $shortcode_value Candidate Custom Shortcode value. |
| 30 | * |
| 31 | * @return string Normalized shortcode, or an empty string when invalid. |
| 32 | */ |
| 33 | function wpbc_bfb_normalize_custom_shortcode( $shortcode_value ) { |
| 34 | $shortcode = trim( (string) $shortcode_value ); |
| 35 | |
| 36 | if ( strlen( $shortcode ) > 4000 ) { |
| 37 | return ''; |
| 38 | } |
| 39 | |
| 40 | $shortcode_characters = array(); |
| 41 | $shortcode_length = preg_match_all( '/./us', $shortcode, $shortcode_characters ); |
| 42 | |
| 43 | if ( |
| 44 | false === $shortcode_length || |
| 45 | $shortcode_length < 3 || |
| 46 | $shortcode_length > 1000 || |
| 47 | '[' !== substr( $shortcode, 0, 1 ) || |
| 48 | ']' !== substr( $shortcode, -1 ) |
| 49 | ) { |
| 50 | return ''; |
| 51 | } |
| 52 | |
| 53 | $shortcode_body = trim( substr( $shortcode, 1, -1 ) ); |
| 54 | |
| 55 | if ( '' === $shortcode_body || preg_match( '/[\x00-\x1F\x7F<>\[\]]/', $shortcode_body ) ) { |
| 56 | return ''; |
| 57 | } |
| 58 | |
| 59 | $shortcode_name_end = strpos( $shortcode_body, ' ' ); |
| 60 | $shortcode_name = false === $shortcode_name_end |
| 61 | ? $shortcode_body |
| 62 | : substr( $shortcode_body, 0, $shortcode_name_end ); |
| 63 | |
| 64 | if ( ! preg_match( '/^[A-Za-z0-9_.*-]+$/D', $shortcode_name ) ) { |
| 65 | return ''; |
| 66 | } |
| 67 | |
| 68 | $active_quote = ''; |
| 69 | $body_length = strlen( $shortcode_body ); |
| 70 | |
| 71 | for ( $index = strlen( $shortcode_name ); $index < $body_length; $index++ ) { |
| 72 | $character = $shortcode_body[ $index ]; |
| 73 | |
| 74 | if ( '' !== $active_quote ) { |
| 75 | if ( $character === $active_quote ) { |
| 76 | $active_quote = ''; |
| 77 | } |
| 78 | continue; |
| 79 | } |
| 80 | |
| 81 | if ( '"' === $character || "'" === $character ) { |
| 82 | $active_quote = $character; |
| 83 | } |
| 84 | } |
| 85 | |
| 86 | return '' === $active_quote ? '[' . $shortcode_body . ']' : ''; |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * Sanitize Custom Shortcode values inside a Form Builder structure. |
| 91 | * |
| 92 | * The recursive traversal understands only the standard field-node envelope |
| 93 | * and leaves every unrelated field, section, page, and unknown extension value |
| 94 | * untouched. |
| 95 | * |
| 96 | * @since 11.8.4 |
| 97 | * |
| 98 | * @param array $structure Form Builder structure about to be persisted. |
| 99 | * |
| 100 | * @return array Structure with Custom Shortcode values normalized. |
| 101 | */ |
| 102 | function wpbc_bfb_sanitize_structure__custom_shortcode( $structure ) { |
| 103 | if ( |
| 104 | 'field' === ( isset( $structure['type'] ) ? $structure['type'] : '' ) && |
| 105 | isset( $structure['data'] ) && |
| 106 | is_array( $structure['data'] ) && |
| 107 | 'custom_shortcode' === ( isset( $structure['data']['type'] ) ? $structure['data']['type'] : '' ) |
| 108 | ) { |
| 109 | $structure['data']['shortcode'] = wpbc_bfb_normalize_custom_shortcode( |
| 110 | isset( $structure['data']['shortcode'] ) ? $structure['data']['shortcode'] : '' |
| 111 | ); |
| 112 | } |
| 113 | |
| 114 | foreach ( $structure as $structure_key => $structure_value ) { |
| 115 | if ( is_array( $structure_value ) ) { |
| 116 | $structure[ $structure_key ] = wpbc_bfb_sanitize_structure__custom_shortcode( $structure_value ); |
| 117 | } |
| 118 | } |
| 119 | |
| 120 | return $structure; |
| 121 | } |
| 122 | add_filter( 'wpbc_bfb_sanitize_structure_before_save', 'wpbc_bfb_sanitize_structure__custom_shortcode', 10, 1 ); |
| 123 | |
| 124 | /** |
| 125 | * Register the Custom Shortcode field schema and Inspector control. |
| 126 | * |
| 127 | * The field stores one string. The browser pack applies a bounded allow-list |
| 128 | * before exporting it, and the existing save endpoint applies WordPress KSES |
| 129 | * to the complete generated Advanced Booking Form. |
| 130 | * |
| 131 | * @param array $packs Registered Builder field packs. |
| 132 | * |
| 133 | * @return array Updated field packs. |
| 134 | */ |
| 135 | function wpbc_bfb_register_field_packs__custom_shortcode( $packs ) { |
| 136 | $packs['custom_shortcode'] = array( |
| 137 | 'kind' => 'field', |
| 138 | 'type' => 'custom_shortcode', |
| 139 | 'label' => __( 'Custom Shortcode', 'booking' ), |
| 140 | 'icon' => 'wpbc-bi-code', |
| 141 | 'usage_key' => 'custom_shortcode', |
| 142 | 'schema' => array( |
| 143 | 'props' => array( |
| 144 | 'shortcode' => array( |
| 145 | 'type' => 'string', |
| 146 | 'default' => '[field_name_hint]', |
| 147 | ), |
| 148 | ), |
| 149 | ), |
| 150 | 'inspector_ui' => array( |
| 151 | 'title' => __( 'Custom Shortcode', 'booking' ), |
| 152 | 'description' => __( 'Add one supported Booking Calendar shortcode. Options and quoted values are allowed, for example [coupon discount ""]. For a Form Options Cost hint, use its field name followed by _hint.', 'booking' ), |
| 153 | 'header_variant' => 'toolbar', |
| 154 | 'header_actions' => array( 'deselect', 'scrollto', 'move-up', 'move-down', 'duplicate', 'delete' ), |
| 155 | 'groups' => array( |
| 156 | array( |
| 157 | 'key' => 'basic', |
| 158 | 'label' => __( 'Shortcode', 'booking' ), |
| 159 | 'open' => true, |
| 160 | 'controls' => array( |
| 161 | array( |
| 162 | 'type' => 'text', |
| 163 | 'key' => 'shortcode', |
| 164 | 'label' => __( 'Shortcode', 'booking' ), |
| 165 | 'placeholder' => '[field_name_hint]', |
| 166 | ), |
| 167 | ), |
| 168 | ), |
| 169 | ), |
| 170 | ), |
| 171 | ); |
| 172 | |
| 173 | return $packs; |
| 174 | } |
| 175 | add_filter( 'wpbc_bfb_register_field_packs', 'wpbc_bfb_register_field_packs__custom_shortcode' ); |
| 176 | |
| 177 | /** |
| 178 | * Enqueue the Custom Shortcode renderer and exporters in Form Builder. |
| 179 | * |
| 180 | * WordPress calls this hook only while loading the Builder page. The runtime |
| 181 | * asset is deliberately loaded from `_out`, matching the project contract. |
| 182 | * |
| 183 | * @param string $page Current administration page slug. |
| 184 | * |
| 185 | * @return void |
| 186 | */ |
| 187 | function wpbc_bfb_enqueue__custom_shortcode_js( $page ) { |
| 188 | wp_enqueue_script( |
| 189 | 'wpbc-bfb_field_custom_shortcode', |
| 190 | wpbc_plugin_url( '/includes/page-form-builder/field-packs/custom-shortcode/_out/custom-shortcode.js' ), |
| 191 | array( 'wpbc-bfb' ), |
| 192 | WP_BK_VERSION_NUM, |
| 193 | true |
| 194 | ); |
| 195 | |
| 196 | wp_localize_script( |
| 197 | 'wpbc-bfb_field_custom_shortcode', |
| 198 | 'WPBC_BFB_Custom_Shortcode_Boot', |
| 199 | array( |
| 200 | 'example_shortcode' => '[field_name_hint]', |
| 201 | 'invalid_message' => __( 'Enter one complete shortcode, for example [field_name_hint] or [coupon discount ""].', 'booking' ), |
| 202 | ) |
| 203 | ); |
| 204 | } |
| 205 | add_action( 'wpbc_enqueue_js_field_pack', 'wpbc_bfb_enqueue__custom_shortcode_js', 10, 1 ); |
| 206 | |
| 207 | /** |
| 208 | * Add Custom Shortcode to the Cost Hints palette group. |
| 209 | * |
| 210 | * The palette placement reflects the primary Form Options Costs use case, but |
| 211 | * the field itself remains a generic single-token exporter. |
| 212 | * |
| 213 | * @param string $group Palette group. |
| 214 | * @param string $position Position inside the group. |
| 215 | * |
| 216 | * @return void |
| 217 | */ |
| 218 | function wpbc_bfb_palette_register_items__custom_shortcode( $group, $position ) { |
| 219 | if ( 'hints' !== $group || 'bottom' !== $position ) { |
| 220 | return; |
| 221 | } |
| 222 | ?> |
| 223 | <li class="wpbc_bfb__field" |
| 224 | data-id="custom_shortcode" |
| 225 | data-type="custom_shortcode" |
| 226 | data-usage_key="custom_shortcode" |
| 227 | data-shortcode="[field_name_hint]"> |
| 228 | <i class="menu_icon icon-1x wpbc-bi-code"></i> |
| 229 | <span class="wpbc_bfb__field-label"><?php echo esc_html__( 'Custom Shortcode', 'booking' ); ?></span> |
| 230 | <span class="wpbc_bfb__field-type">[field_name_hint]</span> |
| 231 | </li> |
| 232 | <?php |
| 233 | } |
| 234 | add_action( 'wpbc_bfb_palette_register_items', 'wpbc_bfb_palette_register_items__custom_shortcode', 10, 2 ); |
| 235 |