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 +91 -19 -3.0.13.3.1 View file →
@@ -8,14 +8,15 @@
8 8 use BitCode\BitForm\Core\Database\FormEntryModel;
9 9 use BitCode\BitForm\Core\Database\IntegrationModel;
10 10 use BitCode\BitForm\Core\Form\FormManager;
11 11 use BitCode\BitForm\Core\Util\FieldValueHandler;
12 +use BitCode\BitForm\Core\Util\Log;
12 13 use BitCode\BitForm\Core\Util\MailNotifier;
13 14 use BitCode\BitForm\Frontend\Form\FrontendFormManager;
14 15
15 16 final class IntegrationHandler
16 17 {
17 - private static $_formID;
18 + private $_formID;
18 19 private static $_integrationModel;
19 20 private $_user_details;
20 21
21 22 /**
@@ -27,9 +28,9 @@
27 28 * @return void
28 29 */
29 30 public function __construct($formID, $user_details = null)
30 31 {
31 - static::$_formID = $formID;
32 + $this->_formID = $formID;
32 33 static::$_integrationModel = new IntegrationModel();
33 34 $this->_user_details = $user_details;
34 35 }
35 36
@@ -35,9 +36,9 @@
35 36
36 37 public function getAIntegration($integrationID, $integrationCategory = null, $integrationType = null)
37 38 {
38 39 $conditions = [
39 - 'form_id' => static::$_formID,
40 + 'form_id' => $this->_formID,
40 41 'id' => $integrationID,
41 42 ];
42 43 if (!is_null($integrationType)) {
43 44 $conditions = array_merge($conditions, ['integration_type' => $integrationType]);
@@ -50,8 +51,9 @@
50 51 'id',
51 52 'integration_name',
52 53 'integration_type',
53 54 'integration_details',
55 + 'status',
54 56 'form_id',
55 57 ],
56 58 $conditions
57 59 );
@@ -59,9 +61,9 @@
59 61
60 62 public function getAllIntegration($integrationCategory = null, $integrationType = null, $status = null, $id = null)
61 63 {
62 64 $conditions = [
63 - 'form_id' => static::$_formID,
65 + 'form_id' => $this->_formID,
64 66 ];
65 67 if (!is_null($integrationType)) {
66 68 $conditions = array_merge($conditions, ['integration_type' => $integrationType]);
67 69 }
@@ -127,9 +129,9 @@
127 129 'integration_name' => $integrationName,
128 130 'integration_type' => $integrationType,
129 131 'integration_details' => $integrationDetails,
130 132 'category' => $integrationCategory,
131 - 'form_id' => static::$_formID,
133 + 'form_id' => $this->_formID,
132 134 'user_id' => $this->_user_details['id'],
133 135 'user_ip' => $this->_user_details['ip'],
134 136 'status' => $status,
135 137 'created_at' => $this->_user_details['time'],
@@ -148,9 +150,9 @@
148 150 'integration_name' => $integrationName,
149 151 'integration_type' => $integrationType,
150 152 'integration_details' => $integrationDetails,
151 153 'category' => $integrationCategory,
152 - 'form_id' => static::$_formID,
154 + 'form_id' => $this->_formID,
153 155 'user_id' => $this->_user_details['id'],
154 156 'user_ip' => $this->_user_details['ip'],
155 157 'status' => $status,
156 158 'updated_at' => $this->_user_details['time'],
@@ -160,8 +162,44 @@
160 162 ]
161 163 );
162 164 }
163 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 +
164 202 public function duplicateAllInAForm($oldFormId)
165 203 {
166 204 $integCols = ['integration_name', 'integration_type', 'integration_details', 'category', 'form_id', 'user_id', 'user_ip', 'status', 'created_at', 'updated_at'];
167 205 $integDupData = [
@@ -168,9 +206,9 @@
168 206 'integration_name',
169 207 'integration_type',
170 208 'integration_details',
171 209 'category',
172 - static::$_formID,
210 + $this->_formID,
173 211 $this->_user_details['id'],
174 212 $this->_user_details['ip'],
175 213 'status',
176 214 $this->_user_details['time'],
@@ -183,9 +221,9 @@
183 221 {
184 222 return static::$_integrationModel->delete(
185 223 [
186 224 'id' => $integrationID,
187 - 'form_id' => static::$_formID,
225 + 'form_id' => $this->_formID,
188 226 ]
189 227 );
190 228 }
191 229
@@ -281,8 +319,11 @@
281 319 $responseData['msg_id'] = isset($workFlowReturnedData['msg_id']) ? $workFlowReturnedData['msg_id'] : 0;
282 320 if (isset($workFlowReturnedData['msg_duration'])) {
283 321 $responseData['msg_duration'] = $workFlowReturnedData['msg_duration'];
284 322 }
323 + if (isset($workFlowReturnedData['afterSubmit'])) {
324 + $responseData['afterSubmit'] = $workFlowReturnedData['afterSubmit'];
325 + }
285 326 }
286 327 if (isset($workFlowReturnedData['redirectPage'])) {
287 328 $responseData['redirectPage'] = $workFlowReturnedData['redirectPage'];
288 329 }
@@ -297,14 +338,19 @@
297 338 }
298 339
299 340 $triggerData = $workFlowReturnedData['triggerData'];
300 341
342 + if (!isset($workFlowReturnedData['fields'])) {
343 + $workFlowReturnedData['fields'] = isset($workFlowReturnedData['updatedData'])
344 + ? $workFlowReturnedData['updatedData']
345 + : [];
346 + }
347 +
301 348 $trnasientData = get_transient("bitform_trigger_transient_{$entryId}");
302 349 $trnasientData = is_string($trnasientData) ? json_decode($trnasientData) : $trnasientData;
303 350
304 351 if (!empty($trnasientData['fields'])) {
305 - $fieldData = isset($workFlowReturnedData['fields']) ? $workFlowReturnedData['fields'] : $workFlowReturnedData['updatedData'];
306 - $workFlowReturnedData['fields'] = array_merge($fieldData, $trnasientData['fields']);
352 + $workFlowReturnedData['fields'] = array_merge($workFlowReturnedData['fields'], $trnasientData['fields']);
307 353 }
308 354
309 355 if (function_exists('fastcgi_finish_request') || !wp_doing_ajax()) {
310 356 if (!headers_sent()) {
@@ -350,9 +396,14 @@
350 396 // $triggerData['fields'] = $workFlowReturnedData['fields'];
351 397 do_action('bitform_double_optin_confirmation', $workFlowReturnedData['integrationDetails'], $triggerData);
352 398 } elseif (isset($triggerData['dblOptin'])) {
353 399 foreach ($triggerData['dblOptin'] as $value) {
354 - MailNotifier::notify($value, $triggerData['formID'], $triggerData['fields'], $triggerData['entryID'], true, $triggerData['logID']);
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 + }
355 406 }
356 407 }
357 408 }
358 409 if (function_exists('fastcgi_finish_request')) {
@@ -363,9 +414,8 @@
363 414 die;
364 415 }
365 416 return $workFlowReturnedData;
366 417 }
367 -
368 418 if (isset($triggerData['mail']) && !empty($triggerData['mail'])) {
369 419 $formManager = new AdminFormManager($triggerData['formID']);
370 420 $formContent = $formManager->getFormContent();
371 421 $submitted_fields = $formContent->fields;
@@ -370,9 +420,14 @@
370 420 $formContent = $formManager->getFormContent();
371 421 $submitted_fields = $formContent->fields;
372 422 $fieldValueForMail = FieldValueHandler::formatFieldValueForMail($submitted_fields, $workFlowReturnedData['fields']);
373 423 foreach ($triggerData['mail'] as $value) {
374 - MailNotifier::notify($value, $triggerData['formID'], $fieldValueForMail, $triggerData['entryID'], false, $triggerData['logID']);
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 + }
375 430 }
376 431 }
377 432 do_action('bitforms_exec_integrations', $triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']);
378 433
@@ -386,9 +441,9 @@
386 441 return $workFlowReturnedData;
387 442 }
388 443
389 444 $entryID = $triggerData['entryID'];
390 - if (isset($workFlowReturnedData['cron']) && $workFlowReturnedData['cron'] && !isset($triggerData['mail']) && isset($workFlowReturnedData['integrationRun']) && $workFlowReturnedData['integrationRun']) {
445 + if (isset($workFlowReturnedData['cron']) && $workFlowReturnedData['cron'] && empty($triggerData['mail']) && isset($workFlowReturnedData['integrationRun']) && $workFlowReturnedData['integrationRun']) {
391 446 $eventScheuled = wp_schedule_single_event(time(), 'bitforms_exec_integrations', [$triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']]);
392 447 $scheduleTime = wp_next_scheduled('bitforms_exec_integrations', [$triggerData['integrations'], $workFlowReturnedData['fields'], $triggerData['formID'], $triggerData['entryID'], $triggerData['logID']]);
393 448 $responseData['cron'] = get_site_url(null, "/wp-cron.php?doing_wp_cron&{$scheduleTime}");
394 449 } elseif (!empty($triggerData['integrations']) || !empty($triggerData['mail']) || isset($workFlowReturnedData['integrationRun']) && !$workFlowReturnedData['integrationRun']) {
@@ -403,8 +458,9 @@
403 458 $triggerToken = wp_hash($triggerData['entryID'] . $triggerData['logID'] . time() . wp_rand(), 'nonce');
404 459 $triggerData['trigger_token'] = $triggerToken;
405 460
406 461 set_transient("bitform_trigger_transient_{$entryID}", $triggerData, HOUR_IN_SECONDS);
462 + $queuedAt = current_time('mysql');
407 463 $entryLog = new FormEntryLogModel();
408 464 $queueuEntry = $entryLog->log_history_insert(
409 465 [
410 466 'log_id' => $triggerData['logID'],
@@ -410,12 +466,27 @@
410 466 'log_id' => $triggerData['logID'],
411 467 'integration_id' => 0,
412 468 'api_type' => wp_json_encode(['type' => 'trigger', 'type_name' => 'Workflow', 'on' => $opType]),
413 469 'response_type' => 'queued',
414 - 'response_obj' => wp_json_encode(['status' => 'queued']),
415 - 'created_at' => current_time('mysql'),
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,
416 474 ]
417 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');
487 + }
488 + }
418 489 $responseData['cronNotOk'] = [
419 490 $triggerData['entryID'],
420 491 $triggerData['logID'],
421 492 $queueuEntry,
@@ -451,11 +522,12 @@
451 522 /**
452 523 *
453 524 * @param mixed $fieldValues
454 525 * @param mixed $returnRepeaterValue default is array
526 + * @param mixed $formID form ID required for string formatting
455 527 * @return array and repeater field value as array or string
456 528 */
457 - public static function formattedRepeaterValue($fieldValues, $returnRepeaterValue = '')
529 + public static function formattedRepeaterValue($fieldValues, $returnRepeaterValue = '', $formID = null)
458 530 {
459 531 // get the repeater field and store it in an array
460 532 $repeaterField = [];
461 533 foreach ($fieldValues as $key => $value) {
@@ -488,9 +560,9 @@
488 560 }
489 561
490 562 // convert the array to string repeater field value
491 563 if ('string' === $returnRepeaterValue) {
492 - $form = new FormManager(static::$_formID);
564 + $form = new FormManager($formID);
493 565 foreach ($repeaterField as $key) {
494 566 if (isset($fieldValues[$key]) && is_array($fieldValues[$key])) {
495 567 $fieldValues[$key] = implode(', ', self::flattenArray($fieldValues[$key]));
496 568 }
@@ -501,9 +573,9 @@
501 573 continue;
502 574 }
503 575 $repeaterValues = $fieldValues[$key];
504 576
505 - $newFieldValues = self::constructRepeaterValue($repeaterValues, static::$_formID);
577 + $newFieldValues = self::constructRepeaterValue($repeaterValues, $formID);
506 578
507 579 $fieldValues[$key] = is_array($newFieldValues) ? json_encode(array_values($newFieldValues), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) : $fieldValues[$key];
508 580 }
509 581 }