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 +213 -65 2.03.3.1 View file →
@@ -1,86 +1,193 @@
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 -final class Actions {
9 +final class Actions
10 +{
11 11 private static $_formID;
12 12
13 - public function __construct($formId) {
13 + public function __construct($formId)
14 + {
14 15 static::$_formID = $formId;
15 16 }
16 17
17 - public function setValue($actionDetail, $fieldData, $fields) {
18 - if (!empty($actionDetail->val)) {
19 - $actionValue = '';
20 - $fieldType = $fields->{$fieldData[$actionDetail->field]['key']}->typ;
21 - $evalMathExpr = preg_match('/month|date/', $fieldType);
22 - $fields->{$fieldData[$actionDetail->field]['key']}->val = '';
23 - $actionValue = Helper::replaceFieldWithValue($actionDetail->val, $fieldData, !(bool)$evalMathExpr);
24 - $fields->{$fieldData[$actionDetail->field]['key']}->val = $actionValue;
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 +
46 + public function setValue($actionDetail, $fieldData, $fields)
47 + {
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;
25 55 $fieldData[$actionDetail->field]['value'] = $actionValue;
26 56 }
27 57 return [$fields, $fieldData];
28 58 }
29 59
30 - public function show($fields, $fieldData, $actionDetail) {
31 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->hide = false;
32 - if ('hidden' === $fields->{$fieldData[$actionDetail->field]['key']}->typ) {
33 - $fields->{$fieldData[$actionDetail->field]['key']}->typ = 'text';
60 + public function getActionValue($actionDetail, $fieldData, $fields)
61 + {
62 + $actionValue = '';
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);
34 69 }
70 + return $actionValue;
35 71 }
36 72
37 - public function setFieldProperty($actions, $fieldData, $fields) {
73 + public function getActiveListIndex($actionDetail, $fieldData, $fields)
74 + {
75 + $activeList = $this->getActionValue($actionDetail, $fieldData, $fields);
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 + }
85 + foreach ($optionsList as $key => $optionObj) {
86 + $valueArr = (array) $optionObj;
87 + if (empty($valueArr)) {
88 + continue;
89 + }
90 + $listName = array_keys($valueArr)[0];
91 + if ($listName === $activeList) {
92 + $activeListIndex = $key;
93 + break;
94 + }
95 + }
96 +
97 + return $activeListIndex;
98 + }
99 +
100 + public function show($fields, $fieldData, $actionDetail)
101 + {
102 + $fk = self::resolveFieldKey($actionDetail, $fieldData);
103 + if (null === $fk || !isset($fields->{$fk})) {
104 + return;
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 + }
110 + }
111 +
112 + public function setFieldProperty($actions, $fieldData, $fields)
113 + {
38 114 if (empty($actions)) {
39 - return $fields;
115 + return [$fields, $fieldData];
40 116 }
41 117 foreach ($actions as $actionDetail) {
42 - if (!empty($actionDetail->action) && !empty($actionDetail->field)) {
43 - switch ($actionDetail->action) {
44 - case 'value':
45 - $data = $this->setValue($actionDetail, $fieldData, $fields);
46 - $fields = $data[0];
47 - $fieldData = $data[1];
48 - break;
49 - case 'hide':
50 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->hide = true;
51 - break;
52 - case 'disable':
53 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->disabled = true;
54 - break;
55 - case 'show':
56 - $this->show($fields, $fieldData, $actionDetail);
57 - break;
58 - case 'enable':
59 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->disabled = false;
60 - break;
61 - case 'readonly':
62 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->readonly = true;
63 - break;
64 - case 'required':
65 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->required = true;
66 - break;
67 - case 'limit':
68 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->limit = true;
69 - break;
70 - case 'min':
71 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->min = true;
72 - break;
73 - case 'max':
74 - $fields->{$fieldData[$actionDetail->field]['key']}->valid->max = true;
75 - break;
76 - }
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;
77 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 + }
78 184 }
79 185 return [$fields, $fieldData];
80 186 }
81 187
82 - public function setOnSubmitSetFieldValue($actions, $fieldValue) {
188 + public function setOnSubmitSetFieldValue($actions, $fieldValue)
189 + {
83 190 if (empty($actions)) {
84 191 return $fieldValue;
85 192 }
86 193 foreach ($actions as $actionDetail) {
@@ -98,9 +205,10 @@
98 205 }
99 206 return $fieldValue;
100 207 }
101 208
102 - public function setOnFormSuccess($data, $actions, $fieldValue) {
209 + public function setOnFormSuccess($data, $actions, $fieldValue, $entryID)
210 + {
103 211 if (empty($actions)) {
104 212 return $data;
105 213 }
106 214
@@ -108,9 +216,9 @@
108 216 $id = $successActionDetail->details->id;
109 217 if (!empty($id)) {
110 218 switch ($successActionDetail->type) {
111 219 case 'successMsg':
112 - $data['workFlowReturnable'] = $this->confirmationMessage($data['workFlowReturnable'], $id, $fieldValue);
220 + $data['workFlowReturnable'] = $this->confirmationMessage($data['workFlowReturnable'], $id, $fieldValue, $entryID);
113 221 break;
114 222 case 'redirectPage':
115 223 $data['workFlowReturnable'] = $this->redirectPage($data['workFlowReturnable'], $id, $fieldValue);
116 224 break;
@@ -136,30 +244,70 @@
136 244 }
137 245 return $data;
138 246 }
139 247
140 - public function confirmationMessage($workFlowReturnable, $successActionDetailId, $fieldValue) {
141 - $id = json_decode($successActionDetailId)->id;
248 + public function confirmationMessage($workFlowReturnable, $successActionDetailId, $fieldValue, $entryID = null)
249 + {
250 + $id = Utilities::jsonObj($successActionDetailId)->id ?? null;
142 251 $messageHandler = new SuccessMessageHandler(static::$_formID);
143 252 $message = $messageHandler->getAMessage($id);
144 253 if (!is_wp_error($message) && !empty($message)) {
145 - $workFlowReturnable['message'] = Helper::replaceFieldWithValue($message[0]->message_content, $fieldValue);
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'])) {
276 + $workFlowReturnable['message'] = do_shortcode($workFlowReturnable['message']);
277 + }
146 278 $workFlowReturnable['msg_id'] = $message[0]->id;
147 - $msgConfig = json_decode($message[0]->message_config);
148 - if ($msgConfig->autoHide) {
149 - $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);
150 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 + }
151 287 }
152 288 return $workFlowReturnable;
153 289 }
154 290
155 - public function redirectPage($workFlowReturnable, $successActionDetailId, $fieldValue) {
156 - $id = json_decode($successActionDetailId)->id;
291 + public function redirectPage($workFlowReturnable, $successActionDetailId, $fieldValue)
292 + {
293 + $id = Utilities::jsonObj($successActionDetailId)->id ?? null;
157 294 $integrationHandler = new IntegrationHandler(static::$_formID);
158 295 $redirectPage = $integrationHandler->getAIntegration($id, 'form', 'redirectPage');
159 296 if (!is_wp_error($redirectPage) && !empty($redirectPage)) {
160 - $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 ?? '';
161 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 + );
162 310 $url = Helper::replaceFieldWithValue($url, $fieldValue);
163 311 }
164 312 $workFlowReturnable['redirectPage'] = empty($url) ? false : esc_url_raw($url);
165 313 }