PluginProbe
The Innovative Form Builder – IvyForms / 1.5
The Innovative Form Builder – IvyForms v1.5
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / backend / src / Services / Integrations / Elementor / Widgets / IvyFormsWidget.php

IvyFormsWidget.php in The Innovative Form Builder – IvyForms 1.5, at backend/src/Services/Integrations/Elementor/Widgets/IvyFormsWidget.php

368 lines 10.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * IvyForms Elementor Widget
5 *
6 * @copyright © Melograno Venture Studio. All rights reserved.
7 * @licence See LICENCE.md for license details.
8 */
9
10 namespace IvyForms\Services\Integrations\Elementor\Widgets;
11
12 // phpcs:disable PSR1.Files.SideEffects
13 if (!defined('ABSPATH')) {
14 exit; // Exit if accessed directly
15 }
16
17 use Elementor\Plugin;
18 use Elementor\Widget_Base;
19 use Elementor\Controls_Manager;
20 use IvyForms\Common\Helpers\FormPreviewHelper;
21 use IvyForms\Common\Sanitizer\HtmlSanitizer;
22 use IvyForms\Services\API\IvyFormsAPI;
23 use IvyForms\Services\Shortcode\ShortcodeService;
24
25 /**
26 * Class IvyFormsWidget
27 *
28 * Elementor widget for displaying IvyForms forms
29 * Following wpDataTables widget pattern with GutenbergBlockRenderer rendering approach
30 *
31 * @package IvyForms\Services\Integrations\Elementor\Widgets
32 */
33 // phpcs:disable PSR1.Methods.CamelCapsMethodName
34 class IvyFormsWidget extends Widget_Base
35 {
36 /**
37 * Get widget name
38 *
39 * @return string Widget name
40 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
41 */
42 public function get_name(): string
43 {
44 return 'ivyforms';
45 }
46
47 /**
48 * Get widget title
49 *
50 * @return string Widget title
51 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
52 */
53 public function get_title(): string
54 {
55 return __('IvyForms', 'ivyforms');
56 }
57
58 /**
59 * Get widget icon
60 *
61 * @return string Widget icon
62 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
63 */
64 public function get_icon(): string
65 {
66 return 'ivyforms-elementor-icon';
67 }
68
69 /**
70 * Get widget categories
71 *
72 * @return array<int, string> Widget categories
73 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
74 */
75 public function get_categories(): array
76 {
77 return ['ivyforms-elementor'];
78 }
79
80 /**
81 * Get widget keywords
82 *
83 * @return array<int, string> Widget keywords for search
84 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
85 */
86 public function get_keywords(): array
87 {
88 return ['form', 'contact', 'ivyforms', 'ivy', 'forms'];
89 }
90
91 /**
92 * Get all forms for the dropdown
93 *
94 * @return array<int, string> Forms array for select control
95 */
96 private function getFormsOptions(): array
97 {
98 $options = [
99 0 => __('— Select Form —', 'ivyforms'),
100 ];
101
102 $forms = IvyFormsAPI::getForms();
103
104 if (is_wp_error($forms) || empty($forms)) {
105 return $options;
106 }
107
108 foreach ($forms as $form) {
109 $options[$form->getId()] = $form->getName();
110 }
111
112 return $options;
113 }
114
115 /**
116 * Register widget controls
117 *
118 * @return void
119 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
120 */
121 protected function register_controls(): void
122 {
123 // Content Section
124 $this->start_controls_section(
125 'ivyforms_content_section',
126 [
127 'label' => __('Form Settings', 'ivyforms'),
128 'tab' => Controls_Manager::TAB_CONTENT,
129 ]
130 );
131
132 $formSelectDescription = __('Choose the form you want to ' .
133 'display.', 'ivyforms');
134 $titleVisibilityDescription = __('Override the form title visibility ' .
135 'setting.', 'ivyforms');
136 $descriptionVisibilityDescription = __('Override the form description visibility ' .
137 'setting.', 'ivyforms');
138 $cssClassDescription = __('Add custom CSS classes separated by ' .
139 'spaces.', 'ivyforms');
140
141 $this->add_control(
142 'form_id',
143 [
144 'label' => __('Select Form', 'ivyforms'),
145 'type' => Controls_Manager::SELECT2,
146 'options' => $this->getFormsOptions(),
147 'default' => 0,
148 'description' => $formSelectDescription,
149 ]
150 );
151
152 $this->add_control(
153 'show_title',
154 [
155 'label' => __('Show Title', 'ivyforms'),
156 'type' => Controls_Manager::SWITCHER,
157 'label_on' => __('Yes', 'ivyforms'),
158 'label_off' => __('No', 'ivyforms'),
159 'return_value' => 'yes',
160 'default' => 'yes',
161 'description' => $titleVisibilityDescription,
162 ]
163 );
164
165 $this->add_control(
166 'show_description',
167 [
168 'label' => __('Show Description', 'ivyforms'),
169 'type' => Controls_Manager::SWITCHER,
170 'label_on' => __('Yes', 'ivyforms'),
171 'label_off' => __('No', 'ivyforms'),
172 'return_value' => 'yes',
173 'default' => '',
174 'description' => $descriptionVisibilityDescription,
175 ]
176 );
177
178 $this->add_control(
179 'css_class_separator',
180 [
181 'type' => Controls_Manager::DIVIDER,
182 ]
183 );
184
185 $this->add_control(
186 'css_class',
187 [
188 'label' => __('Additional CSS Class(es)', 'ivyforms'),
189 'type' => Controls_Manager::TEXT,
190 'label_block' => true,
191 'placeholder' => '',
192 'description' => $cssClassDescription,
193 ]
194 );
195
196 $this->end_controls_section();
197 }
198
199 /**
200 * Check if we're in Elementor editor preview mode
201 *
202 * @return bool
203 */
204 private function isEditorPreview(): bool
205 {
206 $plugin = Plugin::instance();
207 if (!is_object($plugin) || !isset($plugin->editor, $plugin->preview)) {
208 return false;
209 }
210
211 return $plugin->editor->is_edit_mode() || $plugin->preview->is_preview_mode();
212 }
213
214 /**
215 * Render widget output on the frontend
216 *
217 * @return void
218 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
219 */
220 protected function render(): void
221 {
222 $settings = $this->get_settings_for_display();
223 $formId = isset($settings['form_id']) ? (int) $settings['form_id'] : 0;
224
225 if ($formId === 0) {
226 $this->renderNoFormSelected();
227 return;
228 }
229
230 $atts = $this->buildShortcodeAttributes($settings, $formId);
231 $cssClasses = $this->buildCssClasses($settings);
232
233 try {
234 $this->renderFormOutput($formId, $atts, $cssClasses);
235 } catch (\Exception $e) {
236 echo '<div class="ivyforms-error"><p>' .
237 esc_html(__('Error rendering form', 'ivyforms')) .
238 '</p></div>';
239 }
240 }
241
242 /**
243 * Render no form selected message
244 *
245 * @return void
246 */
247 private function renderNoFormSelected(): void
248 {
249 echo '<div class="ivyforms-no-form-selected"><p>' .
250 esc_html(__('Select a form from the widget settings on the left.', 'ivyforms')) .
251 '</p></div>';
252 }
253
254 /**
255 * Build shortcode attributes from settings
256 *
257 * @param array<string, mixed> $settings Widget settings
258 * @param int $formId Form ID
259 * @return array<string, string> Shortcode attributes
260 */
261 private function buildShortcodeAttributes(array $settings, int $formId): array
262 {
263 $atts = ['id' => (string) $formId];
264
265 $showTitleEnabled = !empty($settings['show_title']) && $settings['show_title'] === 'yes';
266 $atts['show_title'] = $showTitleEnabled ? 'true' : 'false';
267
268 $showDescriptionEnabled = !empty($settings['show_description']) && $settings['show_description'] === 'yes';
269 $atts['show_description'] = $showDescriptionEnabled ? 'true' : 'false';
270
271 return $atts;
272 }
273
274 /**
275 * Build CSS classes for the widget
276 *
277 * @param array<string, mixed> $settings Widget settings
278 * @return array<int, string> CSS classes
279 */
280 private function buildCssClasses(array $settings): array
281 {
282 $cssClasses = ['ivyforms-elementor-block'];
283
284 if (!empty($settings['css_class'])) {
285 $sanitizedClasses = HtmlSanitizer::sanitizeCssClasses($settings['css_class']);
286 if ($sanitizedClasses !== '') {
287 $cssClasses = array_merge($cssClasses, explode(' ', $sanitizedClasses));
288 }
289 }
290
291 return $cssClasses;
292 }
293
294 /**
295 * Render form output
296 *
297 * @param int $formId Form ID
298 * @param array<string, string> $atts Shortcode attributes
299 * @param array<int, string> $cssClasses CSS classes
300 * @return void
301 */
302 private function renderFormOutput(int $formId, array $atts, array $cssClasses): void
303 {
304 $formOutput = ShortcodeService::shortcodeHandler($atts);
305
306 if ($formOutput === '') {
307 echo '<div class="ivyforms-form-not-found"><p>' .
308 esc_html(__('Form not found or not published', 'ivyforms')) .
309 '</p></div>';
310 return;
311 }
312
313 $classAttribute = implode(' ', $cssClasses);
314
315 if ($this->isEditorPreview()) {
316 $this->renderEditorPreview($formId, $formOutput, $classAttribute);
317 return;
318 }
319
320 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
321 echo '<div class="' . esc_attr($classAttribute) . '">'
322 . $formOutput
323 . '</div>';
324 }
325
326 /**
327 * Render editor preview with embedded form data
328 *
329 * @param int $formId Form ID
330 * @param string $formOutput Form HTML output
331 * @param string $classAttribute CSS class attribute
332 * @return void
333 */
334 private function renderEditorPreview(int $formId, string $formOutput, string $classAttribute): void
335 {
336 $formDataJson = FormPreviewHelper::generateFormDataJson($formId);
337 $embeddedScripts = FormPreviewHelper::generateJsonScripts($formId);
338
339 $dataAttr = '';
340 if ($formDataJson !== '') {
341 $dataAttr = ' data-ivyforms-data="' . esc_attr($formDataJson) . '"';
342 }
343
344 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
345 echo '<div class="' . esc_attr($classAttribute)
346 . ' ivyforms-elementor-editor-preview"'
347 . $dataAttr
348 . '>'
349 . $formOutput
350 . '</div>'
351 . $embeddedScripts;
352 }
353
354 /**
355 * Render widget output in the editor (content template)
356 *
357 * Leave empty to use server-side rendering
358 *
359 * @return void
360 * @SuppressWarnings(PHPMD.CamelCaseMethodName)
361 */
362 protected function content_template(): void
363 {
364 // Empty - forces Elementor to use server-side render() method
365 }
366 }
367 // phpcs:enable PSR1.Methods.CamelCapsMethodName
368