PluginProbe ʕ •ᴥ•ʔ
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / trunk
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz vtrunk
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 0.0.2 0.0.3 0.0.4 0.0.5 0.0.6 0.0.7 0.0.8 0.0.9 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.1.2 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.12.3 1.13.0 1.13.1 1.13.2 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5.0 1.5.1 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.8.0 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.2.0 2.2.1 2.2.2 2.3.0 2.4.0 2.5.0 2.5.2 2.6.0
sureforms / inc / page-builders / bricks / elements / form-widget.php
sureforms / inc / page-builders / bricks / elements Last commit date
form-widget.php 2 months ago payment-history-widget.php 3 weeks ago
form-widget.php
755 lines
1 <?php
2 /**
3 * Bricks SureForms form element.
4 *
5 * @package sureforms.
6 * @since 0.0.5
7 */
8
9 namespace SRFM\Inc\Page_Builders\Bricks\Elements;
10
11 use Spec_Gb_Helper;
12 use SRFM\Inc\Generate_Form_Markup;
13 use SRFM\Inc\Helper;
14 use SRFM\Inc\Page_Builders\Page_Builders;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit; // Exit if accessed directly.
18 }
19
20 /**
21 * SureForms Bricks element.
22 */
23 class Form_Widget extends \Bricks\Element {
24 /**
25 * Element category.
26 *
27 * @var string
28 */
29 public $category = 'sureforms';
30
31 /**
32 * Element name.
33 *
34 * @var string
35 */
36 public $name = 'sureforms';
37
38 /**
39 * Element icon.
40 *
41 * @var string
42 */
43 public $icon = 'ti-layout-accordion-separated';
44
45 /**
46 * Constructor.
47 *
48 * @param array<mixed> $element Element data.
49 */
50 public function __construct( $element = null ) {
51
52 if ( bricks_is_builder() ) {
53 // call the js functions to handle form submission, load page break, phone, dropdown.
54 $this->scripts = [ 'handleBricksPreviewFormSubmission', 'srfmLoadPageBreak', 'srfmInitializePhoneField', 'srfmInitializeDropdown' ];
55 }
56
57 parent::__construct( $element );
58 }
59
60 /**
61 * Get element name.
62 *
63 * @since 0.0.5
64 * @return string element name.
65 */
66 public function get_label() {
67 return __( 'SureForms', 'sureforms' );
68 }
69
70 /**
71 * Get element keywords.
72 *
73 * @since 0.0.5
74 * @return array<string> element keywords.
75 */
76 public function get_keywords() {
77 return [
78 'sureforms',
79 'contact form',
80 'form',
81 'bricks form',
82 ];
83 }
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 /**
122 * Set element controls.
123 *
124 * @since 0.0.5
125 * @return void
126 */
127 public function set_controls() {
128
129 // === CONTENT TAB ===
130
131 // Select Form.
132 $this->controls['form-id'] = [
133
134 'tab' => 'content',
135 'label' => __( 'Form', 'sureforms' ),
136 'type' => 'select',
137 'options' => Helper::get_sureforms_title_with_ids(),
138 'placeholder' => __( 'Select Form', 'sureforms' ),
139 ];
140
141 // Show Form Title Toggle.
142 $this->controls['form-title'] = [
143 'tab' => 'content',
144 'label' => __( 'Show Form Title', 'sureforms' ),
145 'type' => 'checkbox',
146 'info' => __( 'Enable this to show form title.', 'sureforms' ),
147 'required' => [ 'form-id', '!=', '' ],
148 ];
149
150 $this->controls['srfm_form_submission_info'] = [
151 'tab' => 'content',
152 'content' => __( 'Form submission will be possible on the frontend.', 'sureforms' ),
153 'type' => 'info',
154 'required' => [ 'form-id', '!=', '' ],
155 ];
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 );
481 }
482
483 /**
484 * Enqueue scripts for phone and dropdown field in the Bricks editor.
485 *
486 * @since 0.0.10
487 * @return void
488 */
489 public function enqueue_scripts() {
490 // enqueue common fields assets for the dropdown and phone fields.
491 Page_Builders::enqueue_common_fields_assets();
492 }
493
494 /**
495 * Render element.
496 *
497 * @since 0.0.5
498 * @return void
499 */
500 public function render() {
501 $settings = $this->settings;
502 $form_id = absint( $settings['form-id'] ?? 0 );
503
504 if ( $form_id > 0 ) {
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
529 } else {
530 // Show placeholder when no form is selected.
531 $placeholder = $this->render_element_placeholder(
532 [
533 'icon-class' => $this->icon,
534 'description' => esc_html__( 'Select the form that you wish to add here.', 'sureforms' ),
535 ]
536 );
537 echo $placeholder; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Bricks render_element_placeholder() returns safe markup.
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 );
753 }
754 }
755