PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / V-3.3.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder vV-3.3.0
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 2.10.1 All 138 releases
bit-form / includes / Admin / Form / Helpers.php

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

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