PluginProbe
The Innovative Form Builder – IvyForms / 1.1.1
The Innovative Form Builder – IvyForms v1.1.1
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 / Form / Style / StyleSettingsParser.php

StyleSettingsParser.php in The Innovative Form Builder – IvyForms 1.1.1, at backend/src/Services/Form/Style/StyleSettingsParser.php

495 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * @copyright © Melograno Venture Studio. All rights reserved.
5 * @licence See LICENCE.md for license details.
6 */
7
8 namespace IvyForms\Services\Form\Style;
9
10 // phpcs:disable PSR1.Files.SideEffects
11
12 if (!defined('ABSPATH')) {
13 exit; // Exit if accessed directly
14 }
15
16 /**
17 * Class StyleSettingsParser
18 *
19 * Parses style settings and extracts active colors and styles for CSS generation.
20 * `styleMode` (`quick` | `advanced`) is a builder UI concern only; output must match
21 * the same visual rules regardless of tab. `quickMode` supplies the shortcut subset,
22 * while `advancedMode` holds the full tree (kept in sync in the app). This parser
23 * always applies `quickMode` first, then `advancedMode` (plus general/button slices)
24 * so the rendered form stays consistent.
25 *
26 * @package IvyForms\Services\Form\StyleHelpers
27 */
28 class StyleSettingsParser
29 {
30 /**
31 * @var string[]
32 */
33 private const ALLOWED_ALIGNMENTS = ['left', 'center', 'right'];
34
35 /**
36 * @var array<string, mixed>
37 */
38 private array $styleSettings;
39
40 /**
41 * @var string
42 */
43 private string $styleMode;
44
45 /**
46 * @var array<string, string>
47 */
48 private array $activeColors = [];
49
50 /**
51 * @var array<string, string>
52 */
53 private array $activeStyles = [];
54
55 /**
56 * Constructor
57 *
58 * @param array<string, mixed> $styleSettings
59 */
60 public function __construct(array $styleSettings)
61 {
62 $this->styleSettings = $styleSettings;
63 $this->styleMode = $styleSettings['styleMode'] ?? 'quick';
64 $this->parse();
65 }
66
67 /**
68 * Get active colors
69 *
70 * @return array<string, string>
71 */
72 public function getActiveColors(): array
73 {
74 return $this->activeColors;
75 }
76
77 /**
78 * Get active styles
79 *
80 * @return array<string, string>
81 */
82 public function getActiveStyles(): array
83 {
84 return $this->activeStyles;
85 }
86
87 /**
88 * Get style mode
89 *
90 * @return string
91 */
92 public function getStyleMode(): string
93 {
94 return $this->styleMode;
95 }
96
97 /**
98 * Parse style settings
99 *
100 * @return void
101 */
102 private function parse(): void
103 {
104 $this->activeColors = $this->styleSettings['colors'] ?? [];
105
106 $this->parseQuickModeSettings();
107 $this->parseGeneralSpacingSettings();
108 $this->parseAdvancedButtonSettings();
109 $this->parseAdvancedModeSettings();
110 }
111
112 /**
113 * Parse quick mode settings
114 *
115 * @return void
116 */
117 private function parseQuickModeSettings(): void
118 {
119 if (empty($this->styleSettings['quickMode'])) {
120 return;
121 }
122
123 $quick = $this->styleSettings['quickMode'];
124
125 $this->setIfNotEmpty($this->activeColors, 'formBackground', $quick['formBackground'] ?? null);
126 $this->setIfNotEmpty($this->activeColors, 'formBorderColor', $quick['formBorderColor'] ?? null);
127 $this->setIfNotEmpty($this->activeStyles, 'formBorderWidth', $quick['formBorderWidth'] ?? null);
128
129 $this->resolveCornerSetting('formCornerRadius', $quick['formCorner'] ?? null);
130
131 $this->setIfNotEmpty($this->activeColors, 'formTitleColor', $quick['formTitleText'] ?? null);
132 $this->setIfNotEmpty($this->activeColors, 'fieldBackground', $quick['fieldBackground'] ?? null);
133 $this->setIfNotEmpty($this->activeColors, 'fieldText', $quick['fieldText'] ?? null);
134 $this->setIfNotEmpty($this->activeColors, 'fieldDescriptionColor', $quick['fieldText'] ?? null);
135 $this->setIfNotEmpty($this->activeColors, 'fieldBorderColor', $quick['fieldBorderColor'] ?? null);
136 $this->activeStyles['formBorderStyle'] = 'solid';
137 $this->activeStyles['fieldBorderStyle'] = 'solid';
138
139 $this->resolveCornerSetting('fieldCornerRadius', $quick['fieldCorner'] ?? null);
140
141 $this->setIfNotEmpty($this->activeStyles, 'fieldBorderWidth', $quick['fieldBorderWidth'] ?? null);
142 $this->setIfNotEmpty($this->activeStyles, 'fieldPaddingLeftRight', $quick['fieldPaddingLeftRight'] ?? null);
143 $this->setIfNotEmpty($this->activeStyles, 'fieldPaddingTopBottom', $quick['fieldPaddingTopBottom'] ?? null);
144 $this->setIfNotEmpty($this->activeColors, 'fieldLabels', $quick['labelFontColor'] ?? null);
145
146 $this->setPlaceholderColor($quick);
147
148 $this->setIfNotEmpty($this->activeColors, 'buttonBackground', $quick['buttonBackground'] ?? null);
149 $this->setIfNotEmpty($this->activeColors, 'buttonBorderColor', $quick['buttonBorderColor'] ?? null);
150 $this->setIfNotEmpty($this->activeStyles, 'buttonBorderWidth', $quick['buttonBorderWidth'] ?? null);
151 $this->setIfNotEmpty($this->activeStyles, 'buttonPaddingLeftRight', $quick['buttonPaddingLeftRight'] ?? null);
152 $this->setIfNotEmpty($this->activeStyles, 'buttonPaddingTopBottom', $quick['buttonPaddingTopBottom'] ?? null);
153 $this->setIfNotEmpty($this->activeStyles, 'buttonWidth', $quick['buttonWidth'] ?? null);
154 $this->setIfNotEmpty($this->activeStyles, 'buttonHeight', $quick['buttonHeight'] ?? null);
155 $this->setIfNotEmpty($this->activeColors, 'buttonText', $quick['buttonText'] ?? null);
156
157 $this->setCheckboxRadioColors($quick);
158
159 $this->setIfNotEmpty($this->activeStyles, 'verticalSpacing', $quick['verticalSpacing'] ?? null);
160 }
161
162 /**
163 * Parse general spacing settings (form padding and vertical spacing)
164 *
165 * @return void
166 */
167 private function parseGeneralSpacingSettings(): void
168 {
169 if (empty($this->styleSettings['advancedMode']['general'])) {
170 return;
171 }
172
173 $general = $this->styleSettings['advancedMode']['general'];
174
175 $this->setIfNotEmpty($this->activeStyles, 'formPaddingLeftRight', $general['formPaddingLeftRight'] ?? null);
176 $this->setIfNotEmpty($this->activeStyles, 'formPaddingTopBottom', $general['formPaddingTopBottom'] ?? null);
177
178 if (empty($this->activeStyles['verticalSpacing'])) {
179 $this->setIfNotEmpty($this->activeStyles, 'verticalSpacing', $general['verticalSpacing'] ?? null);
180 }
181
182 $this->setIfNotEmpty($this->activeStyles, 'formBorderStyle', $general['formBorderStyle'] ?? null);
183 }
184
185 /**
186 * Parse advanced button settings (padding, width, height)
187 *
188 * @return void
189 */
190 private function parseAdvancedButtonSettings(): void
191 {
192 if (empty($this->styleSettings['advancedMode']['buttonStates']['normal'])) {
193 return;
194 }
195
196 $buttonNormal = $this->styleSettings['advancedMode']['buttonStates']['normal'];
197
198 // Advanced mode settings override quick mode
199 if (!empty($buttonNormal['background'])) {
200 $this->activeColors['buttonBackground'] = $buttonNormal['background'];
201 }
202 if (!empty($buttonNormal['borderColor'])) {
203 $this->activeColors['buttonBorderColor'] = $buttonNormal['borderColor'];
204 }
205 if (!empty($buttonNormal['borderWidth'])) {
206 $this->activeStyles['buttonBorderWidth'] = $buttonNormal['borderWidth'];
207 }
208 if (!empty($buttonNormal['textColor'])) {
209 $this->activeColors['buttonText'] = $buttonNormal['textColor'];
210 }
211
212 $this->resolveCornerSetting('buttonCornerRadius', $buttonNormal['corner'] ?? null);
213
214 // Button padding
215 $this->setIfNotEmpty($this->activeStyles, 'buttonPaddingLeftRight', $buttonNormal['paddingLeftRight'] ?? null);
216 $this->setIfNotEmpty($this->activeStyles, 'buttonPaddingTopBottom', $buttonNormal['paddingTopBottom'] ?? null);
217
218 // Button dimensions
219 $this->setIfNotEmpty($this->activeStyles, 'buttonWidth', $buttonNormal['width'] ?? null);
220 $this->setIfNotEmpty($this->activeStyles, 'buttonHeight', $buttonNormal['height'] ?? null);
221 }
222
223 /**
224 * Parse the full advancedMode tree into active colors/styles
225 *
226 * @return void
227 */
228 private function parseAdvancedModeSettings(): void
229 {
230 if (empty($this->styleSettings['advancedMode'])) {
231 return;
232 }
233
234 $adv = $this->styleSettings['advancedMode'];
235
236 $this->setIfNotEmpty(
237 $this->activeColors,
238 'fieldLabels',
239 $adv['fieldLabels']['label']['fontColor'] ?? null
240 );
241 $this->setIfNotEmpty(
242 $this->activeStyles,
243 'fieldLabelPaddingLeftRight',
244 $adv['fieldLabels']['label']['paddingLeftRight'] ?? null
245 );
246 $this->setIfNotEmpty(
247 $this->activeStyles,
248 'fieldLabelPaddingTopBottom',
249 $adv['fieldLabels']['label']['paddingTopBottom'] ?? null
250 );
251 $this->setIfNotEmpty(
252 $this->activeStyles,
253 'fieldLabelAlignment',
254 $this->sanitizeAlignment($adv['fieldLabels']['label']['alignment'] ?? null)
255 );
256
257 $this->setIfNotEmpty(
258 $this->activeColors,
259 'fieldPlaceholder',
260 $adv['fieldPlaceholder']['fontColor'] ?? null
261 );
262 $this->setIfNotEmpty(
263 $this->activeStyles,
264 'fieldPlaceholderPaddingLeftRight',
265 $adv['fieldPlaceholder']['paddingLeftRight'] ?? null
266 );
267 $this->setIfNotEmpty(
268 $this->activeStyles,
269 'fieldPlaceholderAlignment',
270 $this->sanitizeAlignment($adv['fieldPlaceholder']['alignment'] ?? null)
271 );
272
273 $this->setIfNotEmpty(
274 $this->activeColors,
275 'formTitleColor',
276 $adv['formTitle']['fontColor'] ?? null
277 );
278 $this->setIfNotEmpty(
279 $this->activeStyles,
280 'formTitlePaddingLeftRight',
281 $adv['formTitle']['paddingLeftRight'] ?? null
282 );
283 $this->setIfNotEmpty(
284 $this->activeStyles,
285 'formTitlePaddingTopBottom',
286 $adv['formTitle']['paddingTopBottom'] ?? null
287 );
288
289 $this->setIfNotEmpty(
290 $this->activeColors,
291 'formDescriptionColor',
292 $adv['formDescription']['fontColor'] ?? null
293 );
294 $this->setIfNotEmpty(
295 $this->activeStyles,
296 'formDescriptionPaddingLeftRight',
297 $adv['formDescription']['paddingLeftRight'] ?? null
298 );
299 $this->setIfNotEmpty(
300 $this->activeStyles,
301 'formDescriptionPaddingTopBottom',
302 $adv['formDescription']['paddingTopBottom'] ?? null
303 );
304 $this->setIfNotEmpty(
305 $this->activeColors,
306 'requiredIndicator',
307 $adv['fieldLabels']['requiredIndicator']['fontColor'] ?? null
308 );
309
310 $this->setIfNotEmpty(
311 $this->activeColors,
312 'fieldDescriptionColor',
313 $adv['fieldStates']['default']['descriptionColor'] ?? null
314 );
315
316 $this->setIfNotEmpty(
317 $this->activeStyles,
318 'fieldBorderStyle',
319 $adv['fieldStates']['default']['borderStyle'] ?? null
320 );
321
322 $this->setIfNotEmpty(
323 $this->activeStyles,
324 'confirmationBorderStyle',
325 $adv['confirmationMessage']['borderStyle'] ?? null
326 );
327
328 // Rating
329 $rating = is_array($adv['rating'] ?? null) ? $adv['rating'] : [];
330 $this->setIfNotEmpty(
331 $this->activeStyles,
332 'ratingIconSize',
333 $rating['iconSize'] ?? null
334 );
335 $this->setIfNotEmpty(
336 $this->activeColors,
337 'ratingIconColor',
338 $rating['iconColor'] ?? null
339 );
340
341 // Date & Time Picker
342 $dateTimePicker = is_array($adv['dateTimePicker'] ?? null) ? $adv['dateTimePicker'] : [];
343 $this->setIfNotEmpty(
344 $this->activeColors,
345 'dateTimeCalendarBackground',
346 $dateTimePicker['calendarBackground'] ?? null
347 );
348 $this->setIfNotEmpty(
349 $this->activeColors,
350 'dateTimeNumberTextColor',
351 $dateTimePicker['numberTextColor'] ?? null
352 );
353 $this->setIfNotEmpty(
354 $this->activeColors,
355 'dateTimeSelectionColor',
356 $dateTimePicker['selectionColor'] ?? null
357 );
358
359 // Select
360 $select = is_array($adv['select'] ?? null) ? $adv['select'] : [];
361 $this->setIfNotEmpty(
362 $this->activeColors,
363 'selectTagColor',
364 $select['tagColor'] ?? null
365 );
366 $this->setIfNotEmpty(
367 $this->activeColors,
368 'selectTagTextColor',
369 $select['tagTextColor'] ?? null
370 );
371 $this->setIfNotEmpty(
372 $this->activeColors,
373 'selectOptionHoverColor',
374 $select['optionHoverColor'] ?? null
375 );
376 $this->setIfNotEmpty(
377 $this->activeColors,
378 'selectSelectionTextColor',
379 $select['selectionTextColor'] ?? null
380 );
381 $this->setIfNotEmpty(
382 $this->activeColors,
383 'selectSelectionBackgroundColor',
384 $select['selectionBackgroundColor'] ?? null
385 );
386 }
387
388 /**
389 * Set a scalar string on activeColors/activeStyles when the value is meaningful.
390 *
391 * Allows CSS zero tokens such as "0", "0px", and numeric 0. Arrays are ignored
392 * (structured values like corners use {@see resolveCornerSetting} instead).
393 *
394 * @param array<string, string> $target
395 * @param string $key
396 * @param mixed $value
397 * @return void
398 */
399 private function setIfNotEmpty(array &$target, string $key, $value): void
400 {
401 if (null === $value || false === $value) {
402 return;
403 }
404
405 if (is_array($value)) {
406 return;
407 }
408
409 if (is_string($value)) {
410 if ('' === $value) {
411 return;
412 }
413 $target[$key] = $value;
414 return;
415 }
416
417 if (is_numeric($value)) {
418 $target[$key] = (string) $value;
419 }
420 }
421
422 /**
423 * Sanitize alignment value.
424 *
425 * @param mixed $value
426 * @return string|null
427 */
428 private function sanitizeAlignment($value): ?string
429 {
430 if (!is_string($value) || '' === $value) {
431 return null;
432 }
433
434 return in_array($value, self::ALLOWED_ALIGNMENTS, true) ? $value : null;
435 }
436
437 /**
438 * Resolve corner setting and store in activeStyles
439 *
440 * @param string $key
441 * @param mixed $cornerSetting
442 * @return void
443 */
444 private function resolveCornerSetting(string $key, $cornerSetting): void
445 {
446 if (empty($cornerSetting)) {
447 return;
448 }
449
450 $resolved = CornerRadiusResolver::resolve($cornerSetting);
451 if (!empty($resolved)) {
452 $this->activeStyles[$key] = $resolved;
453 }
454 }
455
456 /**
457 * Set placeholder color with fallback to field text
458 *
459 * @param array<string, mixed> $quick
460 * @return void
461 */
462 private function setPlaceholderColor(array $quick): void
463 {
464 if (!empty($quick['placeholderFontColor'])) {
465 $this->activeColors['fieldPlaceholder'] = $quick['placeholderFontColor'];
466 return;
467 }
468
469 if (!empty($quick['fieldText'])) {
470 $this->activeColors['fieldPlaceholder'] = $quick['fieldText'];
471 }
472 }
473
474 /**
475 * Set checkbox and radio colors
476 *
477 * @param array<string, mixed> $quick
478 * @return void
479 */
480 private function setCheckboxRadioColors(array $quick): void
481 {
482 if (empty($quick['checkboxRadioSelection'])) {
483 return;
484 }
485
486 $this->activeColors['checkboxRadioSelection'] = $quick['checkboxRadioSelection'];
487 $this->activeColors['checkboxRadioBorder'] = $quick['checkboxRadioBorderColor']
488 ?? $quick['checkboxRadioSelection'];
489
490 if (!empty($quick['checkboxRadioFontColor'])) {
491 $this->activeColors['checkboxRadioLabel'] = $quick['checkboxRadioFontColor'];
492 }
493 }
494 }
495