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 +247 -7 2.10.0 → V3.3.2 View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace BitCode\BitForm\Core\Util;
4 4
5 +use BitCode\BitForm\Core\Form\FormManager;
5 6 use WP_Rewrite;
6 7
7 8 final class FrontendHelpers
8 9 {
@@ -7,9 +8,12 @@
7 8 final class FrontendHelpers
8 9 {
9 10 public static $isPageBuilder = false;
10 11 public static $bfFrontendFormIds = [];
12 + public static $bfFrontendViewIds = [];
11 13 public static $bfFormIdsFromPost = [];
14 + public static $bfViewIdsFromPost = [];
15 + private static $formsPermissions = [];
12 16
13 17 public static $pageBuilderQueryParamsList = [
14 18 'et_pb_preview' => 'true', // divi
15 19 'vc_editable' => 'true', // wp bakery
@@ -33,10 +37,15 @@
33 37 return [];
34 38 }
35 39 $postId = $post->ID;
36 40 $shortcodeFormIds = [];
37 - // $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");
38 - $bfMetaValues = $wpdb->get_results('SELECT meta_value FROM `' . $wpdb->postmeta . "` WHERE `post_id`={$postId}");
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 + );
39 48 $postContent = $post->post_content;
40 49 $bfMetaValues[] = (object) ['meta_value' => $postContent];
41 50 foreach ($bfMetaValues as $bfShortcut) {
42 51 $meta_value = (is_string($bfShortcut->meta_value) && !empty($bfShortcut->meta_value)) ? $bfShortcut->meta_value : '';
@@ -49,14 +58,73 @@
49 58 }
50 59
51 60 public static function getShortCodeIds($content = '')
52 61 {
53 - \preg_match_all("/\[bitform\s+id\s*=\s*('|\")\s*(\d+)\s*('|\")\]/", $content, $shortCode);
54 - $ids = $shortCode[2];
62 + $pattern = '/' . get_shortcode_regex(['bitform']) . '/';
63 + \preg_match_all($pattern, $content, $short);
55 64
56 - return $ids;
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'];
70 + }
71 + }
72 +
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];
79 +
80 + return $formIds;
57 81 }
58 82
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;
96 +
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 + }
103 +
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 + }
120 + }
121 +
122 + // \preg_match_all('/\[bitform-view\s+\b[^\]]*\bid\s*=\s*["\']?(\d+)["\']?[^\]]*\]/', $content, $shortCode);
123 + // $ids = $shortCode[1];
124 + return $viewIds;
125 + }
126 +
59 127 public static function checkIsPageBuilder($srvr)
60 128 {
61 129 if (is_admin()) {
62 130 self::$isPageBuilder = true;
@@ -101,11 +169,12 @@
101 169
102 170 public static function isRestRequest()
103 171 {
104 172 $prefix = rest_get_url_prefix();
173 + // Read-only check to detect REST requests for routing; no state change performed here.
105 174 if (defined('REST_REQUEST') && REST_REQUEST
106 - || isset($_GET['rest_route'])
107 - && 0 === strpos(trim($_GET['rest_route'], '\\/'), $prefix, 0)) {
175 + || (isset($_GET['rest_route'])
176 + && 0 === strpos(trim(sanitize_text_field(wp_unslash($_GET['rest_route'])), '\\/'), $prefix, 0))) {
108 177 return true;
109 178 }
110 179 global $wp_rewrite;
111 180 if (null === $wp_rewrite) {
@@ -115,8 +184,31 @@
115 184 $current_url = wp_parse_url(add_query_arg([]));
116 185 return 0 === strpos($current_url['path'], $rest_url['path'], 0);
117 186 }
118 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 +
119 211 public static function isAdminRequest()
120 212 {
121 213 $current_url = home_url(add_query_arg(null, null));
122 214 $admin_url = strtolower(admin_url());
@@ -166,17 +258,165 @@
166 258 $allFormIds = array_merge($bfFrontendFormIds, $bfFormIdsFromPost);
167 259 return $allFormIds;
168 260 }
169 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 +
170 270 public static function getAllUniqFormIdsInPage()
171 271 {
172 272 return array_unique(self::getAllFormIdsInPage());
173 273 }
174 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 +
175 301 public static function hasMultipleForms()
176 302 {
177 303 $bfUniqFormIds = self::getAllFormIdsInPage();
304 + self::checkIsPageBuilder($_SERVER);
178 305 $isPageBuilder = self::$isPageBuilder;
179 306 $bfMultipleFormsExists = $isPageBuilder ? true : count($bfUniqFormIds) > 1;
180 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);
181 421 }
182 422 }