| @@ -2,13 +2,13 @@ | ||
| 2 | 2 | |
| 3 | 3 | namespace FluentForm\App\Modules\Form; |
| 4 | 4 | |
| 5 | 5 | use FluentForm\Framework\Helpers\ArrayHelper; |
| 6 | -use WpFluent\Exception; | |
| 7 | 6 | |
| 8 | 7 | class FormDataParser |
| 9 | 8 | { |
| 10 | 9 | protected static $data = null; |
| 10 | + protected static $submissionId = null; | |
| 11 | 11 | |
| 12 | 12 | public static function parseFormEntries($entries, $form, $fields = null) |
| 13 | 13 | { |
| 14 | 14 | $fields = $fields ? $fields : FormFieldsParser::getEntryInputs($form); |
| @@ -24,9 +24,12 @@ | ||
| 24 | 24 | { |
| 25 | 25 | $fields = $fields ? $fields : FormFieldsParser::getEntryInputs($form); |
| 26 | 26 | |
| 27 | 27 | $entry->user_inputs = static::parseData( |
| 28 | - json_decode($entry->response), $fields, $form->id, $isHtml | |
| 28 | + json_decode($entry->response), | |
| 29 | + $fields, | |
| 30 | + $form->id, | |
| 31 | + $isHtml | |
| 29 | 32 | ); |
| 30 | 33 | |
| 31 | 34 | return $entry; |
| 32 | 35 | } |
| @@ -32,12 +35,19 @@ | ||
| 32 | 35 | } |
| 33 | 36 | |
| 34 | 37 | public static function parseFormSubmission($submission, $form, $fields, $isHtml = false) |
| 35 | 38 | { |
| 36 | - if (is_null(static::$data)) { | |
| 39 | + // Sometimes submission will change inside loop. So we need to parse submission data for new one | |
| 40 | + $newSubmission = $submission->id != static::$submissionId; | |
| 41 | + | |
| 42 | + if (is_null(static::$data) || $newSubmission) { | |
| 37 | 43 | static::$data = static::parseData( |
| 38 | - json_decode($submission->response), $fields, $form->id, $isHtml | |
| 44 | + json_decode($submission->response), | |
| 45 | + $fields, | |
| 46 | + $form->id, | |
| 47 | + $isHtml | |
| 39 | 48 | ); |
| 49 | + static::$submissionId = $submission->id; | |
| 40 | 50 | } |
| 41 | 51 | |
| 42 | 52 | $submission->user_inputs = static::$data; |
| 43 | 53 | |
| @@ -48,11 +58,26 @@ | ||
| 48 | 58 | { |
| 49 | 59 | $trans = []; |
| 50 | 60 | foreach ($fields as $field_key => $field) { |
| 51 | 61 | if (isset($response->{$field_key})) { |
| 62 | + $value = $response->{$field_key}; | |
| 63 | + | |
| 64 | + $value = apply_filters_deprecated( | |
| 65 | + 'fluentform_response_render_' . $field['element'], | |
| 66 | + [ | |
| 67 | + $value, | |
| 68 | + $field, | |
| 69 | + $formId, | |
| 70 | + $isHtml | |
| 71 | + ], | |
| 72 | + FLUENTFORM_FRAMEWORK_UPGRADE, | |
| 73 | + 'fluentform/response_render_' . $field['element'], | |
| 74 | + 'Use fluentform/response_render_' . $field['element'] . ' instead of fluentform_response_render_' . $field['element'] | |
| 75 | + ); | |
| 76 | + | |
| 52 | 77 | $value = apply_filters( |
| 53 | - 'fluentform_response_render_' . $field['element'], | |
| 54 | - $response->{$field_key}, | |
| 78 | + 'fluentform/response_render_' . $field['element'], | |
| 79 | + $value, | |
| 55 | 80 | $field, |
| 56 | 81 | $formId, |
| 57 | 82 | $isHtml |
| 58 | 83 | ); |
| @@ -67,15 +92,15 @@ | ||
| 67 | 92 | |
| 68 | 93 | public static function formatValue($value) |
| 69 | 94 | { |
| 70 | 95 | if (is_array($value) || is_object($value)) { |
| 71 | - return fluentImplodeRecursive(', ', array_filter(array_values((array)$value))); | |
| 96 | + return fluentImplodeRecursive(', ', array_filter(array_values((array) $value))); | |
| 72 | 97 | } |
| 73 | 98 | |
| 74 | 99 | return $value; |
| 75 | 100 | } |
| 76 | 101 | |
| 77 | - public static function formatFileValues($values, $isHtml) | |
| 102 | + public static function formatFileValues($values, $isHtml, $form_id = null) | |
| 78 | 103 | { |
| 79 | 104 | if (!$values) { |
| 80 | 105 | return $values; |
| 81 | 106 | } |
| @@ -84,10 +109,13 @@ | ||
| 84 | 109 | return $values; |
| 85 | 110 | } |
| 86 | 111 | |
| 87 | 112 | if (!$isHtml) { |
| 88 | - return fluentImplodeRecursive(', ', array_filter(array_values((array)$values))); | |
| 113 | + return fluentImplodeRecursive(', ', array_filter(array_values((array) $values))); | |
| 89 | 114 | } |
| 115 | + if ($form_id && \FluentForm\App\Helpers\Helper::isEntryAutoDeleteEnabled($form_id)) { | |
| 116 | + return ''; | |
| 117 | + } | |
| 90 | 118 | |
| 91 | 119 | $html = '<ul class="ff_entry_list">'; |
| 92 | 120 | foreach ($values as $value) { |
| 93 | 121 | if (!$value) { |
| @@ -92,9 +120,13 @@ | ||
| 92 | 120 | foreach ($values as $value) { |
| 93 | 121 | if (!$value) { |
| 94 | 122 | continue; |
| 95 | 123 | } |
| 96 | - $html .= '<li><a href="' . $value . '" target="_blank">' . basename($value) . '</a></li>'; | |
| 124 | + // SECURITY (FINDING-23): escape the submitted upload value. It reaches this HTML sink | |
| 125 | + // via an unauthenticated submission and is only sanitize_text_field'd (keeps " and :), | |
| 126 | + // so a javascript: URL or an " onmouseover=" attribute breakout would otherwise render | |
| 127 | + // in the admin entry view, notification email and PDF. esc_url enforces a safe scheme. | |
| 128 | + $html .= '<li><a href="' . esc_url($value) . '" target="_blank">' . esc_html(basename($value)) . '</a></li>'; | |
| 97 | 129 | } |
| 98 | 130 | |
| 99 | 131 | $html .= '</ul>'; |
| 100 | 132 | return $html; |
| @@ -99,9 +131,9 @@ | ||
| 99 | 131 | $html .= '</ul>'; |
| 100 | 132 | return $html; |
| 101 | 133 | } |
| 102 | 134 | |
| 103 | - public static function formatImageValues($values, $isHtml) | |
| 135 | + public static function formatImageValues($values, $isHtml, $form_id = null) | |
| 104 | 136 | { |
| 105 | 137 | if (!$values) { |
| 106 | 138 | return $values; |
| 107 | 139 | } |
| @@ -108,19 +140,25 @@ | ||
| 108 | 140 | |
| 109 | 141 | if (is_string($values)) { |
| 110 | 142 | return $values; |
| 111 | 143 | } |
| 144 | + | |
| 145 | + $isHtml = apply_filters('fluentform/render_field_as_html', $isHtml, $values, $form_id); | |
| 112 | 146 | |
| 147 | + | |
| 113 | 148 | if (!$isHtml) { |
| 114 | - return fluentImplodeRecursive(', ', array_filter(array_values((array)$values))); | |
| 149 | + return fluentImplodeRecursive(', ', array_filter(array_values((array) $values))); | |
| 115 | 150 | } |
| 116 | - | |
| 117 | - if (count($values) == 1) { | |
| 151 | + if ($form_id && \FluentForm\App\Helpers\Helper::isEntryAutoDeleteEnabled($form_id)) { | |
| 152 | + return ''; | |
| 153 | + } | |
| 154 | + if (1 == count($values)) { | |
| 118 | 155 | $value = $values[0]; |
| 119 | 156 | if (!$value) { |
| 120 | 157 | return ''; |
| 121 | 158 | } |
| 122 | - return '<a href="' . $value . '" target="_blank"><img style="max-width:180px" src="' . $value . '" /></a>'; | |
| 159 | + // SECURITY (FINDING-23): escape the submitted upload value (see formatFileValues). | |
| 160 | + return '<a href="' . esc_url($value) . '" target="_blank"><img style="max-width:180px" src="' . esc_url($value) . '" /></a>'; | |
| 123 | 161 | } |
| 124 | 162 | |
| 125 | 163 | $html = '<ul class="ff_entry_list ff_entry_images">'; |
| 126 | 164 | foreach ($values as $value) { |
| @@ -126,9 +164,10 @@ | ||
| 126 | 164 | foreach ($values as $value) { |
| 127 | 165 | if (!$value) { |
| 128 | 166 | continue; |
| 129 | 167 | } |
| 130 | - $html .= '<li style="margin: 20px 20px 20px 0px; display: inline-block; margin-right: 20px;"><a href="' . $value . '" target="_blank"><img style="max-width:180px" src="' . $value . '" /></a></li>'; | |
| 168 | + // SECURITY (FINDING-23): escape the submitted upload value (see formatFileValues). | |
| 169 | + $html .= '<li style="margin: 20px 20px 20px 0px; display: inline-block; margin-right: 20px;"><a href="' . esc_url($value) . '" target="_blank"><img style="max-width:180px" src="' . esc_url($value) . '" /></a></li>'; | |
| 131 | 170 | } |
| 132 | 171 | |
| 133 | 172 | $html .= '</ul>'; |
| 134 | 173 | return $html; |
| @@ -155,25 +194,26 @@ | ||
| 155 | 194 | ?> |
| 156 | 195 | <div class="ff_entry_table_wrapper"> |
| 157 | 196 | <table class="ff_entry_table_field ff-table"> |
| 158 | 197 | <thead> |
| 159 | - <tr> | |
| 160 | - <?php foreach ($repeatColumns as $repeatColumn) : ?> | |
| 161 | - <th><?php echo ArrayHelper::get($repeatColumn, 'settings.label'); ?></th> | |
| 162 | - <?php endforeach; ?> | |
| 163 | - </tr> | |
| 198 | + <tr> | |
| 199 | + <?php foreach ($repeatColumns as $repeatColumn) : ?> | |
| 200 | + <th><?php echo fluentform_sanitize_html(ArrayHelper::get($repeatColumn, 'settings.label')); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fluentform_sanitize_html() removes XSS vectors and uses wp_kses() with allowed tags ?> | |
| 201 | + </th> | |
| 202 | + <?php endforeach; ?> | |
| 203 | + </tr> | |
| 164 | 204 | </thead> |
| 165 | 205 | |
| 166 | 206 | <tbody> |
| 167 | - <?php for ($i = 0; $i < $rows; $i++) : ?> | |
| 207 | + <?php for ($i = 0; $i < $rows; $i++) : ?> | |
| 168 | 208 | <tr> |
| 169 | 209 | <?php for ($j = 0; $j < $columns; $j++) : ?> |
| 170 | - <td> | |
| 171 | - <?php echo $value[$j][$i] ?> | |
| 172 | - </td> | |
| 210 | + <td> | |
| 211 | + <?php echo fluentform_sanitize_html($value[$j][$i]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- fluentform_sanitize_html() removes XSS vectors and uses wp_kses() with allowed tags ?> | |
| 212 | + </td> | |
| 173 | 213 | <?php endfor; ?> |
| 174 | 214 | </tr> |
| 175 | - <?php endfor; ?> | |
| 215 | + <?php endfor; ?> | |
| 176 | 216 | </tbody> |
| 177 | 217 | </table> |
| 178 | 218 | </div> |
| 179 | 219 | <?php |
| @@ -178,10 +218,9 @@ | ||
| 178 | 218 | </div> |
| 179 | 219 | <?php |
| 180 | 220 | } |
| 181 | 221 | return ob_get_clean(); |
| 182 | - } catch (Exception $e) { | |
| 183 | - | |
| 222 | + } catch (\Exception $e) { | |
| 184 | 223 | } |
| 185 | 224 | |
| 186 | 225 | return $value; |
| 187 | 226 | } |
| @@ -196,9 +235,9 @@ | ||
| 196 | 235 | return $value; |
| 197 | 236 | } |
| 198 | 237 | |
| 199 | 238 | if (is_array($value)) { |
| 200 | - $value = (object)$value; | |
| 239 | + $value = (object) $value; | |
| 201 | 240 | } |
| 202 | 241 | try { |
| 203 | 242 | if (empty($field['raw'])) { |
| 204 | 243 | return $value; |
| @@ -204,19 +243,18 @@ | ||
| 204 | 243 | return $value; |
| 205 | 244 | } |
| 206 | 245 | $columnLabels = $field['raw']['settings']['grid_columns']; |
| 207 | 246 | $fieldType = $field['raw']['settings']['tabular_field_type']; |
| 208 | - $columnHeaders = implode('</th><th>', array_values($columnLabels)); | |
| 247 | + $columnHeaders = implode('</th><th style="text-align: center;">', array_values($columnLabels)); | |
| 209 | 248 | |
| 210 | - $elMarkup = "<table class='ff-table'><thead><tr><th></th><th>{$columnHeaders}</th></tr></thead><tbody>"; | |
| 249 | + $elMarkup = "<table class='ff-table'><thead><tr><th></th><th style='text-align: center;'>{$columnHeaders}</th></tr></thead><tbody>"; | |
| 211 | 250 | |
| 212 | 251 | foreach (static::makeTabularData($field['raw']) as $row) { |
| 213 | - | |
| 214 | - $elMarkup .= "<tr>"; | |
| 252 | + $elMarkup .= '<tr>'; | |
| 215 | 253 | $elMarkup .= "<td>{$row['label']}</td>"; |
| 216 | 254 | foreach ($row['columns'] as $column) { |
| 217 | 255 | $isChecked = ''; |
| 218 | - if ($fieldType == 'radio') { | |
| 256 | + if ('radio' == $fieldType) { | |
| 219 | 257 | if (isset($value->{$row['name']})) { |
| 220 | 258 | $isChecked = $value->{$row['name']} == $column['name'] ? 'checked' : ''; |
| 221 | 259 | } |
| 222 | 260 | } else { |
| @@ -227,18 +265,17 @@ | ||
| 227 | 265 | $icon = "<input disabled type='{$fieldType}' {$isChecked}>"; |
| 228 | 266 | if ($isChecked) { |
| 229 | 267 | $icon = '✔'; |
| 230 | 268 | } |
| 231 | - $elMarkup .= "<td>" . $icon . "</td>"; | |
| 269 | + $elMarkup .= "<td style='text-align: center;'>" . $icon . '</td>'; | |
| 232 | 270 | } |
| 233 | - $elMarkup .= "</tr>"; | |
| 271 | + $elMarkup .= '</tr>'; | |
| 234 | 272 | } |
| 235 | 273 | |
| 236 | - $elMarkup .= "</tbody></table>"; | |
| 274 | + $elMarkup .= '</tbody></table>'; | |
| 237 | 275 | |
| 238 | 276 | return $elMarkup; |
| 239 | - } catch (Exception $e) { | |
| 240 | - | |
| 277 | + } catch (\Exception $e) { | |
| 241 | 278 | } |
| 242 | 279 | return ''; |
| 243 | 280 | } |
| 244 | 281 | |
| @@ -248,22 +285,22 @@ | ||
| 248 | 285 | $rows = $data['settings']['grid_rows']; |
| 249 | 286 | $columns = $data['settings']['grid_columns']; |
| 250 | 287 | |
| 251 | 288 | foreach ($rows as $rowKey => $rowValue) { |
| 289 | + $rowKey = trim(sanitize_text_field($rowKey)); | |
| 252 | 290 | $table[$rowKey] = [ |
| 253 | 291 | 'name' => $rowKey, |
| 254 | 292 | 'label' => $rowValue, |
| 255 | - 'columns' => [] | |
| 293 | + 'columns' => [], | |
| 256 | 294 | ]; |
| 257 | 295 | |
| 258 | 296 | foreach ($columns as $columnKey => $columnValue) { |
| 259 | 297 | $table[$rowKey]['columns'][] = [ |
| 260 | - 'name' => $columnKey, | |
| 261 | - 'label' => $columnValue | |
| 298 | + 'name' => trim(sanitize_text_field($columnKey)), | |
| 299 | + 'label' => $columnValue, | |
| 262 | 300 | ]; |
| 263 | 301 | } |
| 264 | 302 | } |
| 265 | - | |
| 266 | 303 | return $table; |
| 267 | 304 | } |
| 268 | 305 | |
| 269 | 306 | /** |
| @@ -275,9 +312,16 @@ | ||
| 275 | 312 | */ |
| 276 | 313 | public static function formatName($value) |
| 277 | 314 | { |
| 278 | 315 | if (is_array($value) || is_object($value)) { |
| 279 | - return fluentImplodeRecursive(' ', array_filter(array_values((array)$value))); | |
| 316 | + $value = (array) $value; | |
| 317 | + $order = ['first_name', 'middle_name', 'last_name']; | |
| 318 | + uksort($value, function($a, $b) use ($order) { | |
| 319 | + $posA = array_search($a, $order); | |
| 320 | + $posB = array_search($b, $order); | |
| 321 | + return $posA - $posB; | |
| 322 | + }); | |
| 323 | + return fluentImplodeRecursive(' ', array_filter(array_values($value))); | |
| 280 | 324 | } |
| 281 | 325 | |
| 282 | 326 | return $value; |
| 283 | 327 | } |
| @@ -283,25 +327,51 @@ | ||
| 283 | 327 | } |
| 284 | 328 | |
| 285 | 329 | public static function formatCheckBoxValues($values, $field, $isHtml = false) |
| 286 | 330 | { |
| 287 | - if(!$isHtml) { | |
| 331 | + if (!$isHtml) { | |
| 332 | + if ( | |
| 333 | + defined('FLUENTFORM_RENDERING_ENTRIES') && | |
| 334 | + $values && is_array($values) && | |
| 335 | + $options = ArrayHelper::get($field, 'raw.settings.advanced_options', []) | |
| 336 | + ) { | |
| 337 | + $options = \FluentForm\App\Helpers\Helper::advancedOptionsValueLabelMap($options); | |
| 338 | + foreach ($values as &$value) { | |
| 339 | + if ($label = ArrayHelper::get($options, $value)) { | |
| 340 | + $value = $label; | |
| 341 | + } | |
| 342 | + } | |
| 343 | + } | |
| 288 | 344 | return self::formatValue($values); |
| 289 | 345 | } |
| 290 | 346 | |
| 291 | - if(!is_array($values) || empty($values)) { | |
| 347 | + if (!is_array($values)) { | |
| 292 | 348 | return $values; |
| 293 | 349 | } |
| 294 | 350 | |
| 351 | + if (empty($values)) { | |
| 352 | + return ''; | |
| 353 | + } | |
| 354 | + | |
| 355 | + if (!isset($field['options'])) { | |
| 356 | + $field['options'] = \FluentForm\App\Helpers\Helper::advancedOptionsValueLabelMap( | |
| 357 | + ArrayHelper::get($field, 'raw.settings.advanced_options', []) | |
| 358 | + ); | |
| 359 | + } | |
| 360 | + | |
| 295 | 361 | $html = '<ul style="white-space: normal;">'; |
| 296 | 362 | foreach ($values as $value) { |
| 297 | 363 | $item = $value; |
| 298 | - if($itemLabel = ArrayHelper::get($field, 'options.'.$item)) { | |
| 364 | + if ($itemLabel = ArrayHelper::get($field, 'options.' . $item)) { | |
| 299 | 365 | $item = $itemLabel; |
| 300 | 366 | } |
| 301 | - $html .= '<li>'.$item.'</li>'; | |
| 367 | + $html .= '<li>' . $item . '</li>'; | |
| 302 | 368 | } |
| 303 | 369 | |
| 304 | - return $html.'</ul>'; | |
| 370 | + return $html . '</ul>'; | |
| 371 | + } | |
| 305 | 372 | |
| 373 | + public static function resetData() | |
| 374 | + { | |
| 375 | + static::$data = null; | |
| 306 | 376 | } |
| 307 | 377 | } |