PluginProbe
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder / 2.21.13
Bit Form – Contact Form, Payment Forms, Multi Step Forms, Calculator & Custom Form Builder v2.21.13
V-3.3.0 3.2.2 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 V3.0.3 V3.0.2 -3.0.1 V_3.0.0 1.1.1 1.1.8 1.2 1.3 1.4 1.4.18 1.5.2 1.9 2.0 2.10.0 2.10.1 2.10.2 All 137 releases
bit-form / includes / Admin / Form / FrontEndScriptGenerator.php

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

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