PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.3.1
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.3.1
3.3.1 V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 All 138 releases
← All changes | includes/Core/WorkFlow/Actions.php +170 -86 2.10.13.3.1 View file →
@@ -1,8 +1,6 @@
1 1 <?php
2 2
3 -?><?php
4 -
5 3 namespace BitCode\BitForm\Core\WorkFlow;
6 4
7 5 use BitCode\BitForm\Core\Integration\IntegrationHandler;
8 6 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
@@ -16,17 +14,45 @@
16 14 {
17 15 static::$_formID = $formId;
18 16 }
19 17
18 + /**
19 + * Resolve the field key a workflow action points at.
20 + *
21 + * Both arguments come straight out of stored workflow/form JSON, so neither shape is guaranteed
22 + * — hence the runtime checks rather than type hints.
23 + *
24 + * @param mixed $actionDetail one action row decoded from the workflow JSON
25 + * @param mixed $fieldData field map keyed by field name
26 + *
27 + * @return string|null null when the action points at a field the form no longer has
28 + */
29 + private static function resolveFieldKey($actionDetail, $fieldData)
30 + {
31 + if (!is_object($actionDetail) || !isset($actionDetail->field) || !\is_array($fieldData)) {
32 + return null;
33 + }
34 + $field = $actionDetail->field;
35 + if (!\is_string($field) && !\is_int($field)) {
36 + return null;
37 + }
38 + if (!isset($fieldData[$field]) || !\is_array($fieldData[$field]) || !isset($fieldData[$field]['key'])) {
39 + return null;
40 + }
41 + $key = $fieldData[$field]['key'];
42 +
43 + return \is_string($key) || \is_int($key) ? (string) $key : null;
44 + }
45 +
20 46 public function setValue($actionDetail, $fieldData, $fields)
21 47 {
22 - if (!empty($actionDetail->val)) {
23 - $actionValue = '';
24 - $fieldType = $fields->{$fieldData[$actionDetail->field]['key']}->typ;
25 - $evalMathExpr = preg_match('/month|date/', $fieldType);
26 - $fields->{$fieldData[$actionDetail->field]['key']}->val = '';
27 - $actionValue = Helper::replaceFieldWithValue($actionDetail->val, $fieldData, !(bool)$evalMathExpr);
28 - $fields->{$fieldData[$actionDetail->field]['key']}->val = $actionValue;
48 + $fk = self::resolveFieldKey($actionDetail, $fieldData);
49 + if (null !== $fk && isset($fields->{$fk}) && !empty($actionDetail->val)) {
50 + $fieldType = isset($fields->{$fk}->typ) ? $fields->{$fk}->typ : '';
51 + $evalMathExpr = preg_match('/month|date/', (string) $fieldType);
52 + $fields->{$fk}->val = '';
53 + $actionValue = Helper::replaceFieldWithValue($actionDetail->val, $fieldData, !(bool) $evalMathExpr);
54 + $fields->{$fk}->val = $actionValue;
29 55 $fieldData[$actionDetail->field]['value'] = $actionValue;
30 56 }
31 57 return [$fields, $fieldData];
32 58 }
@@ -33,13 +59,14 @@
33 59
34 60 public function getActionValue($actionDetail, $fieldData, $fields)
35 61 {
36 62 $actionValue = '';
37 - if (!empty($actionDetail->val)) {
38 - $fieldType = $fields->{$fieldData[$actionDetail->field]['key']}->typ;
39 - $evalMathExpr = preg_match('/month|date/', $fieldType);
40 - $fields->{$fieldData[$actionDetail->field]['key']}->val = '';
41 - $actionValue = Helper::replaceFieldWithValue($actionDetail->val, $fieldData, !(bool)$evalMathExpr);
63 + $fk = self::resolveFieldKey($actionDetail, $fieldData);
64 + if (null !== $fk && isset($fields->{$fk}) && !empty($actionDetail->val)) {
65 + $fieldType = isset($fields->{$fk}->typ) ? $fields->{$fk}->typ : '';
66 + $evalMathExpr = preg_match('/month|date/', (string) $fieldType);
67 + $fields->{$fk}->val = '';
68 + $actionValue = Helper::replaceFieldWithValue($actionDetail->val, $fieldData, !(bool) $evalMathExpr);
42 69 }
43 70 return $actionValue;
44 71 }
45 72
@@ -45,12 +72,22 @@
45 72
46 73 public function getActiveListIndex($actionDetail, $fieldData, $fields)
47 74 {
48 75 $activeList = $this->getActionValue($actionDetail, $fieldData, $fields);
49 - $optionsList = $fields->{$fieldData[$actionDetail->field]['key']}->optionsList;
50 76 $activeListIndex = 0;
77 + $fk = self::resolveFieldKey($actionDetail, $fieldData);
78 + if (null === $fk || !isset($fields->{$fk}->optionsList)) {
79 + return $activeListIndex;
80 + }
81 + $optionsList = $fields->{$fk}->optionsList;
82 + if (!\is_array($optionsList) && !\is_object($optionsList)) {
83 + return $activeListIndex;
84 + }
51 85 foreach ($optionsList as $key => $optionObj) {
52 86 $valueArr = (array) $optionObj;
87 + if (empty($valueArr)) {
88 + continue;
89 + }
53 90 $listName = array_keys($valueArr)[0];
54 91 if ($listName === $activeList) {
55 92 $activeListIndex = $key;
56 93 break;
@@ -61,12 +98,16 @@
61 98 }
62 99
63 100 public function show($fields, $fieldData, $actionDetail)
64 101 {
65 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->hide = false;
66 - if ('hidden' === $fields->{$fieldData[$actionDetail->field]['key']}->typ) {
67 - $fields->{$fieldData[$actionDetail->field]['key']}->typ = 'text';
102 + $fk = self::resolveFieldKey($actionDetail, $fieldData);
103 + if (null === $fk || !isset($fields->{$fk})) {
104 + return;
68 105 }
106 + Helper::setNestedProperty($fields, "{$fk}->valid->hide", false);
107 + if (isset($fields->{$fk}->typ) && 'hidden' === $fields->{$fk}->typ) {
108 + $fields->{$fk}->typ = 'text';
109 + }
69 110 }
70 111
71 112 public function setFieldProperty($actions, $fieldData, $fields)
72 113 {
@@ -73,66 +114,74 @@
73 114 if (empty($actions)) {
74 115 return [$fields, $fieldData];
75 116 }
76 117 foreach ($actions as $actionDetail) {
77 - if (!empty($actionDetail->action) && !empty($actionDetail->field) && !empty($fields->{$fieldData[$actionDetail->field]['key']})) {
78 - switch ($actionDetail->action) {
79 - case 'value':
80 - $data = $this->setValue($actionDetail, $fieldData, $fields);
81 - $fields = $data[0];
82 - $fieldData = $data[1];
83 - break;
84 - case 'hide':
85 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->hide = true;
86 - break;
87 - case 'disable':
88 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->disabled = true;
89 - break;
90 - case 'show':
91 - $this->show($fields, $fieldData, $actionDetail);
92 - break;
93 - case 'enable':
94 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->disabled = false;
95 - break;
96 - case 'readonly':
97 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->readonly = true;
98 - break;
99 - case 'writeable':
100 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->readonly = false;
101 - break;
102 - case 'required':
103 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->required = true;
104 - break;
105 - case 'limit':
106 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->limit = true;
107 - break;
108 - case 'min':
109 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->min = true;
110 - break;
111 - case 'max':
112 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->max = true;
113 - break;
114 - case 'activelist':
115 - $fields->{$fieldData[$actionDetail->field]['key']}->config->activeList = $this->getActiveListIndex($actionDetail, $fieldData, $fields);
116 - break;
117 - case 'lbl':
118 - case 'ct':
119 - $fields->{$fieldData[$actionDetail->field]['key']}->lbl = $this->getActionValue($actionDetail, $fieldData, $fields);
120 - break;
121 - case 'sub-titl':
122 - $fields->{$fieldData[$actionDetail->field]['key']}->subtitle = $this->getActionValue($actionDetail, $fieldData, $fields);
123 - break;
124 - case 'hlp-txt':
125 - $fields->{$fieldData[$actionDetail->field]['key']}->helperTxt = $this->getActionValue($actionDetail, $fieldData, $fields);
126 - break;
127 - case 'placeholder':
128 - $fields->{$fieldData[$actionDetail->field]['key']}->ph = $this->getActionValue($actionDetail, $fieldData, $fields);
129 - break;
130 - case 'title':
131 - $fields->{$fieldData[$actionDetail->field]['key']}->title = $this->getActionValue($actionDetail, $fieldData, $fields);
132 - break;
133 - }
118 + // resolveFieldKey() must run *before* $fields is indexed: the old condition built the
119 + // dynamic property name from $fieldData[...]['key'] inside its own isset(), so the missing
120 + // key warned before isset() ever got a chance to short-circuit.
121 + $fk = self::resolveFieldKey($actionDetail, $fieldData);
122 + if (null === $fk || empty($actionDetail->action) || !isset($fields->{$fk}) || empty($fields->{$fk})) {
123 + continue;
134 124 }
125 + switch ($actionDetail->action) {
126 + case 'value':
127 + $data = $this->setValue($actionDetail, $fieldData, $fields);
128 + $fields = $data[0];
129 + $fieldData = $data[1];
130 + break;
131 + case 'hide':
132 + Helper::setNestedProperty($fields, "{$fk}->valid->hide", true);
133 + break;
134 + case 'disable':
135 + Helper::setNestedProperty($fields, "{$fk}->valid->disabled", true);
136 + break;
137 + case 'show':
138 + $this->show($fields, $fieldData, $actionDetail);
139 + break;
140 + case 'enable':
141 + Helper::setNestedProperty($fields, "{$fk}->valid->disabled", false);
142 + break;
143 + case 'readonly':
144 + Helper::setNestedProperty($fields, "{$fk}->valid->readonly", true);
145 + break;
146 + case 'writeable':
147 + Helper::setNestedProperty($fields, "{$fk}->valid->readonly", false);
148 + break;
149 + case 'required':
150 + Helper::setNestedProperty($fields, "{$fk}->valid->required", true);
151 + break;
152 + case 'limit':
153 + Helper::setNestedProperty($fields, "{$fk}->valid->limit", true);
154 + break;
155 + case 'min':
156 + Helper::setNestedProperty($fields, "{$fk}->valid->min", true);
157 + break;
158 + case 'max':
159 + Helper::setNestedProperty($fields, "{$fk}->valid->max", true);
160 + break;
161 + case 'activelist':
162 + // setNestedProperty() creates the intermediate `valid`/`config` object when a legacy
163 + // field JSON does not carry one; the direct writes here used to auto-vivify it and
164 + // emit an undefined-property warning on every run.
165 + Helper::setNestedProperty($fields, "{$fk}->config->activeList", $this->getActiveListIndex($actionDetail, $fieldData, $fields));
166 + break;
167 + case 'lbl':
168 + case 'ct':
169 + $fields->{$fk}->lbl = $this->getActionValue($actionDetail, $fieldData, $fields);
170 + break;
171 + case 'sub-titl':
172 + $fields->{$fk}->subtitle = $this->getActionValue($actionDetail, $fieldData, $fields);
173 + break;
174 + case 'hlp-txt':
175 + $fields->{$fk}->helperTxt = $this->getActionValue($actionDetail, $fieldData, $fields);
176 + break;
177 + case 'placeholder':
178 + $fields->{$fk}->ph = $this->getActionValue($actionDetail, $fieldData, $fields);
179 + break;
180 + case 'title':
181 + $fields->{$fk}->title = $this->getActionValue($actionDetail, $fieldData, $fields);
182 + break;
183 + }
135 184 }
136 185 return [$fields, $fieldData];
137 186 }
138 187
@@ -156,9 +205,9 @@
156 205 }
157 206 return $fieldValue;
158 207 }
159 208
160 - public function setOnFormSuccess($data, $actions, $fieldValue)
209 + public function setOnFormSuccess($data, $actions, $fieldValue, $entryID)
161 210 {
162 211 if (empty($actions)) {
163 212 return $data;
164 213 }
@@ -167,9 +216,9 @@
167 216 $id = $successActionDetail->details->id;
168 217 if (!empty($id)) {
169 218 switch ($successActionDetail->type) {
170 219 case 'successMsg':
171 - $data['workFlowReturnable'] = $this->confirmationMessage($data['workFlowReturnable'], $id, $fieldValue);
220 + $data['workFlowReturnable'] = $this->confirmationMessage($data['workFlowReturnable'], $id, $fieldValue, $entryID);
172 221 break;
173 222 case 'redirectPage':
174 223 $data['workFlowReturnable'] = $this->redirectPage($data['workFlowReturnable'], $id, $fieldValue);
175 224 break;
@@ -195,23 +244,47 @@
195 244 }
196 245 return $data;
197 246 }
198 247
199 - public function confirmationMessage($workFlowReturnable, $successActionDetailId, $fieldValue)
248 + public function confirmationMessage($workFlowReturnable, $successActionDetailId, $fieldValue, $entryID = null)
200 249 {
201 - $id = json_decode($successActionDetailId)->id;
250 + $id = Utilities::jsonObj($successActionDetailId)->id ?? null;
202 251 $messageHandler = new SuccessMessageHandler(static::$_formID);
203 252 $message = $messageHandler->getAMessage($id);
204 253 if (!is_wp_error($message) && !empty($message)) {
205 - $workFlowReturnable['message'] = Helper::replaceFieldWithValue($message[0]->message_content, $fieldValue);
206 - if (!empty($workFlowReturnable['message']) && Utilities::isPro()) {
254 + $msgConfig = Utilities::jsonObj($message[0]->message_config ?? '');
255 + // Honor enable/disable: a disabled confirmation message is skipped so the default confirmation applies.
256 + if (isset($msgConfig->status) && empty($msgConfig->status)) {
257 + return $workFlowReturnable;
258 + }
259 + // Translate before smart-tag replacement, so lookups hit the stored text.
260 + $messageContent = (string) apply_filters(
261 + 'bitform_translate_form_string',
262 + (string) $message[0]->message_content,
263 + 'msg-content-' . $message[0]->id,
264 + static::$_formID
265 + );
266 +
267 + // replace pdf link and password
268 + if (class_exists('\BitCode\BitFormPro\Admin\DownloadFile') && !empty($entryID)) {
269 + $downloadFile = new \BitCode\BitFormPro\Admin\DownloadFile();
270 + $messageContent = $downloadFile->replacePdfShortCodeToLink($messageContent, static::$_formID, $entryID);
271 + $messageContent = $downloadFile->replaceShortCodeToPdfPassword($messageContent, static::$_formID, $entryID);
272 + }
273 +
274 + $workFlowReturnable['message'] = Helper::replaceFieldWithValue($messageContent, $fieldValue, true, static::$_formID, true);
275 + if (!empty($workFlowReturnable['message'])) {
207 276 $workFlowReturnable['message'] = do_shortcode($workFlowReturnable['message']);
208 277 }
209 278 $workFlowReturnable['msg_id'] = $message[0]->id;
210 - $msgConfig = json_decode($message[0]->message_config);
211 - if ($msgConfig->autoHide) {
212 - $workFlowReturnable['msg_duration'] = abs(floatval($msgConfig->duration) * 1000);
279 + $msgConfig = Utilities::jsonObj($message[0]->message_config ?? '');
280 + if (!empty($msgConfig->autoHide)) {
281 + $workFlowReturnable['msg_duration'] = abs(floatval($msgConfig->duration ?? 0) * 1000);
213 282 }
283 + // expose the form's after-submission behaviour ('reset' | 'hide' | 'keep') to the frontend
284 + if (isset($msgConfig->afterSubmit)) {
285 + $workFlowReturnable['afterSubmit'] = $msgConfig->afterSubmit;
286 + }
214 287 }
215 288 return $workFlowReturnable;
216 289 }
217 290
@@ -216,14 +289,25 @@
216 289 }
217 290
218 291 public function redirectPage($workFlowReturnable, $successActionDetailId, $fieldValue)
219 292 {
220 - $id = json_decode($successActionDetailId)->id;
293 + $id = Utilities::jsonObj($successActionDetailId)->id ?? null;
221 294 $integrationHandler = new IntegrationHandler(static::$_formID);
222 295 $redirectPage = $integrationHandler->getAIntegration($id, 'form', 'redirectPage');
223 296 if (!is_wp_error($redirectPage) && !empty($redirectPage)) {
224 - $url = json_decode($redirectPage[0]->integration_details)->url;
297 + // Honor enable/disable: a disabled redirect is skipped.
298 + if (isset($redirectPage[0]->status) && empty($redirectPage[0]->status)) {
299 + return $workFlowReturnable;
300 + }
301 + $url = Utilities::jsonObj($redirectPage[0]->integration_details ?? '')->url ?? '';
225 302 if (!empty($url)) {
303 + // Translated before smart-tag replacement: per-language redirect targets.
304 + $url = (string) apply_filters(
305 + 'bitform_translate_form_string',
306 + (string) $url,
307 + 'redirect-url-' . $redirectPage[0]->id,
308 + static::$_formID
309 + );
226 310 $url = Helper::replaceFieldWithValue($url, $fieldValue);
227 311 }
228 312 $workFlowReturnable['redirectPage'] = empty($url) ? false : esc_url_raw($url);
229 313 }