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

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