PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.2.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.2.0
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 2.10.2 All 137 releases
bit-form / includes / Core / WorkFlow / Helper.php

Helper.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 3.2.0, at includes/Core/WorkFlow/Helper.php

478 lines 15.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace BitCode\BitForm\Core\WorkFlow;
4
5 use BitCode\BitForm\Core\Integration\IntegrationHandler;
6 use BitCode\BitForm\Core\Messages\EmailTemplateHandler;
7 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
8 use BitCode\BitForm\Core\Util\FieldValueHandler;
9 use BitCode\BitForm\Core\Util\SmartTags;
10 use BitCode\BitForm\Core\Util\Utilities;
11
12 final class Helper
13 {
14 public static function getFieldData($fields)
15 {
16 $fieldData = [];
17
18 foreach ($fields as $fieldKey => $fieldDetail) {
19 $value = isset($fieldDetail->val) ? $fieldDetail->val : (isset($fieldDetail->defaultValue) ? $fieldDetail->defaultValue : '');
20 $fieldData[$fieldKey] = [
21 'key' => $fieldKey,
22 'value' => $value,
23 'type' => $fieldDetail->typ,
24 ];
25 if (isset($fieldDetail->mul)) {
26 $fieldData[$fieldKey] =
27 array_merge(
28 $fieldData[$fieldKey],
29 [
30 'mul' => $fieldDetail->mul,
31 ]
32 );
33 }
34 }
35 return $fieldData;
36 }
37
38 public static function smartFldMargeFormFld($logics, $fieldData)
39 {
40 $fieldKeys = SmartTags::smartTagFieldKeys();
41
42 foreach ($logics as $logic) {
43 $removeSpecialStr = ['${', '}', '()'];
44 if (isset($logic->field) && in_array(str_replace($removeSpecialStr, '', $logic->field), $fieldKeys)) {
45 $field = str_replace($removeSpecialStr, '', $logic->field);
46
47 $fldKey = '${' . $field . '}';
48 $customValue = isset($logic->smartKey) ? $logic->smartKey : '';
49 $val = SmartTags::getSmartTagValue($field, false, $customValue);
50
51 $fieldData[$fldKey] = [
52 'key' => $fldKey,
53 'value' => empty($val) ? '' : $val,
54 'type' => is_string($val) ? 'text' : 'array',
55 ];
56 }
57 }
58 return $fieldData;
59 }
60
61 public static function replaceFieldWithValue($stringToReplaceField, $fieldValues, $evalMathExpr = true, $formID = null, $stripShortcodesFromValues = false)
62 {
63 $stringToReplaceField = FieldValueHandler::replaceFieldWithValue($stringToReplaceField, $fieldValues, $formID, $stripShortcodesFromValues);
64 if ($evalMathExpr) {
65 return self::evalMathExpression($stringToReplaceField);
66 }
67 return $stringToReplaceField;
68 }
69
70 public static function setDefaultSubmitConfirmation($confirmationType, $fieldValue, $formId, $logID = 0, $workFlowRun = null)
71 {
72 $messageId = 0;
73 $returnableData = null;
74 $afterSubmit = null;
75 $msgDuration = null;
76 $integrationHandler = new IntegrationHandler($formId);
77 $conditionalActionIds = self::getDefaultSubmitExcludedActionIds($formId, $workFlowRun);
78 switch ($confirmationType) {
79 case 'successMsg':
80 // Standalone confirmation: show the first ENABLED success message without requiring a workflow.
81 $successMessageHandler = new SuccessMessageHandler($formId);
82 $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 $returnableData = self::replaceFieldWithValue($msg->message_content, $fieldValue, true, null, true);
93 $messageId = $msg->id;
94 if (isset($msgConfig->afterSubmit)) {
95 $afterSubmit = $msgConfig->afterSubmit;
96 }
97 if (!empty($msgConfig->autoHide)) {
98 $msgDuration = abs(floatval($msgConfig->duration ?? 0) * 1000);
99 }
100 break;
101 }
102 }
103 break;
104 case 'redirectPage':
105 $redirectPages = $integrationHandler->getAllIntegration('form', 'redirectPage', 1);
106 if (!is_wp_error($redirectPages) && !empty($redirectPages)) {
107 foreach ($redirectPages as $redirectPage) {
108 if (isset($conditionalActionIds['redirectPage'][(string) $redirectPage->id])) {
109 continue;
110 }
111 $url = Utilities::jsonObj($redirectPage->integration_details ?? '')->url ?? '';
112 if (!empty($url)) {
113 $url = self::replaceFieldWithValue($url, $fieldValue);
114 }
115 $returnableData = empty($url) ? '' : esc_url_raw($url);
116 break;
117 }
118 }
119 break;
120 case 'webHooks':
121 // Default redirect/webhook remain workflow-driven for now (revisited in the Redirect/Integration phases).
122 break;
123 default:
124 break;
125 }
126
127 return [
128 'msg_id' => $messageId,
129 'confirmation' => $returnableData,
130 'afterSubmit' => $afterSubmit,
131 'msg_duration' => $msgDuration,
132 ];
133 }
134
135 private static function getDefaultSubmitExcludedActionIds($formId, $workFlowRun = null)
136 {
137 $actionIds = [
138 'successMsg' => [],
139 'redirectPage' => [],
140 'mailNotify' => [],
141 'integrations' => [],
142 ];
143 $filteredActionIds = apply_filters(
144 'bitform_default_submit_confirmation_excluded_action_ids',
145 $actionIds,
146 $formId,
147 $workFlowRun
148 );
149
150 return is_array($filteredActionIds) ? $filteredActionIds : $actionIds;
151 }
152
153 public static function getDefaultMailNotifications($formId, $workFlowRun = null)
154 {
155 $mailData = [];
156 $emailTemplateHandler = new EmailTemplateHandler($formId);
157 $templates = $emailTemplateHandler->getAllTemplate();
158 if (empty($templates) || is_wp_error($templates)) {
159 return $mailData;
160 }
161 $excludedActionIds = self::getDefaultSubmitExcludedActionIds($formId, $workFlowRun);
162 $arrayFields = ['to', 'cc', 'bcc', 'replyto', 'attachment', 'mediaAttachment', 'pdfIds'];
163 foreach ($templates as $template) {
164 $status = isset($template->status) ? (int) $template->status : 1;
165 if (
166 1 !== $status
167 || isset($excludedActionIds['mailNotify'][(string) $template->id])
168 ) {
169 continue;
170 }
171 $config = json_decode($template->config);
172 $details = new \stdClass();
173 $details->id = wp_json_encode(['id' => (string) $template->id]);
174 foreach (['to', 'from', 'from_name', 'cc', 'bcc', 'replyto', 'attachment', 'mediaAttachment', 'pdfId', 'pdfIds'] as $key) {
175 $details->$key = isset($config->$key) ? $config->$key : (in_array($key, $arrayFields, true) ? [] : '');
176 }
177 $mailData[] = $details;
178 }
179 return $mailData;
180 }
181
182 public static function getDefaultIntegrations($formId, $workFlowRun = null)
183 {
184 $integrationIds = [];
185 $integrationHandler = new IntegrationHandler($formId);
186 $allIntegrations = $integrationHandler->getAllIntegration('form', null, 1);
187 if (empty($allIntegrations) || is_wp_error($allIntegrations)) {
188 return $integrationIds;
189 }
190 $excludedActionIds = self::getDefaultSubmitExcludedActionIds($formId, $workFlowRun);
191 foreach ($allIntegrations as $integration) {
192 if (
193 'redirectPage' === $integration->integration_type
194 || isset($excludedActionIds['integrations'][(string) $integration->id])
195 ) {
196 continue;
197 }
198 // triggerData['integrations'] is a list of GROUPS; each group is an array of JSON id strings
199 // (Integrations::executeIntegrations requires is_array() with string members). Wrap each id.
200 $integrationIds[] = [wp_json_encode(['id' => (string) $integration->id])];
201 }
202 return $integrationIds;
203 }
204
205 /**
206 * Free implementation of the default-submit exclusion filter: any message / redirect / email /
207 * integration referenced by a classic/basic onsubmit workflow success action is workflow-gated,
208 * so it must NOT be default-executed (Pro adds the advanced-CL ids separately). Registered in Hooks.
209 */
210 public static function workflowReferencedActionIds($actionIds, $formId, $workFlowRun = null)
211 {
212 static $cache = [];
213
214 $actionIds = is_array($actionIds) ? $actionIds : [];
215 foreach (['successMsg', 'redirectPage', 'mailNotify', 'integrations'] as $bucket) {
216 if (empty($actionIds[$bucket]) || !is_array($actionIds[$bucket])) {
217 $actionIds[$bucket] = [];
218 }
219 }
220
221 $cacheKey = (string) $formId . ':' . (string) $workFlowRun;
222 if (!isset($cache[$cacheKey])) {
223 $workFlow = new WorkFlow($formId);
224 $rows = $workFlow->getWorkFlow(['create_edit', $workFlowRun ?? 'create'], ['onsubmit'], null, 'workflow_order');
225 $cache[$cacheKey] = self::collectReferencedActionIds(is_wp_error($rows) ? [] : $rows);
226 }
227
228 foreach ($cache[$cacheKey] as $bucket => $ids) {
229 $actionIds[$bucket] = $actionIds[$bucket] + $ids;
230 }
231 return $actionIds;
232 }
233
234 /**
235 * Extract the message / redirect / email / integration ids referenced by workflow-row success
236 * actions, grouped into the four exclusion buckets. Shared by the runtime default-submit filter
237 * and the one-time orphan-deactivation migration (SubmitActionFallback).
238 *
239 * @param array $rows workflow rows each having a ->workflow_condition JSON string
240 *
241 * @return array{successMsg:array,redirectPage:array,mailNotify:array,integrations:array}
242 */
243 public static function collectReferencedActionIds($rows)
244 {
245 $referenced = ['successMsg' => [], 'redirectPage' => [], 'mailNotify' => [], 'integrations' => []];
246 if (empty($rows) || !is_array($rows)) {
247 return $referenced;
248 }
249 foreach ($rows as $row) {
250 $conditions = json_decode($row->workflow_condition ?? '');
251 if (empty($conditions) || !is_array($conditions)) {
252 continue;
253 }
254 foreach ($conditions as $condition) {
255 if (empty($condition->actions->success)) {
256 continue;
257 }
258 foreach ($condition->actions->success as $success) {
259 if (empty($success->type) || empty($success->details->id)) {
260 continue;
261 }
262 $bucket = self::actionExclusionBucket($success->type);
263 if (null === $bucket) {
264 continue;
265 }
266 foreach (self::extractActionIds($success->details->id) as $id) {
267 $referenced[$bucket][$id] = true;
268 }
269 }
270 }
271 }
272 return $referenced;
273 }
274
275 private static function actionExclusionBucket($type)
276 {
277 switch ($type) {
278 case 'successMsg':
279 return 'successMsg';
280 case 'redirectPage':
281 return 'redirectPage';
282 case 'mailNotify':
283 return 'mailNotify';
284 case 'integ':
285 case 'webHooks':
286 return 'integrations';
287 default:
288 return null;
289 }
290 }
291
292 private static function extractActionIds($detailId)
293 {
294 $ids = [];
295 $items = is_array($detailId) ? $detailId : [$detailId];
296 foreach ($items as $item) {
297 if (!is_string($item)) {
298 continue;
299 }
300 $decoded = json_decode($item);
301 if (isset($decoded->id)) {
302 $ids[] = (string) $decoded->id;
303 }
304 }
305 return $ids;
306 }
307
308 public static function calculte($firstOperand, $secondOperand, $operator)
309 {
310 switch ($operator) {
311 case '+':
312 return $firstOperand + $secondOperand;
313 case '-':
314 return $firstOperand - $secondOperand;
315 case '*':
316 return $firstOperand * $secondOperand;
317 case '/':
318 return $firstOperand / $secondOperand;
319 case '^':
320 return $firstOperand ** $secondOperand;
321 }
322 }
323
324 public static function filterMailContentType()
325 {
326 return 'text/html';
327 }
328
329 public static function evalMathExpression($stringWithFieldValue)
330 {
331 $mathExpr = $stringWithFieldValue;
332 if (empty($mathExpr)) {
333 return $stringWithFieldValue;
334 }
335 preg_match_all('/[\+\-\*\/\s]+/', $mathExpr, $isMathExpr);
336 if (empty($isMathExpr[0])) {
337 return $stringWithFieldValue;
338 }
339 preg_match_all('/\w+/', $mathExpr, $exprValues);
340 if (empty($exprValues[0])) {
341 return $stringWithFieldValue;
342 }
343 foreach ($exprValues[0] as $opreands) {
344 if (!is_numeric($opreands)) {
345 return $stringWithFieldValue;
346 }
347 }
348 $validOperator = ['+', '-', '*', '^', '/'];
349 foreach ($isMathExpr[0] as $value) {
350 if (!in_array(trim($value), $validOperator)) {
351 return $stringWithFieldValue;
352 }
353 }
354 $mathExpr = str_replace(' ', '', $mathExpr);
355 $mathExpr = preg_replace('/\{|\[|\(/', '(', $mathExpr);
356 $mathExpr = preg_replace('/\}|\]/', ')', $mathExpr);
357 $calculated = self::infixToPostfixEvalute($mathExpr);
358 if (!is_null($calculated)) {
359 return (string) $calculated[0];
360 }
361
362 return (string) $stringWithFieldValue;
363 }
364
365 public static function infixToPostfixEvalute($expression)
366 {
367 $operatorStack = [];
368 $outputQueue = [];
369 $numTemp = null;
370 for ($strIndex = 0; $strIndex < strlen($expression); $strIndex++) {
371 $token = $expression[$strIndex];
372 if ('+' === $token || '-' === $token || '*' === $token || '/' === $token || '^' === $token || '(' === $token || ')' === $token) {
373 if (!is_null($numTemp)) {
374 $outputQueue[] = $numTemp;
375 $numTemp = null;
376 }
377 $stackSize = count($operatorStack);
378 if ($stackSize) {
379 $stackTop = $operatorStack[$stackSize - 1];
380 }
381 if ('(' === $token) {
382 $operatorStack[] = $token;
383 } elseif (')' === $token) {
384 while ('(' !== $operatorStack[count($operatorStack) - 1]) {
385 $outputQueue[] = array_pop($operatorStack);
386 if ('(' === $operatorStack[count($operatorStack) - 1]) {
387 array_pop($operatorStack);
388 break;
389 }
390 }
391 } elseif (isset($stackTop) && self::operatorPrecedence($token) > self::operatorPrecedence($stackTop)) {
392 $operatorStack[] = $token;
393 } elseif ('^' !== $token && $stackSize) {
394 $operatorStack[$stackSize - 1] = $token;
395 $outputQueue[] = $stackTop;
396 } else {
397 $operatorStack[] = $token;
398 }
399 continue;
400 }
401 $numTemp .= $token;
402 if ($strIndex === strlen($expression) - 1 && !is_null($numTemp)) {
403 $outputQueue[] = $numTemp;
404 }
405 }
406
407 if (!is_null($operatorStack)) {
408 $outputQueue = array_merge($outputQueue, array_reverse($operatorStack));
409 }
410 $resultStack = [];
411 foreach ($outputQueue as $value) {
412 if (is_numeric($value)) {
413 $resultStack[] = $value;
414 continue;
415 }
416 $secondOperand = array_pop($resultStack);
417 $firstOperand = array_pop($resultStack);
418 $resultStack[] = self::calculate($firstOperand, $secondOperand, $value);
419 }
420 return $resultStack;
421 }
422
423 public static function operatorPrecedence($operator)
424 {
425 $precedence = [
426 '+' => 2,
427 '-' => 2,
428 '*' => 3,
429 '/' => 3,
430 '^' => 4,
431 ];
432
433 return isset($precedence[$operator]) ? $precedence[$operator] : 0;
434 }
435
436 public static function calculate($firstOperand, $secondOperand, $operator)
437 {
438 $calculated = [
439 '+' => $firstOperand + $secondOperand,
440 '-' => $firstOperand - $secondOperand,
441 '*' => $firstOperand * $secondOperand,
442 '/' => $firstOperand / $secondOperand,
443 '^' => $firstOperand ** $secondOperand,
444 ];
445
446 return isset($calculated[$operator]) ? $calculated[$operator] : '';
447 }
448
449 /**
450 * Recursively sets a nested property in a given object.
451 *
452 * This function takes an object, a string path representing nested properties
453 * (e.g., "fk->valid->hide"), and a value to assign. It ensures that all
454 * intermediate properties exist as objects before setting the final value.
455 *
456 * @param object $object The main object where properties should be set.
457 * @param string $path The nested property path, with keys separated by "->".
458 * @param mixed $value The value to assign to the final property.
459 *
460 * @return void
461 */
462 public static function setNestedProperty(&$object, $path, $value)
463 {
464 $keys = explode('->', $path);
465 $key = array_shift($keys);
466
467 if (!isset($object->$key) || !is_object($object->$key)) {
468 $object->$key = new \stdClass();
469 }
470
471 if (!empty($keys)) {
472 self::setNestedProperty($object->$key, implode('->', $keys), $value);
473 } else {
474 $object->$key = $value;
475 }
476 }
477 }
478