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

SubmitActionFallback.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 3.2.1, at includes/Core/Fallback/SubmitActionFallback.php

155 lines 5.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\Fallback;
4
5 use BitCode\BitForm\Core\Database\DB;
6 use BitCode\BitForm\Core\Database\EmailTemplateModel;
7 use BitCode\BitForm\Core\Database\IntegrationModel;
8 use BitCode\BitForm\Core\Database\SuccessMessageModel;
9 use BitCode\BitForm\Core\Database\WorkFlowModel;
10 use BitCode\BitForm\Core\WorkFlow\Helper;
11
12 /**
13 * Backward-compat migration for the workflow-decoupling refactor.
14 *
15 * Before the refactor, confirmation messages / redirects / email notifications / integrations ran
16 * ONLY when referenced by an active classic workflow. After the refactor they execute by default
17 * (no conditional logic). To preserve existing forms, any such feature that is NOT referenced by an
18 * active classic/basic onsubmit workflow (or referenced only by inactive workflows) is deactivated
19 * here (status = 0). Runs once for existing installs only (FallBack skips fresh installs); idempotent.
20 */
21 class SubmitActionFallback
22 {
23 private const MIGRATED_OPTION = 'bitform_orphan_submit_action_migrated';
24
25 public function deactivateOrphanActions()
26 {
27 if (get_option(self::MIGRATED_OPTION)) {
28 return;
29 }
30
31 // This fallback runs on `init` (any request), but the schema migration that adds
32 // `workflow_category` is gated behind an admin (`manage_options`) request. If a non-admin /
33 // cron / frontend request reaches here first, the column is absent and the referenced-ids
34 // query below would return a WP_Error — which, treated as "nothing referenced", would wrongly
35 // deactivate every action. Ensure the schema exists first; if it still cannot be created, bail
36 // WITHOUT marking migrated so a later (admin) request retries.
37 if (!$this->workflowCategoryColumnReady()) {
38 return;
39 }
40
41 $referenced = $this->activeWorkflowReferencedIds();
42 // A query error here must NOT be interpreted as "nothing is referenced" — abort and retry later.
43 if (null === $referenced) {
44 return;
45 }
46
47 $this->deactivateMessages($referenced['successMsg']);
48 $this->deactivateIntegrations($referenced['redirectPage'], $referenced['integrations']);
49 $this->deactivateEmailTemplates($referenced['mailNotify']);
50
51 update_option(self::MIGRATED_OPTION, true, false);
52 }
53
54 /**
55 * Guarantee the `workflow_category` column exists before the migration reads it. Tries to run the
56 * schema migration once if the column is missing (self-healing regardless of who serves the
57 * request). Returns false if the column still cannot be found.
58 */
59 private function workflowCategoryColumnReady()
60 {
61 if ($this->hasWorkflowCategoryColumn()) {
62 return true;
63 }
64 // Column missing: run the schema migration now (idempotent) instead of waiting for an admin hit.
65 DB::migrate();
66 return $this->hasWorkflowCategoryColumn();
67 }
68
69 private function hasWorkflowCategoryColumn()
70 {
71 global $wpdb;
72 $table = $wpdb->prefix . 'bitforms_workflows';
73 // Table/column name interpolation only — SHOW COLUMNS cannot be parameterized via prepare().
74 return null !== $wpdb->get_var("SHOW COLUMNS FROM `{$table}` LIKE 'workflow_category'");
75 }
76
77 /**
78 * Ids referenced by ANY ACTIVE classic/basic workflow success action (global, ids are unique).
79 * Not limited to onsubmit: integrations/emails can also be referenced by delete workflows
80 * (executeOnDelete runs webhooks/mail) — a feature used by any active workflow must stay active.
81 *
82 * @return array|null keyed action ids, or null when the query errored (caller must abort)
83 */
84 private function activeWorkflowReferencedIds()
85 {
86 $rows = (new WorkFlowModel())->get(
87 ['id', 'workflow_condition'],
88 [
89 'workflow_status' => 1,
90 'workflow_category' => ['classic'],
91 ]
92 );
93 if (is_wp_error($rows)) {
94 // 'result_empty' just means no active classic workflows exist — that is a valid empty set,
95 // not a failure. Any other error means we cannot trust the reference list; abort.
96 return 'result_empty' === $rows->get_error_code() ? Helper::collectReferencedActionIds([]) : null;
97 }
98 return Helper::collectReferencedActionIds($rows);
99 }
100
101 private function deactivateMessages($referencedMsgIds)
102 {
103 $model = new SuccessMessageModel();
104 $messages = $model->get(['id', 'message_config'], []);
105 if (empty($messages) || is_wp_error($messages)) {
106 return;
107 }
108 foreach ($messages as $message) {
109 if (isset($referencedMsgIds[(string) $message->id])) {
110 continue;
111 }
112 $config = !empty($message->message_config) ? json_decode($message->message_config) : null;
113 if (!is_object($config)) {
114 $config = new \stdClass();
115 }
116 if (isset($config->status) && 0 === (int) $config->status) {
117 continue;
118 }
119 $config->status = 0;
120 $model->update(['message_config' => wp_json_encode($config)], ['id' => $message->id]);
121 }
122 }
123
124 private function deactivateIntegrations($referencedRedirectIds, $referencedIntegIds)
125 {
126 $model = new IntegrationModel();
127 $integrations = $model->get(['id', 'integration_type', 'status'], ['category' => 'form']);
128 if (empty($integrations) || is_wp_error($integrations)) {
129 return;
130 }
131 foreach ($integrations as $integration) {
132 $referenced = 'redirectPage' === $integration->integration_type ? $referencedRedirectIds : $referencedIntegIds;
133 if (isset($referenced[(string) $integration->id]) || 0 === (int) $integration->status) {
134 continue;
135 }
136 $model->update(['status' => 0], ['id' => $integration->id]);
137 }
138 }
139
140 private function deactivateEmailTemplates($referencedMailIds)
141 {
142 $model = new EmailTemplateModel();
143 $templates = $model->get(['id', 'status'], []);
144 if (empty($templates) || is_wp_error($templates)) {
145 return;
146 }
147 foreach ($templates as $template) {
148 if (isset($referencedMailIds[(string) $template->id]) || 0 === (int) $template->status) {
149 continue;
150 }
151 $model->update(['status' => 0], ['id' => $template->id]);
152 }
153 }
154 }
155