PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.15.3
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.15.3
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/Helper.php +46 -334 3.3.12.15.3 View file →
@@ -2,13 +2,11 @@
2 2
3 3 namespace BitCode\BitForm\Core\WorkFlow;
4 4
5 5 use BitCode\BitForm\Core\Integration\IntegrationHandler;
6 -use BitCode\BitForm\Core\Messages\EmailTemplateHandler;
7 6 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
8 7 use BitCode\BitForm\Core\Util\FieldValueHandler;
9 8 use BitCode\BitForm\Core\Util\SmartTags;
10 -use BitCode\BitForm\Core\Util\Utilities;
11 9
12 10 final class Helper
13 11 {
14 12 public static function getFieldData($fields)
@@ -15,12 +13,11 @@
15 13 {
16 14 $fieldData = [];
17 15
18 16 foreach ($fields as $fieldKey => $fieldDetail) {
19 - $value = isset($fieldDetail->val) ? $fieldDetail->val : (isset($fieldDetail->defaultValue) ? $fieldDetail->defaultValue : '');
20 17 $fieldData[$fieldKey] = [
21 18 'key' => $fieldKey,
22 - 'value' => $value,
19 + 'value' => empty($fieldDetail->val) ? '' : $fieldDetail->val,
23 20 'type' => $fieldDetail->typ,
24 21 ];
25 22 if (isset($fieldDetail->mul)) {
26 23 $fieldData[$fieldKey] =
@@ -57,11 +54,11 @@
57 54 }
58 55 return $fieldData;
59 56 }
60 57
61 - public static function replaceFieldWithValue($stringToReplaceField, $fieldValues, $evalMathExpr = true, $formID = null, $stripShortcodesFromValues = false)
58 + public static function replaceFieldWithValue($stringToReplaceField, $fieldValues, $evalMathExpr = true)
62 59 {
63 - $stringToReplaceField = FieldValueHandler::replaceFieldWithValue($stringToReplaceField, $fieldValues, $formID, $stripShortcodesFromValues);
60 + $stringToReplaceField = FieldValueHandler::replaceFieldWithValue($stringToReplaceField, $fieldValues);
64 61 if ($evalMathExpr) {
65 62 return self::evalMathExpression($stringToReplaceField);
66 63 }
67 64 return $stringToReplaceField;
@@ -66,74 +63,42 @@
66 63 }
67 64 return $stringToReplaceField;
68 65 }
69 66
70 - public static function setDefaultSubmitConfirmation($confirmationType, $fieldValue, $formId, $logID = 0, $workFlowRun = null)
67 + public static function setDefaultSubmitConfirmation($confirmationType, $fieldValue, $formId, $logID = 0)
71 68 {
72 69 $messageId = 0;
73 70 $returnableData = null;
74 - $afterSubmit = null;
75 - $msgDuration = null;
71 + return [
72 + 'msg_id' => $messageId,
73 + 'confirmation' => $returnableData,
74 + ];
76 75 $integrationHandler = new IntegrationHandler($formId);
77 - $conditionalActionIds = self::getDefaultSubmitExcludedActionIds($formId, $workFlowRun);
78 76 switch ($confirmationType) {
79 77 case 'successMsg':
80 - // Standalone confirmation: show the first ENABLED success message without requiring a workflow.
81 - $successMessageHandler = new SuccessMessageHandler($formId);
78 + $successMessageHandler
79 + = new SuccessMessageHandler($formId);
82 80 $successMessage = $successMessageHandler->getAllMessage();
83 - if (!is_wp_error($successMessage) && !empty($successMessage)) {
84 - foreach ($successMessage as $msg) {
85 - $msgConfig = Utilities::jsonObj($msg->message_config);
86 - if (
87 - isset($msgConfig->status) && empty($msgConfig->status)
88 - || isset($conditionalActionIds['successMsg'][(string) $msg->id])
89 - ) {
90 - continue;
91 - }
92 - // Translate before smart-tag replacement (identity when unhooked).
93 - $messageContent = (string) apply_filters(
94 - 'bitform_translate_form_string',
95 - (string) $msg->message_content,
96 - 'msg-content-' . $msg->id,
97 - $formId
98 - );
99 - $returnableData = self::replaceFieldWithValue($messageContent, $fieldValue, true, null, true);
100 - $messageId = $msg->id;
101 - if (isset($msgConfig->afterSubmit)) {
102 - $afterSubmit = $msgConfig->afterSubmit;
103 - }
104 - if (!empty($msgConfig->autoHide)) {
105 - $msgDuration = abs(floatval($msgConfig->duration ?? 0) * 1000);
106 - }
107 - break;
108 - }
81 + if (!is_wp_error($successMessage) && !empty($successMessage) && count($successMessage) > 0) {
82 + $returnableData = self::replaceFieldWithValue($successMessage[0]->message_content, $fieldValue);
83 + $messageId = $successMessage[0]->id;
109 84 }
110 85 break;
111 86 case 'redirectPage':
112 - $redirectPages = $integrationHandler->getAllIntegration('form', 'redirectPage', 1);
113 - if (!is_wp_error($redirectPages) && !empty($redirectPages)) {
114 - foreach ($redirectPages as $redirectPage) {
115 - if (isset($conditionalActionIds['redirectPage'][(string) $redirectPage->id])) {
116 - continue;
117 - }
118 - $url = Utilities::jsonObj($redirectPage->integration_details ?? '')->url ?? '';
119 - if (!empty($url)) {
120 - // Translated before smart-tag replacement: per-language redirect targets.
121 - $url = (string) apply_filters(
122 - 'bitform_translate_form_string',
123 - (string) $url,
124 - 'redirect-url-' . $redirectPage->id,
125 - $formId
126 - );
127 - $url = self::replaceFieldWithValue($url, $fieldValue);
128 - }
129 - $returnableData = empty($url) ? '' : esc_url_raw($url);
130 - break;
87 + $redirectPage = $integrationHandler->getAllIntegration('form', 'redirectPage');
88 + if (!is_wp_error($redirectPage) && !empty($redirectPage) && count($redirectPage) > 0) {
89 + $url = json_decode($redirectPage[0]->integration_details)->url;
90 + if (!empty($url)) {
91 + $url = self::replaceFieldWithValue($url, $fieldValue);
131 92 }
93 + $returnableData = empty($url) ? '' : esc_url_raw($url);
132 94 }
133 95 break;
134 96 case 'webHooks':
135 - // Default redirect/webhook remain workflow-driven for now (revisited in the Redirect/Integration phases).
97 + $webHooks = $integrationHandler->getAllIntegration('form', 'webHooks');
98 + if (!is_wp_error($webHooks) && !empty($webHooks) && 1 === count($webHooks)) {
99 + $returnableData = ["{\"id\":{$webHooks[0]->id}}"];
100 + }
136 101 break;
137 102 default:
138 103 break;
139 104 }
@@ -140,195 +105,27 @@
140 105
141 106 return [
142 107 'msg_id' => $messageId,
143 108 'confirmation' => $returnableData,
144 - 'afterSubmit' => $afterSubmit,
145 - 'msg_duration' => $msgDuration,
146 109 ];
147 110 }
148 111
149 - private static function getDefaultSubmitExcludedActionIds($formId, $workFlowRun = null)
112 + public static function calculte($firstOperand, $secondOperand, $operator)
150 113 {
151 - $actionIds = [
152 - 'successMsg' => [],
153 - 'redirectPage' => [],
154 - 'mailNotify' => [],
155 - 'integrations' => [],
156 - ];
157 - $filteredActionIds = apply_filters(
158 - 'bitform_default_submit_confirmation_excluded_action_ids',
159 - $actionIds,
160 - $formId,
161 - $workFlowRun
162 - );
163 -
164 - return is_array($filteredActionIds) ? $filteredActionIds : $actionIds;
165 - }
166 -
167 - public static function getDefaultMailNotifications($formId, $workFlowRun = null)
168 - {
169 - $mailData = [];
170 - $emailTemplateHandler = new EmailTemplateHandler($formId);
171 - $templates = $emailTemplateHandler->getAllTemplate();
172 - if (empty($templates) || is_wp_error($templates)) {
173 - return $mailData;
114 + switch ($operator) {
115 + case '+':
116 + return $firstOperand + $secondOperand;
117 + case '-':
118 + return $firstOperand - $secondOperand;
119 + case '*':
120 + return $firstOperand * $secondOperand;
121 + case '/':
122 + return $firstOperand / $secondOperand;
123 + case '^':
124 + return $firstOperand ** $secondOperand;
174 125 }
175 - $excludedActionIds = self::getDefaultSubmitExcludedActionIds($formId, $workFlowRun);
176 - $arrayFields = ['to', 'cc', 'bcc', 'replyto', 'attachment', 'mediaAttachment', 'pdfIds'];
177 - foreach ($templates as $template) {
178 - $status = isset($template->status) ? (int) $template->status : 1;
179 - if (
180 - 1 !== $status
181 - || isset($excludedActionIds['mailNotify'][(string) $template->id])
182 - ) {
183 - continue;
184 - }
185 - $config = json_decode($template->config);
186 - $details = new \stdClass();
187 - $details->id = wp_json_encode(['id' => (string) $template->id]);
188 - foreach (['to', 'from', 'from_name', 'cc', 'bcc', 'replyto', 'attachment', 'mediaAttachment', 'pdfId', 'pdfIds'] as $key) {
189 - $details->$key = isset($config->$key) ? $config->$key : (in_array($key, $arrayFields, true) ? [] : '');
190 - }
191 - $mailData[] = $details;
192 - }
193 - return $mailData;
194 126 }
195 127
196 - public static function getDefaultIntegrations($formId, $workFlowRun = null)
197 - {
198 - $integrationIds = [];
199 - $integrationHandler = new IntegrationHandler($formId);
200 - $allIntegrations = $integrationHandler->getAllIntegration('form', null, 1);
201 - if (empty($allIntegrations) || is_wp_error($allIntegrations)) {
202 - return $integrationIds;
203 - }
204 - $excludedActionIds = self::getDefaultSubmitExcludedActionIds($formId, $workFlowRun);
205 - foreach ($allIntegrations as $integration) {
206 - if (
207 - 'redirectPage' === $integration->integration_type
208 - || isset($excludedActionIds['integrations'][(string) $integration->id])
209 - ) {
210 - continue;
211 - }
212 - // triggerData['integrations'] is a list of GROUPS; each group is an array of JSON id strings
213 - // (Integrations::executeIntegrations requires is_array() with string members). Wrap each id.
214 - $integrationIds[] = [wp_json_encode(['id' => (string) $integration->id])];
215 - }
216 - return $integrationIds;
217 - }
218 -
219 - /**
220 - * Free implementation of the default-submit exclusion filter: any message / redirect / email /
221 - * integration referenced by a classic/basic onsubmit workflow success action is workflow-gated,
222 - * so it must NOT be default-executed (Pro adds the advanced-CL ids separately). Registered in Hooks.
223 - */
224 - public static function workflowReferencedActionIds($actionIds, $formId, $workFlowRun = null)
225 - {
226 - static $cache = [];
227 -
228 - $actionIds = is_array($actionIds) ? $actionIds : [];
229 - foreach (['successMsg', 'redirectPage', 'mailNotify', 'integrations'] as $bucket) {
230 - if (empty($actionIds[$bucket]) || !is_array($actionIds[$bucket])) {
231 - $actionIds[$bucket] = [];
232 - }
233 - }
234 -
235 - $cacheKey = (string) $formId . ':' . (string) $workFlowRun;
236 - if (!isset($cache[$cacheKey])) {
237 - $workFlow = new WorkFlow($formId);
238 - $rows = $workFlow->getWorkFlow(['create_edit', $workFlowRun ?? 'create'], ['onsubmit'], null, 'workflow_order');
239 - $cache[$cacheKey] = self::collectReferencedActionIds(is_wp_error($rows) ? [] : $rows);
240 - }
241 -
242 - foreach ($cache[$cacheKey] as $bucket => $ids) {
243 - $actionIds[$bucket] = $actionIds[$bucket] + $ids;
244 - }
245 - return $actionIds;
246 - }
247 -
248 - /**
249 - * Extract the message / redirect / email / integration ids referenced by workflow-row success
250 - * actions, grouped into the four exclusion buckets. Shared by the runtime default-submit filter
251 - * and the one-time orphan-deactivation migration (SubmitActionFallback).
252 - *
253 - * @param array $rows workflow rows each having a ->workflow_condition JSON string
254 - *
255 - * @return array{successMsg:array,redirectPage:array,mailNotify:array,integrations:array}
256 - */
257 - public static function collectReferencedActionIds($rows)
258 - {
259 - $referenced = ['successMsg' => [], 'redirectPage' => [], 'mailNotify' => [], 'integrations' => []];
260 - if (empty($rows) || !is_array($rows)) {
261 - return $referenced;
262 - }
263 - foreach ($rows as $row) {
264 - $conditions = json_decode($row->workflow_condition ?? '');
265 - if (empty($conditions) || !is_array($conditions)) {
266 - continue;
267 - }
268 - foreach ($conditions as $condition) {
269 - if (empty($condition->actions->success)) {
270 - continue;
271 - }
272 - foreach ($condition->actions->success as $success) {
273 - if (empty($success->type) || empty($success->details->id)) {
274 - continue;
275 - }
276 - $bucket = self::actionExclusionBucket($success->type);
277 - if (null === $bucket) {
278 - continue;
279 - }
280 - foreach (self::extractActionIds($success->details->id) as $id) {
281 - $referenced[$bucket][$id] = true;
282 - }
283 - }
284 - }
285 - }
286 - return $referenced;
287 - }
288 -
289 - private static function actionExclusionBucket($type)
290 - {
291 - switch ($type) {
292 - case 'successMsg':
293 - return 'successMsg';
294 - case 'redirectPage':
295 - return 'redirectPage';
296 - case 'mailNotify':
297 - return 'mailNotify';
298 - case 'integ':
299 - case 'webHooks':
300 - return 'integrations';
301 - default:
302 - return null;
303 - }
304 - }
305 -
306 - private static function extractActionIds($detailId)
307 - {
308 - $ids = [];
309 - $items = is_array($detailId) ? $detailId : [$detailId];
310 - foreach ($items as $item) {
311 - if (!is_string($item)) {
312 - continue;
313 - }
314 - $decoded = json_decode($item);
315 - if (isset($decoded->id)) {
316 - $ids[] = (string) $decoded->id;
317 - }
318 - }
319 - return $ids;
320 - }
321 -
322 - /**
323 - * @deprecated misspelled duplicate of calculate(); kept because it is public API. Same
324 - * divide-by-zero guard so an external caller cannot fatal either.
325 - */
326 - public static function calculte($firstOperand, $secondOperand, $operator)
327 - {
328 - return self::calculate($firstOperand, $secondOperand, $operator);
329 - }
330 -
331 128 public static function filterMailContentType()
332 129 {
333 130 return 'text/html';
334 131 }
@@ -335,26 +132,11 @@
335 132
336 133 public static function evalMathExpression($stringWithFieldValue)
337 134 {
338 135 $mathExpr = $stringWithFieldValue;
339 - if (empty($mathExpr) || !\is_scalar($mathExpr)) {
136 + if (empty($mathExpr)) {
340 137 return $stringWithFieldValue;
341 138 }
342 - $mathExpr = (string) $mathExpr;
343 -
344 - // The operand/operator checks below only look at \w+ runs and operator runs, so any other
345 - // character was invisible to them. A quoted date ('2020-10-10') therefore passed as a
346 - // subtraction chain and its quotes later surfaced as a bogus operator token. Require the whole
347 - // string to be made of things a formula can contain.
348 - if (1 !== preg_match('#^[0-9.+\-*/^()\[\]{}\s]+$#', $mathExpr)) {
349 - return $stringWithFieldValue;
350 - }
351 -
352 - // A bare date is not a subtraction: 2020-10-10 must stay a date, not become 2000.
353 - if (1 === preg_match('/^\s*\d{4}-\d{1,2}-\d{1,2}\s*$/', $mathExpr)) {
354 - return $stringWithFieldValue;
355 - }
356 -
357 139 preg_match_all('/[\+\-\*\/\s]+/', $mathExpr, $isMathExpr);
358 140 if (empty($isMathExpr[0])) {
359 141 return $stringWithFieldValue;
360 142 }
@@ -376,9 +158,9 @@
376 158 $mathExpr = str_replace(' ', '', $mathExpr);
377 159 $mathExpr = preg_replace('/\{|\[|\(/', '(', $mathExpr);
378 160 $mathExpr = preg_replace('/\}|\]/', ')', $mathExpr);
379 161 $calculated = self::infixToPostfixEvalute($mathExpr);
380 - if (!is_null($calculated) && isset($calculated[0])) {
162 + if (!is_null($calculated)) {
381 163 return (string) $calculated[0];
382 164 }
383 165
384 166 return (string) $stringWithFieldValue;
@@ -402,18 +184,10 @@
402 184 }
403 185 if ('(' === $token) {
404 186 $operatorStack[] = $token;
405 187 } elseif (')' === $token) {
406 - // An unbalanced ')' used to read $operatorStack[-1] and warn on every iteration; treat
407 - // the expression as non-arithmetic instead.
408 - if (empty($operatorStack)) {
409 - return null;
410 - }
411 188 while ('(' !== $operatorStack[count($operatorStack) - 1]) {
412 189 $outputQueue[] = array_pop($operatorStack);
413 - if (empty($operatorStack)) {
414 - return null;
415 - }
416 190 if ('(' === $operatorStack[count($operatorStack) - 1]) {
417 191 array_pop($operatorStack);
418 192 break;
419 193 }
@@ -442,22 +216,11 @@
442 216 if (is_numeric($value)) {
443 217 $resultStack[] = $value;
444 218 continue;
445 219 }
446 - // A token that is neither a number nor a real operator means the input was never an
447 - // expression — e.g. a quoted date whose quotes accumulated into a token like "'2020".
448 - // Bail out so evalMathExpression() returns the caller's string untouched; pushing the
449 - // failure onto the stack would end up blanking that string.
450 - if (!\in_array($value, ['+', '-', '*', '/', '^'], true) || count($resultStack) < 2) {
451 - return null;
452 - }
453 220 $secondOperand = array_pop($resultStack);
454 221 $firstOperand = array_pop($resultStack);
455 - $calculated = self::calculate($firstOperand, $secondOperand, $value);
456 - if (\is_null($calculated)) {
457 - return null;
458 - }
459 - $resultStack[] = $calculated;
222 + $resultStack[] = self::calculate($firstOperand, $secondOperand, $value);
460 223 }
461 224 return $resultStack;
462 225 }
463 226
@@ -473,68 +236,17 @@
473 236
474 237 return isset($precedence[$operator]) ? $precedence[$operator] : 0;
475 238 }
476 239
477 - /**
478 - * Apply one arithmetic operator.
479 - *
480 - * @param mixed $firstOperand
481 - * @param mixed $secondOperand
482 - * @param string $operator
483 - *
484 - * @return float|int|null null on unknown operator, non-numeric operand, or division by zero
485 - */
486 240 public static function calculate($firstOperand, $secondOperand, $operator)
487 241 {
488 - if (!is_numeric($firstOperand) || !is_numeric($secondOperand)) {
489 - return null;
490 - }
491 - $firstOperand = $firstOperand + 0;
492 - $secondOperand = $secondOperand + 0;
242 + $calculated = [
243 + '+' => $firstOperand + $secondOperand,
244 + '-' => $firstOperand - $secondOperand,
245 + '*' => $firstOperand * $secondOperand,
246 + '/' => $firstOperand / $secondOperand,
247 + '^' => $firstOperand ** $secondOperand,
248 + ];
493 249
494 - switch ($operator) {
495 - case '+':
496 - return $firstOperand + $secondOperand;
497 - case '-':
498 - return $firstOperand - $secondOperand;
499 - case '*':
500 - return $firstOperand * $secondOperand;
501 - case '/':
502 - // Compare as float, not with `0 ==`: PHP 8 made `0 == ''` false, so a
503 - // non-numeric operand slipped past the old guard into DivisionByZeroError.
504 - return 0.0 === (float) $secondOperand ? null : $firstOperand / $secondOperand;
505 - case '^':
506 - return $firstOperand ** $secondOperand;
507 - }
508 -
509 - return null;
510 - }
511 -
512 - /**
513 - * Recursively sets a nested property in a given object.
514 - *
515 - * This function takes an object, a string path representing nested properties
516 - * (e.g., "fk->valid->hide"), and a value to assign. It ensures that all
517 - * intermediate properties exist as objects before setting the final value.
518 - *
519 - * @param object $object The main object where properties should be set.
520 - * @param string $path The nested property path, with keys separated by "->".
521 - * @param mixed $value The value to assign to the final property.
522 - *
523 - * @return void
524 - */
525 - public static function setNestedProperty(&$object, $path, $value)
526 - {
527 - $keys = explode('->', $path);
528 - $key = array_shift($keys);
529 -
530 - if (!isset($object->$key) || !is_object($object->$key)) {
531 - $object->$key = new \stdClass();
532 - }
533 -
534 - if (!empty($keys)) {
535 - self::setNestedProperty($object->$key, implode('->', $keys), $value);
536 - } else {
537 - $object->$key = $value;
538 - }
250 + return isset($calculated[$operator]) ? $calculated[$operator] : '';
539 251 }
540 252 }