PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
← All changes | inc/page-builders/bricks/elements/form-widget.php +606 -10 0.0.11 → 2.12.8 View file →
@@ -7,8 +7,10 @@
7 7 */
8 8
9 9 namespace SRFM\Inc\Page_Builders\Bricks\Elements;
10 10
11 +use Spec_Gb_Helper;
12 +use SRFM\Inc\Generate_Form_Markup;
11 13 use SRFM\Inc\Helper;
12 14 use SRFM\Inc\Page_Builders\Page_Builders;
13 15
14 16 if ( ! defined( 'ABSPATH' ) ) {
@@ -18,9 +20,8 @@
18 20 /**
19 21 * SureForms Bricks element.
20 22 */
21 23 class Form_Widget extends \Bricks\Element {
22 -
23 24 /**
24 25 * Element category.
25 26 *
26 27 * @var string
@@ -81,8 +82,44 @@
81 82 ];
82 83 }
83 84
84 85 /**
86 + * Set control groups for the Style tab accordion sections.
87 + *
88 + * @since 2.7.0
89 + * @return void
90 + */
91 + public function set_control_groups() {
92 + $this->control_groups['srfm_form_styling'] = [
93 + 'title' => __( 'Form Styling', 'sureforms' ),
94 + 'tab' => 'style',
95 + ];
96 +
97 + $styling_required = [
98 + [ 'form-id', '!=', '' ],
99 + [ 'formTheme', '!=', 'inherit' ],
100 + ];
101 +
102 + $this->control_groups['srfm_layout'] = [
103 + 'title' => __( 'Layout', 'sureforms' ),
104 + 'tab' => 'style',
105 + 'required' => $styling_required,
106 + ];
107 +
108 + $this->control_groups['srfm_button'] = [
109 + 'title' => __( 'Button', 'sureforms' ),
110 + 'tab' => 'style',
111 + 'required' => $styling_required,
112 + ];
113 +
114 + $this->control_groups['srfm_fields'] = [
115 + 'title' => __( 'Fields', 'sureforms' ),
116 + 'tab' => 'style',
117 + 'required' => $styling_required,
118 + ];
119 + }
120 +
121 + /**
85 122 * Set element controls.
86 123 *
87 124 * @since 0.0.5
88 125 * @return void
@@ -88,8 +125,10 @@
88 125 * @return void
89 126 */
90 127 public function set_controls() {
91 128
129 + // === CONTENT TAB ===
130 +
92 131 // Select Form.
93 132 $this->controls['form-id'] = [
94 133
95 134 'tab' => 'content',
@@ -114,8 +153,332 @@
114 153 'type' => 'info',
115 154 'required' => [ 'form-id', '!=', '' ],
116 155 ];
117 156
157 + // === STYLE TAB — Form Styling Group ===
158 +
159 + $form_required = [
160 + [ 'form-id', '!=', '' ],
161 + [ 'formTheme', '!=', 'inherit' ],
162 + ];
163 +
164 + // Form Theme Select.
165 + $this->controls['formTheme'] = [
166 + 'group' => 'srfm_form_styling',
167 + 'label' => __( 'Form Theme', 'sureforms' ),
168 + 'type' => 'select',
169 + 'options' => [
170 + 'inherit' => __( "Inherit Form's Original Style", 'sureforms' ),
171 + 'default' => __( 'Default', 'sureforms' ),
172 + ],
173 + 'default' => 'inherit',
174 + ];
175 +
176 + /**
177 + * Hook for Pro to add form theme control after inherit toggle.
178 + *
179 + * @param Form_Widget $element The element instance.
180 + * @since 2.7.0
181 + */
182 + do_action( 'srfm_bricks_after_basic_styling_controls', $this );
183 +
184 + // Primary Color.
185 + $this->controls['primaryColor'] = [
186 + 'group' => 'srfm_form_styling',
187 + 'label' => __( 'Primary Color', 'sureforms' ),
188 + 'type' => 'color',
189 + 'default' => '#111C44',
190 + 'required' => $form_required,
191 + ];
192 +
193 + // Text Color.
194 + $this->controls['textColor'] = [
195 + 'group' => 'srfm_form_styling',
196 + 'label' => __( 'Text Color', 'sureforms' ),
197 + 'type' => 'color',
198 + 'default' => '#1E1E1E',
199 + 'required' => $form_required,
200 + ];
201 +
202 + // Text on Primary Color.
203 + $this->controls['textOnPrimaryColor'] = [
204 + 'group' => 'srfm_form_styling',
205 + 'label' => __( 'Text on Primary', 'sureforms' ),
206 + 'type' => 'color',
207 + 'default' => '#FFFFFF',
208 + 'required' => $form_required,
209 + ];
210 +
211 + // Background Separator.
212 + $this->controls['bgSeparator'] = [
213 + 'group' => 'srfm_form_styling',
214 + 'label' => __( 'Background', 'sureforms' ),
215 + 'type' => 'separator',
216 + 'required' => $form_required,
217 + ];
218 +
219 + // Background Type.
220 + $this->controls['bgType'] = [
221 + 'group' => 'srfm_form_styling',
222 + 'label' => __( 'Type', 'sureforms' ),
223 + 'type' => 'select',
224 + 'options' => [
225 + 'color' => __( 'Color', 'sureforms' ),
226 + 'gradient' => __( 'Gradient', 'sureforms' ),
227 + 'image' => __( 'Image', 'sureforms' ),
228 + ],
229 + 'default' => 'color',
230 + 'required' => $form_required,
231 + ];
232 +
233 + // Background Color (when bgType=color).
234 + $this->controls['bgColor'] = [
235 + 'group' => 'srfm_form_styling',
236 + 'label' => __( 'Background Color', 'sureforms' ),
237 + 'type' => 'color',
238 + 'default' => '#FFFFFF',
239 + 'required' => array_merge( $form_required, [ [ 'bgType', '=', 'color' ] ] ),
240 + ];
241 +
242 + // --- Gradient Controls (when bgType=gradient) ---
243 + $gradient_required = array_merge( $form_required, [ [ 'bgType', '=', 'gradient' ] ] );
244 +
245 + $this->controls['bgGradientColor1'] = [
246 + 'group' => 'srfm_form_styling',
247 + 'label' => __( 'Gradient Color 1', 'sureforms' ),
248 + 'type' => 'color',
249 + 'required' => $gradient_required,
250 + ];
251 +
252 + $this->controls['bgGradientColor1Stop'] = [
253 + 'group' => 'srfm_form_styling',
254 + 'label' => __( 'Color 1 Location (%)', 'sureforms' ),
255 + 'type' => 'number',
256 + 'default' => 0,
257 + 'min' => 0,
258 + 'max' => 100,
259 + 'unit' => '%',
260 + 'required' => $gradient_required,
261 + ];
262 +
263 + $this->controls['bgGradientColor2'] = [
264 + 'group' => 'srfm_form_styling',
265 + 'label' => __( 'Gradient Color 2', 'sureforms' ),
266 + 'type' => 'color',
267 + 'required' => $gradient_required,
268 + ];
269 +
270 + $this->controls['bgGradientColor2Stop'] = [
271 + 'group' => 'srfm_form_styling',
272 + 'label' => __( 'Color 2 Location (%)', 'sureforms' ),
273 + 'type' => 'number',
274 + 'default' => 100,
275 + 'min' => 0,
276 + 'max' => 100,
277 + 'unit' => '%',
278 + 'required' => $gradient_required,
279 + ];
280 +
281 + $this->controls['bgGradientType'] = [
282 + 'group' => 'srfm_form_styling',
283 + 'label' => __( 'Gradient Type', 'sureforms' ),
284 + 'type' => 'select',
285 + 'options' => [
286 + 'linear' => __( 'Linear', 'sureforms' ),
287 + 'radial' => __( 'Radial', 'sureforms' ),
288 + ],
289 + 'default' => 'linear',
290 + 'required' => $gradient_required,
291 + ];
292 +
293 + $this->controls['bgGradientAngle'] = [
294 + 'group' => 'srfm_form_styling',
295 + 'label' => __( 'Angle', 'sureforms' ),
296 + 'type' => 'number',
297 + 'default' => 90,
298 + 'min' => 0,
299 + 'max' => 360,
300 + 'unit' => 'deg',
301 + 'required' => array_merge( $gradient_required, [ [ 'bgGradientType', '=', 'linear' ] ] ),
302 + ];
303 +
304 + // --- Background Image Controls (when bgType=image) ---
305 + $image_required = array_merge( $form_required, [ [ 'bgType', '=', 'image' ] ] );
306 +
307 + $this->controls['bgImage'] = [
308 + 'group' => 'srfm_form_styling',
309 + 'label' => __( 'Image', 'sureforms' ),
310 + 'type' => 'image',
311 + 'required' => $image_required,
312 + ];
313 +
314 + $this->controls['bgImageSize'] = [
315 + 'group' => 'srfm_form_styling',
316 + 'label' => __( 'Size', 'sureforms' ),
317 + 'type' => 'select',
318 + 'options' => [
319 + 'cover' => __( 'Cover', 'sureforms' ),
320 + 'contain' => __( 'Contain', 'sureforms' ),
321 + 'auto' => __( 'Auto', 'sureforms' ),
322 + ],
323 + 'default' => 'cover',
324 + 'required' => $image_required,
325 + ];
326 +
327 + $this->controls['bgImagePosition'] = [
328 + 'group' => 'srfm_form_styling',
329 + 'label' => __( 'Position', 'sureforms' ),
330 + 'type' => 'select',
331 + 'options' => [
332 + 'left top' => __( 'Left Top', 'sureforms' ),
333 + 'left center' => __( 'Left Center', 'sureforms' ),
334 + 'left bottom' => __( 'Left Bottom', 'sureforms' ),
335 + 'center top' => __( 'Center Top', 'sureforms' ),
336 + 'center center' => __( 'Center Center', 'sureforms' ),
337 + 'center bottom' => __( 'Center Bottom', 'sureforms' ),
338 + 'right top' => __( 'Right Top', 'sureforms' ),
339 + 'right center' => __( 'Right Center', 'sureforms' ),
340 + 'right bottom' => __( 'Right Bottom', 'sureforms' ),
341 + ],
342 + 'default' => 'center center',
343 + 'required' => $image_required,
344 + ];
345 +
346 + $this->controls['bgImageRepeat'] = [
347 + 'group' => 'srfm_form_styling',
348 + 'label' => __( 'Repeat', 'sureforms' ),
349 + 'type' => 'select',
350 + 'options' => [
351 + 'no-repeat' => __( 'No Repeat', 'sureforms' ),
352 + 'repeat' => __( 'Repeat', 'sureforms' ),
353 + 'repeat-x' => __( 'Repeat X', 'sureforms' ),
354 + 'repeat-y' => __( 'Repeat Y', 'sureforms' ),
355 + ],
356 + 'default' => 'no-repeat',
357 + 'required' => $image_required,
358 + ];
359 +
360 + $this->controls['bgImageAttachment'] = [
361 + 'group' => 'srfm_form_styling',
362 + 'label' => __( 'Attachment', 'sureforms' ),
363 + 'type' => 'select',
364 + 'options' => [
365 + 'scroll' => __( 'Scroll', 'sureforms' ),
366 + 'fixed' => __( 'Fixed', 'sureforms' ),
367 + ],
368 + 'default' => 'scroll',
369 + 'required' => $image_required,
370 + ];
371 +
372 + // === STYLE TAB — Layout Group ===
373 +
374 + // Form Padding.
375 + $this->controls['formPadding'] = [
376 + 'group' => 'srfm_layout',
377 + 'label' => __( 'Form Padding', 'sureforms' ),
378 + 'type' => 'spacing',
379 + 'css' => [
380 + [
381 + 'property' => '--srfm-form-padding-{key}',
382 + 'selector' => '.srfm-form-container',
383 + ],
384 + ],
385 + 'default' => [
386 + 'top' => '0',
387 + 'right' => '0',
388 + 'bottom' => '0',
389 + 'left' => '0',
390 + 'unit' => 'px',
391 + ],
392 + 'required' => $form_required,
393 + ];
394 +
395 + // Form Border Radius.
396 + $this->controls['formBorderRadius'] = [
397 + 'group' => 'srfm_layout',
398 + 'label' => __( 'Form Border Radius', 'sureforms' ),
399 + 'type' => 'spacing',
400 + 'css' => [
401 + [
402 + 'property' => '--srfm-form-border-radius-{key}',
403 + 'selector' => '.srfm-form-container',
404 + ],
405 + ],
406 + 'default' => [
407 + 'top' => '0',
408 + 'right' => '0',
409 + 'bottom' => '0',
410 + 'left' => '0',
411 + 'unit' => 'px',
412 + ],
413 + 'required' => $form_required,
414 + ];
415 +
416 + /**
417 + * Hook for Pro to add additional layout controls (e.g., row/column gap).
418 + *
419 + * @param Form_Widget $element The element instance.
420 + * @since 2.7.0
421 + */
422 + do_action( 'srfm_bricks_layout_controls', $this );
423 +
424 + // === STYLE TAB — Button Group ===
425 +
426 + // Button Alignment.
427 + $this->controls['buttonAlignment'] = [
428 + 'group' => 'srfm_button',
429 + 'label' => __( 'Alignment', 'sureforms' ),
430 + 'type' => 'select',
431 + 'options' => [
432 + 'left' => __( 'Left', 'sureforms' ),
433 + 'center' => __( 'Center', 'sureforms' ),
434 + 'right' => __( 'Right', 'sureforms' ),
435 + 'justify' => __( 'Full Width', 'sureforms' ),
436 + ],
437 + 'default' => 'left',
438 + 'required' => $form_required,
439 + ];
440 +
441 + /**
442 + * Hook for Pro to add additional button controls.
443 + *
444 + * @param Form_Widget $element The element instance.
445 + * @since 2.7.0
446 + */
447 + do_action( 'srfm_bricks_button_controls', $this );
448 +
449 + // === STYLE TAB — Fields Group ===
450 +
451 + // Field Spacing.
452 + // Hidden when Pro's custom theme is selected (replaced by Row/Column Gap).
453 + $this->controls['fieldSpacing'] = [
454 + 'group' => 'srfm_fields',
455 + 'label' => __( 'Field Spacing', 'sureforms' ),
456 + 'type' => 'select',
457 + 'options' => [
458 + 'small' => __( 'Small', 'sureforms' ),
459 + 'medium' => __( 'Medium', 'sureforms' ),
460 + 'large' => __( 'Large', 'sureforms' ),
461 + ],
462 + 'default' => 'medium',
463 + 'required' => array_merge( $form_required, [ [ 'formTheme', '!=', 'custom' ] ] ),
464 + ];
465 +
466 + /**
467 + * Hook for Pro to add additional field controls.
468 + *
469 + * @param Form_Widget $element The element instance.
470 + * @since 2.7.0
471 + */
472 + do_action( 'srfm_bricks_field_controls', $this );
473 +
474 + /**
475 + * Hook for Pro to add additional style sections (e.g., Messages).
476 + *
477 + * @param Form_Widget $element The element instance.
478 + * @since 2.7.0
479 + */
480 + do_action( 'srfm_bricks_after_styling_section', $this );
118 481 }
119 482
120 483 /**
121 484 * Enqueue scripts for phone and dropdown field in the Bricks editor.
@@ -134,25 +497,258 @@
134 497 * @since 0.0.5
135 498 * @return void
136 499 */
137 500 public function render() {
138 - $settings = $this->settings;
139 - $form_id = isset( $settings['form-id'] ) ? $settings['form-id'] : '';
140 - $form_title = isset( $settings['form-title'] );
501 + $settings = $this->settings;
502 + $form_id = absint( $settings['form-id'] ?? 0 );
141 503
142 504 if ( $form_id > 0 ) {
143 - // phpcs:ignore -- WordPress.Security.EscapeOutput.OutputNotEscaped - Escaping not required.
144 - echo '<div ' . $this->render_attributes( '_root' ) . '>' . do_shortcode( sprintf( '[sureforms id="%s" show_title="%s"]', $form_id, ! $form_title ) ) . '</div>';
145 - // phpcs:ignoreEnd
505 + $form = get_post( $form_id );
506 + // 'protected' is a custom SureForms post status for password-protected forms.
507 + if ( ! $form || ! in_array( $form->post_status, [ 'publish', 'protected' ], true ) ) {
508 + echo esc_html__( 'This form has been deleted or is unavailable.', 'sureforms' );
509 + return;
510 + }
511 +
512 + $form_title = isset( $settings['form-title'] );
513 + $block_attrs = $this->get_block_attrs( $settings );
514 +
515 + // Get spectra blocks CSS/JS.
516 + $blocks = parse_blocks( get_post_field( 'post_content', $form_id ) );
517 + $styles = Spec_Gb_Helper::get_instance()->get_assets( $blocks );
518 + ?>
519 + <div <?php echo $this->render_attributes( '_root' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>>
520 + <?php
521 + // Bypass shortcode — call Generate_Form_Markup directly with block_attrs.
522 + $form_markup = Generate_Form_Markup::get_form_markup( $form_id, $form_title, '', 'post', true, $block_attrs );
523 + echo $form_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in Generate_Form_Markup.
524 + ?>
525 + <style><?php echo $styles['css']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></style>
526 + <script><?php echo $styles['js']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></script>
527 + </div>
528 + <?php
146 529 } else {
147 530 // Show placeholder when no form is selected.
148 - // phpcs:ignore -- WordPress.Security.EscapeOutput.OutputNotEscaped - Escaping not required.
149 - echo $this->render_element_placeholder(
531 + $placeholder = $this->render_element_placeholder(
150 532 [
151 533 'icon-class' => $this->icon,
152 534 'description' => esc_html__( 'Select the form that you wish to add here.', 'sureforms' ),
153 535 ]
154 536 );
155 - // phpcs:ignoreEnd
537 + echo $placeholder; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Bricks render_element_placeholder() returns safe markup.
156 538 }
539 + }
540 +
541 + /**
542 + * Resolve a Bricks color value to a CSS-safe color string.
543 + * Bricks color controls may return a string or an array with 'hex'/'raw' keys.
544 + * Since Bricks 2.x, global palette colors are stored as CSS variable references
545 + * (e.g., var(--bricks-color-xxx)) instead of raw hex values.
546 + *
547 + * @param mixed $color_value Raw value from Bricks color control.
548 + * @return string|null CSS-safe color string or null.
549 + * @since 2.7.0
550 + */
551 + public static function resolve_bricks_color( $color_value ) {
552 + if ( empty( $color_value ) ) {
553 + return null;
554 + }
555 +
556 + $raw = null;
557 +
558 + if ( is_string( $color_value ) ) {
559 + $raw = $color_value;
560 + } elseif ( is_array( $color_value ) ) {
561 + // Use Bricks' native color resolution for global palette colors (Bricks 2.x+).
562 + // This correctly handles palette IDs, light/dark mode, and CSS variable references.
563 + if ( method_exists( '\Bricks\Assets', 'generate_css_color' ) ) {
564 + $raw = \Bricks\Assets::generate_css_color( $color_value );
565 + } else {
566 + $raw = $color_value['hex'] ?? $color_value['raw'] ?? null;
567 + }
568 + }
569 +
570 + if ( null === $raw || '' === $raw ) {
571 + return null;
572 + }
573 +
574 + // Validate as a CSS-safe color value.
575 + $hex = sanitize_hex_color( $raw );
576 + if ( $hex ) {
577 + return $hex;
578 + }
579 +
580 + // Allow rgb/rgba/hsl/hsla functional notation — restrict to safe characters only.
581 + if ( preg_match( '/^(rgb|rgba|hsl|hsla)\s*\([0-9.,\s\/%]+\)$/i', $raw ) ) {
582 + return $raw;
583 + }
584 +
585 + // Allow CSS custom property references (e.g., var(--bricks-color-xxx)) for Bricks 2.x global palette colors.
586 + if ( preg_match( '/^var\(\s*--[a-zA-Z0-9_$-]+\s*\)$/', $raw ) ) {
587 + return $raw;
588 + }
589 +
590 + return null;
591 + }
592 +
593 + /**
594 + * Map Bricks 'spacing' control to individual camelCase block_attrs keys.
595 + * Bricks spacing returns: ['top' => '10', 'right' => '10', ..., 'unit' => 'px']
596 + *
597 + * @param array<string, mixed> $settings Bricks settings.
598 + * @param string $setting_key Key in settings for the spacing control.
599 + * @param string $attr_prefix Prefix for output block_attrs keys.
600 + * @return array<string, string> Mapped attributes.
601 + * @since 2.7.0
602 + */
603 + public static function map_bricks_spacing( $settings, $setting_key, $attr_prefix ) {
604 + $attrs = [];
605 +
606 + if ( empty( $settings[ $setting_key ] ) || ! is_array( $settings[ $setting_key ] ) ) {
607 + return $attrs;
608 + }
609 +
610 + $dims = $settings[ $setting_key ];
611 + $allowed_units = [ 'px', 'em', 'rem', '%', 'vw', 'vh' ];
612 + $raw_unit = $dims['unit'] ?? 'px';
613 + $unit = in_array( $raw_unit, $allowed_units, true ) ? $raw_unit : 'px';
614 +
615 + foreach ( [ 'top', 'right', 'bottom', 'left' ] as $side ) {
616 + if ( isset( $dims[ $side ] ) && '' !== (string) $dims[ $side ] ) {
617 + $attrs[ $attr_prefix . ucfirst( $side ) ] = floatval( $dims[ $side ] ) . $unit;
618 + }
619 + }
620 +
621 + return $attrs;
622 + }
623 +
624 + /**
625 + * Build CSS gradient string from individual Bricks controls.
626 + * Since Bricks has no built-in gradient group control, we use individual controls.
627 + *
628 + * @param array<string, mixed> $settings Bricks settings.
629 + * @param string $prefix Control name prefix (e.g., 'bg' for bgGradientColor1).
630 + * @return string|null CSS gradient string or null.
631 + * @since 2.7.0
632 + */
633 + public static function build_bricks_gradient_css( $settings, $prefix ) {
634 + $color_1 = self::resolve_bricks_color( $settings[ $prefix . 'GradientColor1' ] ?? null );
635 + $color_2 = self::resolve_bricks_color( $settings[ $prefix . 'GradientColor2' ] ?? null );
636 +
637 + // Both colors are required.
638 + if ( ! $color_1 || ! $color_2 ) {
639 + return null;
640 + }
641 +
642 + $type = 'radial' === ( $settings[ $prefix . 'GradientType' ] ?? 'linear' ) ? 'radial' : 'linear';
643 + $angle = absint( Helper::get_string_value( $settings[ $prefix . 'GradientAngle' ] ?? 90 ) );
644 + $color1_stop = absint( Helper::get_string_value( $settings[ $prefix . 'GradientColor1Stop' ] ?? 0 ) );
645 + $color2_stop = absint( Helper::get_string_value( $settings[ $prefix . 'GradientColor2Stop' ] ?? 100 ) );
646 +
647 + if ( 'radial' === $type ) {
648 + return sprintf(
649 + 'radial-gradient(at center center, %s %s%%, %s %s%%)',
650 + $color_1,
651 + $color1_stop,
652 + $color_2,
653 + $color2_stop
654 + );
655 + }
656 +
657 + return sprintf(
658 + 'linear-gradient(%sdeg, %s %s%%, %s %s%%)',
659 + $angle,
660 + $color_1,
661 + $color1_stop,
662 + $color_2,
663 + $color2_stop
664 + );
665 + }
666 +
667 + /**
668 + * Convert Bricks settings to block_attrs array.
669 + * Uses same camelCase keys as Gutenberg for code reuse with Form_Styling.
670 + *
671 + * @param array<string, mixed> $settings Bricks element settings.
672 + * @return array<string, mixed> Block attributes.
673 + * @since 2.7.0
674 + */
675 + protected function get_block_attrs( $settings ) {
676 + $block_attrs = [
677 + 'blockId' => 'bricks-' . ( $this->id ?? wp_unique_id() ),
678 + ];
679 +
680 + // Check form theme — if inheriting, don't pass any custom styling attributes.
681 + $form_theme = $settings['formTheme'] ?? 'inherit';
682 + $block_attrs['formTheme'] = $form_theme;
683 +
684 + if ( 'inherit' === $form_theme ) {
685 + return $block_attrs;
686 + }
687 +
688 + // Color controls.
689 + $color_keys = [
690 + 'primaryColor',
691 + 'textColor',
692 + 'textOnPrimaryColor',
693 + 'bgColor',
694 + ];
695 +
696 + foreach ( $color_keys as $key ) {
697 + $color = self::resolve_bricks_color( $settings[ $key ] ?? null );
698 + if ( $color ) {
699 + $block_attrs[ $key ] = $color;
700 + }
701 + }
702 +
703 + // Pass-through keys with allowed values for server-side validation.
704 + $passthrough_keys = [
705 + 'fieldSpacing' => [ 'small', 'medium', 'large' ],
706 + 'buttonAlignment' => [ 'left', 'center', 'right', 'justify' ],
707 + 'bgType' => [ 'color', 'gradient', 'image' ],
708 + 'bgImageSize' => [ 'cover', 'contain', 'auto' ],
709 + 'bgImagePosition' => [ 'left top', 'left center', 'left bottom', 'center top', 'center center', 'center bottom', 'right top', 'right center', 'right bottom' ],
710 + 'bgImageRepeat' => [ 'no-repeat', 'repeat', 'repeat-x', 'repeat-y' ],
711 + 'bgImageAttachment' => [ 'scroll', 'fixed' ],
712 + ];
713 +
714 + foreach ( $passthrough_keys as $key => $allowed ) {
715 + if ( isset( $settings[ $key ] ) && in_array( $settings[ $key ], $allowed, true ) ) {
716 + $block_attrs[ $key ] = $settings[ $key ];
717 + }
718 + }
719 +
720 + // Build gradient CSS string from individual Bricks controls.
721 + if ( 'gradient' === ( $settings['bgType'] ?? '' ) ) {
722 + $gradient_css = self::build_bricks_gradient_css( $settings, 'bg' );
723 + if ( $gradient_css ) {
724 + $block_attrs['bgGradient'] = $gradient_css;
725 + }
726 + }
727 +
728 + // Handle spacing controls (4-sided) → individual camelCase keys with unit appended.
729 + $block_attrs = array_merge( $block_attrs, self::map_bricks_spacing( $settings, 'formPadding', 'formPadding' ) );
730 + $block_attrs = array_merge( $block_attrs, self::map_bricks_spacing( $settings, 'formBorderRadius', 'formBorderRadius' ) );
731 +
732 + // Handle bgImage — Bricks image control returns ['url', 'id', ...].
733 + if ( ! empty( $settings['bgImage'] ) && is_array( $settings['bgImage'] ) ) {
734 + if ( ! empty( $settings['bgImage']['url'] ) ) {
735 + $raw_url = esc_url_raw( $settings['bgImage']['url'] );
736 + // Encode parentheses to prevent CSS injection in url() context.
737 + $block_attrs['bgImage'] = str_replace( [ '(', ')' ], [ '%28', '%29' ], $raw_url );
738 + }
739 + }
740 +
741 + /**
742 + * Filters the Bricks block attributes after sanitization.
743 + *
744 + * Third-party code hooking this filter is responsible for sanitizing
745 + * any values it adds or modifies. Unsanitized values may be output
746 + * directly into CSS custom properties.
747 + *
748 + * @param array<string, mixed> $block_attrs Sanitized block attributes.
749 + * @param array<string, mixed> $settings Raw Bricks element settings.
750 + * @since 2.7.0
751 + */
752 + return apply_filters( 'srfm_bricks_block_attrs', $block_attrs, $settings );
157 753 }
158 754 }