PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.21.13
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.21.13
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 / Admin / Form / Helpers.php

Helpers.php in Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder 2.21.13, at includes/Admin/Form/Helpers.php

734 lines 23.7 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\Admin\Form;
4
5 use BitCode\BitForm\Core\Cryptography\Cryptography;
6 use BitCode\BitForm\Core\Database\FormEntryModel;
7 use BitCode\BitForm\Core\Util\FileHandler;
8 use BitCode\BitForm\Core\Util\Log;
9 use Exception;
10 use WP_Error;
11
12 class Helpers
13 {
14 private static $encryptEntryIds = [];
15
16 public static $file_upload_types = ['file-up', 'advanced-file-up'];
17
18 public static $repeated_array_type_data_fields = ['check', 'image-select'];
19
20 public static function filterNullEntries($entries)
21 {
22 $filteredEntries = [];
23 foreach ($entries as $entry) {
24 foreach ($entry as $key => $value) {
25 if (is_null($value)) {
26 unset($entry->$key);
27 }
28 }
29 if (count((array) $entry)) {
30 $filteredEntries[] = $entry;
31 }
32 }
33 return $filteredEntries;
34 }
35
36 public static function scriptLoader($src, $id, $instanceObj = null, $selector = '', $attrs = [], $integrity = null, $contentId = '')
37 {
38 $attributes = wp_json_encode($attrs);
39 $instObj = '';
40 if ($instanceObj) {
41 $instObj .= sprintf(
42 '
43 script.onload = function () {
44 bfSelect("#{%1$s}").querySelectorAll("{%2$s}").forEach(function(fld){
45 %3$s;
46 });
47 }
48 ',
49 $contentId,
50 $selector,
51 $instanceObj
52 );
53 }
54 return sprintf(
55 '
56 var script = document.createElement("script"), integrity = "%1$s", attrs = %2$s, id = "%3$s";
57 script.src = "%4$s";
58 script.id = id;
59 if(integrity){
60 script.integrity = integrity;
61 script.crossOrigin = "anonymous";
62 }
63 if(attrs){
64 Object.entries(attrs).forEach(function([key, val]){
65 script.setAttribute(key,val);
66 })
67 }
68 $instObj;
69 var bodyElm = document.body;
70 var alreadyExistScriptElm = bodyElm ? bodyElm.querySelector("script#$id"):null;
71 if(alreadyExistScriptElm){
72 bodyElm.removeChild(alreadyExistScriptElm)
73 }
74 if(!(window.recaptcha && id === "g-recaptcha-script")){
75 bodyElm.appendChild(script);
76 }
77 ',
78 $integrity,
79 $attributes,
80 $id,
81 $src,
82 );
83 }
84
85 public static function minifyJs($input)
86 {
87 if ('' === trim($input)) {
88 return $input;
89 }
90 return preg_replace(
91 [
92 '/ {2,}/',
93 '/\s*=\s*/',
94 '/\s*,\s*/',
95 '/\s+(?=\(|\{|\:|\?)|\t|(?:\r?\n[ \t]*)+/s'
96 ],
97 [' ', '=', ',', ''],
98 $input
99 );
100 }
101
102 public static function removeJsSingleLineComments($code)
103 {
104 $length = strlen($code);
105 $result = '';
106 $inString = false;
107 $inTemplate = false;
108 $inRegex = false;
109 $escapeNext = false;
110 $stringDelimiter = '';
111 $i = 0;
112
113 while ($i < $length) {
114 $char = $code[$i];
115 $nextChar = $i + 1 < $length ? $code[$i + 1] : '';
116
117 if ($escapeNext) {
118 $result .= $char;
119 $escapeNext = false;
120 } elseif ($inString) {
121 $result .= $char;
122 if ('\\' === $char) {
123 $escapeNext = true;
124 } elseif ($char === $stringDelimiter) {
125 $inString = false;
126 }
127 } elseif ($inTemplate) {
128 $result .= $char;
129 if ('\\' === $char) {
130 $escapeNext = true;
131 } elseif ('`' === $char) {
132 $inTemplate = false;
133 }
134 } elseif ($inRegex) {
135 $result .= $char;
136 if ('\\' === $char) {
137 $escapeNext = true;
138 } elseif ('/' === $char) {
139 $inRegex = false;
140 }
141 } else {
142 if ('"' === $char || "'" === $char) {
143 $inString = true;
144 $stringDelimiter = $char;
145 $result .= $char;
146 } elseif ('`' === $char) {
147 $inTemplate = true;
148 $result .= $char;
149 } elseif ('/' === $char) {
150 if ('/' === $nextChar) {
151 // Single-line comment found
152 while ($i < $length && "\n" !== $code[$i]) {
153 $i++;
154 }
155 continue; // skip until newline
156 } elseif ('*' === $nextChar) {
157 // Block comment start, just copy it (optional, depending on need)
158 $result .= $char;
159 } else {
160 // Assume division or regex
161 $result .= $char;
162 }
163 } else {
164 $result .= $char;
165 }
166 }
167 $i++;
168 }
169
170 return $result;
171 }
172
173 /**
174 * @method name : saveFile
175 * @description : save js/css field to disk
176 * @param : $path => like(dirName/css), $fileName => main.css, $script
177 * @return : boolean
178 */
179 public static function saveFile($path, $fileName, $script, $fileOpenMode = 'a')
180 {
181 try {
182 $rootDir = BITFORMS_CONTENT_DIR . DIRECTORY_SEPARATOR;
183 $path = trim($path, '/');
184 $pathArr = explode('/', $path); // like "fieldname/user => [Fieldname, user]
185 foreach ($pathArr as $d) {
186 $rootDir .= $d . DIRECTORY_SEPARATOR;
187 if (!realpath($rootDir)) {
188 wp_mkdir_p($rootDir);
189 }
190 }
191 $fullPath = $rootDir . $fileName;
192 if ('a' === $fileOpenMode) {
193 $result = FileHandler::appendFile($fullPath, $script);
194 } else {
195 $result = FileHandler::writeFile($fullPath, $script);
196 }
197 if (false === $result) {
198 throw new Exception("Failed to write to file: $fullPath");
199 }
200 return true;
201 } catch (\Exception $e) {
202 Log::debug_log($e->getMessage());
203 return false;
204 }
205 }
206
207 /**
208 * @method name : generatePathDirOrFile
209 * @dscription : generate path for js/css file
210 * @params : $path => like(dirName/css)
211 * @return : a string of full path
212 */
213 public static function generatePathDirOrFile($path)
214 {
215 $rootDir = BITFORMS_CONTENT_DIR . DIRECTORY_SEPARATOR;
216 $path = trim($path, '/');
217 $pathArr = explode('/', $path); // like "fieldname/user => [Fieldname, user]
218 foreach ($pathArr as $d) {
219 $rootDir .= $d . DIRECTORY_SEPARATOR;
220 }
221 return rtrim($rootDir, DIRECTORY_SEPARATOR);
222 }
223
224 public static function fileRead($filePath)
225 {
226 return FileHandler::readFile($filePath);
227 }
228
229 public static function getDataFromNestedPath($data, $key)
230 {
231 $keys = explode('->', $key);
232 $lastKey = array_pop($keys);
233 $dataType = is_array($data) ? 'array' : (is_object($data) ? 'object' : '');
234 if ('array' === $dataType) {
235 return self::accessFromArray($data, $keys, $lastKey);
236 }
237 if ('object' === $dataType) {
238 return self::accessFromObject($data, $keys, $lastKey);
239 }
240 }
241
242 private static function accessFromObject($data, $keys, $lastKey)
243 {
244 foreach ($keys as $k) {
245 if (!property_exists($data, $k)) {
246 return null;
247 }
248 $data = $data->$k;
249 }
250 return isset($data->$lastKey) ? $data->$lastKey : null;
251 }
252
253 private static function accessFromArray($data, $keys, $lastKey)
254 {
255 foreach ($keys as $k) {
256 if (!array_key_exists($k, $data)) {
257 return null;
258 }
259 $data = $data[$k];
260 }
261 return isset($data[$lastKey]) ? $data[$lastKey] : null;
262 }
263
264 public static function setDataToNestedPath($data, $key, $value)
265 {
266 $keys = explode('->', $key);
267 $lastKey = array_pop($keys);
268 foreach ($keys as $k) {
269 if (!array_key_exists($k, $data)) {
270 $data->$k = (object) [];
271 }
272 $data = $data->$k;
273 }
274 $data->$lastKey = json_decode(wp_json_encode($value));
275 ;
276 return $data;
277 }
278
279 public static function property_exists_nested($obj, $path = '', $valToCheck = null, $checkNegativeVal = 0)
280 {
281 $path = explode('->', $path);
282 $current = $obj;
283 foreach ($path as $key) {
284 if (is_object($current)) {
285 if (property_exists($current, $key)) {
286 $current = $current->{$key};
287 } else {
288 return false;
289 }
290 } else {
291 return false;
292 }
293 }
294 if (isset($valToCheck)) {
295 if ($checkNegativeVal) {
296 return $current !== $valToCheck;
297 }
298 return $current === $valToCheck;
299 }
300 return true;
301 }
302
303 public static function validateEntryTokenAndUser($entryToken, $entryId)
304 {
305 // check if the user is logged in
306 if (is_user_logged_in()) {
307 $user = wp_get_current_user();
308 if (in_array('administrator', $user->roles) || current_user_can('manage_bitform')) {
309 return true;
310 }
311 $entryModel = new FormEntryModel();
312 $entry = $entryModel->get(
313 'id, user_id, form_id',
314 [
315 'id' => $entryId,
316 'user_id' => $user->ID
317 ]
318 );
319 if (!is_wp_error($entry) && !empty($entry)) {
320 return true;
321 }
322 }
323 // check if the entry token is valid
324 if (isset($entryToken) && $entryToken) {
325 $decryptEntryId = Cryptography::decrypt($entryToken, self::getBitformSalt());
326 if ($decryptEntryId === $entryId) {
327 return true;
328 }
329 }
330
331 return false;
332 }
333
334 /**
335 * Validate workflow trigger token with proper input sanitization
336 *
337 * @param object $request The AJAX request object
338 * @param string $formID The form ID (already sanitized)
339 * @return array ['valid' => bool, 'error' => string, 'triggerData' => object|null, 'isAdminBypass' => bool]
340 */
341 public static function validateWorkflowTriggerToken($request, $formID)
342 {
343 // Sanitize and validate cronNotOk array
344 if (!isset($request->cronNotOk) || !is_array($request->cronNotOk)) {
345 return [
346 'valid' => false,
347 'error' => 'Missing or invalid cronNotOk data',
348 'triggerData' => null,
349 'isAdminBypass' => false
350 ];
351 }
352
353 // Validate and sanitize entry ID and log ID (must be integers)
354 if (!isset($request->cronNotOk[0]) || !is_numeric($request->cronNotOk[0])) {
355 Log::debug_log('Invalid entry ID in cronNotOk[0]');
356 return ['valid' => false, 'error' => 'Invalid entry ID', 'triggerData' => null, 'isAdminBypass' => false];
357 }
358
359 if (!isset($request->cronNotOk[1]) || !is_numeric($request->cronNotOk[1])) {
360 Log::debug_log('Invalid log ID in cronNotOk[1]');
361 return ['valid' => false, 'error' => 'Invalid log ID', 'triggerData' => null, 'isAdminBypass' => false];
362 }
363
364 $entryID = absint($request->cronNotOk[0]);
365 $logID = absint($request->cronNotOk[1]);
366
367 // Check for administrator bypass
368 $isAdminBypass = false;
369 if (is_user_logged_in()) {
370 $user = wp_get_current_user();
371 if (in_array('administrator', $user->roles) || current_user_can('manage_bitform')) {
372 Log::debug_log('Admin bypass: Workflow triggered by ' . $user->user_login . ' for entryID=' . $entryID);
373 return [
374 'valid' => true,
375 'error' => '',
376 'triggerData' => null,
377 'isAdminBypass' => true
378 ];
379 }
380
381 // For logged-in non-admin users: verify nonce
382 if (isset($request->token, $request->id)) {
383 if (!wp_verify_nonce($request->token, $request->id)) {
384 Log::debug_log('Nonce verification failed for logged-in user. FormID=' . $formID);
385 return [
386 'valid' => false,
387 'error' => 'Invalid nonce for logged-in user',
388 'triggerData' => null,
389 'isAdminBypass' => false
390 ];
391 }
392 } else {
393 Log::debug_log('Missing nonce for logged-in user. FormID=' . $formID);
394 return [
395 'valid' => false,
396 'error' => 'Missing nonce',
397 'triggerData' => null,
398 'isAdminBypass' => false
399 ];
400 }
401 }
402
403 // For non-admin users (both logged-in and anonymous): validate one-time trigger token
404 if (!isset($request->cronNotOk[3]) || empty($request->cronNotOk[3])) {
405 Log::debug_log('Missing trigger token for formID=' . $formID . ', entryID=' . $entryID);
406 return [
407 'valid' => false,
408 'error' => 'Missing trigger token',
409 'triggerData' => null,
410 'isAdminBypass' => false
411 ];
412 }
413
414 $submittedToken = sanitize_text_field($request->cronNotOk[3]);
415
416 // Validate trigger token from transient
417 $transientData = get_transient("bitform_trigger_transient_{$entryID}");
418
419 if (empty($transientData)) {
420 Log::debug_log('Trigger token transient missing for entryID=' . $entryID . ', logID=' . $logID);
421 return [
422 'valid' => false,
423 'error' => 'Trigger token expired or missing',
424 'triggerData' => null,
425 'isAdminBypass' => false
426 ];
427 }
428
429 $triggerData = is_string($transientData) ? json_decode($transientData) : $transientData;
430 // Verify token matches and belongs to this entry/log
431 if (
432 !isset($triggerData['trigger_token'])
433 || !hash_equals($triggerData['trigger_token'], $submittedToken)
434 || (int)$triggerData['entryID'] !== $entryID
435 || (int)$triggerData['logID'] !== $logID
436 ) {
437 Log::debug_log('Invalid trigger token for entryID=' . $entryID . ', logID=' . $logID);
438 return [
439 'valid' => false,
440 'error' => 'Invalid trigger token',
441 'triggerData' => null,
442 'isAdminBypass' => false
443 ];
444 }
445
446 // Token is valid - delete transient to prevent reuse (single-use token)
447 delete_transient("bitform_trigger_transient_{$entryID}");
448 Log::debug_log('Valid trigger token consumed for entryID=' . $entryID);
449
450 return [
451 'valid' => true,
452 'error' => '',
453 'triggerData' => $triggerData,
454 'isAdminBypass' => false
455 ];
456 }
457
458 public static function validateFormEntryEditPermission($formId, $entryId)
459 {
460 if (is_user_logged_in()) {
461 if (current_user_can('manage_bitform') || current_user_can('bitform_entry_edit') || current_user_can('edit_post')) {
462 return true;
463 }
464 }
465 return false;
466 }
467
468 public static function honeypotEncryptedToken($str)
469 {
470 $token = base64_encode(base64_encode($str));
471 return $token;
472 }
473
474 public static function csrfEecrypted()
475 {
476 $secretKey = get_option('bf_csrf_secret');
477 if (!$secretKey) {
478 $secretKey = 'bf-' . time();
479 update_option('bf_csrf_secret', $secretKey);
480 }
481 $tIdenty = base64_encode(\random_bytes(32));
482 $csrf = \base64_encode(\hash_hmac('sha256', $tIdenty, $secretKey, true));
483 return ['csrf' => $csrf, 't_identity' => $tIdenty];
484 }
485
486 public static function csrfDecrypted($identy, $token)
487 {
488 $secretKey = get_option('bf_csrf_secret');
489 return \hash_equals(
490 \base64_encode(\hash_hmac('sha256', $identy, $secretKey, true)),
491 $token
492 );
493 }
494
495 public static function checkIsIntArr($arr)
496 {
497 $filteredArray = array_filter($arr, 'is_numeric');
498 $intArray = array_map('intval', $filteredArray);
499 $result = count($arr) === count($intArray);
500
501 return $result;
502 }
503
504 public static function getTruncatedEncryptToken($str, $length = 20)
505 {
506 $token = hash_hmac('sha256', $str, self::getBitformSalt());
507 return substr($token, 0, $length);
508 }
509
510 public static function getAuthSaltEncryptToken($str, $length = 20)
511 {
512 if (!$str) {
513 return '';
514 }
515 if (!defined('AUTH_SALT')) {
516 return '';
517 }
518
519 return substr(hash_hmac('sha256', $str, AUTH_SALT), 0, $length);
520 }
521
522 public static function getEncryptedEntryId($entryId)
523 {
524 if (!isset(self::$encryptEntryIds[$entryId])) {
525 self::$encryptEntryIds[$entryId] = self::getTruncatedEncryptToken($entryId);
526 }
527 return self::$encryptEntryIds[$entryId];
528 }
529
530 public static function getFullPathWithEncryptedEntryId($formId, $entryId)
531 {
532 $encryptDirectory = Helpers::getEncryptedEntryId($entryId);
533 return BITFORMS_UPLOAD_DIR . DIRECTORY_SEPARATOR . $formId . DIRECTORY_SEPARATOR . $encryptDirectory;
534 }
535
536 public static function getWebPathWithEncryptedEntryId($formId, $entryId)
537 {
538 $encryptDirectory = Helpers::getEncryptedEntryId($entryId);
539 return BITFORMS_UPLOAD_BASE_URL . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . $formId . DIRECTORY_SEPARATOR . $encryptDirectory;
540 }
541
542 public static function PDFPassHash($entryId)
543 {
544 return abs(crc32($entryId));
545 }
546
547 public static function encryptBinaryData($plaintext)
548 {
549 $iv = openssl_random_pseudo_bytes(16);
550 $encrypted = openssl_encrypt($plaintext, 'AES-256-CBC', BITFORMS_SECRET_KEY, OPENSSL_RAW_DATA, $iv);
551
552 return bin2hex($iv . $encrypted);
553 }
554
555 public static function decryptBinaryData($encryptedHex)
556 {
557 $decoded = hex2bin($encryptedHex);
558 $iv = substr($decoded, 0, 16);
559 $cipherText = substr($decoded, 16);
560
561 return openssl_decrypt($cipherText, 'AES-256-CBC', BITFORMS_SECRET_KEY, OPENSSL_RAW_DATA, $iv);
562 }
563
564 /**
565 * Sanitize user-provided HTML content by removing dangerous JS code
566 * while allowing all valid HTML/CSS.
567 *
568 * @param string $html Raw HTML from user input
569 * @return string Sanitized safe HTML
570 */
571 public static function sanitizeUserHTML(string $html): string
572 {
573 // Remove <script> tags entirely
574 $html = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $html);
575
576 // Remove event handler attributes (like onclick, onload, etc.)
577 $html = preg_replace_callback('/<[^>]+>/i', function ($matches) {
578 return preg_replace('/\s*on\w+\s*=\s*"[^"]*"/i', '', $matches[0]); // on*=""
579 }, $html);
580
581 $html = preg_replace_callback('/<[^>]+>/i', function ($matches) {
582 return preg_replace("/\s*on\w+\s*=\s*'[^']*'/i", '', $matches[0]); // on*=''
583 }, $html);
584
585 // Remove javascript: from href or src
586 $html = preg_replace('/(href|src)\s*=\s*([\'"])\s*javascript:[^\'"]*\2/i', '', $html);
587
588 return $html;
589 }
590
591 public static function sanitizeUrlParam($param)
592 {
593 if (preg_match('/\.\.?\//', $param)) {
594 return new WP_Error('parameter_error', 'Invalid URL parameter');
595 }
596
597 $param = htmlspecialchars(trim($param), ENT_QUOTES, 'UTF-8');
598 return sanitize_text_field($param);
599 }
600
601 public static function replaceFieldsDefaultErrorMsg($fields)
602 {
603 try {
604 $appSettings = get_option('bitform_app_settings', (object) []);
605 if (!isset($appSettings->globalMessages) || !isset($appSettings->globalMessages->err)) {
606 return $fields;
607 }
608
609 $globalErrMsg = $appSettings->globalMessages->err;
610 $templateCache = []; // [type_errKey] => compiled template
611
612 foreach ($fields as $fieldKey => $field) {
613 if (!isset($field->err) || !is_object($field->err)) {
614 continue;
615 }
616
617 foreach ($field->err as $errKey => $errObj) {
618 if (!isset($errObj->dflt)) {
619 continue;
620 }
621
622 $cacheKey = $field->typ . '_' . $errKey;
623 $template = null;
624
625 // 1. Check Cache First
626 if (isset($templateCache[$cacheKey])) {
627 $template = $templateCache[$cacheKey];
628 } else {
629 // 2. Lookup from globalErrMsg
630 if (isset($globalErrMsg->{$field->typ}->{$errKey})) {
631 $template = $globalErrMsg->{$field->typ}->{$errKey};
632 } elseif (isset($globalErrMsg->{$errKey}) && !is_object($globalErrMsg->{$errKey})) {
633 $template = $globalErrMsg->{$errKey};
634 }
635
636 // 3. Cache it
637 if ($template) {
638 $templateCache[$cacheKey] = $template;
639 }
640 }
641
642 // 4. Apply Template if Found
643 if ($template) {
644 $finalMsg = self::replaceShortcodeInErrorMsg($template, $field);
645 // 5. Sanitize final output
646 $field->err->{$errKey}->dflt = wp_kses_post($finalMsg);
647 }
648 }
649
650 $fields->{$fieldKey} = $field;
651 }
652 } catch (Exception $e) {
653 Log::debug_log('Error In Replacing Fields Default Error messages: ' . $e->getMessage());
654 }
655 return $fields;
656 }
657
658 //replace shortcode in error message
659 public static function replaceShortcodeInErrorMsg($msg, $field)
660 {
661 $shortcodes = [
662 '${field.label}' => isset($field->lbl) ? $field->lbl : '',
663 '${field.minimum}' => isset($field->mn) ? $field->mn : '',
664 '${field.maximum}' => isset($field->mx) ? $field->mx : '',
665 '${field.minimum_file}' => isset($field->config->minFile) ? $field->config->minFile : '',
666 '${field.maximum_file}' => isset($field->config->maxFile) ? $field->config->maxFile : '',
667 '${field.maximum_size}' => isset($field->config->maxSize) ? $field->config->maxSize : '',
668 '${field.minimum_amount}' => isset($field->config->minValue) ? $field->config->minValue : '',
669 '${field.maximum_amount}' => isset($field->config->maxValue) ? $field->config->maxValue : '',
670 ];
671 $msg = str_replace(array_keys($shortcodes), array_values($shortcodes), $msg);
672 return $msg;
673 }
674
675 public static function getDefaultGlobalMessages()
676 {
677 $defaultGlobalMessages = [
678 'err' => [
679 'req' => '<p style="margin:0">' . __('This field is required', 'bit-form') . '</p>',
680 'email' => [
681 'invalid' => '<p style="margin:0">' . __('Please, enter a valid email address', 'bit-form') . '</p>',
682 ],
683 'url' => [
684 'invalid' => '<p style="margin:0">' . __('Please, enter a valid URL', 'bit-form') . '</p>',
685 ],
686 'mn' => '<p style="margin:0">' . __('Minimum ${field.minimum} is required', 'bit-form') . '</p>',
687 'mx' => '<p style="margin:0">' . __('Maximum ${field.maximum} is allowed', 'bit-form') . '</p>',
688 'number' => [
689 'invalid' => '<p style="margin:0">' . __('Please, enter only numbers', 'bit-form') . '</p>',
690 ],
691 'phone-number' => [
692 'invalid' => '<p style="margin:0">' . __('Please, enter a valid phone number', 'bit-form') . '</p>',
693 ],
694 'check' => [
695 'mn' => '<p style="margin:0">' . __('Select at least ${field.minimum} option(s)', 'bit-form') . '</p>',
696 'mx' => '<p style="margin:0">' . __('Please, select no more than ${field.maximum} option(s)', 'bit-form') . '</p>',
697 ],
698 'select' => [
699 'mn' => '<p style="margin:0">' . __('Select at least ${field.minimum} option(s)', 'bit-form') . '</p>',
700 'mx' => '<p style="margin:0">' . __('Please, select no more than ${field.maximum} option(s)', 'bit-form') . '</p>',
701 ],
702 'image-select' => [
703 'mn' => '<p style="margin:0">' . __('Select at least ${field.minimum} option(s)', 'bit-form') . '</p>',
704 'mx' => '<p style="margin:0">' . __('Please, select no more than ${field.maximum} option(s)', 'bit-form') . '</p>',
705 ],
706 'inputMask' => '<p style="margin:0">' . __('Input does not match the required pattern', 'bit-form') . '</p>',
707 'regexr' => '<p style="margin:0">' . __('Input does not match the required pattern', 'bit-form') . '</p>',
708 'minFile' => '<p style="margin:0">' . __('Minimum ${field.minimum_file} file(s) required', 'bit-form') . '</p>',
709 'maxFile' => '<p style="margin:0">' . __('Maximum ${field.maximum_file} file(s) allowed', 'bit-form') . '</p>',
710 'maxSize' => '<p style="margin:0">' . __('Maximum file size exceeded. (Max: ${field.maximum_size}MB)', 'bit-form') . '</p>',
711 'fileType' => '<p style="margin:0">' . __('File type is not supported', 'bit-form') . '</p>',
712 'entryUnique' => '<p style="margin:0">' . __('This value is already taken. Please, choose a different one.', 'bit-form') . '</p>',
713 'userUnique' => '<p style="margin:0">' . __('This username or email is already registered. Please, use another.', 'bit-form') . '</p>',
714 'otherOptReq' => '<p style="margin:0">' . __('Custom Option Required', 'bit-form') . '</p>',
715 'minValue' => '<p style="margin:0">' . __('Minimum amount of ${field.minimum_amount} is required', 'bit-form') . '</p>',
716 'maxValue' => '<p style="margin:0">' . __('Maximum amount of ${field.maximum_amount} is allowed', 'bit-form') . '</p>',
717 ],
718 ];
719
720 // Convert array to object recursively
721 return json_decode(json_encode($defaultGlobalMessages));
722 }
723
724 public static function getBitformSalt()
725 {
726 $salt = get_option('bitforms_salt');
727 if (!$salt) {
728 $salt = bin2hex(\random_bytes(32));
729 update_option('bitforms_salt', $salt);
730 }
731 return $salt;
732 }
733 }
734