PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.2.2
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.2.2
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 / Api / BitFormPublicApi.php

BitFormPublicApi.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 3.2.2, at includes/Core/Api/BitFormPublicApi.php

226 lines 7.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Public helper API for sibling Bit Apps plugins (currently consumed by Bit CRM).
5 *
6 * Exposed through the `bitform/api/*` filters registered in Core\Hooks\Hooks -
7 * consumers should call apply_filters() with a null default instead of touching
8 * this class directly, so an absent or older Bit Form can never fatal them.
9 */
10
11 namespace BitCode\BitForm\Core\Api;
12
13 if (!defined('ABSPATH')) {
14 exit;
15 }
16
17 use BitCode\BitForm\Admin\Form\AdminFormHandler;
18 use BitCode\BitForm\Admin\Form\AdminFormManager;
19 use WP_Error;
20
21 final class BitFormPublicApi
22 {
23 public const CRM_INTEGRATION_TYPE = 'Bit CRM';
24
25 /**
26 * Forms that have a Bit CRM integration attached
27 *
28 * @return array[]|WP_Error items: {formId, formName, shortcode, createdAt, entriesCount,
29 * formStatus, integrationId, integrationStatus, urls}
30 */
31 public static function getCrmIntegratedForms()
32 {
33 $permission = self::guard();
34 if (is_wp_error($permission)) {
35 return $permission;
36 }
37
38 global $wpdb;
39 $rows = $wpdb->get_results(
40 $wpdb->prepare(
41 "SELECT forms.id, forms.form_name, forms.status as form_status, forms.created_at,
42 integ.id as integration_id, integ.status as integration_status,
43 COUNT(entries.id) as entries_count
44 FROM `{$wpdb->prefix}bitforms_integration` as integ
45 INNER JOIN `{$wpdb->prefix}bitforms_form` as forms ON forms.id = integ.form_id
46 LEFT JOIN `{$wpdb->prefix}bitforms_form_entries` as entries ON forms.id = entries.form_id
47 WHERE integ.integration_type = %s AND integ.category = %s
48 GROUP BY forms.id, forms.form_name, forms.status, forms.created_at, integ.id, integ.status",
49 self::CRM_INTEGRATION_TYPE,
50 'form'
51 )
52 );
53
54 if (is_null($rows)) {
55 return [];
56 }
57
58 $forms = [];
59 foreach ($rows as $row) {
60 $formId = (int) $row->id;
61 $forms[] = [
62 'formId' => $formId,
63 'formName' => $row->form_name,
64 'shortcode' => '[bitform id="' . $formId . '"]',
65 'createdAt' => $row->created_at,
66 'entriesCount' => (int) $row->entries_count,
67 'formStatus' => (int) $row->form_status,
68 'integrationId' => (int) $row->integration_id,
69 'integrationStatus' => (int) $row->integration_status,
70 'urls' => self::buildUrls($formId),
71 ];
72 }
73 return $forms;
74 }
75
76 /**
77 * Publish/unpublish a form (controls the form list toggle in Bit CRM)
78 *
79 * @param int $formId form id
80 * @param int $status 1 published, 0 unpublished
81 *
82 * @return true|WP_Error
83 */
84 public static function toggleFormStatus($formId, $status)
85 {
86 $permission = self::guard();
87 if (is_wp_error($permission)) {
88 return $permission;
89 }
90
91 $formId = absint($formId);
92 $status = absint($status) ? 1 : 0;
93 if (empty($formId)) {
94 return new WP_Error('bitform_invalid_args', __('Form id is required', 'bit-form'));
95 }
96 if (!(new AdminFormManager($formId))->isExist()) {
97 return new WP_Error('bitform_not_found', __('Form not found', 'bit-form'));
98 }
99
100 // reuse the exact form-status handler the admin Form List uses (user tracking + validation)
101 $result = (new AdminFormHandler())->changeFormStatus(
102 ['id' => $formId, 'status' => $status ? 'true' : 'false'],
103 (object) []
104 );
105 if (is_wp_error($result)) {
106 // a 0-row update means the form already has the requested status - not a failure
107 if ('result_empty' === $result->get_error_code()) {
108 return true;
109 }
110 return $result;
111 }
112 if (!$result) {
113 return new WP_Error('bitform_status_failed', __('Failed to change form status', 'bit-form'));
114 }
115 return true;
116 }
117
118 /**
119 * Build the URL that opens the Bit Form builder to create a CRM-integrated form.
120 *
121 * A valid form cannot be built server-side: the builder's theme/style state
122 * (themeVars/themeColors/style) is JCOF-encoded output of the React themeProvider,
123 * generated in the browser from the template's JS data. So instead of inserting a
124 * half-built row (which opens blank), we return a URL that drives the exact same
125 * client flow as the template gallery's "Use Template" button. Bit CRM opens it in
126 * a new tab; the builder loads the template, sets the title, attaches the Bit CRM
127 * integration, and auto-saves. If a same-origin returnUrl is given, the tab then
128 * navigates back to Bit CRM; otherwise the user stays in the editor on the new form.
129 *
130 * The flow is integration-agnostic. To auto-attach an integration to the new form,
131 * pass an `integration` descriptor; Bit CRM sends {type: 'Bit CRM', config: {tagIds,
132 * newTagTitles}}. Any plugin can drive the same flow with its own integration type.
133 *
134 * $args:
135 * - title string required, max 50 chars
136 * - templateSlug string optional frontend template slug, default 'contact_form'
137 * - integration array optional {type: string, name?: string, config?: array}
138 * - returnUrl string optional same-origin URL to return to after creation
139 * - closeAfterCreate bool optional close the tab after save (needs a script-opened
140 * tab, i.e. window.open); falls back to returnUrl if blocked
141 *
142 * @return array|WP_Error {createUrl}
143 */
144 public static function getCreateFormUrl($args)
145 {
146 $permission = self::guard();
147 if (is_wp_error($permission)) {
148 return $permission;
149 }
150
151 $args = (array) $args;
152 $title = isset($args['title']) ? sanitize_text_field($args['title']) : '';
153 if ('' === $title) {
154 return new WP_Error('bitform_invalid_args', __('Form title is required', 'bit-form'));
155 }
156 if (strlen($title) > 50) {
157 $title = substr($title, 0, 50);
158 }
159 $slug = empty($args['templateSlug']) ? 'contact_form' : sanitize_text_field($args['templateSlug']);
160
161 // only allow a same-site returnUrl - never an open redirect to another host
162 $returnUrl = '';
163 if (!empty($args['returnUrl'])) {
164 $candidate = esc_url_raw($args['returnUrl']);
165 if ($candidate && wp_parse_url($candidate, PHP_URL_HOST) === wp_parse_url(site_url(), PHP_URL_HOST)) {
166 $returnUrl = $candidate;
167 }
168 }
169
170 $integration = '';
171 if (!empty($args['integration']) && !empty($args['integration']['type'])) {
172 $integration = wp_json_encode($args['integration']);
173 }
174
175 $query = array_filter(
176 [
177 'title' => $title,
178 'template' => $slug,
179 'integration' => $integration,
180 'returnUrl' => $returnUrl,
181 'closeAfterCreate' => empty($args['closeAfterCreate']) ? '' : '1',
182 ],
183 static function ($value) {
184 return '' !== $value;
185 }
186 );
187
188 // hash route consumed by the React ExternalFormCreate entry (see frontend-dev App.jsx)
189 $createUrl = admin_url('admin.php?page=bitform') . '#/create-form?' . http_build_query($query);
190
191 return ['createUrl' => $createUrl];
192 }
193
194 /**
195 * Admin URLs of a form inside the Bit Form SPA
196 *
197 * @param int $formId
198 *
199 * @return array
200 */
201 public static function buildUrls($formId)
202 {
203 $formId = absint($formId);
204 return [
205 'editForm' => admin_url('admin.php?page=bitform#/form/builder/edit/' . $formId . '/add-fields'),
206 // per-integration deep link is index-based in the SPA, so link to the list
207 'editIntegration' => admin_url('admin.php?page=bitform#/form/settings/edit/' . $formId . '/integrations'),
208 'viewEntries' => admin_url('admin.php?page=bitform#/form/responses/edit/' . $formId . '/'),
209 'preview' => site_url('/?bitform-form-view=' . $formId),
210 ];
211 }
212
213 /**
214 * Capability gate: never trust the caller's (e.g. Bit CRM REST) context
215 *
216 * @return true|WP_Error
217 */
218 private static function guard()
219 {
220 if (!current_user_can('manage_bitform') && !current_user_can('manage_options')) {
221 return new WP_Error('bitform_forbidden', __('Insufficient permissions.', 'bit-form'));
222 }
223 return true;
224 }
225 }
226