PluginProbe
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder / 6.2.15
Fluent Forms – Customizable Contact Forms, Survey, Quiz, & Conversational Form Builder v6.2.15
6.2.15 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 All 197 releases
← All changes | app/Services/FormBuilder/EditorShortcodeParser.php +191 -74 3.6.62 → 6.2.15 View file →
@@ -1,16 +1,16 @@
1 1 <?php
2 2
3 3 namespace FluentForm\App\Services\FormBuilder;
4 4
5 +use FluentForm\App\Helpers\Helper;
5 6 use FluentForm\App\Services\Browser\Browser;
6 -use FluentForm\Framework\Helpers\ArrayHelper;
7 -use FluentForm\Request;
8 7
9 8 class EditorShortcodeParser
10 9 {
11 10 /**
12 11 * Available dynamic short codes
12 + *
13 13 * @var null
14 14 */
15 15 private static $dynamicShortcodes = null;
16 16
@@ -15,8 +15,9 @@
15 15 private static $dynamicShortcodes = null;
16 16
17 17 /**
18 18 * mappings of methods to parse the shortcode
19 + *
19 20 * @var array
20 21 */
21 22 private static $handlers = [
22 23 'ip' => 'parseIp',
@@ -41,24 +42,26 @@
41 42
42 43 'browser.name' => 'parseBrowserProperties',
43 44 'browser.platform' => 'parseBrowserProperties',
44 45
45 - 'get.param_name' => 'parseQueryParam'
46 + 'get.param_name' => 'parseRequestParam',
47 + 'random_string.param_name' => 'parseRandomString',
46 48 ];
47 49
48 50 /**
49 51 * Filter dynamic shortcodes in input value
52 + *
50 53 * @param string $value
54 + *
51 55 * @return string
52 56 */
53 57 public static function filter($value, $form)
54 58 {
55 - if (strpos($value, '{ ') === 0) {
59 + if (0 === strpos($value, '{ ')) {
56 60 // it's the css
57 61 return $value;
58 62 }
59 63
60 -
61 64 if (is_null(static::$dynamicShortcodes)) {
62 65 static::$dynamicShortcodes = fluentFormEditorShortCodes();
63 66 }
64 67
@@ -69,44 +72,53 @@
69 72 return call_user_func_array(
70 73 [__CLASS__, static::$handlers[$handler]],
71 74 ['{' . $handler . '}', $form]
72 75 );
73 - } elseif (strpos($handler, 'get.') !== false) {
74 - return static::parseQueryParam($handler);
75 - } else if (strpos($handler, 'user.meta.') !== false) {
76 - $key = substr(str_replace(['{', '}'], '', $value), 10);
77 - $user = wp_get_current_user();
78 - if ($user) {
79 - $value = get_post_meta($user->ID, $key, true);
80 - if (!is_array($value) && !is_object($value)) {
81 - return $value;
82 - }
83 - }
84 - return '';
85 - } else if (strpos($handler, 'user.') !== false) {
86 - $value = self::parseUserProperties($handler);
87 - if (is_array($value) || is_object($value)) {
76 + }
77 +
78 + if (false !== strpos($handler, 'get.')) {
79 + return static::parseRequestParam($handler);
80 + }
81 + if (false !== strpos($handler, 'random_string.')) {
82 + return static::parseRandomString($handler);
83 + }
84 +
85 + if (false !== strpos($handler, 'user.')) {
86 + $parsedValue = self::parseUserProperties($handler);
87 + if (is_array($parsedValue) || is_object($parsedValue)) {
88 88 return '';
89 89 }
90 - return $value;
91 - } else if (strpos($handler, 'date.') !== false) {
92 - return self::parseDate($handler);
93 - } else if (strpos($handler, 'embed_post.meta.') !== false) {
90 + return esc_html($parsedValue);
91 + }
92 +
93 + if (false !== strpos($handler, 'date.')) {
94 + return esc_html(self::parseDate($handler));
95 + }
96 +
97 + if (false !== strpos($handler, 'embed_post.meta.')) {
94 98 $key = substr(str_replace(['{', '}'], '', $value), 16);
95 99 global $post;
96 100 if ($post) {
97 - $value = get_post_meta($post->ID, $key, true);
98 - if (!is_array($value) && !is_object($value)) {
99 - return $value;
101 + $metaValue = get_post_meta($post->ID, $key, true);
102 + if (!is_array($metaValue) && !is_object($metaValue)) {
103 + return esc_html($metaValue);
100 104 }
101 105 }
102 106 return '';
103 - } else if (strpos($handler, 'embed_post.') !== false) {
107 + }
108 +
109 + if (false !== strpos($handler, 'embed_post.')) {
104 110 return self::parsePostProperties($handler, $form);
105 - } else if (strpos($handler, 'cookie.') !== false) {
111 + }
112 +
113 + if (false !== strpos($handler, 'cookie.')) {
106 114 $scookieProperty = substr($handler, strlen('cookie.'));
107 - return ArrayHelper::get($_COOKIE, $scookieProperty);
108 - } else if (strpos($handler, 'dynamic.') !== false) {
115 + $cookieValue = array_key_exists($scookieProperty, $_COOKIE) ? sanitize_text_field(wp_unslash($_COOKIE[$scookieProperty])) : '';
116 +
117 + return static::escapeReflectedValue($cookieValue);
118 + }
119 +
120 + if (false !== strpos($handler, 'dynamic.')) {
109 121 $dynamicKey = substr($handler, strlen('dynamic.'));
110 122 // maybe has fallback value
111 123 $dynamicKey = explode('|', $dynamicKey);
112 124 $fallBack = '';
@@ -113,21 +125,35 @@
113 125 $ref = '';
114 126 if (count($dynamicKey) > 1) {
115 127 $fallBack = $dynamicKey[1];
116 128 }
117 - $ref = $dynamicKey[0];
129 + if (isset($dynamicKey[0])) {
130 + $ref = $dynamicKey[0];
131 + }
118 132
119 - if ($ref == 'payment_summary') {
120 - return '<div class="ff_dynamic_value ff_dynamic_payment_summary" data-ref="payment_summary"><div class="ff_payment_summary"></div><div class="ff_payment_summary_fallback">' . $fallBack . '</div></div>';
133 + if ('payment_summary' == $ref) {
134 + return fluentform_sanitize_html('<div class="ff_dynamic_value ff_dynamic_payment_summary" data-ref="payment_summary"><div class="ff_payment_summary"></div><div class="ff_payment_summary_fallback">' . $fallBack . '</div></div>');
121 135 }
122 136
123 - return '<span class="ff_dynamic_value" data-ref="' . $ref . '" data-fallback="' . $fallBack . '">' . $fallBack . '</span>';
124 - } else {
125 - // This can be the css
126 - $handlerValue = apply_filters('fluentform_editor_shortcode_callback_' . $handler, '{' . $handler . '}', $form);
127 - // In not found then return the original please
128 - $filteredValue = $handlerValue;
137 + return fluentform_sanitize_html('<span class="ff_dynamic_value" data-ref="' . $ref . '" data-fallback="' . $fallBack . '">' . $fallBack . '</span>');
129 138 }
139 +
140 + // if it's multi line then just return
141 + if (false !== strpos($handler, PHP_EOL)) { // most probably it's a css
142 + return '{' . $handler . '}';
143 + }
144 +
145 + $handlerArray = explode('.', $handler);
146 +
147 + if (count($handlerArray) > 1) {
148 + // it's a grouped handler
149 + $group = array_shift($handlerArray);
150 + $parsedValue = apply_filters('fluentform_editor_shortcode_callback_group_' . $group, '{' . $handler . '}', $form, $handlerArray);
151 + return apply_filters('fluentform/editor_shortcode_callback_group_' . $group, $parsedValue, $form, $handlerArray);
152 + }
153 +
154 + $parsedValue = apply_filters('fluentform_editor_shortcode_callback_' . $handler, '{' . $handler . '}', $form);
155 + return apply_filters('fluentform/editor_shortcode_callback_' . $handler, $parsedValue, $form);
130 156 }
131 157
132 158 return $filteredValue;
133 159 }
@@ -132,10 +158,60 @@
132 158 return $filteredValue;
133 159 }
134 160
135 161 /**
162 + * Parse request query param.
163 + *
164 + * @param string $value
165 + * @param \stdClass $form
166 + *
167 + * @return string
168 + */
169 + public static function parseRequestParam($value)
170 + {
171 + $exploded = explode('.', $value);
172 + $param = array_pop($exploded);
173 + $value = wpFluentForm('request')->get($param);
174 +
175 + if (null === $value || '' === $value) {
176 + return '';
177 + }
178 +
179 + return static::escapeReflectedValue(Helper::flattenRequestValue($value));
180 + }
181 +
182 + /**
183 + * Escape a visitor-supplied value ({get.x}, {cookie.x}) for the assembled form HTML.
184 + *
185 + * Smartcodes are substituted after Custom HTML was sanitized, so the value can land in
186 + * any attribute, including an iframe src or anchor href. esc_attr() leaves a javascript:
187 + * scheme intact and keeps existing entities (?p=java&#9;script:...), so encode every
188 + * ampersand and drop values that would resolve to a script-capable URL.
189 + *
190 + * @param string $value
191 + *
192 + * @return string
193 + */
194 + public static function escapeReflectedValue($value)
195 + {
196 + $value = wp_check_invalid_utf8((string) $value);
197 +
198 + // Browsers strip control chars and whitespace from URLs before reading the scheme.
199 + $scheme = strtolower(preg_replace('/[\x00-\x20]+/', '', $value));
200 +
201 + if (preg_match('/^(javascript|vbscript|data):/', $scheme)) {
202 + return '';
203 + }
204 +
205 + // Encode braces too, so the value cannot plant a smartcode for a later replacement pass.
206 + return str_replace(['{', '}'], ['&#123;', '&#125;'], htmlspecialchars($value, ENT_QUOTES, 'UTF-8', true));
207 + }
208 +
209 + /**
136 210 * Parse the curly braced shortcode into array
211 + *
137 212 * @param string $value
213 + *
138 214 * @return mixed
139 215 */
140 216 public static function parseValue($value)
141 217 {
@@ -142,9 +218,9 @@
142 218 if (!is_array($value)) {
143 219 return preg_split(
144 220 '/{(.*?)}/',
145 221 $value,
146 - null,
222 + -1,
147 223 PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY
148 224 );
149 225 }
150 226
@@ -156,9 +232,11 @@
156 232 */
157 233
158 234 /**
159 235 * Parse loggedin user properties
236 + *
160 237 * @param string $value
238 + *
161 239 * @return string
162 240 */
163 241 private static function parseUserProperties($value, $form = null)
164 242 {
@@ -164,19 +242,28 @@
164 242 {
165 243 if ($user = wp_get_current_user()) {
166 244 $prop = substr(str_replace(['{', '}'], '', $value), 5);
167 245
168 - if (strpos($prop, 'meta.') !== false) {
246 + if (false !== strpos($prop, 'meta.')) {
169 247 $metaKey = substr($prop, strlen('meta.'));
248 + $metaKey = sanitize_text_field($metaKey);
249 + if (empty($metaKey) || ShortCodeParser::isDeniedUserProperty($metaKey)) {
250 + return '';
251 + }
170 252 $userId = $user->ID;
171 253 $data = get_user_meta($userId, $metaKey, true);
254 + $data = Helper::safeUnserialize($data);
172 255 if (!is_array($data)) {
173 - return $data;
256 + return esc_html($data);
174 257 }
258 + return esc_html(implode(',', $data));
259 + }
260 +
261 + if (ShortCodeParser::isDeniedUserProperty($prop)) {
175 262 return '';
176 263 }
177 264
178 - return $user->{$prop};
265 + return esc_html($user->{$prop});
179 266 }
180 267
181 268 return '';
182 269 }
@@ -182,9 +269,11 @@
182 269 }
183 270
184 271 /**
185 272 * Parse embedded post properties
273 + *
186 274 * @param string $value
275 + *
187 276 * @return string
188 277 */
189 278 private static function parsePostProperties($value, $form = null)
190 279 {
@@ -194,67 +283,68 @@
194 283 }
195 284
196 285 $key = $prop = substr(str_replace(['{', '}'], '', $value), 11);
197 286
198 - if (strpos($key, 'author.') !== false) {
287 + if (false !== strpos($key, 'author.')) {
199 288 $authorProperty = substr($key, strlen('author.'));
200 289 $authorId = $post->post_author;
201 - if ($authorId) {
290 + if ($authorId && !ShortCodeParser::isDeniedUserProperty($authorProperty)) {
202 291 $data = get_the_author_meta($authorProperty, $authorId);
203 292 if (!is_array($data)) {
204 - return $data;
293 + return esc_html($data);
205 294 }
206 295 }
207 296 return '';
208 - } else if (strpos($key, 'meta.') !== false) {
297 + } elseif (false !== strpos($key, 'meta.')) {
209 298 $metaKey = substr($key, strlen('meta.'));
210 299 $postId = $post->ID;
211 300 $data = get_post_meta($postId, $metaKey, true);
212 301 if (!is_array($data)) {
213 - return $data;
302 + return esc_html($data);
214 303 }
215 304 return '';
216 - } else if (strpos($key, 'acf.') !== false) {
305 + } elseif (false !== strpos($key, 'acf.')) {
217 306 $metaKey = substr($key, strlen('acf.'));
218 307 $postId = $post->ID;
219 308 if (function_exists('get_field')) {
220 309 $data = get_field($metaKey, $postId, true);
221 310 if (!is_array($data)) {
222 - return $data;
311 + return esc_html($data);
223 312 }
224 313 return '';
225 314 }
226 315 }
227 316
228 - if ($prop == 'permalink') {
229 - return htmlspecialchars(site_url(wp_unslash($_SERVER['REQUEST_URI'])));
317 + if ('permalink' == $prop) {
318 + return site_url(esc_attr(urldecode(wpFluentForm('request')->server('REQUEST_URI'))));
230 319 }
231 320
232 - if (property_exists($post, $prop)) {
233 - return $post->{$prop};
321 + if ('post_password' !== $prop && property_exists($post, $prop)) {
322 + return esc_html($post->{$prop});
234 323 }
235 324 return '';
236 325 }
237 326
238 -
239 327 /**
240 328 * Parse WP Properties
329 + *
241 330 * @param string $value
331 + *
242 332 * @return string
243 333 */
244 334 private static function parseWPProperties($value, $form = null)
245 335 {
246 - if ($value == '{wp.admin_email}') {
247 - return get_option('admin_email');
336 + if ('{wp.admin_email}' == $value) {
337 + return esc_html(get_option('admin_email'));
248 338 }
249 - if ($value == '{wp.site_url}') {
250 - return site_url();
339 + if ('{wp.site_url}' == $value) {
340 + return esc_url(site_url());
251 341 }
252 - if ($value == '{wp.site_title}') {
253 - return get_option('blogname');
342 + if ('{wp.site_title}' == $value) {
343 + return esc_html(get_option('blogname'));
254 344 }
255 - if ($value == '{http_referer}') {
256 - return wp_get_referer();
345 + if ('{http_referer}' == $value) {
346 + return esc_url(wp_get_referer());
257 347 }
258 348
259 349 return '';
260 350 }
@@ -260,18 +350,20 @@
260 350 }
261 351
262 352 /**
263 353 * Parse browser/user-agent properties
354 + *
264 355 * @param string $value
356 + *
265 357 * @return string
266 358 */
267 359 private static function parseBrowserProperties($value, $form = null)
268 360 {
269 - $browser = new Browser;
270 - if ($value == '{browser.name}') {
271 - return $browser->getBrowser();
272 - } elseif ($value == '{browser.platform}') {
273 - return $browser->getPlatform();
361 + $browser = new Browser();
362 + if ('{browser.name}' == $value) {
363 + return esc_html($browser->getBrowser());
364 + } elseif ('{browser.platform}' == $value) {
365 + return esc_html($browser->getPlatform());
274 366 }
275 367
276 368 return '';
277 369 }
@@ -277,20 +369,25 @@
277 369 }
278 370
279 371 /**
280 372 * Parse ip shortcode
373 + *
281 374 * @param string $value
375 + *
282 376 * @return string
283 377 */
284 378 private static function parseIp($value, $form = null)
285 379 {
286 - $ip = Request::getIp();
287 - return $ip ? $ip : $value;
380 + $rawIp = wpFluentForm('request')->getIp();
381 + $ip = sanitize_text_field($rawIp);
382 + return $ip ? esc_html($ip) : $value;
288 383 }
289 384
290 385 /**
291 386 * Parse date shortcode
387 + *
292 388 * @param string $value
389 + *
293 390 * @return string
294 391 */
295 392 private static function parseDate($value, $form = null)
296 393 {
@@ -295,9 +392,9 @@
295 392 private static function parseDate($value, $form = null)
296 393 {
297 394 $format = substr(str_replace(['}', '{'], '', $value), 5);
298 395 $date = date($format, strtotime(current_time('mysql')));
299 - return $date ? $date : '';
396 + return $date ? esc_html($date) : '';
300 397 }
301 398
302 399 /**
303 400 * Parse request query param.
@@ -303,8 +400,9 @@
303 400 * Parse request query param.
304 401 *
305 402 * @param string $value
306 403 * @param \stdClass $form
404 + *
307 405 * @return string
308 406 */
309 407 public static function parseQueryParam($value)
310 408 {
@@ -309,14 +407,33 @@
309 407 public static function parseQueryParam($value)
310 408 {
311 409 $exploded = explode('.', $value);
312 410 $param = array_pop($exploded);
313 - if (!isset($_REQUEST[$param])) {
411 + $value = wpFluentForm('request')->get($param);
412 +
413 + if (!$value) {
314 414 return '';
315 415 }
316 - $value = $_REQUEST[$param];
416 +
317 417 if (is_array($value)) {
318 418 return sanitize_textarea_field(implode(', ', $value));
319 419 }
420 +
320 421 return sanitize_textarea_field($value);
422 + }
423 +
424 + /**
425 + * Generate random a string with prefix
426 + *
427 + * @param $value
428 + *
429 + * @return string
430 + */
431 + public static function parseRandomString($value)
432 + {
433 + $exploded = explode('.', $value);
434 + $prefix = array_pop($exploded);
435 + $value = $prefix . uniqid();
436 +
437 + return esc_html(apply_filters('fluentform/shortcode_parser_callback_random_string', $value, $prefix, new static()));
321 438 }
322 439 }