PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.14
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.14
6.2.14 6.2.13 6.2.12 6.2.10 6.2.11 6.2.9 6.2.8 6.2.7 6.2.6 6.2.5 6.2.4 6.2.3 6.2.2 3.6.22 3.6.31 3.6.40 3.6.41 3.6.42 3.6.50 3.6.51 3.6.60 3.6.61 3.6.62 3.6.64 3.6.65 All 196 releases
fluentform / app / Services / FormBuilder / LegacyDateConfigDecoder.php

LegacyDateConfigDecoder.php in Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder 6.2.14, at app/Services/FormBuilder/LegacyDateConfigDecoder.php

208 lines 7.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentForm\App\Services\FormBuilder;
4
5 defined('ABSPATH') || die;
6
7 /**
8 * Decodes date configuration tokens stored by Fluent Forms 6.2.7-6.2.12.
9 *
10 * This is intentionally render-only. New saves retain the submitted advanced
11 * configuration verbatim, while forms saved by those releases keep working
12 * without a migration or resave.
13 */
14 class LegacyDateConfigDecoder
15 {
16 const JS_STRING_FLAGS = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT;
17
18 /**
19 * Decode a legacy tokenized JSON object to executable Flatpickr config.
20 *
21 * Returns null when the value is not strict JSON or contains no exact
22 * legacy token node, allowing callers to preserve the original value.
23 *
24 * @param string $json
25 *
26 * @return string|null
27 */
28 public static function decode($json)
29 {
30 if (!is_string($json) || '' === $json) {
31 return null;
32 }
33
34 $decoded = json_decode($json, true);
35
36 if (JSON_ERROR_NONE !== json_last_error() || !is_array($decoded)) {
37 return null;
38 }
39
40 $foundLegacyToken = false;
41 $parts = [];
42
43 foreach ($decoded as $key => $value) {
44 $encodedKey = wp_json_encode((string) $key, self::JS_STRING_FLAGS);
45 $rendered = in_array($key, ['defaultDate', 'minDate', 'maxDate', 'enable', 'disable'], true)
46 ? self::renderOption($key, $value, $foundLegacyToken)
47 : wp_json_encode($value, self::JS_STRING_FLAGS);
48 $parts[] = $encodedKey . ': ' . $rendered;
49 }
50
51 $javascript = '{' . implode(', ', $parts) . '}';
52
53 return $foundLegacyToken ? $javascript : null;
54 }
55
56 /**
57 * Convert the restricted legacy expression tree to JavaScript.
58 *
59 * @param mixed $node
60 *
61 * @return string|null
62 */
63 private static function renderPredicate($node)
64 {
65 static $functions = ['getDay' => 1, 'getMonth' => 1, 'getDate' => 1, 'getFullYear' => 1];
66 static $comparators = ['==' => 1, '===' => 1, '!=' => 1, '!==' => 1, '>=' => 1, '<=' => 1, '>' => 1, '<' => 1];
67
68 if (!is_array($node)) {
69 return null;
70 }
71
72 foreach (['or' => ' || ', 'and' => ' && '] as $operator => $glue) {
73 if (isset($node[$operator])) {
74 if (1 !== count($node) || !is_array($node[$operator]) || [] === $node[$operator]) {
75 return null;
76 }
77
78 $parts = [];
79 foreach ($node[$operator] as $child) {
80 $javascript = self::renderPredicate($child);
81 if (null === $javascript) {
82 return null;
83 }
84 $parts[] = $javascript;
85 }
86
87 return '(' . implode($glue, $parts) . ')';
88 }
89 }
90
91 if (3 !== count($node) || !isset($node['fn'], $node['cmp'], $node['val'])) {
92 return null;
93 }
94
95 if (
96 !is_string($node['fn']) ||
97 !is_string($node['cmp']) ||
98 !isset($functions[$node['fn']], $comparators[$node['cmp']])
99 ) {
100 return null;
101 }
102
103 return 'date.' . $node['fn'] . '() ' . $node['cmp'] . ' ' . (int) $node['val'];
104 }
105
106 /**
107 * Convert a decoded legacy config node to JavaScript.
108 *
109 * @param string $option
110 * @param mixed $value
111 * @param bool $foundLegacyToken
112 *
113 * @return string
114 */
115 private static function renderOption($option, $value, &$foundLegacyToken)
116 {
117 if (in_array($option, ['defaultDate', 'minDate', 'maxDate'], true)) {
118 if (is_array($value) && array_keys($value) === range(0, count($value) - 1)) {
119 $parts = [];
120 foreach ($value as $item) {
121 $rendered = self::renderToken($item, ['date', 'fp_incr'], $foundLegacyToken);
122 $parts[] = null === $rendered ? wp_json_encode($item, self::JS_STRING_FLAGS) : $rendered;
123 }
124
125 return '[' . implode(', ', $parts) . ']';
126 }
127 $rendered = self::renderToken($value, ['date', 'fp_incr'], $foundLegacyToken);
128
129 return null === $rendered ? wp_json_encode($value, self::JS_STRING_FLAGS) : $rendered;
130 }
131
132 if (is_array($value) && array_keys($value) === range(0, count($value) - 1)) {
133 $parts = [];
134 foreach ($value as $item) {
135 $rendered = self::renderOptionItem($item, $foundLegacyToken);
136 $parts[] = null === $rendered ? wp_json_encode($item, self::JS_STRING_FLAGS) : $rendered;
137 }
138
139 return '[' . implode(', ', $parts) . ']';
140 }
141
142 $rendered = self::renderToken($value, ['date', 'fp_incr', 'disable_days', 'disable_expr'], $foundLegacyToken);
143
144 return null === $rendered ? wp_json_encode($value, self::JS_STRING_FLAGS) : $rendered;
145 }
146
147 private static function renderOptionItem($item, &$foundLegacyToken)
148 {
149 $rendered = self::renderToken($item, ['date', 'fp_incr', 'disable_days', 'disable_expr'], $foundLegacyToken);
150 if (null !== $rendered || !is_array($item) || [] === $item) {
151 return $rendered;
152 }
153
154 $keys = array_keys($item);
155 sort($keys);
156 if (['from', 'to'] !== $keys) {
157 return null;
158 }
159
160 $parts = [];
161 foreach (['from', 'to'] as $key) {
162 $value = self::renderToken($item[$key], ['date', 'fp_incr'], $foundLegacyToken);
163 $parts[] = wp_json_encode($key, self::JS_STRING_FLAGS) . ': ' . (null === $value
164 ? wp_json_encode($item[$key], self::JS_STRING_FLAGS)
165 : $value);
166 }
167
168 return '{' . implode(', ', $parts) . '}';
169 }
170
171 private static function renderToken($node, array $allowed, &$foundLegacyToken)
172 {
173 if (!is_array($node) || 1 !== count($node)) {
174 return null;
175 }
176
177 if (isset($node['__ff_fp_incr']) && in_array('fp_incr', $allowed, true)) {
178 $foundLegacyToken = true;
179 $days = (int) $node['__ff_fp_incr'];
180 return $days ? "new Date().fp_incr({$days})" : 'new Date()';
181 }
182
183 if (isset($node['__ff_disable_days']) && in_array('disable_days', $allowed, true) && is_array($node['__ff_disable_days'])) {
184 $foundLegacyToken = true;
185 $days = implode(', ', array_map('intval', $node['__ff_disable_days']));
186 return "function(date) { return [{$days}].indexOf(date.getDay()) !== -1; }";
187 }
188
189 if (isset($node['__ff_date']) && in_array('date', $allowed, true)) {
190 $foundLegacyToken = true;
191 $date = is_string($node['__ff_date']) ? $node['__ff_date'] : '';
192 if (!preg_match('/^[0-9A-Za-z ,:.\\/\\-]{1,40}$/', $date)) {
193 return 'null';
194 }
195
196 return 'new Date(' . wp_json_encode($date, self::JS_STRING_FLAGS) . ')';
197 }
198
199 if (isset($node['__ff_disable_expr']) && in_array('disable_expr', $allowed, true)) {
200 $foundLegacyToken = true;
201 $expression = self::renderPredicate($node['__ff_disable_expr']);
202 return null === $expression ? 'null' : "function(date) { return {$expression}; }";
203 }
204
205 return null;
206 }
207 }
208