PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.1.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.1.0
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 3.1.0, at includes/Admin/Form/Helpers.php

775 lines 25.4 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 return $data;
276 }
277
278 public static function property_exists_nested($obj, $path = '', $valToCheck = null, $checkNegativeVal = 0)
279 {
280 $path = explode('->', $path);
281 $current = $obj;
282 foreach ($path as $key) {
283 if (is_object($current)) {
284 if (property_exists($current, $key)) {
285 $current = $current->{$key};
286 } else {
287 return false;
288 }
289 } else {
290 return false;
291 }
292 }
293 if (isset($valToCheck)) {
294 if ($checkNegativeVal) {
295 return $current !== $valToCheck;
296 }
297 return $current === $valToCheck;
298 }
299 return true;
300 }
301
302 public static function validateEntryTokenAndUser($entryToken, $entryId)
303 {
304 // check if the user is logged in
305 if (is_user_logged_in()) {
306 $user = wp_get_current_user();
307 if (in_array('administrator', $user->roles) || current_user_can('manage_bitform')) {
308 return true;
309 }
310 $entryModel = new FormEntryModel();
311 $entry = $entryModel->get(
312 'id, user_id, form_id',
313 [
314 'id' => $entryId,
315 'user_id' => $user->ID
316 ]
317 );
318 if (!is_wp_error($entry) && !empty($entry)) {
319 return true;
320 }
321 }
322 // check if the entry token is valid
323 if (isset($entryToken) && $entryToken) {
324 $decryptEntryId = Cryptography::decrypt($entryToken, self::getBitformSalt());
325 if ($decryptEntryId === $entryId) {
326 return true;
327 }
328 }
329
330 return false;
331 }
332
333 /**
334 * Validate workflow trigger token with proper input sanitization
335 *
336 * @param object $request The AJAX request object
337 * @param string $formID The form ID (already sanitized)
338 * @return array ['valid' => bool, 'error' => string, 'triggerData' => object|null, 'isAdminBypass' => bool]
339 */
340 public static function validateWorkflowTriggerToken($request, $formID)
341 {
342 // Sanitize and validate cronNotOk array
343 if (!isset($request->cronNotOk) || !is_array($request->cronNotOk)) {
344 return [
345 'valid' => false,
346 'error' => 'Missing or invalid cronNotOk data',
347 'triggerData' => null,
348 'isAdminBypass' => false
349 ];
350 }
351
352 // Validate and sanitize entry ID and log ID (must be integers)
353 if (!isset($request->cronNotOk[0]) || !is_numeric($request->cronNotOk[0])) {
354 Log::debug_log('Invalid entry ID in cronNotOk[0]');
355 return ['valid' => false, 'error' => 'Invalid entry ID', 'triggerData' => null, 'isAdminBypass' => false];
356 }
357
358 if (!isset($request->cronNotOk[1]) || !is_numeric($request->cronNotOk[1])) {
359 Log::debug_log('Invalid log ID in cronNotOk[1]');
360 return ['valid' => false, 'error' => 'Invalid log ID', 'triggerData' => null, 'isAdminBypass' => false];
361 }
362
363 $entryID = absint($request->cronNotOk[0]);
364 $logID = absint($request->cronNotOk[1]);
365
366 // Check for administrator bypass
367 $isAdminBypass = false;
368 if (is_user_logged_in()) {
369 $user = wp_get_current_user();
370 if (in_array('administrator', $user->roles) || current_user_can('manage_bitform')) {
371 Log::debug_log('Admin bypass: Workflow triggered by ' . $user->user_login . ' for entryID=' . $entryID);
372 return [
373 'valid' => true,
374 'error' => '',
375 'triggerData' => null,
376 'isAdminBypass' => true
377 ];
378 }
379
380 // For logged-in non-admin users: verify nonce
381 if (isset($request->token, $request->id)) {
382 if (!wp_verify_nonce($request->token, $request->id)) {
383 Log::debug_log('Nonce verification failed for logged-in user. FormID=' . $formID);
384 return [
385 'valid' => false,
386 'error' => 'Invalid nonce for logged-in user',
387 'triggerData' => null,
388 'isAdminBypass' => false
389 ];
390 }
391 } else {
392 Log::debug_log('Missing nonce for logged-in user. FormID=' . $formID);
393 return [
394 'valid' => false,
395 'error' => 'Missing nonce',
396 'triggerData' => null,
397 'isAdminBypass' => false
398 ];
399 }
400 }
401
402 // For non-admin users (both logged-in and anonymous): validate one-time trigger token
403 if (!isset($request->cronNotOk[3]) || empty($request->cronNotOk[3])) {
404 Log::debug_log('Missing trigger token for formID=' . $formID . ', entryID=' . $entryID);
405 return [
406 'valid' => false,
407 'error' => 'Missing trigger token',
408 'triggerData' => null,
409 'isAdminBypass' => false
410 ];
411 }
412
413 $submittedToken = sanitize_text_field($request->cronNotOk[3]);
414
415 // Validate trigger token from transient
416 $transientData = get_transient("bitform_trigger_transient_{$entryID}");
417
418 if (empty($transientData)) {
419 Log::debug_log('Trigger token transient missing for entryID=' . $entryID . ', logID=' . $logID);
420 return [
421 'valid' => false,
422 'error' => 'Trigger token expired or missing',
423 'triggerData' => null,
424 'isAdminBypass' => false
425 ];
426 }
427
428 $triggerData = is_string($transientData) ? json_decode($transientData) : $transientData;
429 // Verify token matches and belongs to this entry/log
430 if (
431 !isset($triggerData['trigger_token'])
432 || !hash_equals($triggerData['trigger_token'], $submittedToken)
433 || (int)$triggerData['entryID'] !== $entryID
434 || (int)$triggerData['logID'] !== $logID
435 ) {
436 Log::debug_log('Invalid trigger token for entryID=' . $entryID . ', logID=' . $logID);
437 return [
438 'valid' => false,
439 'error' => 'Invalid trigger token',
440 'triggerData' => null,
441 'isAdminBypass' => false
442 ];
443 }
444
445 // Token is valid - delete transient to prevent reuse (single-use token)
446 delete_transient("bitform_trigger_transient_{$entryID}");
447 Log::debug_log('Valid trigger token consumed for entryID=' . $entryID);
448
449 return [
450 'valid' => true,
451 'error' => '',
452 'triggerData' => $triggerData,
453 'isAdminBypass' => false
454 ];
455 }
456
457 public static function validateFormEntryEditPermission($formId, $entryId)
458 {
459 if (is_user_logged_in()) {
460 if (current_user_can('manage_bitform') || current_user_can('bitform_entry_edit') || current_user_can('edit_post')) {
461 return true;
462 }
463 }
464 return false;
465 }
466
467 public static function honeypotEncryptedToken($str)
468 {
469 $token = base64_encode(base64_encode($str));
470 return $token;
471 }
472
473 public static function csrfEecrypted()
474 {
475 $secretKey = get_option('bitform_csrf_secret');
476 if (!$secretKey) {
477 $secretKey = 'bf-' . time();
478 update_option('bitform_csrf_secret', $secretKey);
479 }
480 $tIdenty = base64_encode(\random_bytes(32));
481 $csrf = \base64_encode(\hash_hmac('sha256', $tIdenty, $secretKey, true));
482 return ['csrf' => $csrf, 't_identity' => $tIdenty];
483 }
484
485 public static function csrfDecrypted($identy, $token)
486 {
487 $secretKey = get_option('bitform_csrf_secret');
488 return \hash_equals(
489 \base64_encode(\hash_hmac('sha256', $identy, $secretKey, true)),
490 $token
491 );
492 }
493
494 public static function checkIsIntArr($arr)
495 {
496 $filteredArray = array_filter($arr, 'is_numeric');
497 $intArray = array_map('intval', $filteredArray);
498 $result = count($arr) === count($intArray);
499
500 return $result;
501 }
502
503 public static function getTruncatedEncryptToken($str, $length = 20)
504 {
505 $token = hash_hmac('sha256', $str, self::getBitformSalt());
506 return substr($token, 0, $length);
507 }
508
509 public static function getAuthSaltEncryptToken($str, $length = 20)
510 {
511 if (!$str) {
512 return '';
513 }
514 if (!defined('AUTH_SALT')) {
515 return '';
516 }
517
518 return substr(hash_hmac('sha256', $str, AUTH_SALT), 0, $length);
519 }
520
521 public static function getEncryptedEntryId($entryId)
522 {
523 if (!isset(self::$encryptEntryIds[$entryId])) {
524 self::$encryptEntryIds[$entryId] = self::getTruncatedEncryptToken($entryId);
525 }
526 return self::$encryptEntryIds[$entryId];
527 }
528
529 public static function getFullPathWithEncryptedEntryId($formId, $entryId)
530 {
531 $uploadDir = rtrim(BITFORMS_UPLOAD_DIR, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $formId . DIRECTORY_SEPARATOR;
532 $encryptDirectoryId = Helpers::getEncryptedEntryId($entryId);
533 $encryptDirectory = $uploadDir . $encryptDirectoryId;
534 if (is_dir($encryptDirectory)) {
535 return $encryptDirectory;
536 }
537
538 $oldEntriesFileUploadDir = Helpers::getOldEntriesFileUploadDir($uploadDir, $entryId);
539 if (!empty($oldEntriesFileUploadDir) && is_dir($oldEntriesFileUploadDir)) {
540 return $oldEntriesFileUploadDir;
541 }
542
543 return $encryptDirectory;
544 }
545
546 public static function getWebPathWithEncryptedEntryId($formId, $entryId)
547 {
548 $serverFileUploadDir = rtrim(BITFORMS_UPLOAD_DIR, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $formId . DIRECTORY_SEPARATOR;
549 $webFileDirectory = BITFORMS_UPLOAD_BASE_URL . '/' . 'uploads' . '/' . $formId . '/';
550 $encryptDirectoryId = Helpers::getEncryptedEntryId($entryId);
551 $encryptDirectory = $serverFileUploadDir . $encryptDirectoryId;
552 if (is_dir($encryptDirectory)) {
553 return $webFileDirectory . $encryptDirectoryId;
554 }
555
556 $authSaltEncryptedEntryId = Helpers::getAuthSaltEncryptToken($entryId);
557 $authSaltEncryptedDirectory = $serverFileUploadDir . $authSaltEncryptedEntryId;
558 if (!empty($authSaltEncryptedEntryId) && is_dir($authSaltEncryptedDirectory)) {
559 return $webFileDirectory . $authSaltEncryptedEntryId;
560 }
561 $previousEntryDirectory = $serverFileUploadDir . $entryId;
562 if (!empty($previousEntryDirectory) && is_dir($previousEntryDirectory)) {
563 return $webFileDirectory . $entryId;
564 }
565
566 return $webFileDirectory . $encryptDirectoryId;
567 }
568
569 public static function getOldEntriesFileUploadDir($uploadDir, $entry_id)
570 {
571 $authSaltEncryptedEntryId = Helpers::getAuthSaltEncryptToken($entry_id);
572 $authSaltEncryptedDirectory = $uploadDir . $authSaltEncryptedEntryId;
573 if (!empty($authSaltEncryptedEntryId) && is_dir($authSaltEncryptedDirectory)) {
574 return $authSaltEncryptedDirectory;
575 }
576 $previousEntryDirectory = $uploadDir . $entry_id;
577 if (!empty($previousEntryDirectory) && is_dir($previousEntryDirectory)) {
578 return $previousEntryDirectory;
579 }
580 return '';
581 }
582
583 public static function PDFPassHash($entryId)
584 {
585 return abs(crc32($entryId));
586 }
587
588 public static function encryptBinaryData($plaintext)
589 {
590 $iv = openssl_random_pseudo_bytes(16);
591 $encrypted = openssl_encrypt($plaintext, 'AES-256-CBC', BITFORMS_SECRET_KEY, OPENSSL_RAW_DATA, $iv);
592
593 return bin2hex($iv . $encrypted);
594 }
595
596 public static function decryptBinaryData($encryptedHex)
597 {
598 $decoded = hex2bin($encryptedHex);
599 $iv = substr($decoded, 0, 16);
600 $cipherText = substr($decoded, 16);
601
602 return openssl_decrypt($cipherText, 'AES-256-CBC', BITFORMS_SECRET_KEY, OPENSSL_RAW_DATA, $iv);
603 }
604
605 /**
606 * Sanitize user-provided HTML content by removing dangerous JS code
607 * while allowing all valid HTML/CSS.
608 *
609 * @param string $html Raw HTML from user input
610 * @return string Sanitized safe HTML
611 */
612 public static function sanitizeUserHTML(string $html): string
613 {
614 // Remove <script> tags entirely
615 $html = preg_replace('/<script\b[^>]*>(.*?)<\/script>/is', '', $html);
616
617 // Remove event handler attributes (like onclick, onload, etc.)
618 $html = preg_replace_callback('/<[^>]+>/i', function ($matches) {
619 return preg_replace('/\s*on\w+\s*=\s*"[^"]*"/i', '', $matches[0]); // on*=""
620 }, $html);
621
622 $html = preg_replace_callback('/<[^>]+>/i', function ($matches) {
623 return preg_replace("/\s*on\w+\s*=\s*'[^']*'/i", '', $matches[0]); // on*=''
624 }, $html);
625
626 // Remove javascript: from href or src
627 $html = preg_replace('/(href|src)\s*=\s*([\'"])\s*javascript:[^\'"]*\2/i', '', $html);
628
629 return $html;
630 }
631
632 public static function sanitizeUrlParam($param)
633 {
634 if (preg_match('/\.\.?\//', $param)) {
635 return new WP_Error('parameter_error', 'Invalid URL parameter');
636 }
637
638 $param = htmlspecialchars(trim($param), ENT_QUOTES, 'UTF-8');
639 return sanitize_text_field($param);
640 }
641
642 public static function replaceFieldsDefaultErrorMsg($fields)
643 {
644 try {
645 $appSettings = get_option('bitform_app_settings', (object) []);
646 if (!isset($appSettings->globalMessages) || !isset($appSettings->globalMessages->err)) {
647 return $fields;
648 }
649
650 $globalErrMsg = $appSettings->globalMessages->err;
651 $templateCache = []; // [type_errKey] => compiled template
652
653 foreach ($fields as $fieldKey => $field) {
654 if (!isset($field->err) || !is_object($field->err)) {
655 continue;
656 }
657
658 foreach ($field->err as $errKey => $errObj) {
659 if (!isset($errObj->dflt)) {
660 continue;
661 }
662
663 $cacheKey = $field->typ . '_' . $errKey;
664 $template = null;
665
666 // 1. Check Cache First
667 if (isset($templateCache[$cacheKey])) {
668 $template = $templateCache[$cacheKey];
669 } else {
670 // 2. Lookup from globalErrMsg
671 if (isset($globalErrMsg->{$field->typ}->{$errKey})) {
672 $template = $globalErrMsg->{$field->typ}->{$errKey};
673 } elseif (isset($globalErrMsg->{$errKey}) && !is_object($globalErrMsg->{$errKey})) {
674 $template = $globalErrMsg->{$errKey};
675 }
676
677 // 3. Cache it
678 if ($template) {
679 $templateCache[$cacheKey] = $template;
680 }
681 }
682
683 // 4. Apply Template if Found
684 if ($template) {
685 $finalMsg = self::replaceShortcodeInErrorMsg($template, $field);
686 // 5. Sanitize final output
687 $field->err->{$errKey}->dflt = wp_kses_post($finalMsg);
688 }
689 }
690
691 $fields->{$fieldKey} = $field;
692 }
693 } catch (Exception $e) {
694 Log::debug_log('Error In Replacing Fields Default Error messages: ' . $e->getMessage());
695 }
696 return $fields;
697 }
698
699 //replace shortcode in error message
700 public static function replaceShortcodeInErrorMsg($msg, $field)
701 {
702 $shortcodes = [
703 '${field.label}' => isset($field->lbl) ? $field->lbl : '',
704 '${field.minimum}' => isset($field->mn) ? $field->mn : '',
705 '${field.maximum}' => isset($field->mx) ? $field->mx : '',
706 '${field.minimum_file}' => isset($field->config->minFile) ? $field->config->minFile : '',
707 '${field.maximum_file}' => isset($field->config->maxFile) ? $field->config->maxFile : '',
708 '${field.maximum_size}' => isset($field->config->maxSize) ? $field->config->maxSize : '',
709 '${field.minimum_amount}' => isset($field->config->minValue) ? $field->config->minValue : '',
710 '${field.maximum_amount}' => isset($field->config->maxValue) ? $field->config->maxValue : '',
711 ];
712 $msg = str_replace(array_keys($shortcodes), array_values($shortcodes), $msg);
713 return $msg;
714 }
715
716 public static function getDefaultGlobalMessages()
717 {
718 $defaultGlobalMessages = [
719 'err' => [
720 'req' => '<p style="margin:0">' . __('This field is required', 'bit-form') . '</p>',
721 'email' => [
722 'invalid' => '<p style="margin:0">' . __('Please, enter a valid email address', 'bit-form') . '</p>',
723 ],
724 'url' => [
725 'invalid' => '<p style="margin:0">' . __('Please, enter a valid URL', 'bit-form') . '</p>',
726 ],
727 'mn' => '<p style="margin:0">' . __('Minimum ${field.minimum} is required', 'bit-form') . '</p>',
728 'mx' => '<p style="margin:0">' . __('Maximum ${field.maximum} is allowed', 'bit-form') . '</p>',
729 'number' => [
730 'invalid' => '<p style="margin:0">' . __('Please, enter only numbers', 'bit-form') . '</p>',
731 ],
732 'phone-number' => [
733 'invalid' => '<p style="margin:0">' . __('Please, enter a valid phone number', 'bit-form') . '</p>',
734 ],
735 'check' => [
736 'mn' => '<p style="margin:0">' . __('Select at least ${field.minimum} option(s)', 'bit-form') . '</p>',
737 'mx' => '<p style="margin:0">' . __('Please, select no more than ${field.maximum} option(s)', 'bit-form') . '</p>',
738 ],
739 'select' => [
740 'mn' => '<p style="margin:0">' . __('Select at least ${field.minimum} option(s)', 'bit-form') . '</p>',
741 'mx' => '<p style="margin:0">' . __('Please, select no more than ${field.maximum} option(s)', 'bit-form') . '</p>',
742 ],
743 'image-select' => [
744 'mn' => '<p style="margin:0">' . __('Select at least ${field.minimum} option(s)', 'bit-form') . '</p>',
745 'mx' => '<p style="margin:0">' . __('Please, select no more than ${field.maximum} option(s)', 'bit-form') . '</p>',
746 ],
747 'inputMask' => '<p style="margin:0">' . __('Input does not match the required pattern', 'bit-form') . '</p>',
748 'regexr' => '<p style="margin:0">' . __('Input does not match the required pattern', 'bit-form') . '</p>',
749 'minFile' => '<p style="margin:0">' . __('Minimum ${field.minimum_file} file(s) required', 'bit-form') . '</p>',
750 'maxFile' => '<p style="margin:0">' . __('Maximum ${field.maximum_file} file(s) allowed', 'bit-form') . '</p>',
751 'maxSize' => '<p style="margin:0">' . __('Maximum file size exceeded. (Max: ${field.maximum_size}MB)', 'bit-form') . '</p>',
752 'fileType' => '<p style="margin:0">' . __('File type is not supported', 'bit-form') . '</p>',
753 'entryUnique' => '<p style="margin:0">' . __('This value is already taken. Please, choose a different one.', 'bit-form') . '</p>',
754 'userUnique' => '<p style="margin:0">' . __('This username or email is already registered. Please, use another.', 'bit-form') . '</p>',
755 'otherOptReq' => '<p style="margin:0">' . __('Custom Option Required', 'bit-form') . '</p>',
756 'minValue' => '<p style="margin:0">' . __('Minimum amount of ${field.minimum_amount} is required', 'bit-form') . '</p>',
757 'maxValue' => '<p style="margin:0">' . __('Maximum amount of ${field.maximum_amount} is allowed', 'bit-form') . '</p>',
758 ],
759 ];
760
761 // Convert array to object recursively
762 return json_decode(json_encode($defaultGlobalMessages));
763 }
764
765 public static function getBitformSalt()
766 {
767 $salt = get_option('bitforms_salt');
768 if (!$salt) {
769 $salt = bin2hex(\random_bytes(32));
770 update_option('bitforms_salt', $salt);
771 }
772 return $salt;
773 }
774 }
775