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/Integration/IntegrationHandler.php +670 -239 1.43.3.1 View file →
@@ -1,291 +1,722 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Core\Integration;
4 4
5 +use BitCode\BitForm\Admin\Form\AdminFormManager;
6 +use BitCode\BitForm\Admin\Form\Helpers;
7 +use BitCode\BitForm\Core\Database\FormEntryLogModel;
8 +use BitCode\BitForm\Core\Database\FormEntryModel;
9 +use BitCode\BitForm\Core\Database\IntegrationModel;
10 +use BitCode\BitForm\Core\Form\FormManager;
11 +use BitCode\BitForm\Core\Util\FieldValueHandler;
12 +use BitCode\BitForm\Core\Util\Log;
5 13 use BitCode\BitForm\Core\Util\MailNotifier;
6 -use BitCode\BitForm\Core\Database\IntegrationModel;
7 -use BitCode\BitForm\Core\Database\FormEntryLogModel;
14 +use BitCode\BitForm\Frontend\Form\FrontendFormManager;
8 15
9 -
10 16 final class IntegrationHandler
11 17 {
12 - private static $_formID;
13 - private static $_integrationModel;
14 - private $_user_details;
18 + private $_formID;
19 + private static $_integrationModel;
20 + private $_user_details;
15 21
16 - /**
17 - * Constructor of Integration Handler
18 - *
19 - * @param Integer $formID If Integration is accessible globally then
20 - * $formID will be 0 and category app
21 - * @param Array $user_details Details of user accessing data
22 - * @return void
23 - */
24 - public function __construct($formID, $user_details = null)
25 - {
26 - static::$_formID = $formID;
27 - static::$_integrationModel = new IntegrationModel();
28 - $this->_user_details = $user_details;
22 + /**
23 + * Constructor of Integration Handler
24 + *
25 + * @param Integer $formID If Integration is accessible globally then
26 + * $formID will be 0 and category app
27 + * @param Array $user_details Details of user accessing data
28 + * @return void
29 + */
30 + public function __construct($formID, $user_details = null)
31 + {
32 + $this->_formID = $formID;
33 + static::$_integrationModel = new IntegrationModel();
34 + $this->_user_details = $user_details;
35 + }
36 +
37 + public function getAIntegration($integrationID, $integrationCategory = null, $integrationType = null)
38 + {
39 + $conditions = [
40 + 'form_id' => $this->_formID,
41 + 'id' => $integrationID,
42 + ];
43 + if (!is_null($integrationType)) {
44 + $conditions = array_merge($conditions, ['integration_type' => $integrationType]);
29 45 }
46 + if (!is_null($integrationCategory)) {
47 + $conditions = array_merge($conditions, ['category' => $integrationCategory]);
48 + }
49 + return static::$_integrationModel->get(
50 + [
51 + 'id',
52 + 'integration_name',
53 + 'integration_type',
54 + 'integration_details',
55 + 'status',
56 + 'form_id',
57 + ],
58 + $conditions
59 + );
60 + }
30 61
62 + public function getAllIntegration($integrationCategory = null, $integrationType = null, $status = null, $id = null)
63 + {
64 + $conditions = [
65 + 'form_id' => $this->_formID,
66 + ];
67 + if (!is_null($integrationType)) {
68 + $conditions = array_merge($conditions, ['integration_type' => $integrationType]);
69 + }
31 70
32 - public function getAIntegration($integrationID, $integrationCategory = null, $integrationType = null)
33 - {
34 - $conditions = array(
35 - 'form_id' => static::$_formID,
36 - 'id' => $integrationID,
37 - );
38 - if (!is_null($integrationType)) {
39 - $conditions = array_merge($conditions, ['integration_type' => $integrationType]);
40 - }
41 - if (!is_null($integrationCategory)) {
42 - $conditions = array_merge($conditions, ['category' => $integrationCategory]);
43 - }
44 - return static::$_integrationModel->get(
45 - array(
46 - 'id',
47 - 'integration_name',
48 - 'integration_type',
49 - 'integration_details',
50 - 'form_id'
51 - ),
52 - $conditions
53 - );
71 + if (!is_null($integrationCategory)) {
72 + $conditions = array_merge($conditions, ['category' => $integrationCategory]);
54 73 }
74 + if (!is_null($status)) {
75 + $conditions = array_merge($conditions, ['status' => 1]);
76 + }
77 + if (!is_null($id)) {
78 + $conditions = array_merge($conditions, ['id' => $id]);
79 + }
80 + return static::$_integrationModel->get(
81 + [
82 + 'id',
83 + 'form_id',
84 + 'integration_name',
85 + 'integration_type',
86 + 'integration_details',
87 + 'status',
88 + ],
89 + $conditions
90 + );
91 + }
55 92
56 - public function getAllIntegration($integrationCategory = null, $integrationType = null, $status = null)
57 - {
58 - $conditions = array(
59 - 'form_id' => static::$_formID
60 - );
61 - if (!is_null($integrationType)) {
62 - $conditions = array_merge($conditions, ['integration_type' => $integrationType]);
93 + public function getIntegrationWithoutFormId($integrationCategory = null, $integrationType = null, $status = null, $id = null)
94 + {
95 + $conditions = [];
96 + if (!is_null($integrationType)) {
97 + $conditions = array_merge($conditions, ['integration_type' => $integrationType]);
98 + }
99 +
100 + if (!is_null($integrationCategory)) {
101 + $conditions = array_merge($conditions, ['category' => $integrationCategory]);
102 + }
103 + if (!is_null($status)) {
104 + $conditions = array_merge($conditions, ['status' => 1]);
105 + }
106 + if (!is_null($id)) {
107 + $conditions = array_merge($conditions, ['id' => $id]);
108 + }
109 + return static::$_integrationModel->get(
110 + [
111 + 'id',
112 + 'form_id',
113 + 'integration_name',
114 + 'integration_type',
115 + 'integration_details',
116 + 'status',
117 + ],
118 + $conditions
119 + );
120 + }
121 +
122 + public function saveIntegration($integrationName, $integrationType, $integrationDetails, $integrationCategory, $status = null)
123 + {
124 + if (null === $status) {
125 + $status = 1;
126 + }
127 + return static::$_integrationModel->insert(
128 + [
129 + 'integration_name' => $integrationName,
130 + 'integration_type' => $integrationType,
131 + 'integration_details' => $integrationDetails,
132 + 'category' => $integrationCategory,
133 + 'form_id' => $this->_formID,
134 + 'user_id' => $this->_user_details['id'],
135 + 'user_ip' => $this->_user_details['ip'],
136 + 'status' => $status,
137 + 'created_at' => $this->_user_details['time'],
138 + 'updated_at' => $this->_user_details['time'],
139 + ]
140 + );
141 + }
142 +
143 + public function updateIntegration($integrationID, $integrationName, $integrationType, $integrationDetails, $integrationCategory, $status = null)
144 + {
145 + if (null === $status) {
146 + $status = 1;
147 + }
148 + return static::$_integrationModel->update(
149 + [
150 + 'integration_name' => $integrationName,
151 + 'integration_type' => $integrationType,
152 + 'integration_details' => $integrationDetails,
153 + 'category' => $integrationCategory,
154 + 'form_id' => $this->_formID,
155 + 'user_id' => $this->_user_details['id'],
156 + 'user_ip' => $this->_user_details['ip'],
157 + 'status' => $status,
158 + 'updated_at' => $this->_user_details['time'],
159 + ],
160 + [
161 + 'id' => $integrationID,
162 + ]
163 + );
164 + }
165 +
166 + /**
167 + * Name-only rename of a connected account row, scoped to
168 + * category=connected_integration_apps and this handler's form id (0).
169 + * updateIntegration() is unusable here: it overwrites details/type/category/status.
170 + */
171 + public function renameConnectedApp($integrationID, $integrationName)
172 + {
173 + $data = ['integration_name' => $integrationName];
174 + if (!empty($this->_user_details['time'])) {
175 + $data['updated_at'] = $this->_user_details['time'];
176 + }
177 + return static::$_integrationModel->update(
178 + $data,
179 + [
180 + 'id' => $integrationID,
181 + 'form_id' => $this->_formID,
182 + 'category' => 'connected_integration_apps',
183 + ]
184 + );
185 + }
186 +
187 + public function updateIntegrationStatus($integrationID, $status)
188 + {
189 + $data = ['status' => (int) $status];
190 + if (!empty($this->_user_details['time'])) {
191 + $data['updated_at'] = $this->_user_details['time'];
192 + }
193 + return static::$_integrationModel->update(
194 + $data,
195 + [
196 + 'id' => $integrationID,
197 + 'form_id' => $this->_formID,
198 + ]
199 + );
200 + }
201 +
202 + public function duplicateAllInAForm($oldFormId)
203 + {
204 + $integCols = ['integration_name', 'integration_type', 'integration_details', 'category', 'form_id', 'user_id', 'user_ip', 'status', 'created_at', 'updated_at'];
205 + $integDupData = [
206 + 'integration_name',
207 + 'integration_type',
208 + 'integration_details',
209 + 'category',
210 + $this->_formID,
211 + $this->_user_details['id'],
212 + $this->_user_details['ip'],
213 + 'status',
214 + $this->_user_details['time'],
215 + $this->_user_details['time'],
216 + ];
217 + return static::$_integrationModel->duplicate($integCols, $integDupData, ['form_id' => $oldFormId]);
218 + }
219 +
220 + public function deleteIntegration($integrationID)
221 + {
222 + return static::$_integrationModel->delete(
223 + [
224 + 'id' => $integrationID,
225 + 'form_id' => $this->_formID,
226 + ]
227 + );
228 + }
229 +
230 + public static function replaceFieldWithValue($dataToReplaceField, $fieldValues)
231 + {
232 + if (empty($dataToReplaceField)) {
233 + return false;
234 + }
235 + if (is_string($dataToReplaceField)) {
236 + $dataToReplaceField = static::replaceFieldWithValueHelper($dataToReplaceField, $fieldValues);
237 + } elseif (is_array($dataToReplaceField)) {
238 + foreach ($dataToReplaceField as $field => $value) {
239 + if (is_array($value) && 1 === count($value)) {
240 + $dataToReplaceField[$field] = static::replaceFieldWithValueHelper($value[0], $fieldValues);
241 + } elseif (is_array($value)) {
242 + $dataToReplaceField[$field] = static::replaceFieldWithValue($value, $fieldValues);
243 + } else {
244 + $dataToReplaceField[$field] = static::replaceFieldWithValueHelper($value, $fieldValues);
63 245 }
246 + }
247 + }
248 + return $dataToReplaceField;
249 + }
64 250
65 - if (!is_null($integrationCategory)) {
66 - $conditions = array_merge($conditions, ['category' => $integrationCategory]);
67 - }
68 - if (!is_null($status)) {
69 - $conditions = array_merge($conditions, ['status' => 1]);
70 - }
71 - return static::$_integrationModel->get(
72 - array(
73 - 'id',
74 - 'form_id',
75 - 'integration_name',
76 - 'integration_type',
77 - 'integration_details',
78 - 'status'
79 - ),
80 - $conditions
81 - );
251 + private static function replaceFieldWithValueHelper($stringToReplaceField, $fieldValues)
252 + {
253 + if (empty($stringToReplaceField)) {
254 + return $stringToReplaceField;
82 255 }
256 + $fieldPattern = '/\${\w[^ ${}]*}/';
257 + preg_match_all($fieldPattern, $stringToReplaceField, $matchedField);
258 + $uniqueFieldsInStr = array_unique($matchedField[0]);
83 259
84 - public function singleIntegration($integrationCategory = null, $integrationType = null, $status = null){
85 -
86 - $conditions='';
87 - if (!is_null($integrationType)) {
88 - $conditions .= "WHERE `integration_type`='$integrationType'";
260 + foreach ($uniqueFieldsInStr as $value) {
261 + $fieldName = substr($value, 2, strlen($value) - 3);
262 +
263 + $repeaterArr = explode('.', $fieldName);
264 + if (isset($fieldValues[$fieldName])) {
265 + $stringToReplaceField = is_string($fieldValues[$fieldName])
266 + ? str_replace($value, $fieldValues[$fieldName], $stringToReplaceField)
267 + : wp_json_encode($fieldValues[$fieldName]);
268 + } elseif (2 === count($repeaterArr) && isset($fieldValues[$repeaterArr[0]])) {
269 + $repeaterValues = [];
270 + $repeaterFieldValues = $fieldValues[$repeaterArr[0]];
271 + foreach ($repeaterFieldValues as $value) {
272 + if (isset($value[$repeaterArr[1]])) {
273 + $repeaterValues[] = $value[$repeaterArr[1]];
274 + }
89 275 }
90 276
91 - if (!is_null($integrationCategory)) {
92 - $conditions .= "AND `category`='$integrationCategory'";
93 - }
94 - if (!is_null($status)) {
95 - $conditions .= "AND status=1";
96 - }
97 - return static::$_integrationModel->single_row(
98 - $conditions,
99 - array('id')
100 - );
101 -
277 + $stringToReplaceField = $repeaterValues;
278 + } else {
279 + $stringToReplaceField = FieldValueHandler::replaceSmartTagWithValue($value);
280 + }
102 281 }
282 + return $stringToReplaceField;
283 + }
103 284
104 - public function saveIntegration($integrationName, $integrationType, $integrationDetails, $integrationCategory, $status = null)
105 - {
106 - if ($status == null) {
107 - $status = 1;
108 - }
109 - return static::$_integrationModel->insert(
110 - array(
111 - "integration_name" => $integrationName,
112 - "integration_type" => $integrationType,
113 - "integration_details" => $integrationDetails,
114 - 'category' => $integrationCategory,
115 - 'form_id' => static::$_formID,
116 - "user_id" => $this->_user_details['id'],
117 - "user_ip" => $this->_user_details['ip'],
118 - "status" => $status,
119 - "created_at" => $this->_user_details['time'],
120 - "updated_at" => $this->_user_details['time']
121 - )
122 - );
285 + public static function updatedHiddenFieldValue($formID)
286 + {
287 + $hiddenFields = [];
288 + $frontendFormManger = FrontendFormManager::getInstance($formID);
289 +
290 + if ($frontendFormManger->isHoneypotActive()) {
291 + $str = '_bitform' . '_' . $formID . '_' . time() . '_';
292 + $honpotToken = Helpers::honeypotEncryptedToken($str);
293 + $hiddenFields[] = [
294 + 'name' => 'b_h_t',
295 + 'value' => $honpotToken
296 + ];
123 297 }
298 + $csrfTokens = Helpers::csrfEecrypted();
299 + $hiddenFields[] = [
300 + 'name' => 't_identity',
301 + 'value' => $csrfTokens['t_identity'],
302 + ];
303 + $hiddenFields[] = [
304 + 'name' => 'csrf',
305 + 'value' => $csrfTokens['csrf']
306 + ];
307 + return $hiddenFields;
308 + }
124 309
125 - public function updateIntegration($integrationID, $integrationName, $integrationType, $integrationDetails, $integrationCategory, $status = null)
126 - {
127 - if ($status === null) {
128 - $status = 1;
129 - }
130 - return static::$_integrationModel->update(
131 - array(
132 - "integration_name" => $integrationName,
133 - "integration_type" => $integrationType,
134 - "integration_details" => $integrationDetails,
135 - 'category' => $integrationCategory,
136 - 'form_id' => static::$_formID,
137 - "user_id" => $this->_user_details['id'],
138 - "user_ip" => $this->_user_details['ip'],
139 - "status" => $status,
140 - "updated_at" => $this->_user_details['time']
141 - ),
142 - array(
143 - "id" => $integrationID
144 - )
145 - );
310 + public static function maybeSetCronForIntegration($workFlowReturnedData, $opType, $isFormRequest = true)
311 + {
312 + $responseData = [];
313 + $entryId = $workFlowReturnedData['triggerData']['entryID'];
314 + $responseData['entry_id'] = $entryId;
315 + if (isset($workFlowReturnedData['message'])) {
316 + $formID = $workFlowReturnedData['triggerData']['formID'];
317 + $responseData['message'] = $workFlowReturnedData['message'];
318 + $responseData['hidden_fields'] = self::updatedHiddenFieldValue($formID);
319 + $responseData['msg_id'] = isset($workFlowReturnedData['msg_id']) ? $workFlowReturnedData['msg_id'] : 0;
320 + if (isset($workFlowReturnedData['msg_duration'])) {
321 + $responseData['msg_duration'] = $workFlowReturnedData['msg_duration'];
322 + }
323 + if (isset($workFlowReturnedData['afterSubmit'])) {
324 + $responseData['afterSubmit'] = $workFlowReturnedData['afterSubmit'];
325 + }
146 326 }
327 + if (isset($workFlowReturnedData['redirectPage'])) {
328 + $responseData['redirectPage'] = $workFlowReturnedData['redirectPage'];
329 + }
330 + if ('update' === $opType && isset($workFlowReturnedData['updatedData'])) {
331 + $responseData['updatedData'] = $workFlowReturnedData['updatedData'];
332 + }
333 + if (isset($workFlowReturnedData['new_nonce'])) {
334 + $responseData['new_nonce'] = $workFlowReturnedData['new_nonce'];
335 + }
336 + if (!isset($workFlowReturnedData['triggerData'])) {
337 + return $responseData;
338 + }
147 339
148 - public function duplicateAllInAForm($oldFormId)
149 - {
150 - $integCols = ["integration_name", "integration_type", "integration_details", 'category', 'form_id', "user_id", "user_ip", "status", "created_at", "updated_at"];
151 - $integDupData = array(
152 - "integration_name",
153 - "integration_type",
154 - "integration_details",
155 - 'category',
156 - static::$_formID,
157 - $this->_user_details['id'],
158 - $this->_user_details['ip'],
159 - "status",
160 - $this->_user_details['time'],
161 - $this->_user_details['time']
162 - );
163 - return static::$_integrationModel->duplicate($integCols, $integDupData, ['form_id' => $oldFormId]);
340 + $triggerData = $workFlowReturnedData['triggerData'];
341 +
342 + if (!isset($workFlowReturnedData['fields'])) {
343 + $workFlowReturnedData['fields'] = isset($workFlowReturnedData['updatedData'])
344 + ? $workFlowReturnedData['updatedData']
345 + : [];
164 346 }
165 347
166 - public function deleteIntegration($integrationID)
167 - {
168 - return static::$_integrationModel->delete(
169 - array(
170 - 'id' => $integrationID,
171 - 'form_id' => static::$_formID,
172 - )
173 - );
348 + $trnasientData = get_transient("bitform_trigger_transient_{$entryId}");
349 + $trnasientData = is_string($trnasientData) ? json_decode($trnasientData) : $trnasientData;
350 +
351 + if (!empty($trnasientData['fields'])) {
352 + $workFlowReturnedData['fields'] = array_merge($workFlowReturnedData['fields'], $trnasientData['fields']);
174 353 }
175 354
176 - public static function replaceFieldWithValue($dataToReplaceField, $fieldValues)
177 - {
178 - if (empty($dataToReplaceField)) {
179 - return false;
355 + if (function_exists('fastcgi_finish_request') || !wp_doing_ajax()) {
356 + if (!headers_sent()) {
357 + header('Connection: close');
358 + $contentType = wp_doing_ajax() || defined('REST_REQUEST') ? 'application/json' : 'text/html';
359 + header('Content-Type: ' . $contentType . '; charset=' . get_option('blog_charset'));
360 + status_header(200);
361 + if (!$isFormRequest) {
362 + $response = [
363 + 'status' => 200,
364 + 'code' => 4000,
365 + 'message' => 'Data Added Successfully!!',
366 + 'success' => true
367 + ];
368 + } else {
369 + $response = [
370 + 'success' => true,
371 + 'data' => $responseData,
372 + ];
180 373 }
181 - if (is_string($dataToReplaceField)) {
182 - $dataToReplaceField = static::replaceFieldWithValueHelper($dataToReplaceField, $fieldValues);
183 - } elseif (is_array($dataToReplaceField)) {
184 - foreach ($dataToReplaceField as $field => $value) {
185 - if (is_array($value) && count($value) === 1) {
186 - $dataToReplaceField[$field] = static::replaceFieldWithValueHelper($value[0], $fieldValues);
187 - } elseif (is_array($value)) {
188 - $dataToReplaceField[$field] = static::replaceFieldWithValue($value, $fieldValues);
189 - } else {
190 - $dataToReplaceField[$field] = static::replaceFieldWithValueHelper($value, $fieldValues);
191 - }
374 + }
375 + ob_start();
376 + echo wp_doing_ajax() || defined('REST_REQUEST') ? wp_json_encode($response) : esc_html($workFlowReturnedData['message']);
377 + ob_end_flush();
378 + flush();
379 + session_write_close();
380 +
381 + if (isset($workFlowReturnedData['integrationRun']) && !$workFlowReturnedData['integrationRun']) {
382 + $entryModel = new FormEntryModel();
383 + $updatedStatus = $entryModel->update(
384 + [
385 + 'status' => 2,
386 + ],
387 + [
388 + 'form_id' => $triggerData['formID'],
389 + 'id' => $triggerData['entryID'],
390 + ]
391 + );
392 +
393 + $triggerData['fields'] = $workFlowReturnedData['fields'];
394 + if (!is_wp_error($updatedStatus)) {
395 + if ($workFlowReturnedData['dflt_template']) {
396 + // $triggerData['fields'] = $workFlowReturnedData['fields'];
397 + do_action('bitform_double_optin_confirmation', $workFlowReturnedData['integrationDetails'], $triggerData);
398 + } elseif (isset($triggerData['dblOptin'])) {
399 + foreach ($triggerData['dblOptin'] as $value) {
400 + try {
401 + MailNotifier::notify($value, $triggerData['formID'], $triggerData['fields'], $triggerData['entryID'], true, $triggerData['logID']);
402 + } catch (\Throwable $mailError) {
403 + // response already flushed — a mail failure here must not kill the rest of the run
404 + Log::debug_log('[+] Double opt-in mail failed: ' . $mailError->getMessage());
405 + }
192 406 }
407 + }
193 408 }
194 - return $dataToReplaceField;
409 + if (function_exists('fastcgi_finish_request')) {
410 + fastcgi_finish_request();
411 + }
412 +
413 + if (defined('REST_REQUEST') || wp_doing_ajax()) {
414 + die;
415 + }
416 + return $workFlowReturnedData;
417 + }
418 + if (isset($triggerData['mail']) && !empty($triggerData['mail'])) {
419 + $formManager = new AdminFormManager($triggerData['formID']);
420 + $formContent = $formManager->getFormContent();
421 + $submitted_fields = $formContent->fields;
422 + $fieldValueForMail = FieldValueHandler::formatFieldValueForMail($submitted_fields, $workFlowReturnedData['fields']);
423 + foreach ($triggerData['mail'] as $value) {
424 + try {
425 + MailNotifier::notify($value, $triggerData['formID'], $fieldValueForMail, $triggerData['entryID'], false, $triggerData['logID']);
426 + } catch (\Throwable $mailError) {
427 + // response already flushed — a mail failure here must not skip the integrations below
428 + Log::debug_log('[+] Mail notification failed: ' . $mailError->getMessage());
429 + }
430 + }
431 + }
432 + do_action('bitforms_exec_integrations', $triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']);
433 +
434 + if (function_exists('fastcgi_finish_request')) {
435 + fastcgi_finish_request();
436 + }
437 +
438 + if (defined('REST_REQUEST') || wp_doing_ajax()) {
439 + die;
440 + }
441 + return $workFlowReturnedData;
195 442 }
196 443
197 - private static function replaceFieldWithValueHelper($stringToReplaceField, $fieldValues)
198 - {
199 - if (empty($stringToReplaceField)) {
200 - return $stringToReplaceField;
444 + $entryID = $triggerData['entryID'];
445 + if (isset($workFlowReturnedData['cron']) && $workFlowReturnedData['cron'] && empty($triggerData['mail']) && isset($workFlowReturnedData['integrationRun']) && $workFlowReturnedData['integrationRun']) {
446 + $eventScheuled = wp_schedule_single_event(time(), 'bitforms_exec_integrations', [$triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']]);
447 + $scheduleTime = wp_next_scheduled('bitforms_exec_integrations', [$triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']]);
448 + $responseData['cron'] = get_site_url(null, "/wp-cron.php?doing_wp_cron&{$scheduleTime}");
449 + } elseif (!empty($triggerData['integrations']) || !empty($triggerData['mail']) || isset($workFlowReturnedData['integrationRun']) && !$workFlowReturnedData['integrationRun']) {
450 + $triggerData['fields'] = $workFlowReturnedData['fields'];
451 + if (!$workFlowReturnedData['integrationRun'] && isset($workFlowReturnedData['integrationDetails'])) {
452 + $triggerData['dbl_opt_donf'] = $workFlowReturnedData['integrationDetails'];
453 + $triggerData['dbl_opt_dflt_template'] = $workFlowReturnedData['dflt_template'];
454 + $triggerData['integrationRun'] = $workFlowReturnedData['integrationRun'];
455 + }
456 +
457 + // Generate one-time trigger token for security
458 + $triggerToken = wp_hash($triggerData['entryID'] . $triggerData['logID'] . time() . wp_rand(), 'nonce');
459 + $triggerData['trigger_token'] = $triggerToken;
460 +
461 + set_transient("bitform_trigger_transient_{$entryID}", $triggerData, HOUR_IN_SECONDS);
462 + $queuedAt = current_time('mysql');
463 + $entryLog = new FormEntryLogModel();
464 + $queueuEntry = $entryLog->log_history_insert(
465 + [
466 + 'log_id' => $triggerData['logID'],
467 + 'integration_id' => 0,
468 + 'api_type' => wp_json_encode(['type' => 'trigger', 'type_name' => 'Workflow', 'on' => $opType]),
469 + 'response_type' => 'queued',
470 + // 'trigger' is a durable copy of the transient payload: object-cache hosts can
471 + // evict the transient (and it expires after 1h), which would otherwise lose the run
472 + 'response_obj' => wp_json_encode(['status' => 'queued', 'queued_at' => $queuedAt, 'trigger' => $triggerData]),
473 + 'created_at' => $queuedAt,
474 + ]
475 + );
476 + if (\is_int($queueuEntry)) {
477 + // Safety net: the browser-fired bitforms_trigger_workflow request can be lost
478 + // (navigation abort, WAF, network drop). This event re-runs the queued workflow;
479 + // it no-ops if the browser trigger already claimed the run.
480 + wp_schedule_single_event(
481 + time() + 2 * MINUTE_IN_SECONDS,
482 + 'bitforms_reclaim_workflow',
483 + [(int) $entryID, (int) $triggerData['formID'], (int) $triggerData['logID'], $queueuEntry]
484 + );
485 + if (!wp_next_scheduled('bitforms_reclaim_sweep')) {
486 + wp_schedule_event(time() + HOUR_IN_SECONDS, 'hourly', 'bitforms_reclaim_sweep');
201 487 }
202 - $fieldPattern = '/\${\w[^ ${}]*}/';
203 - preg_match_all($fieldPattern, $stringToReplaceField, $matchedField);
204 - $uniqueFieldsInStr = array_unique($matchedField[0]);
205 - foreach ($uniqueFieldsInStr as $key => $value) {
206 - $fieldName = substr($value, 2, strlen($value) - 3);
207 - if (!empty($fieldValues[$fieldName])) {
208 - $stringToReplaceField = is_string($fieldValues[$fieldName]) ? str_replace($value, $fieldValues[$fieldName], $stringToReplaceField) :
209 - wp_json_encode($fieldValues[$fieldName]);
488 + }
489 + $responseData['cronNotOk'] = [
490 + $triggerData['entryID'],
491 + $triggerData['logID'],
492 + $queueuEntry,
493 + $triggerToken, // Add one-time token as 4th element
494 + ];
495 + }
496 + return $responseData;
497 + }
498 +
499 + public static function repeaterFieldValueFormatter($fieldValues)
500 + {
501 + $formattedArray = [];
502 +
503 + foreach ($fieldValues as $key => $value) {
504 + if (is_array($value)) {
505 + foreach ($value as $index => $v) {
506 + if (is_array($v)) {
507 + foreach ($v as $k1 => $v1) {
508 + $formattedArray[$k1][] = $v1;
210 509 }
510 + } else {
511 + $formattedArray[$key][] = $v;
512 + }
211 513 }
212 - return $stringToReplaceField;
514 + } else {
515 + $formattedArray[$key] = $value;
516 + }
213 517 }
214 518
215 - public static function maybeSetCronForIntegration($workFlowReturnedData, $opType)
216 - {
217 - $responseData = [];
218 - if (isset($workFlowReturnedData['message'])) {
219 - $responseData['message'] = $workFlowReturnedData['message'];
519 + return $formattedArray;
520 + }
521 +
522 + /**
523 + *
524 + * @param mixed $fieldValues
525 + * @param mixed $returnRepeaterValue default is array
526 + * @param mixed $formID form ID required for string formatting
527 + * @return array and repeater field value as array or string
528 + */
529 + public static function formattedRepeaterValue($fieldValues, $returnRepeaterValue = '', $formID = null)
530 + {
531 + // get the repeater field and store it in an array
532 + $repeaterField = [];
533 + foreach ($fieldValues as $key => $value) {
534 + if (!preg_match('/\./', $key)) {
535 + continue;
536 + }
537 + $repeaterField[] = $key;
538 + }
539 +
540 + // put the value of the child key to the parent repeater field\
541 + foreach ($repeaterField as $key) {
542 + list($parentKey, $childKey) = explode('.', $key);
543 + $repeatValues = isset($fieldValues[$parentKey]) ? $fieldValues[$parentKey] : $key;
544 +
545 + if (!is_array($repeatValues)) {
546 + $fieldValues[$key] = $fieldValues[$childKey];
547 + continue;
548 + }
549 +
550 + foreach ($repeatValues as $value) {
551 + if (isset($value[$childKey])) {
552 + if (!isset($fieldValues[$key]) || !is_array($fieldValues[$key])) {
553 + $fieldValues[$key] = [];
554 + }
555 + if (!empty($value[$childKey])) {
556 + $fieldValues[$key][] = $value[$childKey];
557 + }
220 558 }
221 - if (isset($workFlowReturnedData['redirectPage'])) {
222 - $responseData['redirectPage'] = $workFlowReturnedData['redirectPage'];
559 + }
560 + }
561 +
562 + // convert the array to string repeater field value
563 + if ('string' === $returnRepeaterValue) {
564 + $form = new FormManager($formID);
565 + foreach ($repeaterField as $key) {
566 + if (isset($fieldValues[$key]) && is_array($fieldValues[$key])) {
567 + $fieldValues[$key] = implode(', ', self::flattenArray($fieldValues[$key]));
223 568 }
224 - if ($opType === 'update' && isset($workFlowReturnedData['updatedData'])) {
225 - $responseData['updatedData'] = $workFlowReturnedData['updatedData'];
569 + }
570 +
571 + foreach ($fieldValues as $key=>$value) {
572 + if (!$form->isRepeaterField($key)) {
573 + continue;
226 574 }
227 - if (!isset($workFlowReturnedData['triggerData'])) {
228 - return $responseData;
229 - }
230 - $triggerData = $workFlowReturnedData['triggerData'];
231 - if (function_exists('fastcgi_finish_request') || !wp_doing_ajax()) {
232 - if (!headers_sent()) {
233 - header("Connection: close");
234 - $contentType = wp_doing_ajax() || defined('REST_REQUEST') ? "application/json" : "text/html";
235 - header('Content-Type: '. $contentType .'; charset=' . get_option('blog_charset'));
236 - status_header(200);
237 - $response = array(
238 - 'success' => true,
239 - 'data' => $responseData,
240 - );
575 + $repeaterValues = $fieldValues[$key];
576 +
577 + $newFieldValues = self::constructRepeaterValue($repeaterValues, $formID);
578 +
579 + $fieldValues[$key] = is_array($newFieldValues) ? json_encode(array_values($newFieldValues), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $fieldValues[$key];
580 + }
581 + }
582 +
583 + return $fieldValues;
584 + }
585 +
586 + private static function flattenArray($array)
587 + {
588 + return array_map(function ($val) {
589 + if (is_array($val)) {
590 + return count($val) > 1 ? ('[' . implode(', ') . ']') : implode(', ', $val);
591 + } else {
592 + return $val;
593 + }
594 + }, $array);
595 + }
596 +
597 + /**
598 + * Replace file field value with web url
599 + * @param mixed $fieldValues
600 + * @param number $formId
601 + * @param number $entryId
602 + * @return mixed $fieldValues
603 + */
604 + public static function handleFileUrl($fieldValues, $formId, $entryId)
605 + {
606 + if (!$formId || !$entryId) {
607 + return $fieldValues;
608 + }
609 + $form = FormManager::getInstance($formId);
610 + $formFields = $form->getFields();
611 +
612 + $fldsIncludeFile = ['file-up', 'advanced-file-up', 'repeater', 'signature'];
613 + foreach ($fieldValues as $key =>$value) {
614 + $fldTyp = $formFields[$key]['type'] ?? null;
615 + if (!in_array($fldTyp, $fldsIncludeFile)) {
616 + continue;
617 + }
618 +
619 + if ('repeater' === $fldTyp && is_array($value)) {
620 + foreach ($value as $rIndex => $rValue) {
621 + foreach ($rValue as $subKey=>$subValue) {
622 + if (!in_array($formFields[$subKey]['type'], $fldsIncludeFile)) {
623 + continue;
241 624 }
242 - ob_start();
243 - echo wp_doing_ajax() || defined('REST_REQUEST') ? wp_json_encode($response) : $workFlowReturnedData['message'];
244 - ob_end_flush();
245 - flush();
246 - session_write_close();
247 - if (function_exists('fastcgi_finish_request')) {
248 - fastcgi_finish_request();
625 + // for file fields inside repeater
626 + if (is_array($subValue)) {
627 + foreach ($subValue as $fileIndex=>$fileValue) {
628 + $fieldValues[$key][$rIndex][$subKey][$fileIndex] = self::constructFileUrl($formId, $entryId, $fileValue);
629 + }
630 + } else {
631 + // for signature field inside repeater;
632 + $fieldValues[$key][$rIndex][$subKey] = self::constructFileUrl($formId, $entryId, $subValue);
249 633 }
634 + }
635 + }
636 + }
250 637
251 - if (isset($triggerData['mail'])) {
252 - foreach ($triggerData['mail'] as $value) {
253 - MailNotifier::notify($value, $triggerData['formID'], $workFlowReturnedData['fields'], $triggerData['entryID']);
254 - }
638 + if (in_array($fldTyp, ['file-up', 'advanced-file-up', 'signature'], true)) {
639 + if (is_array($value)) {
640 + foreach ($value as $fldIndex=>$fldValue) {
641 + $fieldValues[$key][$fldIndex] = self::constructFileUrl($formId, $entryId, $fldValue);
642 + }
643 + } else {
644 + $fieldValues[$key] = self::constructFileUrl($formId, $entryId, $value);
645 + }
646 + }
647 + }
648 +
649 + return $fieldValues;
650 + }
651 +
652 + public static function assignRepeaterFieldValue($fieldValues, $formId)
653 + {
654 + if (!$formId) {
655 + return $fieldValues;
656 + }
657 + $form = new FormManager($formId);
658 + $formFields = $form->getFields();
659 +
660 + foreach ($fieldValues as $key=>$value) {
661 + if (!$form->isRepeaterField($key)) {
662 + continue;
663 + }
664 +
665 + if ('repeater' === $formFields[$key]['type']) {
666 + if (is_array($value)) {
667 + foreach ($value as $rIndex=>$rValue) {
668 + foreach ($rValue as $subKey =>$subValue) {
669 + $fieldValues[$subKey] = $subValue;
255 670 }
256 - do_action("bitforms_exec_integrations", $triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']);
257 - if (defined('REST_REQUEST') || wp_doing_ajax()) {
258 - die;
259 - }
260 - return $workFlowReturnedData;
671 + }
261 672 }
673 + }
674 + }
262 675
263 - $entryID = $triggerData['entryID'];
264 - if (isset($workFlowReturnedData['cron']) && $workFlowReturnedData['cron'] && !isset($triggerData['mail'])) {
265 - $eventScheuled = wp_schedule_single_event(time(), "bitforms_exec_integrations", array($triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']));
266 - $scheduleTime = wp_next_scheduled("bitforms_exec_integrations", array($triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']));
267 - $responseData['cron'] = get_site_url(null, "/wp-cron.php?doing_wp_cron&{$scheduleTime}");
268 - } else if (!empty($triggerData['integrations']) || !empty($triggerData['mail'])) {
269 - $triggerData['fields'] = $workFlowReturnedData['fields'];
270 - set_transient("bitform_trigger_transient_{$entryID}", $triggerData, HOUR_IN_SECONDS);
271 - $entryLog = new FormEntryLogModel();
272 - $queueuEntry = $entryLog->log_history_insert(
273 - array(
274 - "log_id" => $triggerData['logID'],
275 - "integration_id" => 0,
276 - "api_type" => wp_json_encode(["type" => "trigger", "type_name" => "Workflow", "on" => $opType]),
277 - "response_type" => "queued",
278 - "response_obj" => json_encode(['status' => 'queued']),
279 - "created_at" => current_time("mysql"),
280 - )
281 - );
282 - $responseData['cronNotOk'] = [
283 - $triggerData['entryID'],
284 - $triggerData['logID'],
285 - $queueuEntry
286 - ];
287 - }
676 + return $fieldValues;
677 + }
288 678
289 - return $responseData;
679 + public static function constructRepeaterValue($repeaterValues, $formId)
680 + {
681 + if (!$formId || !is_array($repeaterValues)) {
682 + return $repeaterValues;
290 683 }
684 + $form = FormManager::getInstance($formId);
685 + $formFields = $form->getFields();
686 + $newRptrValue = [];
687 + foreach ($repeaterValues as $key=>$value) {
688 + $newRepeatedValue = [];
689 + foreach ($value as $rKey =>$rValue) {
690 + $fldLbl = isset($formFields[$rKey]) ? $formFields[$rKey]['label'] : $rKey;
691 + $newRepeatedValue[$fldLbl] = $rValue;
692 + }
693 + $newRptrValue[$key] = $newRepeatedValue;
694 + }
695 +
696 + return $newRptrValue;
697 + }
698 +
699 + private static function constructFileUrl($formId, $entryId, $fileName)
700 + {
701 + $webPath = Helpers::getWebPathWithEncryptedEntryId($formId, $entryId);
702 +
703 + return rtrim($webPath, '/') . '/' . ltrim($fileName, '/');
704 + }
705 +
706 + // private static function constructFileUrl($formId, $entryId, $fileName)
707 + // {
708 + // $restBaseUrl = get_rest_url();
709 + // if (!$restBaseUrl) {
710 + // return $fileName;
711 + // }
712 + // //http://bitlocal.local/wp-json/bitform/v1/bitform-file-download/?formID=1&entryID=16&fileID=bit-form-1005.pdf
713 + // $queryParam = build_query([
714 + // 'formID' => $formId,
715 + // 'entryID' => $entryId,
716 + // 'fileID' => $fileName
717 + // ]);
718 +
719 + // $path = $restBaseUrl . 'bitform' . '/' . 'v1' . '/' . 'bitform-file-download' . '/' . '?' . $queryParam;
720 + // return rtrim($path, '/');
721 + // }
291 722 }