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 / elementor / form-widget.php
sureforms / inc / page-builders / elementor Last commit date
assets 3 weeks ago form-widget.php 2 months ago payment-history-widget.php 3 weeks ago service-provider.php 1 month ago
form-widget.php
996 lines
1 <?php
2 /**
3 * Elementor SureForms form widget.
4 *
5 * @package sureforms.
6 * @since 0.0.5
7 */
8
9 namespace SRFM\Inc\Page_Builders\Elementor;
10
11 use Elementor\Plugin;
12 use Elementor\Widget_Base;
13 use Spec_Gb_Helper;
14 use SRFM\Inc\Generate_Form_Markup;
15 use SRFM\Inc\Helper;
16 use SRFM\Inc\Page_Builders\Page_Builders;
17
18 if ( ! defined( 'ABSPATH' ) ) {
19 exit; // Exit if accessed directly.
20 }
21
22 /**
23 * SureForms widget that displays a form.
24 */
25 class Form_Widget extends Widget_Base {
26 /**
27 * Whether we are in the preview mode.
28 *
29 * @var bool
30 */
31 public $is_preview_mode;
32
33 /**
34 * Constructor.
35 *
36 * @param array<mixed> $data Widget data.
37 * @param array<mixed> $args Widget arguments.
38 */
39 public function __construct( $data = [], $args = null ) {
40
41 parent::__construct( $data, $args );
42
43 // (Preview iframe)
44 $this->is_preview_mode = \Elementor\Plugin::$instance->preview->is_preview_mode();
45
46 if ( $this->is_preview_mode ) {
47
48 // enqueue common fields assets for the dropdown and phone fields.
49 Page_Builders::enqueue_common_fields_assets();
50
51 wp_register_script( 'srfm-elementor-preview', SRFM_URL . 'inc/page-builders/elementor/assets/elementor-editor-preview.js', [ 'elementor-frontend' ], SRFM_VER, true );
52 wp_localize_script(
53 'srfm-elementor-preview',
54 'srfmElementorData',
55 [
56 'isProActive' => Helper::has_pro(),
57 ]
58 );
59
60 // Register styling preview script for live preview.
61 wp_register_script(
62 'srfm-elementor-preview-styling',
63 SRFM_URL . 'assets/build/elementorPreviewStyling.js',
64 [ 'elementor-frontend' ],
65 SRFM_VER,
66 true
67 );
68 wp_localize_script(
69 'srfm-elementor-preview-styling',
70 'srfmElementorStyling',
71 [
72 'fieldSpacingVars' => Helper::get_css_vars(),
73 ]
74 );
75
76 /**
77 * Hook for Pro to register additional preview assets (scripts and styles).
78 *
79 * @since 2.7.0
80 */
81 do_action( 'srfm_elementor_register_preview_assets' );
82 }
83 }
84
85 /**
86 * Get script depends.
87 *
88 * @since 0.0.5
89 * @return array<string> Script dependencies.
90 */
91 public function get_script_depends() {
92 if ( $this->is_preview_mode ) {
93 $scripts = [ 'srfm-elementor-preview', 'srfm-elementor-preview-styling' ];
94
95 /**
96 * Filter the widget's script dependencies.
97 * Pro uses this to add its preview scripts.
98 *
99 * @param array<string> $scripts Script handles.
100 * @since 2.7.0
101 */
102 return apply_filters( 'srfm_elementor_widget_script_depends', $scripts );
103 }
104
105 return [];
106 }
107
108 /**
109 * Get style depends.
110 *
111 * @since 2.7.0
112 * @return array<string> Style dependencies.
113 */
114 public function get_style_depends() {
115 $styles = [];
116
117 /**
118 * Filter the widget's style dependencies.
119 * Pro uses this to add its custom-styles CSS.
120 *
121 * @param array<string> $styles Style handles.
122 * @since 2.7.0
123 */
124 return apply_filters( 'srfm_elementor_widget_style_depends', $styles );
125 }
126
127 /**
128 * Get widget name.
129 *
130 * @since 0.0.5
131 * @return string Widget name.
132 */
133 public function get_name() {
134 return SRFM_FORMS_POST_TYPE;
135 }
136
137 /**
138 * Get widget title.
139 *
140 * @since 0.0.5
141 * @return string Widget title.
142 */
143 public function get_title() {
144 return __( 'SureForms', 'sureforms' );
145 }
146
147 /**
148 * Get widget icon.
149 *
150 * @since 0.0.5
151 * @return string Widget icon.
152 */
153 public function get_icon() {
154 return 'eicon-form-horizontal srfm-elementor-widget-icon';
155 }
156
157 /**
158 * Get widget categories. Used to determine where to display the widget in the editor.
159 *
160 * @since 0.0.5
161 * @return array<string> Widget categories.
162 */
163 public function get_categories() {
164 return [ 'sureforms-elementor' ];
165 }
166
167 /**
168 * Get widget keywords.
169 *
170 * @since 0.0.5
171 * @return array<string> Widget keywords.
172 */
173 public function get_keywords() {
174 return [
175 'sureforms',
176 'contact form',
177 'form',
178 'elementor form',
179 ];
180 }
181
182 /**
183 * Static method to get color value from settings.
184 * Can be used by Pro plugin to resolve global colors.
185 *
186 * @param array<string, mixed> $settings Widget settings.
187 * @param string $control_key The control key.
188 * @return string|null The color value or null if not set.
189 * @since 2.7.0
190 */
191 public static function get_resolved_color( $settings, $control_key ) {
192 // First check for a global color reference.
193 $globals = isset( $settings['__globals__'] ) && is_array( $settings['__globals__'] ) ? $settings['__globals__'] : [];
194 if ( ! empty( $globals[ $control_key ] ) ) {
195 $global_key = is_string( $globals[ $control_key ] ) ? $globals[ $control_key ] : '';
196
197 // Use Elementor's data manager to resolve the global value.
198 if ( $global_key && class_exists( '\Elementor\Plugin' ) && isset( Plugin::$instance->data_manager_v2 ) ) {
199 $data = Plugin::$instance->data_manager_v2->run( $global_key );
200
201 if ( is_array( $data ) && ! empty( $data['value'] ) && is_string( $data['value'] ) ) {
202 return $data['value'];
203 }
204 }
205 }
206
207 // Fall back to direct value.
208 if ( isset( $settings[ $control_key ] ) && '' !== $settings[ $control_key ] && 'default' !== $settings[ $control_key ] ) {
209 $value = $settings[ $control_key ];
210 return is_string( $value ) ? $value : null;
211 }
212
213 return null;
214 }
215
216 /**
217 * Build CSS gradient string from Group_Control_Background settings.
218 *
219 * Constructs a CSS gradient value from Elementor's gradient control settings.
220 * Supports both linear and radial gradients. Radial gradients use 'center center' position.
221 *
222 * @param array<string, mixed> $settings Widget settings containing gradient values.
223 * @return string|null CSS gradient string or null if colors not set.
224 * @since 2.7.0
225 */
226 public static function build_gradient_css( $settings ) {
227 // Get gradient colors - resolve global colors if used.
228 $color_1 = self::get_resolved_color( $settings, 'bgGradient_color' );
229 $color_2 = self::get_resolved_color( $settings, 'bgGradient_color_b' );
230
231 // Both colors are required.
232 if ( ! $color_1 || ! $color_2 ) {
233 return null;
234 }
235
236 // Get gradient settings with defaults - use Helper::get_string_value for type safety.
237 $type = isset( $settings['bgGradient_gradient_type'] ) && '' !== $settings['bgGradient_gradient_type']
238 ? Helper::get_string_value( $settings['bgGradient_gradient_type'] )
239 : 'linear';
240
241 // Get angle settings.
242 $angle_settings = isset( $settings['bgGradient_gradient_angle'] ) && is_array( $settings['bgGradient_gradient_angle'] )
243 ? $settings['bgGradient_gradient_angle']
244 : [];
245 $angle = isset( $angle_settings['size'] ) ? Helper::get_string_value( $angle_settings['size'] ) : '180';
246 $angle_unit = isset( $angle_settings['unit'] ) ? Helper::get_string_value( $angle_settings['unit'] ) : 'deg';
247
248 // Get color 1 stop settings.
249 $color_1_settings = isset( $settings['bgGradient_color_stop'] ) && is_array( $settings['bgGradient_color_stop'] )
250 ? $settings['bgGradient_color_stop']
251 : [];
252 $color_1_stop = isset( $color_1_settings['size'] ) ? Helper::get_string_value( $color_1_settings['size'] ) : '0';
253 $color_1_unit = isset( $color_1_settings['unit'] ) ? Helper::get_string_value( $color_1_settings['unit'] ) : '%';
254
255 // Get color 2 stop settings.
256 $color_2_settings = isset( $settings['bgGradient_color_b_stop'] ) && is_array( $settings['bgGradient_color_b_stop'] )
257 ? $settings['bgGradient_color_b_stop']
258 : [];
259 $color_2_stop = isset( $color_2_settings['size'] ) ? Helper::get_string_value( $color_2_settings['size'] ) : '100';
260 $color_2_unit = isset( $color_2_settings['unit'] ) ? Helper::get_string_value( $color_2_settings['unit'] ) : '%';
261
262 // Build radial gradient: radial-gradient(at center center, color1 stop1, color2 stop2).
263 if ( 'radial' === $type ) {
264 return sprintf(
265 'radial-gradient(at center center, %s %s%s, %s %s%s)',
266 esc_attr( $color_1 ),
267 esc_attr( $color_1_stop ),
268 esc_attr( $color_1_unit ),
269 esc_attr( $color_2 ),
270 esc_attr( $color_2_stop ),
271 esc_attr( $color_2_unit )
272 );
273 }
274
275 // Build linear gradient: linear-gradient(angle, color1 stop1, color2 stop2).
276 return sprintf(
277 'linear-gradient(%s%s, %s %s%s, %s %s%s)',
278 esc_attr( $angle ),
279 esc_attr( $angle_unit ),
280 esc_attr( $color_1 ),
281 esc_attr( $color_1_stop ),
282 esc_attr( $color_1_unit ),
283 esc_attr( $color_2 ),
284 esc_attr( $color_2_stop ),
285 esc_attr( $color_2_unit )
286 );
287 }
288
289 /**
290 * Register form widget controls.
291 * Adds different input fields to allow the user to change and customize the widget settings.
292 *
293 * @since 0.0.5
294 * @return void
295 */
296 protected function register_controls() {
297
298 $this->start_controls_section(
299 'section_form',
300 [
301 'label' => __( 'SureForms', 'sureforms' ),
302 ]
303 );
304
305 $this->add_control(
306 'srfm_form_block',
307 [
308 'label' => __( 'Select Form', 'sureforms' ),
309 'type' => \Elementor\Controls_Manager::SELECT2,
310 'options' => Helper::get_sureforms_title_with_ids(),
311 'default' => '',
312 ]
313 );
314
315 $this->add_control(
316 'srfm_show_form_title',
317 [
318 'label' => __( 'Form Title', 'sureforms' ),
319 'type' => \Elementor\Controls_Manager::SWITCHER,
320 'label_on' => __( 'Show', 'sureforms' ),
321 'label_off' => __( 'Hide', 'sureforms' ),
322 'return_value' => 'true',
323 'condition' => [
324 'srfm_form_block!' => [ '' ],
325 ],
326 ]
327 );
328
329 $this->add_control(
330 'srfm_edit_form',
331 [
332 'label' => __( 'Edit Form', 'sureforms' ),
333 'separator' => 'before',
334 'type' => \Elementor\Controls_Manager::BUTTON,
335 'text' => __( 'Edit', 'sureforms' ),
336 'event' => 'sureforms:form:edit',
337 'condition' => [
338 'srfm_form_block!' => [ '' ],
339 ],
340 ]
341 );
342
343 $this->add_control(
344 'srfm_create_form',
345 [
346 'label' => __( 'Create New Form', 'sureforms' ),
347 'type' => \Elementor\Controls_Manager::BUTTON,
348 'text' => __( 'Create', 'sureforms' ),
349 'event' => 'sureforms:form:create',
350 ]
351 );
352
353 $this->add_control(
354 'srfm_form_submission_info',
355 [
356 'content' => __( 'Form submission will be possible on the frontend.', 'sureforms' ),
357 'type' => \Elementor\Controls_Manager::ALERT,
358 'condition' => [
359 'srfm_form_block!' => [ '' ],
360 ],
361 ]
362 );
363
364 $this->end_controls_section();
365
366 // Style Tab - Form Styling Section.
367 $this->start_controls_section(
368 'srfm_style_section',
369 [
370 'label' => __( 'Form Styling', 'sureforms' ),
371 'tab' => \Elementor\Controls_Manager::TAB_STYLE,
372 'condition' => [
373 'srfm_form_block!' => '',
374 ],
375 ]
376 );
377
378 // Form Theme Select.
379 $this->add_control(
380 'formTheme',
381 [
382 'label' => __( 'Form Theme', 'sureforms' ),
383 'type' => \Elementor\Controls_Manager::SELECT,
384 'default' => 'inherit',
385 'options' => [
386 'inherit' => __( "Inherit Form's Original Style", 'sureforms' ),
387 'default' => __( 'Default', 'sureforms' ),
388 ],
389 'separator' => 'after',
390 ]
391 );
392
393 // Primary Color.
394 $this->add_control(
395 'primaryColor',
396 [
397 'label' => __( 'Primary Color', 'sureforms' ),
398 'type' => \Elementor\Controls_Manager::COLOR,
399 'condition' => [
400 'formTheme!' => 'inherit',
401 ],
402 ]
403 );
404
405 // Text Color.
406 $this->add_control(
407 'textColor',
408 [
409 'label' => __( 'Text Color', 'sureforms' ),
410 'type' => \Elementor\Controls_Manager::COLOR,
411 'condition' => [
412 'formTheme!' => 'inherit',
413 ],
414 ]
415 );
416
417 // Text on Primary (button text color).
418 $this->add_control(
419 'textOnPrimaryColor',
420 [
421 'label' => __( 'Text on Primary', 'sureforms' ),
422 'type' => \Elementor\Controls_Manager::COLOR,
423 'condition' => [
424 'formTheme!' => 'inherit',
425 ],
426 ]
427 );
428
429 // Background Type Heading.
430 $this->add_control(
431 'bgHeading',
432 [
433 'label' => __( 'Background', 'sureforms' ),
434 'type' => \Elementor\Controls_Manager::HEADING,
435 'condition' => [
436 'formTheme!' => 'inherit',
437 ],
438 ]
439 );
440
441 // Background Type.
442 $this->add_control(
443 'bgType',
444 [
445 'label' => __( 'Type', 'sureforms' ),
446 'type' => \Elementor\Controls_Manager::SELECT,
447 'default' => 'color',
448 'options' => [
449 'color' => __( 'Color', 'sureforms' ),
450 'gradient' => __( 'Gradient', 'sureforms' ),
451 'image' => __( 'Image', 'sureforms' ),
452 ],
453 'condition' => [
454 'formTheme!' => 'inherit',
455 ],
456 ]
457 );
458
459 // Background Color.
460 $this->add_control(
461 'bgColor',
462 [
463 'label' => __( 'Color', 'sureforms' ),
464 'type' => \Elementor\Controls_Manager::COLOR,
465 'default' => '#FFFFFF',
466 'condition' => [
467 'formTheme!' => 'inherit',
468 'bgType' => 'color',
469 ],
470 ]
471 );
472
473 // Background Gradient - using Group_Control_Background for visual gradient picker.
474 // Position control is hidden - radial gradients use 'center center' position by default.
475 $this->add_group_control(
476 \Elementor\Group_Control_Background::get_type(),
477 [
478 'name' => 'bgGradient',
479 'types' => [ 'gradient' ],
480 'exclude' => [ 'image' ], // phpcs:ignore WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- Elementor Group_Control_Background option, not a query argument.
481 'fields_options' => [
482 'background' => [
483 'label' => __( 'Gradient', 'sureforms' ),
484 'default' => 'gradient',
485 ],
486 'color' => [
487 'label' => __( 'Color 1', 'sureforms' ),
488 'default' => '#FFC9B2',
489 ],
490 'color_stop' => [
491 'label' => __( 'Location 1', 'sureforms' ),
492 'size_units' => [ '%' ],
493 'responsive' => false,
494 'description' => '',
495 ],
496 'color_b' => [
497 'label' => __( 'Color 2', 'sureforms' ),
498 'default' => '#C7CBFF',
499 ],
500 'color_b_stop' => [
501 'label' => __( 'Location 2', 'sureforms' ),
502 'size_units' => [ '%' ],
503 'responsive' => false,
504 'description' => '',
505 ],
506 'gradient_type' => [],
507 'gradient_angle' => [
508 'label' => __( 'Angle', 'sureforms' ),
509 'size_units' => [ 'deg' ],
510 'responsive' => false,
511 'description' => '',
512 ],
513 // Hide position control - use 'center center' as default for radial gradients.
514 'gradient_position' => [
515 'default' => 'center center',
516 'type' => \Elementor\Controls_Manager::HIDDEN,
517 ],
518 ],
519 'selector' => '{{WRAPPER}} .srfm-gradient-dummy-selector', // Dummy selector - we handle styling via CSS variables.
520 'condition' => [
521 'formTheme!' => 'inherit',
522 'bgType' => 'gradient',
523 ],
524 ]
525 );
526
527 // Background Image.
528 $this->add_control(
529 'bgImage',
530 [
531 'label' => __( 'Image', 'sureforms' ),
532 'type' => \Elementor\Controls_Manager::MEDIA,
533 'default' => [
534 'url' => '',
535 ],
536 'condition' => [
537 'formTheme!' => 'inherit',
538 'bgType' => 'image',
539 ],
540 ]
541 );
542
543 // Background Image Size.
544 $this->add_control(
545 'bgImageSize',
546 [
547 'label' => __( 'Size', 'sureforms' ),
548 'type' => \Elementor\Controls_Manager::SELECT,
549 'default' => 'cover',
550 'options' => [
551 'cover' => __( 'Cover', 'sureforms' ),
552 'contain' => __( 'Contain', 'sureforms' ),
553 'auto' => __( 'Auto', 'sureforms' ),
554 ],
555 'condition' => [
556 'formTheme!' => 'inherit',
557 'bgType' => 'image',
558 ],
559 ]
560 );
561
562 // Background Image Position.
563 $this->add_control(
564 'bgImagePosition',
565 [
566 'label' => __( 'Position', 'sureforms' ),
567 'type' => \Elementor\Controls_Manager::SELECT,
568 'default' => 'center center',
569 'options' => [
570 'left top' => __( 'Left Top', 'sureforms' ),
571 'left center' => __( 'Left Center', 'sureforms' ),
572 'left bottom' => __( 'Left Bottom', 'sureforms' ),
573 'center top' => __( 'Center Top', 'sureforms' ),
574 'center center' => __( 'Center Center', 'sureforms' ),
575 'center bottom' => __( 'Center Bottom', 'sureforms' ),
576 'right top' => __( 'Right Top', 'sureforms' ),
577 'right center' => __( 'Right Center', 'sureforms' ),
578 'right bottom' => __( 'Right Bottom', 'sureforms' ),
579 ],
580 'condition' => [
581 'formTheme!' => 'inherit',
582 'bgType' => 'image',
583 ],
584 ]
585 );
586
587 // Background Image Repeat.
588 $this->add_control(
589 'bgImageRepeat',
590 [
591 'label' => __( 'Repeat', 'sureforms' ),
592 'type' => \Elementor\Controls_Manager::SELECT,
593 'default' => 'no-repeat',
594 'options' => [
595 'no-repeat' => __( 'No Repeat', 'sureforms' ),
596 'repeat' => __( 'Repeat', 'sureforms' ),
597 'repeat-x' => __( 'Repeat X', 'sureforms' ),
598 'repeat-y' => __( 'Repeat Y', 'sureforms' ),
599 ],
600 'condition' => [
601 'formTheme!' => 'inherit',
602 'bgType' => 'image',
603 ],
604 ]
605 );
606
607 // Background Image Attachment.
608 $this->add_control(
609 'bgImageAttachment',
610 [
611 'label' => __( 'Attachment', 'sureforms' ),
612 'type' => \Elementor\Controls_Manager::SELECT,
613 'default' => 'scroll',
614 'options' => [
615 'scroll' => __( 'Scroll', 'sureforms' ),
616 'fixed' => __( 'Fixed', 'sureforms' ),
617 ],
618 'condition' => [
619 'formTheme!' => 'inherit',
620 'bgType' => 'image',
621 ],
622 ]
623 );
624
625 /**
626 * Hook for Pro to add advanced styling controls.
627 *
628 * @param \Elementor\Widget_Base $widget The widget instance.
629 * @since 2.7.0
630 */
631 do_action( 'srfm_elementor_after_basic_styling_controls', $this );
632
633 $this->end_controls_section();
634
635 // Style Tab - Layout Section.
636 $this->start_controls_section(
637 'srfm_layout_section',
638 [
639 'label' => __( 'Layout', 'sureforms' ),
640 'tab' => \Elementor\Controls_Manager::TAB_STYLE,
641 'condition' => [
642 'srfm_form_block!' => '',
643 'formTheme!' => 'inherit',
644 ],
645 ]
646 );
647
648 // Form Padding.
649 $this->add_control(
650 'formPadding',
651 [
652 'label' => __( 'Form Padding', 'sureforms' ),
653 'type' => \Elementor\Controls_Manager::DIMENSIONS,
654 'size_units' => [ 'px', '%', 'em' ],
655 'default' => [
656 'top' => '0',
657 'right' => '0',
658 'bottom' => '0',
659 'left' => '0',
660 'unit' => 'px',
661 'isLinked' => true,
662 ],
663 'separator' => 'after',
664 ]
665 );
666
667 // Form Border Radius.
668 $this->add_control(
669 'formBorderRadius',
670 [
671 'label' => __( 'Form Border Radius', 'sureforms' ),
672 'type' => \Elementor\Controls_Manager::DIMENSIONS,
673 'size_units' => [ 'px', '%', 'em' ],
674 'default' => [
675 'top' => '0',
676 'right' => '0',
677 'bottom' => '0',
678 'left' => '0',
679 'unit' => 'px',
680 'isLinked' => true,
681 ],
682 ]
683 );
684
685 /**
686 * Hook for Pro to add additional layout controls.
687 *
688 * @param \Elementor\Widget_Base $widget The widget instance.
689 * @since 2.7.0
690 */
691 do_action( 'srfm_elementor_layout_controls', $this );
692
693 $this->end_controls_section();
694
695 // Style Tab - Button Section.
696 $this->start_controls_section(
697 'srfm_button_section',
698 [
699 'label' => __( 'Button', 'sureforms' ),
700 'tab' => \Elementor\Controls_Manager::TAB_STYLE,
701 'condition' => [
702 'srfm_form_block!' => '',
703 'formTheme!' => 'inherit',
704 ],
705 ]
706 );
707
708 // Button Alignment.
709 $this->add_control(
710 'buttonAlignment',
711 [
712 'label' => __( 'Alignment', 'sureforms' ),
713 'type' => \Elementor\Controls_Manager::CHOOSE,
714 'options' => [
715 'left' => [
716 'title' => __( 'Left', 'sureforms' ),
717 'icon' => 'eicon-text-align-left',
718 ],
719 'center' => [
720 'title' => __( 'Center', 'sureforms' ),
721 'icon' => 'eicon-text-align-center',
722 ],
723 'right' => [
724 'title' => __( 'Right', 'sureforms' ),
725 'icon' => 'eicon-text-align-right',
726 ],
727 'justify' => [
728 'title' => __( 'Full Width', 'sureforms' ),
729 'icon' => 'eicon-text-align-justify',
730 ],
731 ],
732 'default' => '',
733 'toggle' => true,
734 ]
735 );
736
737 /**
738 * Hook for Pro to add additional button controls.
739 *
740 * @param \Elementor\Widget_Base $widget The widget instance.
741 * @since 2.7.0
742 */
743 do_action( 'srfm_elementor_button_controls', $this );
744
745 $this->end_controls_section();
746
747 // Style Tab - Fields Section.
748 $this->start_controls_section(
749 'srfm_field_section',
750 [
751 'label' => __( 'Fields', 'sureforms' ),
752 'tab' => \Elementor\Controls_Manager::TAB_STYLE,
753 'condition' => [
754 'srfm_form_block!' => '',
755 'formTheme!' => 'inherit',
756 ],
757 ]
758 );
759
760 // Field Spacing.
761 // Hidden when custom theme is selected (Pro provides Row Gap/Column Gap controls).
762 $this->add_control(
763 'fieldSpacing',
764 [
765 'label' => __( 'Field Spacing', 'sureforms' ),
766 'type' => \Elementor\Controls_Manager::SELECT,
767 'default' => 'medium',
768 'options' => [
769 'small' => __( 'Small', 'sureforms' ),
770 'medium' => __( 'Medium', 'sureforms' ),
771 'large' => __( 'Large', 'sureforms' ),
772 ],
773 'condition' => [
774 'formTheme!' => 'custom',
775 ],
776 ]
777 );
778
779 /**
780 * Hook for Pro to add additional field controls.
781 *
782 * @param \Elementor\Widget_Base $widget The widget instance.
783 * @since 2.7.0
784 */
785 do_action( 'srfm_elementor_field_controls', $this );
786
787 $this->end_controls_section();
788
789 /**
790 * Hook for Pro to add additional style sections.
791 *
792 * @param \Elementor\Widget_Base $widget The widget instance.
793 * @since 2.7.0
794 */
795 do_action( 'srfm_elementor_after_styling_section', $this );
796 }
797
798 /**
799 * Render form widget output on the frontend.
800 *
801 * @since 0.0.5
802 * @return void|string
803 */
804 protected function render() {
805 $settings = $this->get_settings_for_display();
806
807 if ( ! is_array( $settings ) ) {
808 return;
809 }
810
811 $is_editor = Plugin::instance()->editor->is_edit_mode();
812 $form_id = intval( $settings['srfm_form_block'] ?? 0 );
813
814 // Show placeholder in editor when no form selected.
815 if ( $is_editor && empty( $form_id ) ) {
816 ?>
817 <div style="background: #D9DEE1; color: #9DA5AE; padding: 10px; font-family: Roboto, sans-serif">
818 <?php echo esc_html__( 'Select the form that you wish to add here.', 'sureforms' ); ?>
819 </div>
820 <?php
821 return;
822 }
823
824 // Validation: Same as shortcode for backward compatibility.
825 $form = get_post( $form_id );
826 if ( empty( $form_id ) || ! $form || ! in_array( $form->post_status, [ 'publish', 'protected' ], true ) ) {
827 echo esc_html__( 'This form has been deleted or is unavailable.', 'sureforms' );
828 return;
829 }
830
831 $show_title = 'true' === ( $settings['srfm_show_form_title'] ?? '' );
832
833 // Build block_attrs from widget settings.
834 $block_attrs = $this->get_block_attrs( $settings );
835
836 // Bypass shortcode - call get_form_markup() directly.
837 // $do_blocks = true to match current shortcode behavior.
838 $form_markup = Generate_Form_Markup::get_form_markup( $form_id, $show_title, '', 'post', true, $block_attrs );
839 echo $form_markup; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Escaped in Generate_Form_Markup.
840
841 // Get spectra blocks and add css and js.
842 $blocks = parse_blocks( get_post_field( 'post_content', $form_id ) );
843 $styles = Spec_Gb_Helper::get_instance()->get_assets( $blocks );
844 ?>
845 <style><?php echo $styles['css']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></style>
846 <script><?php echo $styles['js']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></script>
847 <?php
848 }
849
850 /**
851 * Convert widget settings to block_attrs array.
852 * Uses same camelCase keys as Gutenberg for code reuse.
853 *
854 * @param array<string, mixed> $settings Widget settings.
855 * @return array<string, mixed> Block attributes.
856 * @since 2.7.0
857 */
858 protected function get_block_attrs( $settings ) {
859 $block_attrs = [
860 'blockId' => 'elementor-' . $this->get_id(),
861 ];
862
863 // Check form theme — if inheriting, don't pass any custom styling attributes.
864 $form_theme = $settings['formTheme'] ?? 'inherit';
865 $block_attrs['formTheme'] = $form_theme;
866
867 if ( 'inherit' === $form_theme ) {
868 return $block_attrs;
869 }
870
871 // Color controls that support Elementor Global Colors.
872 $color_keys = [
873 'primaryColor',
874 'textColor',
875 'textOnPrimaryColor',
876 'bgColor',
877 ];
878
879 foreach ( $color_keys as $key ) {
880 $color_value = self::get_resolved_color( $settings, $key );
881 if ( $color_value ) {
882 $block_attrs[ $key ] = $color_value;
883 }
884 }
885
886 // Non-color styling keys to pass through (camelCase).
887 $styling_keys = [
888 'fieldSpacing',
889 'buttonAlignment',
890 // Background (non-color).
891 'bgType',
892 'bgImageSize',
893 'bgImagePosition',
894 'bgImageRepeat',
895 'bgImageAttachment',
896 ];
897
898 foreach ( $styling_keys as $key ) {
899 if ( isset( $settings[ $key ] ) && '' !== $settings[ $key ] && 'default' !== $settings[ $key ] ) {
900 $block_attrs[ $key ] = $settings[ $key ];
901 }
902 }
903
904 // Build gradient CSS string from Group_Control_Background settings.
905 // Use get_settings() (raw) instead of get_settings_for_display() because Elementor
906 // nullifies group control child fields when the parent 'background' type field
907 // is not explicitly saved (it uses our default 'gradient' but doesn't persist it).
908 if ( 'gradient' === ( $settings['bgType'] ?? '' ) ) {
909 /** Raw widget settings.
910 *
911 * @var array<string, mixed> $raw_settings
912 */
913 $raw_settings = $this->get_settings();
914 $gradient_css = self::build_gradient_css( $raw_settings );
915 if ( $gradient_css ) {
916 $block_attrs['bgGradient'] = $gradient_css;
917 }
918 }
919
920 // Handle DIMENSIONS controls - map to individual camelCase keys for Gutenberg compatibility.
921 $block_attrs = self::map_elementor_dimensions( $block_attrs, $settings, 'formPadding' );
922 $block_attrs = self::map_elementor_dimensions( $block_attrs, $settings, 'formBorderRadius' );
923
924 // Handle bgImage separately as it returns an object from Elementor.
925 $bg_image = isset( $settings['bgImage'] ) && is_array( $settings['bgImage'] ) ? $settings['bgImage'] : [];
926 if ( ! empty( $bg_image['url'] ) ) {
927 $raw_url = esc_url_raw( $bg_image['url'] );
928 // Encode parentheses to prevent CSS injection in url() context.
929 $block_attrs['bgImage'] = str_replace( [ '(', ')' ], [ '%28', '%29' ], $raw_url );
930 }
931 if ( ! empty( $bg_image['id'] ) ) {
932 $block_attrs['bgImageId'] = $bg_image['id'];
933 }
934
935 /**
936 * Filters the Elementor block attributes after sanitization.
937 *
938 * Third-party code hooking this filter is responsible for sanitizing
939 * any values it adds or modifies. Unsanitized values may be output
940 * directly into CSS custom properties.
941 *
942 * Uses get_settings() (raw) instead of get_settings_for_display() because Elementor
943 * nullifies Group_Control_Background child fields when the parent 'background' type
944 * field is not explicitly saved (defaults aren't persisted to the database).
945 *
946 * @param array<string, mixed> $block_attrs Block attributes.
947 * @param array<string, mixed> $settings Raw widget settings.
948 * @since 2.7.0
949 */
950 /** Raw widget settings for filter.
951 *
952 * @var array<string, mixed> $raw_settings_for_filter
953 */
954 $raw_settings_for_filter = $this->get_settings();
955 return apply_filters( 'srfm_elementor_block_attrs', $block_attrs, $raw_settings_for_filter );
956 }
957
958 /**
959 * Map Elementor dimensions control to individual camelCase block attributes.
960 *
961 * Elementor returns dimensions as an array with top/right/bottom/left/unit keys.
962 * This maps them to individual keys like formPaddingTop, formPaddingRight, etc.
963 *
964 * @param array<string, mixed> $block_attrs Block attributes.
965 * @param array<string, mixed> $settings Widget settings.
966 * @param string $control_key The Elementor dimensions control key (e.g., 'formPadding').
967 * @return array<string, mixed> Updated block attributes.
968 * @since 2.7.0
969 */
970 private static function map_elementor_dimensions( $block_attrs, $settings, $control_key ) {
971 if ( empty( $settings[ $control_key ] ) || ! is_array( $settings[ $control_key ] ) ) {
972 return $block_attrs;
973 }
974
975 $dimensions = $settings[ $control_key ];
976 $allowed_units = [ 'px', '%', 'em' ];
977 $raw_unit = $dimensions['unit'] ?? 'px';
978 $unit = in_array( $raw_unit, $allowed_units, true ) ? $raw_unit : 'px';
979 $sides = [ 'top', 'right', 'bottom', 'left' ];
980
981 foreach ( $sides as $side ) {
982 if ( ! isset( $dimensions[ $side ] ) ) {
983 continue;
984 }
985 if ( ! empty( $dimensions[ $side ] ) || '0' === $dimensions[ $side ] ) {
986 $block_attrs[ $control_key . ucfirst( $side ) ] = $dimensions[ $side ];
987 }
988 }
989
990 $block_attrs[ $control_key . 'Unit' ] = $unit;
991
992 return $block_attrs;
993 }
994
995 }
996