PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V3.3.2
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV3.3.2
V3.3.2 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 All 139 releases
← All changes | includes/Core/Util/FrontendHelpers.php +380 -59 2.0 → V3.3.2 View file →
@@ -1,85 +1,166 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Core\Util;
4 4
5 -final class FrontendHelpers {
6 - public static $pageBuilderList = [
7 - 'action' => 'elementor', // elementor
8 - 'vcv-action' => 'frontend', // visual composer
9 - 'fl_builder' => '', // beaver builder
10 - 'action' => 'in-front-editor', // brizy
11 - 'ct_builder' => 'true', // oxygen
12 - 'breakdance' => 'builder', // breakdance
13 - 'lc_action_launch_editing' => '1', // live canvas
14 - 'vc_action' => 'vc_inline', // wp bakery
15 - 'et_fb' => '1', // divi
5 +use BitCode\BitForm\Core\Form\FormManager;
6 +use WP_Rewrite;
7 +
8 +final class FrontendHelpers
9 +{
10 + public static $isPageBuilder = false;
11 + public static $bfFrontendFormIds = [];
12 + public static $bfFrontendViewIds = [];
13 + public static $bfFormIdsFromPost = [];
14 + public static $bfViewIdsFromPost = [];
15 + private static $formsPermissions = [];
16 +
17 + public static $pageBuilderQueryParamsList = [
16 18 'et_pb_preview' => 'true', // divi
17 - 'bricks' => 'run', // bricks
18 - 'action' => 'architect', // thrive architect
19 - 'page' => 'livecomposer_editor', // live composer
19 + 'vc_editable' => 'true', // wp bakery
20 + 'action' => 'ct_render_shortcode' // oxygen
20 21 ];
21 22
22 - public static function getFormIdsFromPost() {
23 + public static $pageBuilderURLParamsList = [
24 + 'wp-json/bricks/v1/render', // bricks
25 + ];
26 +
27 + public static $pageBuilderRefererQueryParamsList = [
28 + 'breakdance' => 'builder', // breakdance
29 + ];
30 +
31 + public static function getFormIdsFromPost()
32 + {
23 33 global $post;
24 34 global $wpdb;
25 - global $bfUniqFormIds;
26 -
27 35 if (empty($post)) {
28 - $bfUniqFormIds = [];
29 - return;
30 - };
36 + self::$bfFormIdsFromPost = [];
37 + return [];
38 + }
31 39 $postId = $post->ID;
32 - $formsIds = [];
33 - // $bfMetaValues = $wpdb->get_results("SELECT pmt1.meta_value FROM wp_postmeta pmt1 LEFT OUTER JOIN wp_postmeta pmt2 ON (pmt1.meta_id < pmt2.meta_id AND pmt1.meta_key = pmt2.meta_key) WHERE pmt2.meta_id IS NULL AND pmt1.post_id = {$postId} AND pmt1.meta_value LIKE '%[bitform%' ORDER BY pmt1.meta_id DESC");
34 - $bfMetaValues = $wpdb->get_results('SELECT meta_value FROM `' . $wpdb->postmeta . "` WHERE `post_id`={$post->ID} AND meta_value LIKE '%[bitform id=%'");
40 + $shortcodeFormIds = [];
41 + // postmeta table name from $wpdb->postmeta (WordPress-managed, not user input). post_id parameterized via %d.
42 + $bfMetaValues = $wpdb->get_results(
43 + $wpdb->prepare(
44 + 'SELECT meta_value FROM `' . $wpdb->postmeta . '` WHERE `post_id`=%d',
45 + $postId
46 + )
47 + );
35 48 $postContent = $post->post_content;
36 49 $bfMetaValues[] = (object) ['meta_value' => $postContent];
37 50 foreach ($bfMetaValues as $bfShortcut) {
38 - $meta_value = $bfShortcut->meta_value;
39 - $meta_value = str_replace('\\', '', $meta_value);
40 - \preg_match_all("/\[bitform\s+id\s*=\s*('|\")\s*(\d+)\s*('|\")\]/", $meta_value, $shortCode);
41 - $shortCodes = $shortCode[2];
42 - if (!empty($shortCodes)) {
43 - $formsIds = array_merge($formsIds, $shortCodes);
51 + $meta_value = (is_string($bfShortcut->meta_value) && !empty($bfShortcut->meta_value)) ? $bfShortcut->meta_value : '';
52 + $shortcodeIds = self::getShortCodeIds($meta_value);
53 + $shortcodeFormIds = array_merge($shortcodeFormIds, $shortcodeIds);
54 + }
55 +
56 + self::$bfFormIdsFromPost = $shortcodeFormIds;
57 + return $shortcodeFormIds;
58 + }
59 +
60 + public static function getShortCodeIds($content = '')
61 + {
62 + $pattern = '/' . get_shortcode_regex(['bitform']) . '/';
63 + \preg_match_all($pattern, $content, $short);
64 +
65 + $formIds = [];
66 + foreach ($short[3] as $attr_string) {
67 + $attr = shortcode_parse_atts($attr_string);
68 + if (!empty($attr['id'])) {
69 + $formIds[] = $attr['id'];
44 70 }
45 71 }
46 72
47 - $bfUniqFormIds = array_unique($formsIds);
73 + // Regex handles:
74 + // 1. [bitform ... id=... ]
75 + // 2. id="123" or id='123' or id=123
76 + // 3. Escaped quotes id=\"123\" or id=\'123\' (common in builder meta)
77 + // \preg_match_all('/\[bitform\s+\b[^\]]*\bid\s*=\s*(?:\\\\?[\'"])?(\d+)(?:\\\\?[\'"])?[^\]]*\]/', $content, $shortCode);
78 + // $ids = $shortCode[1];
48 79
49 - return $bfUniqFormIds;
80 + return $formIds;
50 81 }
51 82
52 - public static function isPageBuilder() {
53 - global $bfMultipleFormsExists;
54 - global $isPageBuilder;
83 + public static function getViewIdsFromPost()
84 + {
85 + global $post;
86 + global $wpdb;
87 + if (empty($post)) {
88 + self::$bfViewIdsFromPost = [];
89 + return [];
90 + }
91 + $postId = $post->ID;
92 + $shortcodeViewIds = [];
93 + // postmeta table name from $wpdb->postmeta (WordPress-managed, not user input). post_id parameterized via %d.
94 + $bfMetaValues = $wpdb->get_results($wpdb->prepare('SELECT meta_value FROM `' . $wpdb->postmeta . '` WHERE `post_id` = %d', $postId));
95 + $postContent = $post->post_content;
55 96
56 - $current_url = $_SERVER['REQUEST_URI'];
57 - $isAdminSide = false !== strpos($current_url, '/wp-admin/');
97 + $bfMetaValues[] = (object) ['meta_value' => $postContent];
98 + foreach ($bfMetaValues as $bfShortcut) {
99 + $meta_value = (is_string($bfShortcut->meta_value) && !empty($bfShortcut->meta_value)) ? $bfShortcut->meta_value : '';
100 + $shortcodeIds = self::getViewShortCodeIds($meta_value);
101 + $shortcodeViewIds = array_merge($shortcodeViewIds, $shortcodeIds);
102 + }
58 103
59 - $isPageBuilder = get_transient('is_page_builder');
60 - $isPageBuilderSessionExists = false !== $isPageBuilder;
61 - if ($isAdminSide) {
62 - $isPageBuilder = true;
104 + self::$bfViewIdsFromPost = $shortcodeViewIds;
105 + return $shortcodeViewIds;
106 + }
107 +
108 + public static function getViewShortCodeIds($content = '')
109 + {
110 + $pattern = '/' . get_shortcode_regex(['bitform-view']) . '/';
111 + \preg_match_all($pattern, $content, $short);
112 +
113 + $viewIds = [];
114 +
115 + foreach ($short[3] as $attr_string) {
116 + $attr = shortcode_parse_atts($attr_string);
117 + if (!empty($attr['id'])) {
118 + $viewIds[] = $attr['id'];
119 + }
63 120 }
64 121
65 - if ($isPageBuilder) {
66 - $bfMultipleFormsExists = true;
122 + // \preg_match_all('/\[bitform-view\s+\b[^\]]*\bid\s*=\s*["\']?(\d+)["\']?[^\]]*\]/', $content, $shortCode);
123 + // $ids = $shortCode[1];
124 + return $viewIds;
125 + }
126 +
127 + public static function checkIsPageBuilder($srvr)
128 + {
129 + if (is_admin()) {
130 + self::$isPageBuilder = true;
67 131 return true;
68 132 }
133 + $current_url = $srvr['REQUEST_URI'];
134 + $queryParams = self::parseQueryParams($current_url);
135 + foreach (self::$pageBuilderQueryParamsList as $key => $value) {
136 + if (isset($queryParams[$key]) && $queryParams[$key] === $value) {
137 + self::$isPageBuilder = true;
138 + return true;
139 + }
140 + }
141 + foreach (self::$pageBuilderURLParamsList as $value) {
142 + if (false !== strpos($current_url, $value)) {
143 + self::$isPageBuilder = true;
144 + return true;
145 + }
146 + }
69 147
70 - if ($isPageBuilderSessionExists) {
71 - set_transient('is_page_builder', $isPageBuilder);
148 + $referrer = isset($srvr['HTTP_REFERER']) ? $srvr['HTTP_REFERER'] : '';
149 + $referrerQueryParams = self::parseQueryParams($referrer);
150 + foreach (self::$pageBuilderRefererQueryParamsList as $key => $value) {
151 + if (isset($referrerQueryParams[$key]) && $referrerQueryParams[$key] === $value) {
152 + self::$isPageBuilder = true;
153 + return true;
154 + }
72 155 }
73 156
74 - self::getFormIdsFromPost();
75 - global $bfUniqFormIds;
76 - $bfMultipleFormsExists = $isPageBuilder ? true : count($bfUniqFormIds) > 1;
77 - return $isPageBuilder;
157 + return self::$isPageBuilder;
78 158 }
79 159
80 - public static function parseQueryParams($url) {
81 - $url_components = parse_url($url);
160 + public static function parseQueryParams($url)
161 + {
162 + $url_components = wp_parse_url($url);
82 163 if (isset($url_components['query'])) {
83 164 parse_str($url_components['query'], $queryParams);
84 165 return $queryParams;
85 166 }
@@ -85,17 +166,257 @@
85 166 }
86 167 return [];
87 168 }
88 169
89 - public static function handleBfFormIdsSession($formId = null) {
90 - global $bfFrontendFormIds;
91 - $bfFrontendFormIds = get_transient('bf_frontend_form_ids');
92 - if (empty($bfFrontendFormIds)) {
93 - $bfFrontendFormIds = [];
170 + public static function isRestRequest()
171 + {
172 + $prefix = rest_get_url_prefix();
173 + // Read-only check to detect REST requests for routing; no state change performed here.
174 + if (defined('REST_REQUEST') && REST_REQUEST
175 + || (isset($_GET['rest_route'])
176 + && 0 === strpos(trim(sanitize_text_field(wp_unslash($_GET['rest_route'])), '\\/'), $prefix, 0))) {
177 + return true;
94 178 }
95 - if (is_null($formId)) {
96 - return $bfFrontendFormIds;
179 + global $wp_rewrite;
180 + if (null === $wp_rewrite) {
181 + $wp_rewrite = new WP_Rewrite();
97 182 }
98 - $bfFrontendFormIds[] = $formId;
99 - set_transient('bf_frontend_form_ids', $bfFrontendFormIds);
183 + $rest_url = wp_parse_url(trailingslashit(rest_url()));
184 + $current_url = wp_parse_url(add_query_arg([]));
185 + return 0 === strpos($current_url['path'], $rest_url['path'], 0);
186 + }
187 +
188 + public static function isAjaxRequest()
189 + {
190 + if (function_exists('wp_doing_ajax') && wp_doing_ajax()) {
191 + return true;
192 + }
193 + if (self::isRestRequest()) {
194 + return true;
195 + }
196 +
197 + if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && 'xmlhttprequest' === strtolower(sanitize_text_field(wp_unslash($_SERVER['HTTP_X_REQUESTED_WITH'])))) {
198 + return true;
199 + }
200 + if (isset($_SERVER['HTTP_SEC_FETCH_MODE'], $_SERVER['HTTP_SEC_FETCH_DEST'])) {
201 + $destination = strtolower(sanitize_text_field(wp_unslash($_SERVER['HTTP_SEC_FETCH_DEST'])));
202 + $mode = strtolower(sanitize_text_field(wp_unslash($_SERVER['HTTP_SEC_FETCH_MODE'])));
203 + if (('empty' === $destination && in_array($mode, ['cors', 'same-origin'], true))) {
204 + return true;
205 + }
206 + }
207 +
208 + return false;
209 + }
210 +
211 + public static function isAdminRequest()
212 + {
213 + $current_url = home_url(add_query_arg(null, null));
214 + $admin_url = strtolower(admin_url());
215 + $referrer = strtolower(wp_get_referer());
216 +
217 + $requestFromBackend = self::isRestRequest() && strpos($admin_url, '/wp-admin/') > 0 && !strpos($admin_url, '/wp-admin/admin-ajax.php');
218 +
219 + if ($requestFromBackend) {
220 + return true;
221 + }
222 +
223 + if (0 === strpos($current_url, $admin_url)) {
224 + if (0 === strpos($referrer, $admin_url)) {
225 + return true;
226 + } else {
227 + if (function_exists('wp_doing_ajax')) {
228 + return !wp_doing_ajax();
229 + } else {
230 + return !(defined('DOING_AJAX') && DOING_AJAX);
231 + }
232 + }
233 + } else {
234 + return false;
235 + }
236 + }
237 +
238 + public static function parseUrlParams($url)
239 + {
240 + $url_components = wp_parse_url($url);
241 + if (isset($url_components['path'])) {
242 + $urlParams = explode('/', $url_components['path']);
243 + return $urlParams;
244 + }
245 +
246 + return [];
247 + }
248 +
249 + public static function setBfFrontendFormIds($formId)
250 + {
251 + self::$bfFrontendFormIds[] = $formId;
252 + }
253 +
254 + public static function getAllFormIdsInPage()
255 + {
256 + $bfFrontendFormIds = self::$bfFrontendFormIds;
257 + $bfFormIdsFromPost = self::getFormIdsFromPost();
258 + $allFormIds = array_merge($bfFrontendFormIds, $bfFormIdsFromPost);
259 + return $allFormIds;
260 + }
261 +
262 + public static function getAllViewIdsInPage()
263 + {
264 + $bfFrontendViewIds = self::$bfFrontendViewIds;
265 + $bfViewIdsFromPost = self::getViewIdsFromPost();
266 + $allViewIds = array_merge($bfFrontendViewIds, $bfViewIdsFromPost);
267 + return $allViewIds;
268 + }
269 +
270 + public static function getAllUniqFormIdsInPage()
271 + {
272 + return array_unique(self::getAllFormIdsInPage());
273 + }
274 +
275 + /**
276 + * URL of a generated stylesheet, carrying the form update version in the src.
277 + *
278 + * WP's own $ver argument is stripped by "remove query strings from static
279 + * resources" hardening (WP Ghost, optimizer snippets), which leaves a
280 + * regenerated stylesheet behind a CDN under its unchanged URL - the markup
281 + * then carries class names the cached CSS has never heard of. bfv rides
282 + * inside the src the way the generated JS bundle already does, so those
283 + * filters leave it alone. Falls back to the plugin version so the URL always
284 + * changes on upgrade, even where the option was never written.
285 + *
286 + * @param string $relativePath path under the bitforms upload dir, e.g. form-styles/bitform-15.css
287 + *
288 + * @return string
289 + */
290 + public static function styleSrc($relativePath)
291 + {
292 + $version = get_option('bitform_form_update_version');
293 + if (!$version) {
294 + $version = BITFORMS_VERSION;
295 + }
296 + $src = BITFORMS_UPLOAD_BASE_URL . '/' . ltrim((string) $relativePath, '/');
297 +
298 + return add_query_arg('bfv', rawurlencode((string) $version), $src);
299 + }
300 +
301 + public static function hasMultipleForms()
302 + {
303 + $bfUniqFormIds = self::getAllFormIdsInPage();
304 + self::checkIsPageBuilder($_SERVER);
305 + $isPageBuilder = self::$isPageBuilder;
306 + $bfMultipleFormsExists = $isPageBuilder ? true : count($bfUniqFormIds) > 1;
307 + return $bfMultipleFormsExists;
308 + }
309 +
310 + /**
311 + * Field keys the browser reported as hidden at submit time.
312 + *
313 + * Comma joined, and must be compared key by key: a substring test lets a hidden `b1-175`
314 + * also match `b1-17`.
315 + *
316 + * @param mixed $rawHiddenFields the posted `hidden_fields` value (string or array)
317 + *
318 + * @return string[]
319 + */
320 + public static function parseHiddenFieldKeys($rawHiddenFields)
321 + {
322 + if (is_array($rawHiddenFields)) {
323 + $keys = $rawHiddenFields;
324 + } elseif (is_string($rawHiddenFields)) {
325 + $keys = explode(',', $rawHiddenFields);
326 + } else {
327 + return [];
328 + }
329 +
330 + $keys = array_map(function ($key) {
331 + return is_string($key) || is_numeric($key) ? trim((string) $key) : '';
332 + }, $keys);
333 +
334 + return array_values(array_unique(array_filter($keys, function ($key) {
335 + return '' !== $key;
336 + })));
337 + }
338 +
339 + /**
340 + * @param mixed $hiddenFieldKeys parsed key list; anything else is treated as "nothing hidden"
341 + * @param string $fieldKey
342 + *
343 + * @return bool
344 + */
345 + public static function isFieldHidden($hiddenFieldKeys, $fieldKey)
346 + {
347 + return is_array($hiddenFieldKeys) && in_array($fieldKey, $hiddenFieldKeys, true);
348 + }
349 +
350 + public static function getFormPermissions($formId)
351 + {
352 + if (!isset(self::$formsPermissions[$formId])) {
353 + $formManager = FormManager::getInstance($formId);
354 + self::$formsPermissions[$formId] = $formManager->getFormPermission();
355 + }
356 +
357 + return self::$formsPermissions[$formId];
358 + }
359 +
360 + public static function is_current_user_can_access($formId, $action = 'entryViewAccess', $scope = '', $entryUserId = '')
361 + {
362 + $formPermissions = self::getFormPermissions($formId);
363 + $accessPermission = isset($formPermissions->{$action}) ? $formPermissions->{$action} : null;
364 + if (empty($accessPermission)) {
365 + return false;
366 + }
367 + if ('entryViewAccess' === $action && (!isset($accessPermission->preventPublicAccess) || !$accessPermission->preventPublicAccess)) {
368 + return true;
369 + }
370 + if (is_user_logged_in()) {
371 + $user = wp_get_current_user();
372 + $userId = (string) $user->ID;
373 + if (in_array('administrator', $user->roles) || current_user_can('manage_bitform')) {
374 + return true;
375 + }
376 + if ('entryEditAccess' === $action && !(isset($accessPermission->allowEntriesEdit) && $accessPermission->allowEntriesEdit)) {
377 + return false;
378 + }
379 + if (!empty($scope) && !empty($accessPermission->{$scope}) && is_string($accessPermission->{$scope})) {
380 + $accessRolesArray = explode(',', $accessPermission->{$scope});
381 + if (self::has_access_for_roles($user, $accessRolesArray) && empty($entryUserId)) {
382 + return true;
383 + }
384 + if (!empty($entryUserId) && (('ownEntries' === $scope && $userId === $entryUserId) || ('othersEntries' === $scope && $userId !== $entryUserId))) {
385 + return true;
386 + }
387 + }
388 +
389 + if (empty($scope) && isset($accessPermission->ownEntries) && !empty($accessPermission->ownEntries) && is_string($accessPermission->ownEntries)) {
390 + $accessRolesArray = explode(',', $accessPermission->ownEntries);
391 + if (self::has_access_for_roles($user, $accessRolesArray) && !empty($entryUserId) && $userId === $entryUserId) {
392 + return true;
393 + }
394 + if (self::has_access_for_roles($user, $accessRolesArray) && empty($entryUserId)) {
395 + return true;
396 + }
397 + }
398 +
399 + if (empty($scope) && isset($accessPermission->othersEntries) && !empty($accessPermission->othersEntries) && is_string($accessPermission->othersEntries)) {
400 + $accessRolesArray = explode(',', $accessPermission->othersEntries);
401 + if (self::has_access_for_roles($user, $accessRolesArray) && !empty($entryUserId) && $userId !== $entryUserId) {
402 + return true;
403 + }
404 + if (self::has_access_for_roles($user, $accessRolesArray) && empty($entryUserId)) {
405 + return true;
406 + }
407 + }
408 + }
409 + return false;
410 + }
411 +
412 + private static function has_access_for_roles($user, $accessRoles)
413 + {
414 + // If "all_logged_in_users" is in the allowed roles, grant access
415 + if (in_array('all_logged_in_users', $accessRoles)) {
416 + return true;
417 + }
418 + // Check if any of the user's roles match the allowed roles
419 + $userRoles = array_intersect($user->roles, $accessRoles);
420 + return !empty($userRoles);
100 421 }
101 422 }