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