PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 3.2.0
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v3.2.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 / FrontEndScriptGenerator.php

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

590 lines 20.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 if (!defined('ABSPATH')) {
6 exit;
7 }
8
9 use BitCode\BitForm\Core\Util\FileHandler;
10 use BitCode\BitForm\Core\Util\Log;
11 use WP_Error;
12
13 class FrontEndScriptGenerator
14 {
15 private $_scriptAddedFlags;
16 private $_fields;
17 private $_formContents;
18 private $_jsFilesNeeded;
19 private $_loadedScriptsList;
20 private $_validationJsFilesNeeded;
21
22 public function __construct()
23 {
24 $this->_scriptAddedFlags = [];
25 $this->_fields = [];
26 $this->_jsFilesNeeded = [];
27 $this->_loadedScriptsList = [];
28 $this->_validationJsFilesNeeded = ScriptFilePriorityManager::validationAndOtherScriptFile();
29 }
30
31 private static function generateBfGlobalObjJS($contentIds = [])
32 {
33 $contentidArray = wp_json_encode($contentIds);
34 return ' if(!window.bf_globals){ window.bf_globals = {};}
35 ' . $contentidArray . '.forEach(function(contentId){
36 const form = document.getElementById(contentId);
37 if(!form){
38 delete window.bf_globals[contentId];
39 return;
40 }
41 if(!window.bf_globals[contentId]){
42 window.bf_globals[contentId] = {inits: {}, contentId: contentId};
43 }else{
44 window.bf_globals[contentId].inits = {};
45 window.bf_globals[contentId].contentId = contentId;
46 }
47
48 });';
49 }
50
51 private static function isValidationNeeded($fldData)
52 {
53 $validationsToCheck = ['valid->req', 'valid->regexr', 'mn', 'mx', 'opt', 'err->invalid->show', 'err->notVerified->show'];
54 foreach ($validationsToCheck as $property) {
55 $needValidation = Helpers::property_exists_nested($fldData, $property);
56 if ($needValidation) {
57 return true;
58 }
59 }
60 return false;
61 }
62
63 public function generateJsFile($formContents, $fields, $contentIds, $postId, $formIDs = null, $preview = 'classic')
64 {
65 add_option('bitforms_frontend_js_generating', true);
66 $this->_formContents = $formContents;
67 $this->_fields = $fields;
68
69 $this->appendJs(self::generateBfGlobalObjJS($contentIds), $postId, $preview, 'w');
70 $this->_jsFilesNeeded = ScriptFilePriorityManager::jsFile();
71
72 // helper js files for whole form
73 foreach ($this->_jsFilesNeeded['helperScript'] as $jsFile) {
74 $this->addScriptInLoadedScriptsList($jsFile);
75 }
76
77 // for add validation js files
78 $this->jsValidationNeededFile();
79
80 // js files for all fields
81 $this->jsFieldsNeededFile();
82
83 // for frontend helper js files
84 $this->jsFrontendScript();
85
86 // for hidden Field when cache plugin on
87 $this->jsHiddenFieldScript();
88
89 // for recaptcha v3 (form-level setting)
90 $this->jsRecaptchaV3Scripts();
91
92 // for form abandonment scripts
93 $this->jsFormAbandonmentScripts();
94
95 // multi step form scripts
96 $this->multiStepFormScripts();
97
98 // conversational form scripts
99 $this->conversationalFormScripts();
100
101 //⚠👆 every script file generate method call before this method
102 $this->appendJs($this->getScript(), $postId, $preview);
103 $this->appendJs($this->generateFieldConfigsJs($contentIds), $postId, $preview);
104 // init all js events and functions at last
105 $this->appendJs(self::getFileFromAssetsJs('bitform-init.min.js'), $postId, $preview);
106 if (defined('ELEMENTOR_PRO_VERSION')) {
107 $this->appendJs(self::getFileFromAssetsJs('bitform-elementor.min.js'), $postId, $preview);
108 }
109
110 // for js file minification
111 $this->appendJs(self::getCustomJsCodes($contentIds), $postId, $preview);
112 delete_option('bitforms_frontend_js_generating');
113 return true;
114 }
115
116 private function multiStepFormScripts()
117 {
118 foreach ($this->_formContents as $formContent) {
119 $layout = $formContent->layout;
120 if (is_array($layout) && count($layout) > 1) {
121 $files = ScriptFilePriorityManager::multiStepFiles();
122 foreach ($files as $file) {
123 $this->addScriptInLoadedScriptsList($file);
124 }
125 }
126 }
127 }
128
129 private function conversationalFormScripts()
130 {
131 foreach ($this->_formContents as $formContent) {
132 if (!empty($formContent->formInfo->conversationalSettings) && $formContent->formInfo->conversationalSettings->enable) {
133 $files = ScriptFilePriorityManager::conversationalFormFiles();
134 foreach ($files as $file) {
135 $this->addScriptInLoadedScriptsList($file);
136 }
137 }
138 }
139 }
140
141 private function jsFormAbandonmentScripts()
142 {
143 /**
144 * Add-ons can inject required script files here.
145 *
146 * @param array $files Array of file arrays: ['priority' => int, 'filename' => string]
147 * @param array $formContents
148 * @param array $fields
149 */
150 $files = apply_filters('bitform_form_abandonment_script_files', [], $this->_formContents, $this->_fields);
151 if (empty($files) || !is_array($files)) {
152 return;
153 }
154 foreach ($files as $jsFile) {
155 if (!empty($jsFile['filename']) && !empty($jsFile['priority'])) {
156 $this->addScriptInLoadedScriptsList($jsFile);
157 }
158 }
159 }
160
161 private function appendJs($script, $postId, $previewMode, $mode = 'a')
162 {
163 $fileName = '';
164 $path = '';
165
166 if ('conversational' === $previewMode) {
167 $fileName = "bitform-conversational-{$postId}.js";
168 $path = 'form-scripts';
169 } elseif ('preview' === $previewMode) {
170 $fileName = "preview-{$postId}.js";
171 $path = 'form-scripts';
172 } else {
173 $fileName = "bitform-js-$postId.js";
174 $path = "form-scripts/{$postId}";
175 }
176 Helpers::saveFile($path, $fileName, Helpers::minifyJs($script), $mode);
177 }
178
179 private function getScript()
180 {
181 usort($this->_loadedScriptsList, function ($a, $b) {
182 return $a['priority'] - $b['priority'];
183 });
184
185 $script = '';
186 foreach ($this->_loadedScriptsList as $scriptFileArr) {
187 $fileNam = $scriptFileArr['filename'];
188 $fileContent = self::getFileFromAssetsJs($fileNam);
189 if ($fileContent) {
190 $script .= $fileContent;
191 }
192 }
193 return $script;
194 }
195
196 private function jsValidationNeededFile()
197 {
198 foreach ($this->_fields as $flds) {
199 foreach ($flds as $fld) {
200 $fldData = $fld['field'];
201 // Corrupt/partial form JSON can produce a null field or one without typ.
202 if (!is_object($fldData)) {
203 continue;
204 }
205 // for validation js files
206 if (self::isValidationNeeded($fldData)) {
207 $validation = $this->_validationJsFilesNeeded['validation'];
208 $this->addScriptInLoadedScriptsList($validation);
209 }
210 // for required
211 if (Helpers::property_exists_nested($fldData, 'valid->req')) {
212 $fileArr = $this->_validationJsFilesNeeded['requiredFldValidation'];
213 $this->addScriptInLoadedScriptsList($fileArr);
214 }
215 // for regex
216 if (Helpers::property_exists_nested($fldData, 'valid->regexr')) {
217 $patternFile = $this->_validationJsFilesNeeded['generateBackslashPattern'];
218 $rgx = $this->_validationJsFilesNeeded['regexPatternValidation'];
219 $this->addScriptInLoadedScriptsList($patternFile);
220 $this->addScriptInLoadedScriptsList($rgx);
221 }
222
223 $validationScriptFileMapping = ScriptFilePriorityManager::validationScriptFileMapping($fldData->typ);
224 if ($validationScriptFileMapping) {
225 foreach ($validationScriptFileMapping as $key => $value) {
226 $paths = $value['paths'] ?? [];
227 $hasMatchingPath = false;
228 foreach ($paths as $path) {
229 if (Helpers::property_exists_nested($fldData, $path)) {
230 $hasMatchingPath = true;
231 break; // one match is enough
232 }
233 }
234
235 if (!$hasMatchingPath) {
236 continue;
237 }
238
239 // Add the main script once
240 if (!empty($this->_validationJsFilesNeeded[$key])) {
241 $this->addScriptInLoadedScriptsList($this->_validationJsFilesNeeded[$key]);
242 }
243
244 // Add dependencies once each
245 foreach (($value['dependencies'] ?? []) as $dep) {
246 if (!empty($this->_validationJsFilesNeeded[$dep])) {
247 $this->addScriptInLoadedScriptsList($this->_validationJsFilesNeeded[$dep]);
248 }
249 }
250 }
251 }
252 }
253 }
254 }
255
256 private function jsFieldsNeededFile()
257 {
258 $filePondPluginList = apply_filters('bitform_filepond_plugins_list', []);
259 foreach ($this->_fields as $typ => $flds) {
260 if (!array_key_exists($typ, $this->_jsFilesNeeded)) {
261 continue;
262 }
263 $fldScriptArr = $this->_jsFilesNeeded[$typ];
264
265 foreach ($flds as $fld) {
266 if ('advanced-file-up' === $typ) {
267 $configs = $fld['field']->config;
268 foreach ($configs as $configKey => $config) {
269 if ($configs->$configKey) {
270 if (array_key_exists($configKey, $filePondPluginList)) {
271 $this->addScriptInLoadedScriptsList($filePondPluginList[$configKey]);
272 }
273 }
274 }
275 }
276 foreach ($fldScriptArr as $scriptFile) {
277 if (isset($scriptFile['paths']) && is_array($scriptFile['paths'])) {
278 $pathMatched = false;
279 foreach ($scriptFile['paths'] as $path) {
280 if (Helpers::property_exists_nested($fld['field'], $path)) {
281 $pathMatched = true;
282 break;
283 }
284 }
285 if (!$pathMatched) {
286 continue;
287 }
288 } elseif (isset($scriptFile['path']) && !Helpers::property_exists_nested($fld['field'], $scriptFile['path'])) {
289 continue;
290 }
291 $this->addScriptInLoadedScriptsList($scriptFile);
292 }
293 }
294 }
295 }
296
297 private static function getCustomJsCodes($contentIds = [])
298 {
299 $script = 'let bfContentId = "", bfSlNo = "1", bfVars= "";';
300 foreach ($contentIds as $contentId) {
301 $contentIdArr = explode('_', $contentId);
302 $jsCode = self::getCustomCodes($contentIdArr[1])['JavaScript'];
303 $bfSlNo = array_key_exists(3, $contentIdArr) ? $contentIdArr[3] : '1';
304 if (!empty($jsCode)) {
305 $script .= " if(bfSelect('#{$contentId}')){ bfContentId = '{$contentId}'; bfSlNo = '{$bfSlNo}'; bfVars = window.bf_globals.{$contentId}.smartTags; {$jsCode}}";
306 }
307 }
308
309 return $script;
310 }
311
312 private static function getFileFromAssetsJs($fileName)
313 {
314 $sourceJsFile = BITFORMS_PLUGIN_DIR_PATH . 'assets' . DIRECTORY_SEPARATOR . $fileName;
315 if (file_exists($sourceJsFile)) {
316 return file_get_contents($sourceJsFile);
317 }
318 Log::debug_log('file not found: ' . $fileName);
319 return false;
320 }
321
322 public static function saveCssFile($formId, $atomicCssText)
323 {
324 $path = 'form-styles';
325 $fileName = "bitform-$formId.css";
326 if (!isset($formId) || '' === $formId) {
327 return new WP_Error('missing_form_id', __('Error Occurred, Please Reload', 'bit-form'));
328 }
329 return Helpers::saveFile($path, $fileName, $atomicCssText, 'w');
330 }
331
332 public static function customCodeFile($formId, $customCodes)
333 {
334 // for js file
335 $path = 'form-scripts';
336 $fileName = "bitform-custom-$formId.js";
337 $filteredJs = Helpers::removeJsSingleLineComments($customCodes->JavaScript);
338 self::customCodeFileSaveOrDelete($filteredJs, $path, $fileName);
339
340 // for css file
341 $path = 'form-styles';
342 $fileName = "bitform-custom-$formId.css";
343 self::customCodeFileSaveOrDelete($customCodes->CSS, $path, $fileName);
344
345 return true;
346 }
347
348 public static function customCodeFileSaveOrDelete($script, $path, $fileName)
349 {
350 if ($script) {
351 Helpers::saveFile($path, $fileName, $script, 'w');
352 } else {
353 $uploadPath = "$path/$fileName";
354 $uploadFilePath = Helpers::generatePathDirOrFile($uploadPath);
355 FileHandler::deleteIsFileExists($uploadFilePath);
356 }
357 return true;
358 }
359
360 public static function getCustomCodes($formId)
361 {
362 $customCodes = ['JavaScript' => '', 'CSS' => ''];
363 $customJsPath = Helpers::generatePathDirOrFile("form-scripts/bitform-custom-$formId.js");
364 $customCodes['JavaScript'] = Helpers::fileRead($customJsPath);
365
366 $customCSSPath = Helpers::generatePathDirOrFile("form-styles/bitform-custom-$formId.css");
367 $customCodes['CSS'] = Helpers::fileRead($customCSSPath);
368
369 return $customCodes;
370 }
371
372 private $fldContainersByType = [
373 'address' => '.__$fk__-parent-fld-wrp',
374 'select' => '.__$fk__-dpd-fld-wrp',
375 'country' => '.__$fk__-country-fld-wrp',
376 'currency' => '.__$fk__-currency-fld-wrp',
377 'phone-number' => '.__$fk__-phone-fld-wrp',
378 'file-up' => '.__$fk__-file-up-wrpr',
379 'advanced-file-up' => '#filepond-__$fk__-container',
380 'paypal' => '.__$fk__-paypal-wrp',
381 'razorpay' => '.__$fk__-razorpay-wrp',
382 'recaptcha' => '.__$fk__-recaptcha-wrp',
383 'stripe' => '.__$fk__-stripe-fld',
384 'mollie' => '.__$fk__-mollie-wrp',
385 'repeater' => '.__$fk__-rpt-fld-wrp',
386 'signature' => '.__$fk__-inp-fld-wrp',
387 'rating' => '.__$fk__-inp-fld-wrp',
388 'email-otp' => '.__$fk__-inp-fld-wrp',
389 'hcaptcha' => '.__$fk__-h-captcha-wrp',
390 'advanced-datetime' => '.__$fk__-advanced-datetime',
391 ];
392
393 private function generateFieldConfigsJs()
394 {
395 $customFlds = ['address', 'select', 'country', 'currency', 'phone-number', 'file-up', 'advanced-file-up', 'paypal', 'razorpay', 'stripe', 'mollie', 'recaptcha', 'repeater', 'signature', 'rating', 'turnstile', 'hcaptcha', 'advanced-datetime', 'email-otp'];
396 $allFieldTypes = array_keys($this->_fields);
397 $customFldsInForms = array_intersect($allFieldTypes, $customFlds);
398 $formContents = $this->_formContents;
399 $recaptchaV3Enabled = false;
400 foreach ($formContents as $content) {
401 if (isset($content->additional->enabled->recaptchav3) && $content->additional->enabled->recaptchav3) {
402 $recaptchaV3Enabled = true;
403 break;
404 }
405 }
406 if (empty($customFldsInForms) && !$recaptchaV3Enabled) {
407 return '';
408 }
409
410 $containers = [];
411 foreach ($customFldsInForms as $customFldTyp) {
412 if (isset($this->fldContainersByType[$customFldTyp])) {
413 $containers[$customFldTyp] = $this->fldContainersByType[$customFldTyp];
414 }
415 }
416
417 $customFldConfigPaths = [];
418
419 $allConfs = ScriptFilePriorityManager::getAllFldConfs();
420 foreach ($customFldsInForms as $customFldTyp) {
421 if (isset($allConfs[$customFldTyp])) {
422 $customFldConfigPaths[$customFldTyp] = $allConfs[$customFldTyp];
423 } else {
424 $customFldConfigPaths[$customFldTyp] = (object) [];
425 }
426 }
427
428 $customFldConfigPaths = wp_json_encode($customFldConfigPaths);
429 $containers = wp_json_encode($containers);
430
431 $script = ' const customFldConfigPaths = ' . $customFldConfigPaths . ';
432 const fldContainers = ' . $containers . ';
433
434 function initAllCustomFlds (formContentId = null) {
435 const allContendIds = formContentId ? [formContentId] : Object.keys(bf_globals);
436 allContendIds.forEach((contentId) => {
437 const contentData = bf_globals[contentId];
438 if(!contentData?.inits) contentData.inits = {};
439 const flds = bf_globals[contentId]?.fields || {};
440 const fldKeys = Object.keys(flds).reverse();
441 fldKeys.forEach((fldKey) => {
442 const fldData = flds[fldKey];
443 const fldType = fldData.typ;
444 if(fldType === \'paypal\') {
445 initPaypalFld(contentId, fldKey, fldData, fldType);
446 } else if(fldType === \'razorpay\') {
447 initRazorpayFld(contentId, fldKey, fldType);
448 } else if(fldType === \'recaptcha\') {
449 initRecaptchaFld(contentId, fldKey, fldType);
450 } else if(fldType === \'turnstile\') {
451 initTurnstileFld(contentId, fldKey);
452 } else if(fldType === \'hcaptcha\') {
453 initHCaptchaFld(contentId, fldKey, fldType);
454 } else if(fldType === \'stripe\' || fldType === \'mollie\') {
455 initStripeFld(contentId, fldKey, fldType);
456 } else if (customFldConfigPaths[fldType]) {
457 contentData.inits[fldKey] = getFldInstance(contentId, fldKey, fldType);
458 }
459 });
460 if(contentData.gRecaptchaVersion === \'v3\' && contentData.gRecaptchaSiteKey){
461 initRecaptchaV3Fld(contentId, contentData);
462 }
463 });
464 };
465 function getFldInstance(contentId, fldKey, fldTyp, nestedSelector = \'\') {
466 const fldClass = this[\'bit_\'+fldTyp.replace(/-/g, \'_\')+\'_field\'];
467 const selector = \'#form-\'+contentId+\' \'+nestedSelector+fldContainers[fldTyp].replace("__$fk__", fldKey);
468 if(!fldClass || !bfSelect(selector)) return;
469 return new fldClass(selector, getFldConf(contentId, fldKey, fldTyp));
470 };
471 function getFldConf(contentId, fieldKey, fldTyp) {
472 const fldData = bf_globals[contentId].fields[fieldKey];
473 const fldConfPaths = Object.entries(customFldConfigPaths[fldTyp]);
474 let fldConf = {};
475 const { formId } = bf_globals[contentId];
476 if (!("config" in customFldConfigPaths[fldTyp]) && "config" in fldData) fldConf = fldData.config;
477 const varData = { contentId, fieldKey, formId };
478 fldConfPaths.forEach(([ confPath, fldPath ]) => {
479 let value = "";
480 if (fldPath.var) value = varData[fldPath.var];
481 if (!value && fldPath.path) {
482 if(Array.isArray(fldPath.path)) {
483 fldPath.path.forEach((path) => {
484 if(!value) value = getDataFromNestedPath(fldData, path);
485 });
486 } else {
487 value = getDataFromNestedPath(fldData, fldPath.path);
488 }
489 }
490 if (!value && "val" in fldPath) {
491 value = fldPath.val;
492 if(typeof value === \'string\') {
493 Object.entries(varData).forEach(([key, val]) => {
494 value = value.replace("__$"+key+"__", val);
495 });
496 }
497 }
498 fldConf = setDataToNestedPath(fldConf, confPath, value);
499 });
500 return fldConf;
501 };
502 function getDataFromNestedPath(data, key) {
503 const keys = key.split("->");
504 const lastKey = keys.pop();
505 let current = {...data};
506 for (const k of keys) {
507 if (!(k in current)) return null;
508 current = current[k];
509 }
510 return current[lastKey] || null;
511 }
512 function setDataToNestedPath(data, key, value) {
513 const keys = key.split("->");
514 const lastKey = keys.pop();
515 let current = {...data};
516 keys.forEach((k) => {
517 if (!current[k]) current[k] = {};
518 current = current[k];
519 });
520 current[lastKey] = value;
521 return current;
522 }';
523
524 return $script;
525 }
526
527 private function addScriptInLoadedScriptsList($fileArr)
528 {
529 $fileName = $fileArr['filename'];
530 if (!in_array($fileName, $this->_scriptAddedFlags)) {
531 $this->_loadedScriptsList[] = $fileArr;
532 $this->_scriptAddedFlags[] = $fileName;
533 }
534 return true;
535 }
536
537 private function jsFrontendScript()
538 {
539 foreach ($this->_formContents as $formContent) {
540 if (Helpers::property_exists_nested($formContent, 'workFlowExist->oninput', true)) {
541 $fileArr = ScriptFilePriorityManager::validationAndOtherScriptFile()['conditionalLogic'];
542 $this->addScriptInLoadedScriptsList($fileArr);
543 $fileArr = ScriptFilePriorityManager::validationAndOtherScriptFile()['resetPlaceholders'];
544 $this->addScriptInLoadedScriptsList($fileArr);
545 $fileArr = ScriptFilePriorityManager::validationAndOtherScriptFile()['bfResetDefaultValue'];
546 $this->addScriptInLoadedScriptsList($fileArr);
547 $fileArr = ScriptFilePriorityManager::validationAndOtherScriptFile()['validateFocusLost'];
548 $this->addScriptInLoadedScriptsList($fileArr);
549 $fileArr = ScriptFilePriorityManager::frontendScriptFile()['observeElm'];
550 $this->addScriptInLoadedScriptsList($fileArr);
551 }
552 if (Helpers::property_exists_nested($formContent, 'additional->enabled->validateFocusLost', true)) {
553 $fileArr = ScriptFilePriorityManager::validationAndOtherScriptFile()['validateFocusLost'];
554 $this->addScriptInLoadedScriptsList($fileArr);
555 }
556 }
557 }
558
559 private function jsHiddenFieldScript()
560 {
561 $appConfig = get_option('bitform_app_config');
562 if (Helpers::property_exists_nested($appConfig, 'cache_plugin', true)) {
563 $fileArr = ScriptFilePriorityManager::frontendScriptFile()['hidden-token-field'];
564 $this->addScriptInLoadedScriptsList($fileArr);
565 return;
566 }
567 }
568
569 private function jsRecaptchaV3Scripts()
570 {
571 foreach ($this->_formContents as $content) {
572 if (isset($content->additional->enabled->recaptchav3) && $content->additional->enabled->recaptchav3) {
573 $this->addScriptInLoadedScriptsList(['priority' => 302, 'filename' => 'scriptLoader.min.js']);
574 $this->addScriptInLoadedScriptsList(['priority' => 303, 'filename' => 'initRecaptchaV3Fld.min.js']);
575 return;
576 }
577 }
578 }
579
580 public function dd($data, $exit = false)
581 {
582 echo '+++++++++++++';
583 echo '<pre>';
584 var_dump($data);
585 echo '</pre>';
586 echo '+++++++++++++';
587 $exit ? exit : '';
588 }
589 }
590