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 +159 -85 V3.0.33.3.1 View file →
@@ -1,12 +1,11 @@
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;
7 +use BitCode\BitForm\Core\Util\Utilities;
9 8
10 9 final class Actions
11 10 {
12 11 private static $_formID;
@@ -15,17 +14,45 @@
15 14 {
16 15 static::$_formID = $formId;
17 16 }
18 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 +
19 46 public function setValue($actionDetail, $fieldData, $fields)
20 47 {
21 - if (!empty($actionDetail->val)) {
22 - $actionValue = '';
23 - $fieldType = $fields->{$fieldData[$actionDetail->field]['key']}->typ;
24 - $evalMathExpr = preg_match('/month|date/', $fieldType);
25 - $fields->{$fieldData[$actionDetail->field]['key']}->val = '';
26 - $actionValue = Helper::replaceFieldWithValue($actionDetail->val, $fieldData, !(bool)$evalMathExpr);
27 - $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;
28 55 $fieldData[$actionDetail->field]['value'] = $actionValue;
29 56 }
30 57 return [$fields, $fieldData];
31 58 }
@@ -32,13 +59,14 @@
32 59
33 60 public function getActionValue($actionDetail, $fieldData, $fields)
34 61 {
35 62 $actionValue = '';
36 - if (!empty($actionDetail->val)) {
37 - $fieldType = $fields->{$fieldData[$actionDetail->field]['key']}->typ;
38 - $evalMathExpr = preg_match('/month|date/', $fieldType);
39 - $fields->{$fieldData[$actionDetail->field]['key']}->val = '';
40 - $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);
41 69 }
42 70 return $actionValue;
43 71 }
44 72
@@ -44,12 +72,22 @@
44 72
45 73 public function getActiveListIndex($actionDetail, $fieldData, $fields)
46 74 {
47 75 $activeList = $this->getActionValue($actionDetail, $fieldData, $fields);
48 - $optionsList = $fields->{$fieldData[$actionDetail->field]['key']}->optionsList;
49 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 + }
50 85 foreach ($optionsList as $key => $optionObj) {
51 86 $valueArr = (array) $optionObj;
87 + if (empty($valueArr)) {
88 + continue;
89 + }
52 90 $listName = array_keys($valueArr)[0];
53 91 if ($listName === $activeList) {
54 92 $activeListIndex = $key;
55 93 break;
@@ -60,12 +98,16 @@
60 98 }
61 99
62 100 public function show($fields, $fieldData, $actionDetail)
63 101 {
64 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->hide = false;
65 - if ('hidden' === $fields->{$fieldData[$actionDetail->field]['key']}->typ) {
66 - $fields->{$fieldData[$actionDetail->field]['key']}->typ = 'text';
102 + $fk = self::resolveFieldKey($actionDetail, $fieldData);
103 + if (null === $fk || !isset($fields->{$fk})) {
104 + return;
67 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 + }
68 110 }
69 111
70 112 public function setFieldProperty($actions, $fieldData, $fields)
71 113 {
@@ -72,68 +114,74 @@
72 114 if (empty($actions)) {
73 115 return [$fields, $fieldData];
74 116 }
75 117 foreach ($actions as $actionDetail) {
76 - if (!empty($actionDetail->action) && !empty($actionDetail->field) && isset($fields->{$fieldData[$actionDetail->field]['key']}) && !empty($fields->{$fieldData[$actionDetail->field]['key']})) {
77 - $fk = $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->{$fk}->valid->hide = true;
86 - Helper::setNestedProperty($fields, "{$fk}->valid->hide", true);
87 - break;
88 - case 'disable':
89 - $fields->{$fk}->valid->disabled = true;
90 - break;
91 - case 'show':
92 - $this->show($fields, $fieldData, $actionDetail);
93 - break;
94 - case 'enable':
95 - $fields->{$fk}->valid->disabled = false;
96 - break;
97 - case 'readonly':
98 - $fields->{$fk}->valid->readonly = true;
99 - break;
100 - case 'writeable':
101 - $fields->{$fk}->valid->readonly = false;
102 - break;
103 - case 'required':
104 - $fields->{$fk}->valid->required = true;
105 - break;
106 - case 'limit':
107 - $fields->{$fk}->valid->limit = true;
108 - break;
109 - case 'min':
110 - $fields->{$fk}->valid->min = true;
111 - break;
112 - case 'max':
113 - $fields->{$fk}->valid->max = true;
114 - break;
115 - case 'activelist':
116 - $fields->{$fk}->config->activeList = $this->getActiveListIndex($actionDetail, $fieldData, $fields);
117 - break;
118 - case 'lbl':
119 - case 'ct':
120 - $fields->{$fk}->lbl = $this->getActionValue($actionDetail, $fieldData, $fields);
121 - break;
122 - case 'sub-titl':
123 - $fields->{$fk}->subtitle = $this->getActionValue($actionDetail, $fieldData, $fields);
124 - break;
125 - case 'hlp-txt':
126 - $fields->{$fk}->helperTxt = $this->getActionValue($actionDetail, $fieldData, $fields);
127 - break;
128 - case 'placeholder':
129 - $fields->{$fk}->ph = $this->getActionValue($actionDetail, $fieldData, $fields);
130 - break;
131 - case 'title':
132 - $fields->{$fk}->title = $this->getActionValue($actionDetail, $fieldData, $fields);
133 - break;
134 - }
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;
135 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 + }
136 184 }
137 185 return [$fields, $fieldData];
138 186 }
139 187
@@ -198,13 +246,24 @@
198 246 }
199 247
200 248 public function confirmationMessage($workFlowReturnable, $successActionDetailId, $fieldValue, $entryID = null)
201 249 {
202 - $id = json_decode($successActionDetailId)->id;
250 + $id = Utilities::jsonObj($successActionDetailId)->id ?? null;
203 251 $messageHandler = new SuccessMessageHandler(static::$_formID);
204 252 $message = $messageHandler->getAMessage($id);
205 253 if (!is_wp_error($message) && !empty($message)) {
206 - $messageContent = $message[0]->message_content;
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 + );
207 266
208 267 // replace pdf link and password
209 268 if (class_exists('\BitCode\BitFormPro\Admin\DownloadFile') && !empty($entryID)) {
210 269 $downloadFile = new \BitCode\BitFormPro\Admin\DownloadFile();
@@ -211,17 +270,21 @@
211 270 $messageContent = $downloadFile->replacePdfShortCodeToLink($messageContent, static::$_formID, $entryID);
212 271 $messageContent = $downloadFile->replaceShortCodeToPdfPassword($messageContent, static::$_formID, $entryID);
213 272 }
214 273
215 - $workFlowReturnable['message'] = Helper::replaceFieldWithValue($messageContent, $fieldValue, true, static::$_formID);
274 + $workFlowReturnable['message'] = Helper::replaceFieldWithValue($messageContent, $fieldValue, true, static::$_formID, true);
216 275 if (!empty($workFlowReturnable['message'])) {
217 276 $workFlowReturnable['message'] = do_shortcode($workFlowReturnable['message']);
218 277 }
219 278 $workFlowReturnable['msg_id'] = $message[0]->id;
220 - $msgConfig = json_decode($message[0]->message_config);
221 - if ($msgConfig->autoHide) {
222 - $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);
223 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 + }
224 287 }
225 288 return $workFlowReturnable;
226 289 }
227 290
@@ -226,14 +289,25 @@
226 289 }
227 290
228 291 public function redirectPage($workFlowReturnable, $successActionDetailId, $fieldValue)
229 292 {
230 - $id = json_decode($successActionDetailId)->id;
293 + $id = Utilities::jsonObj($successActionDetailId)->id ?? null;
231 294 $integrationHandler = new IntegrationHandler(static::$_formID);
232 295 $redirectPage = $integrationHandler->getAIntegration($id, 'form', 'redirectPage');
233 296 if (!is_wp_error($redirectPage) && !empty($redirectPage)) {
234 - $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 ?? '';
235 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 + );
236 310 $url = Helper::replaceFieldWithValue($url, $fieldValue);
237 311 }
238 312 $workFlowReturnable['redirectPage'] = empty($url) ? false : esc_url_raw($url);
239 313 }