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

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