| @@ -244,8 +244,9 @@ | ||
| 244 | 244 | // Guard: never persist orphan fields (in fields but in no layout) — they |
| 245 | 245 | // render nothing yet their validation rules block submission. Covers old |
| 246 | 246 | // clients and hand-edited imports (importAForm funnels through here). |
| 247 | 247 | $this->pruneOrphanFields($fields, $nestedLayout, $layout, null, $post); |
| 248 | + FormManager::syncCompositeChildNames($fields); | |
| 248 | 249 | $form_content = [ |
| 249 | 250 | 'fields' => $fields, |
| 250 | 251 | 'layout' => $layout, |
| 251 | 252 | 'nestedLayout' => $nestedLayout, |
| @@ -524,8 +525,12 @@ | ||
| 524 | 525 | $updated_data['form_content']['is_default'] = 1; |
| 525 | 526 | } |
| 526 | 527 | $updated_data['message'] = __('Form Saved successfully', 'bit-form'); |
| 527 | 528 | |
| 529 | + // Form and dependent rows are persisted; multilingual providers register | |
| 530 | + // the form's translatable strings here. | |
| 531 | + do_action('bitform_form_saved', $save_status); | |
| 532 | + | |
| 528 | 533 | return $updated_data; |
| 529 | 534 | } |
| 530 | 535 | } |
| 531 | 536 | |
| @@ -584,8 +589,9 @@ | ||
| 584 | 589 | } |
| 585 | 590 | |
| 586 | 591 | // Guard: never persist orphan fields (see createNewForm). Fail closed. |
| 587 | 592 | $this->pruneOrphanFields($fields, $nestedLayout, $layout, $formID, $post); |
| 593 | + FormManager::syncCompositeChildNames($fields); | |
| 588 | 594 | |
| 589 | 595 | $form_content = [ |
| 590 | 596 | 'fields' => $fields, |
| 591 | 597 | 'layout' => $layout, |
| @@ -664,9 +670,14 @@ | ||
| 664 | 670 | $templateDetail->id = $savedID; |
| 665 | 671 | unset($templateDetail->clRef); |
| 666 | 672 | } |
| 667 | 673 | } else { |
| 668 | - $emailTemplateHandler->updateTemplate($templateDetail); | |
| 674 | + $updated = $emailTemplateHandler->updateTemplate($templateDetail); | |
| 675 | + if (is_wp_error($updated) && 'db_error' === $updated->get_error_code()) { | |
| 676 | + $newData['mailTemplate'] = 2; | |
| 677 | + } elseif (0 === $newData['mailTemplate']) { | |
| 678 | + $newData['mailTemplate'] = 1; | |
| 679 | + } | |
| 669 | 680 | } |
| 670 | 681 | } |
| 671 | 682 | } |
| 672 | 683 | // return $formSettings; |
| @@ -792,8 +803,11 @@ | ||
| 792 | 803 | } |
| 793 | 804 | $form_content = array_merge($form_content, ['workFlowExist' => $workFlowExist]); |
| 794 | 805 | //wrokFlows [end] |
| 795 | 806 | //reports [start] */ |
| 807 | + // read unconditionally after the reports block; a save without currentReport | |
| 808 | + // must not trip an undefined-variable warning | |
| 809 | + $reportIsDefault = null; | |
| 796 | 810 | if (!empty($reports)) { |
| 797 | 811 | $reportsModel = new ReportsModel(); |
| 798 | 812 | $fieldNames = []; |
| 799 | 813 | foreach ($fields as $key => $field) { |
| @@ -811,9 +825,13 @@ | ||
| 811 | 825 | $fieldNames['__created_at'] = __('Created Time', 'bit-form'); |
| 812 | 826 | $fieldNames['__updated_at'] = __('Modified Time', 'bit-form'); |
| 813 | 827 | |
| 814 | 828 | $reportIsDefault = null; |
| 815 | - if (isset($form_content['report_id'])) { | |
| 829 | + // The client posts currentReport as {} whenever its report atom has not resolved yet. That | |
| 830 | + // is an empty stdClass, which empty() above treats as non-empty, so we reach here with | |
| 831 | + // nothing to save — and writing the validated result would replace the stored report's | |
| 832 | + // column order, hidden columns, page size and name with an empty list. Leave the row alone. | |
| 833 | + if (isset($form_content['report_id']) && ReportsModel::isValidatableReport($reports)) { | |
| 816 | 834 | $validDateReport = $reportsModel->validateReportFields($reports, $fieldNames); |
| 817 | 835 | if (isset($reports->isDefault)) { |
| 818 | 836 | $reportIsDefault = $reports->isDefault; |
| 819 | 837 | } else { |
| @@ -924,8 +942,12 @@ | ||
| 924 | 942 | } |
| 925 | 943 | if (2 === $newData['reports']) { |
| 926 | 944 | $errorIN .= empty($errorIN) ? 'reports' : ', reports'; |
| 927 | 945 | } |
| 946 | + // See createNewForm(). Fired before the partial-failure return: the rows | |
| 947 | + // that did save are already persisted, so their strings still need registering. | |
| 948 | + do_action('bitform_form_saved', $formId); | |
| 949 | + | |
| 928 | 950 | if (!empty($errorIN)) { |
| 929 | 951 | /* translators: %s: comma-separated list of areas where error occurred */ |
| 930 | 952 | $updated_data['message'] = sprintf(__('Error Occured in saving %s', 'bit-form'), $errorIN); |
| 931 | 953 | return new WP_Error('Form update Error.', $updated_data); |
| @@ -933,8 +955,9 @@ | ||
| 933 | 955 | if (null !== $reportIsDefault) { |
| 934 | 956 | $updated_data['form_content']['is_default'] = $reportIsDefault; |
| 935 | 957 | } |
| 936 | 958 | $updated_data['message'] = __('Form updated successfully.', 'bit-form'); |
| 959 | + | |
| 937 | 960 | return $updated_data; |
| 938 | 961 | } |
| 939 | 962 | } |
| 940 | 963 | |
| @@ -1112,9 +1135,9 @@ | ||
| 1112 | 1135 | 'nestedLayout' => !empty($form_content->nestedLayout) ? $form_content->nestedLayout : (object) [], |
| 1113 | 1136 | 'formInfo' => !empty($form_content->formInfo) ? $form_content->formInfo : (object) ['formName' => $formManager->getFormName()], |
| 1114 | 1137 | 'fields' => $form_content->fields, |
| 1115 | 1138 | 'form_name' => $formManager->getFormName(), |
| 1116 | - 'workFlowExist' => $form_content->workFlowExist, | |
| 1139 | + 'workFlowExist' => isset($form_content->workFlowExist) ? $form_content->workFlowExist : [], | |
| 1117 | 1140 | 'report_id' => isset($form_content->report_id) ? $form_content->report_id : null |
| 1118 | 1141 | ]; |
| 1119 | 1142 | $successMessageHandler |
| 1120 | 1143 | = new SuccessMessageHandler($formID); |
| @@ -1142,9 +1165,9 @@ | ||
| 1142 | 1165 | 'title' => $emailTemplatevalue->title, |
| 1143 | 1166 | 'sub' => $emailTemplatevalue->sub, |
| 1144 | 1167 | 'body' => $emailTemplatevalue->body, |
| 1145 | 1168 | 'status' => isset($emailTemplatevalue->status) ? (int) $emailTemplatevalue->status : 1, |
| 1146 | - 'config' => !empty($emailTemplatevalue->config) ? json_decode($emailTemplatevalue->config) : (object) [], | |
| 1169 | + 'config' => EmailTemplateHandler::normalizeConfig($emailTemplatevalue->config ?? null), | |
| 1147 | 1170 | ]; |
| 1148 | 1171 | } |
| 1149 | 1172 | } |
| 1150 | 1173 | // get all pdf template |
| @@ -1410,9 +1433,9 @@ | ||
| 1410 | 1433 | $form_content_arr = [ |
| 1411 | 1434 | 'layout' => $form_content->layout, |
| 1412 | 1435 | 'fields' => $form_content->fields, |
| 1413 | 1436 | 'form_name' => $formManager->getFormName(), |
| 1414 | - 'workFlowExist' => $form_content->workFlowExist, | |
| 1437 | + 'workFlowExist' => isset($form_content->workFlowExist) ? $form_content->workFlowExist : [], | |
| 1415 | 1438 | 'report_id' => isset($form_content->report_id) ? $form_content->report_id : null |
| 1416 | 1439 | ]; |
| 1417 | 1440 | $successMessageHandler |
| 1418 | 1441 | = new SuccessMessageHandler($formID); |
| @@ -1439,9 +1462,9 @@ | ||
| 1439 | 1462 | 'title' => $emailTemplatevalue->title, |
| 1440 | 1463 | 'sub' => $emailTemplatevalue->sub, |
| 1441 | 1464 | 'body' => $emailTemplatevalue->body, |
| 1442 | 1465 | 'status' => isset($emailTemplatevalue->status) ? (int) $emailTemplatevalue->status : 1, |
| 1443 | - 'config' => !empty($emailTemplatevalue->config) ? json_decode($emailTemplatevalue->config) : (object) [], | |
| 1466 | + 'config' => EmailTemplateHandler::normalizeConfig($emailTemplatevalue->config ?? null), | |
| 1444 | 1467 | ]; |
| 1445 | 1468 | } |
| 1446 | 1469 | } |
| 1447 | 1470 | $integrationHandler = new IntegrationHandler($formID); |
| @@ -2019,8 +2042,65 @@ | ||
| 2019 | 2042 | $response['fieldDetails'] = $labels; |
| 2020 | 2043 | return $response; |
| 2021 | 2044 | } |
| 2022 | 2045 | |
| 2046 | + /** | |
| 2047 | + * Sanitize a request-supplied entry query condition before it reaches the Model | |
| 2048 | + * layer. Only known entry-table columns are allowed as condition keys, operators | |
| 2049 | + * are restricted to a safe allow-list ['form_id' => $id] when nothing valid remains. | |
| 2050 | + * | |
| 2051 | + * @param mixed $queryCondition | |
| 2052 | + * @param int $id | |
| 2053 | + * @return array | |
| 2054 | + */ | |
| 2055 | + private function sanitizeEntryQueryCondition($queryCondition, $id) | |
| 2056 | + { | |
| 2057 | + $allowedColumns = [ | |
| 2058 | + 'id', 'form_id', 'status', 'created_at', 'updated_at', | |
| 2059 | + 'user_id', 'user_ip', 'user_device', 'user_location', 'referer', | |
| 2060 | + ]; | |
| 2061 | + $allowedOperators = ['=', '!=', '<>', '<', '>', '<=', '>=', 'LIKE', 'IN']; | |
| 2062 | + | |
| 2063 | + if (!is_array($queryCondition)) { | |
| 2064 | + return ['form_id' => $id]; | |
| 2065 | + } | |
| 2066 | + | |
| 2067 | + $safe = []; | |
| 2068 | + foreach ($queryCondition as $key => $value) { | |
| 2069 | + if (!in_array($key, $allowedColumns, true)) { | |
| 2070 | + continue; | |
| 2071 | + } | |
| 2072 | + | |
| 2073 | + if (is_array($value)) { | |
| 2074 | + // Never honour a request-supplied raw SQL fragment. | |
| 2075 | + unset($value['raw']); | |
| 2076 | + | |
| 2077 | + if (array_key_exists('operator', $value) || array_key_exists('value', $value)) { | |
| 2078 | + $operator = isset($value['operator']) && is_string($value['operator']) | |
| 2079 | + ? strtoupper(trim($value['operator'])) | |
| 2080 | + : '='; | |
| 2081 | + if (!in_array($operator, $allowedOperators, true)) { | |
| 2082 | + $operator = '='; | |
| 2083 | + } | |
| 2084 | + $condValue = isset($value['value']) ? $value['value'] : ''; | |
| 2085 | + if (is_scalar($condValue)) { | |
| 2086 | + $safe[$key] = ['operator' => $operator, 'value' => $condValue]; | |
| 2087 | + } | |
| 2088 | + } else { | |
| 2089 | + // IN-style list: keep scalar members only (bound by the Model). | |
| 2090 | + $list = array_values(array_filter($value, 'is_scalar')); | |
| 2091 | + if (!empty($list)) { | |
| 2092 | + $safe[$key] = $list; | |
| 2093 | + } | |
| 2094 | + } | |
| 2095 | + } elseif (is_scalar($value)) { | |
| 2096 | + $safe[$key] = $value; | |
| 2097 | + } | |
| 2098 | + } | |
| 2099 | + | |
| 2100 | + return empty($safe) ? ['form_id' => $id] : $safe; | |
| 2101 | + } | |
| 2102 | + | |
| 2023 | 2103 | public function getFormEntry($Request, $post) |
| 2024 | 2104 | { |
| 2025 | 2105 | if (!empty($Request['id'])) { |
| 2026 | 2106 | $id = absint(wp_unslash($Request['id'])); |
| @@ -2040,8 +2120,9 @@ | ||
| 2040 | 2120 | $pageSize = isset($post->pageSize) ? |
| 2041 | 2121 | wp_unslash($post->pageSize) : 10; |
| 2042 | 2122 | $queryCondition = isset($post->queryCondition) ? wp_unslash($post->queryCondition) : ['form_id' => $id]; |
| 2043 | 2123 | } |
| 2124 | + $queryCondition = $this->sanitizeEntryQueryCondition($queryCondition, $id); | |
| 2044 | 2125 | if (is_null($id)) { |
| 2045 | 2126 | return new WP_Error('empty_form', __('Form id is empty.', 'bit-form')); |
| 2046 | 2127 | } |
| 2047 | 2128 | $formManager = new AdminFormManager($id); |
| @@ -2410,10 +2491,15 @@ | ||
| 2410 | 2491 | $layout = $formContent->layout; |
| 2411 | 2492 | foreach ($form_fields as $key => $value) { |
| 2412 | 2493 | // $field_name = preg_replace('/[\`\~\!\@\#\$\'\.\s\?\+\-\*\&\|\/\\!]/', '_', $value->lbl); |
| 2413 | 2494 | if (isset($entries[$key])) { |
| 2414 | - $form_fields->{$key}->val = $entries[$key]; | |
| 2495 | + $isPassword = isset($value->typ) && 'password' === $value->typ; | |
| 2496 | + // Never hand a stored password back to the browser; blank keeps it on update. | |
| 2497 | + $form_fields->{$key}->val = $isPassword ? '' : $entries[$key]; | |
| 2415 | 2498 | $form_fields->{$key}->name = $key; |
| 2499 | + if ($isPassword && '' !== (string) $entries[$key] && isset($value->valid) && is_object($value->valid)) { | |
| 2500 | + $form_fields->{$key}->valid->req = false; | |
| 2501 | + } | |
| 2416 | 2502 | } |
| 2417 | 2503 | } |
| 2418 | 2504 | $workFlowRunHelper = new WorkFlow($formID); |
| 2419 | 2505 | $workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad( |