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 / Frontend / Form / FrontendFormManager.php

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

1,133 lines 44.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Get set Form,fields
5 */
6
7 namespace BitCode\BitForm\Frontend\Form;
8
9 /**
10 * FrontendFormManager class
11 */
12
13 use BitCode\BitForm\Admin\Form\AdminFormHandler;
14 use BitCode\BitForm\Admin\Form\Helpers;
15 use BitCode\BitForm\Core\Database\FormEntryModel;
16 use BitCode\BitForm\Core\Form\FormManager;
17 use BitCode\BitForm\Core\Form\Validator\FormFieldValidator;
18 use BitCode\BitForm\Core\Integration\IntegrationHandler;
19 use BitCode\BitForm\Core\Messages\SuccessMessageHandler;
20 use BitCode\BitForm\Core\Util\ApiResponse as UtilApiResponse;
21 use BitCode\BitForm\Core\Util\DateTimeHelper;
22 use BitCode\BitForm\Core\Util\EntryLimitHelper;
23 use BitCode\BitForm\Core\Util\HttpHelper;
24 use BitCode\BitForm\Core\Util\IpTool;
25 use BitCode\BitForm\Core\WorkFlow\WorkFlow;
26 use BitCode\BitForm\Core\WorkFlow\WorkFlowHandler;
27 use BitCode\BitForm\Frontend\Form\View\FormViewer;
28 use BitCode\BitFormPro\Admin\FormSettings\FormAbandonment;
29 use WP_Error;
30
31 final class FrontendFormManager extends FormManager
32 {
33 private $_form_identifier;
34 private $_form_token;
35 private $_form_id;
36 private $_work_flows;
37 private $_conf_messages;
38 private static $_instance = [];
39
40 // private $_has_upload = false;
41 public function __construct($form_id, $shortCodeCounter = null)
42 {
43 parent::__construct($form_id);
44 $this->_form_identifier = 'bitforms_' . $form_id;
45 $this->_form_identifier .= !empty(get_post()->ID) ? '_' . get_post()->ID : '';
46 $this->_form_identifier .= !empty($shortCodeCounter) ? "_$shortCodeCounter" : '';
47 $this->_form_token = wp_create_nonce('bitforms_' . $form_id);
48 $this->_form_id = $form_id;
49 }
50
51 public static function getInstance($form_id, $shortCodeCounter = null)
52 {
53 $key = $form_id . ':' . ($shortCodeCounter ?? 'default');
54
55 if (!isset(self::$_instance[$key])) {
56 self::$_instance[$key] = new self($form_id, $shortCodeCounter);
57 }
58
59 return self::$_instance[$key];
60 }
61
62 public function getFormIdentifier()
63 {
64 return $this->_form_identifier;
65 }
66
67 public function getFormID()
68 {
69 return $this->_form_id;
70 }
71
72 public function getFormToken()
73 {
74 return $this->_form_token;
75 }
76
77 public function isSubmitted()
78 {
79 // return isset($_POST[$this->_form_identifier]) ? true : false;
80 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verified via verifySubmissionNonce() in handleSubmission()/handleUpdateEntry(); called from FrontendAjax entry points.
81 return (isset($_POST['bitforms_id']) && $_POST['bitforms_id'] === $this->_form_identifier) ? true : false;
82 }
83
84 public function getSubmittedFields($submitted_data)
85 {
86 unset($submitted_data[$this->_form_identifier]);
87 // unset($submitted_data['bit-form-submit-btn']);
88 return array_keys($submitted_data);
89 }
90
91 public function formView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null)
92 {
93 $formContents = $this->getFormContent();
94 $formAtomicClsMap = $this->getAtomicClsMap();
95 if (!empty($fields)) {
96 $formContents->fields = is_string($fields) ? json_decode($fields) : $fields;
97 } else {
98 $workFlowRunHelper = new WorkFlow($this->form_id);
99 $workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad(
100 'create',
101 $formContents->fields
102 );
103 $formContents->fields = empty($workFlowreturnedOnLoad['fields']) ? $formContents->fields : $workFlowreturnedOnLoad['fields'];
104 }
105 $formViewer = new FormViewer($this, $formContents, $formAtomicClsMap, $errorMessages, $previousValue);
106 $isRestricted = $this->checkSubmissionRestriction(false);
107 $msg = !empty($isRestricted) ? $isRestricted[0] : '';
108 return $formViewer->getView($hasFile, $msg);
109 }
110
111 public function conversationalFormView($fields = null, $hasFile = false, $errorMessages = null, $previousValue = null)
112 {
113 $formContents = $this->getFormContent();
114 $formAtomicClsMap = $this->getAtomicClsMap();
115 if (!empty($fields)) {
116 $formContents->fields = is_string($fields) ? json_decode($fields) : $fields;
117 } else {
118 $workFlowRunHelper = new WorkFlow($this->form_id);
119 $workFlowreturnedOnLoad = $workFlowRunHelper->executeOnLoad(
120 'create',
121 $formContents->fields
122 );
123 $formContents->fields = empty($workFlowreturnedOnLoad['fields']) ? $formContents->fields : $workFlowreturnedOnLoad['fields'];
124 }
125 $formViewer = new FormViewer($this, $formContents, $formAtomicClsMap, $errorMessages, $previousValue);
126 $isRestricted = $this->checkSubmissionRestriction(false);
127 $msg = !empty($isRestricted) ? $isRestricted[0] : '';
128 return $formViewer->getConversationalView($hasFile, $msg);
129 }
130
131 public function checkEmptySubmission($data, $file)
132 {
133 $formFields = $this->getFields();
134 foreach ($formFields as $key => $field) {
135 $fieldType = $field['type'];
136 if ('button' === $fieldType) {
137 continue;
138 }
139 $fileUploadFieldTypes = ['file-up', 'advanced-file-up'];
140 if ('decision-box' === $fieldType || 'gdpr' === $fieldType) {
141 continue;
142 }
143 $isFileType = in_array($fieldType, $fileUploadFieldTypes);
144 if ($this->isRepeatedField($key)) {
145 $fileData = !empty($file[$key]) ? $file[$key] : [];
146 $dataVal = !empty($data[$key]) ? $data[$key] : [];
147 if (!$this->checkRepeatedFieldEmptySubmission($isFileType, $dataVal, $fileData)) {
148 return false;
149 }
150 continue;
151 }
152 if (!$isFileType && (!empty($data[$key]) || (isset($data[$key]) && is_numeric($data[$key])))) {
153 return false;
154 }
155 if ($isFileType && !empty($file[$key]['name']) && is_string($file[$key]['name'])) {
156 return false;
157 }
158 if ($isFileType && !empty($file[$key]['name'][0])) {
159 return false;
160 }
161 }
162 return true;
163 }
164
165 private function checkRepeatedFieldEmptySubmission($isFileType, $data, $file = [])
166 {
167 if (!$isFileType) {
168 foreach ($data as $value) {
169 if (!empty($value)) {
170 return false;
171 }
172 }
173 }
174 if ($isFileType) {
175 foreach ($file['name'] as $value) {
176 if (!empty($value) && is_string($value)) {
177 return false;
178 }
179 if (is_array($value) && !empty($value[0])) {
180 return false;
181 }
182 }
183 }
184 return true;
185 }
186
187 private function getParams()
188 {
189 $url = wp_parse_url(wp_get_referer());
190 $parameter = [];
191 if (isset($url['query'])) {
192 $queries = explode('&', $url['query']);
193 foreach ($queries as $query) {
194 list($field, $value) = explode('=', $query);
195 $parameter[$field] = $value;
196 }
197 }
198 return $parameter;
199 }
200
201 private function getFormFields($formID)
202 {
203 $adminFormHandler = new AdminFormHandler();
204 $post = new \stdClass();
205 $post = (object) [
206 'id' => $formID
207 ];
208 $getForm = $adminFormHandler->getAForm('', $post);
209 $formContainer = $getForm['form_content'];
210
211 return $formContainer['fields'];
212 }
213
214 private function transformDrpdwnValue($post)
215 {
216 $formFields = $this->getFormFields($this->_form_id);
217
218 foreach ($post as $key => $value) {
219 if (!str_starts_with($key, 'repeater') && 'select' === $formFields->{$key}->typ) {
220 if (is_array($value)) {
221 foreach ($value as $k => $v) {
222 $post[$key][$k] = !is_array($v) && is_string($v) ? explode(BITFORMS_BF_SEPARATOR, $v) : $v;
223 }
224 } else {
225 $post[$key] = explode(BITFORMS_BF_SEPARATOR, $value);
226 }
227 };
228 }
229
230 return $post;
231 }
232
233 public function handleSubmission()
234 {
235 $this->fieldNameReplaceOfPost();
236
237 $validated = $this->beforeSubmittedValidate();
238
239 $validated = apply_filters('bitform_filter_form_validation', $validated, $this->_form_id);
240
241 if (true === $validated) {
242 do_action('bitform_validation_success', $this->_form_id);
243 unset($_POST['hidden_fields']);
244
245 $redirectPage = '';
246 $regSuccMsg = '';
247
248 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
249 if (!is_wp_error($existAuth) && count($existAuth) > 0) {
250 $parameter = $this->getParams();
251 $existAuthFilter = has_filter('bf_wp_user_auth');
252
253 if (true === $existAuthFilter) {
254 $result = apply_filters('bf_wp_user_auth', $existAuth[0], $_POST, $parameter); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
255
256 $result = apply_filters('bitform_filter_wp_user_auth_response', $result, $this->_form_id, $_POST, $parameter);
257
258 do_action('bitform_wp_user_auth_response', $result, $this->_form_id, $_POST, $parameter);
259
260 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
261 if (!$result['success']) {
262 // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
263 return new WP_Error('errors', sprintf(__('%s', 'bit-form'), esc_html($result['message'])));
264 } elseif (isset($result['success'])) {
265 $redirectPage = $result['redirectPage'];
266 $regSuccMsg = $result['message'];
267 }
268 } else {
269 if (!$result['success']) {
270 return new WP_Error('errors', sprintf(__('%s', 'bit-form'), esc_html($result['message'])));
271 } else {
272 return $result;
273 }
274 }
275 }
276 }
277
278 $saveResponse = $this->saveFormEntry($_POST);
279 if (is_wp_error($saveResponse)) {
280 return $saveResponse;
281 }
282
283 $entryID = $saveResponse['entry_id'];
284
285 // transformed dropdown value from string to array
286 $newPost = $this->transformDrpdwnValue($_POST);
287
288 do_action('bitform_submit_success', $this->_form_id, $entryID, $newPost, $_FILES);
289
290 $captchaV3Settings = $this->getCaptchaV3Settings();
291 if ($captchaV3Settings) {
292 $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
293 $integrationHandler = new IntegrationHandler(0);
294 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'gReCaptchaV3');
295 if (!is_wp_error($allFormIntegrations)) {
296 foreach ($allFormIntegrations as $integration) {
297 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
298 $integrationDetails = json_decode($integration->integration_details);
299 $integrationDetails->id = $integration->id;
300 $reCAPTCHA = $integrationDetails;
301 }
302 }
303 }
304 if (!empty($reCAPTCHA->secretKey)) {
305 $gRecaptchaResponse = HttpHelper::post(
306 'https://www.google.com/recaptcha/api/siteverify',
307 ['secret' => $reCAPTCHA->secretKey, 'response' => $token]
308 );
309 if ($captchaV3Settings && !empty($saveResponse['triggerData'])) {
310 $logID = $saveResponse['triggerData']['logID'];
311 $integId = $reCAPTCHA->id;
312 $saveApiResponse = new UtilApiResponse();
313 $saveApiResponse->apiResponse($logID, $integId, ['type_name' => 'ReCaptcha', 'type' => 'v3'], 'success', $gRecaptchaResponse);
314 }
315 }
316 unset($_POST['g-recaptcha-response']);
317 }
318 if (!empty($redirectPage) && empty($saveResponse['redirectPage']) || null === $saveResponse['redirectPage']) {
319 $saveResponse['redirectPage'] = $redirectPage;
320 }
321 if (!empty($regSuccMsg) && isset($saveResponse['dflt_message'])) {
322 $saveResponse['message'] = $regSuccMsg;
323 }
324 $saveResponse['new_nonce'] = wp_create_nonce('bitforms_' . $this->_form_id);
325
326 $saveResponse = IntegrationHandler::maybeSetCronForIntegration($saveResponse, 'create');
327 $entryId = $saveResponse['entry_id'];
328
329 $responseMsg = is_array($saveResponse) && !empty($saveResponse) ? $saveResponse : __('Form Submitted Successfully', 'bit-form');
330 $_POST = [];
331 $responseMsg['entry_id'] = $entryId;
332 return $responseMsg;
333 }
334 do_action('bitform_validation_error', $this->_form_id, $validated);
335 return $validated;
336 }
337
338 public function handleUpdateEntry()
339 {
340 $this->fieldNameReplaceOfPost();
341 $validated = $this->beforeSubmittedValidate();
342 $validated = apply_filters('bitform_filter_form_validation', $validated, $this->_form_id);
343
344 $entryID = isset($_REQUEST['entryID']) ? sanitize_text_field(wp_unslash($_REQUEST['entryID'])) : null;
345 // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound
346 $GLOBALS['bitform_entry_id'] = $entryID;
347 if (is_null($entryID)) {
348 return new WP_Error('empty_form', __('Entries id is invalid', 'bit-form'));
349 }
350 if (true === $validated) {
351 do_action('bitform_validation_success', $this->_form_id);
352 unset($_POST['hidden_fields'], $_POST['entryID']);
353
354 $redirectPage = '';
355 $regSuccMsg = '';
356
357 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
358 if (!is_wp_error($existAuth) && count($existAuth) > 0) {
359 $parameter = $this->getParams();
360 $existAuthFilter = has_filter('bf_wp_user_auth');
361
362 if (true === $existAuthFilter) {
363 $result = apply_filters('bf_wp_user_auth', $existAuth[0], $_POST, $parameter); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
364
365 if (isset($result['auth_type']) && 'register' === $result['auth_type']) {
366 if (!$result['success']) {
367 // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
368 return new WP_Error('errors', sprintf(__('%s', 'bit-form'), esc_html($result['message'])));
369 } elseif (isset($result['success'])) {
370 $redirectPage = $result['redirectPage'];
371 $regSuccMsg = $result['message'];
372 }
373 } else {
374 if (!$result['success']) {
375 return new WP_Error('errors', sprintf(__('%s', 'bit-form'), esc_html($result['message'])));
376 } else {
377 return $result;
378 }
379 }
380 }
381 }
382
383 $updateResponse = $this->updateFormEntry($_POST, $this->getFormID(), $entryID);
384 if (is_wp_error($updateResponse)) {
385 return $updateResponse;
386 }
387
388 // transformed dropdown value from string to array
389 $newPost = $this->transformDrpdwnValue($_POST);
390
391 //TO DO:: submit success action temporarily added for solution of a issue
392 do_action('bitform_submit_success', $this->_form_id, $entryID, $newPost, $_FILES);
393 do_action('bitform_update_success', $this->_form_id, $entryID, $newPost, $_FILES);
394
395 $captchaV3Settings = $this->getCaptchaV3Settings();
396 if ($captchaV3Settings) {
397 $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
398 $integrationHandler = new IntegrationHandler(0);
399 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'gReCaptchaV3');
400 if (!is_wp_error($allFormIntegrations)) {
401 foreach ($allFormIntegrations as $integration) {
402 if (!is_null($integration->integration_type) && 'gReCaptchaV3' === $integration->integration_type) {
403 $integrationDetails = json_decode($integration->integration_details);
404 $integrationDetails->id = $integration->id;
405 $reCAPTCHA = $integrationDetails;
406 }
407 }
408 }
409 if (!empty($reCAPTCHA->secretKey)) {
410 $gRecaptchaResponse = HttpHelper::post(
411 'https://www.google.com/recaptcha/api/siteverify',
412 ['secret' => $reCAPTCHA->secretKey, 'response' => $token]
413 );
414 if ($captchaV3Settings && !empty($updateResponse['triggerData'])) {
415 $logID = $updateResponse['triggerData']['logID'];
416 $integId = $reCAPTCHA->id;
417 $saveApiResponse = new UtilApiResponse();
418 $saveApiResponse->apiResponse($logID, $integId, ['type_name' => 'ReCaptcha', 'type' => 'v3'], 'success', $gRecaptchaResponse);
419 }
420 }
421 unset($_POST['g-recaptcha-response']);
422 }
423 if (!empty($redirectPage) && empty($updateResponse['redirectPage']) || null === $updateResponse['redirectPage']) {
424 $updateResponse['redirectPage'] = $redirectPage;
425 }
426 if (!empty($regSuccMsg) && isset($updateResponse['dflt_message'])) {
427 $updateResponse['message'] = $regSuccMsg;
428 }
429 $updateResponse['new_nonce'] = wp_create_nonce('bitforms_' . $this->_form_id);
430 $updateResponse = IntegrationHandler::maybeSetCronForIntegration($updateResponse, 'update');
431 $entryId = $updateResponse['entry_id'];
432
433 $responseMsg = is_array($updateResponse) && !empty($updateResponse) ? $updateResponse : __('Entry Update Successfully', 'bit-form');
434
435 $_POST = [];
436 $responseMsg['entry_id'] = $entryId;
437 return $responseMsg;
438 }
439 do_action('bitform_validation_error', $this->_form_id, $validated);
440 return $validated;
441 }
442
443 public function validateFormSubmission($submitted_data)
444 {
445 $hidden_fields = isset($submitted_data['hidden_fields']) ? $submitted_data['hidden_fields'] : '';
446 $submitted_fields = $this->getSubmittedFields($submitted_data);
447 $form_fields = $this->getFields();
448 $form_fields_names = array_keys($form_fields);
449 if ($this->isGCLIDEnabled()) {
450 array_push($form_fields_names, 'GCLID');
451 }
452 foreach ($submitted_fields as $field) {
453 if ('hidden_fields' !== $field && !in_array($field, $form_fields_names) || false !== strpos($hidden_fields, $field)) {
454 unset($submitted_data[$field]);
455 }
456 }
457 return $submitted_data;
458 }
459
460 public function beforeSubmittedValidate($verifyCaptcha = true)
461 {
462 if ($this->verifySubmissionNonce()) {
463 if ($this->isExist()) {
464 $isRestricted = $this->checkSubmissionRestriction();
465 if ($isRestricted && !empty($isRestricted)) {
466 return new WP_Error('spam_detection', $isRestricted[0]);
467 }
468 if ($this->isTrappedInHoneypot()) {
469 return new WP_Error('spam_detection', __('Token verification failed', 'bit-form'));
470 }
471 $formCurrentStep = isset($_POST['form-current-step']) ? sanitize_text_field(wp_unslash($_POST['form-current-step'])) : null;
472 // TODO: Temporary parameter to skip captcha verification in step change of multi step form
473 if ($verifyCaptcha) {
474 $verifyGRecaptchaResult = $this->verifyGRecaptcha();
475 if (is_wp_error($verifyGRecaptchaResult)) {
476 return $verifyGRecaptchaResult;
477 }
478 $verifyHCaptchaResult = $this->verifyHCaptcha();
479 if (is_wp_error($verifyHCaptchaResult)) {
480 return $verifyHCaptchaResult;
481 }
482 /* Implement Turnstile Captcha start */
483 $verifyTurnstileCaptchaResult = $this->verifyTurnstileCaptcha();
484 if (is_wp_error($verifyTurnstileCaptchaResult)) {
485 return $verifyTurnstileCaptchaResult;
486 }
487 }
488 /* Implement Turnstile Captcha end */
489
490 $existAuth = (new IntegrationHandler($this->_form_id))->getAllIntegration('wp_user_auth', 'wp_auth', 1);
491
492 // check if user is already logged in and form has auth integration
493 do_action('bitform_checked_exist_auth', $this->_form_id, $existAuth);
494 if (!is_wp_error($existAuth) && count($existAuth) > 0 && is_user_logged_in()) {
495 return new WP_Error('auth_error', __('You are already logged in', 'bit-form'));
496 }
497 $validateForm = $this->validateFormSubmission($_POST);
498 $validateFormFiles = $this->validateFormSubmission($_FILES);
499 $validateForm = array_merge($validateForm, $validateFormFiles);
500 $form_fields = $this->getFields();
501 // check if form-current-step is set and form is multi-step
502 $formCurrentStep = isset($_POST['form-current-step']) ? sanitize_text_field(wp_unslash($_POST['form-current-step'])) : null;
503 if (!is_null($formCurrentStep)) {
504 $formContents = $this->getFormContent();
505 $layout = $formContents->layout;
506 $stepIndex = (int) $formCurrentStep - 1;
507 $stepLayout = $layout[$stepIndex]->layout->lg;
508 $nestedLayout = $formContents->nestedLayout;
509 $step_fields = [];
510 foreach ($stepLayout as $lay) {
511 $fk = $lay->i;
512 if (isset($nestedLayout->{$fk})) {
513 $nestedLg = $nestedLayout->{$fk}->lg;
514 foreach ($nestedLg as $nestedLay) {
515 $nestedFk = $nestedLay->i;
516 $step_fields[$nestedFk] = $form_fields[$nestedFk];
517 }
518 }
519 $step_fields[$fk] = $form_fields[$fk];
520 }
521 $form_fields = $step_fields;
522 }
523 $formFieldValidator = new FormFieldValidator($form_fields, $_POST, $_FILES);
524 $validUniuqFields = [];
525 $existFilter = has_filter('bf_check_duplicate_entry');
526 if (true === $existFilter) {
527 $validUniuqFields = apply_filters('bf_check_duplicate_entry', $form_fields, $_POST); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
528
529 $fieldKeys = array_keys($validUniuqFields);
530 $form_fields_keys = array_keys($form_fields);
531 $uniqueFields = [];
532 foreach ($fieldKeys as $key) {
533 if (in_array($key, $form_fields_keys)) {
534 $uniqueFields[] = $form_fields[$key];
535 }
536 }
537 do_action('bitform_Unique_entry', $uniqueFields, $validUniuqFields, $this->_form_id, $_POST);
538 }
539 $validateField = $formFieldValidator->validate('create', $this->_form_id);
540
541 if ($validateForm && $validateField && 0 === count($validUniuqFields)) {
542 return true;
543 } else {
544 $error = __('Please submit form with valid fields', 'bit-form');
545 if (!$validateForm) {
546 $errorMessages = $error;
547 } elseif (count($formFieldValidator->getMessage()) > 0) {
548 $errorMessages = $formFieldValidator->getMessage();
549 } else {
550 $errorMessages = 0 === count($validUniuqFields) ? $error : $validUniuqFields;
551 }
552 return new WP_Error('validation_error', $errorMessages);
553 }
554 }
555 return new WP_Error('unknown_form', __('Form does not exist', 'bit-form'));
556 } else {
557 return new WP_Error('token_expired', __('Token expired', 'bit-form'));
558 }
559 }
560
561 private function verifyGRecaptcha()
562 {
563 $captchaSettings = $this->getCaptchaSettings();
564 $captchaV3Settings = $this->getCaptchaV3Settings();
565 if ($captchaSettings || $captchaV3Settings) {
566 $token = isset($_POST['g-recaptcha-response']) ? sanitize_text_field(wp_unslash($_POST['g-recaptcha-response'])) : '';
567 if (!isset($_POST['g-recaptcha-response'])) {
568 return new WP_Error('spam_detection', __('Please recheck your reCaptcha Configuration', 'bit-form'));
569 }
570 $integrationHandler = new IntegrationHandler(0);
571 $allFormIntegrations = $integrationHandler->getAllIntegration('app', $captchaSettings ? 'gReCaptcha' : 'gReCaptchaV3');
572 if (!is_wp_error($allFormIntegrations)) {
573 foreach ($allFormIntegrations as $integration) {
574 if (!is_null($integration->integration_type) && $integration->integration_type === ($captchaSettings ? 'gReCaptcha' : 'gReCaptchaV3')) {
575 $integrationDetails = json_decode($integration->integration_details);
576 $integrationDetails->id = $integration->id;
577 $reCAPTCHA = $integrationDetails;
578 }
579 }
580 }
581 if (!empty($reCAPTCHA->secretKey)) {
582 $gRecaptchaResponse = HttpHelper::post(
583 'https://www.google.com/recaptcha/api/siteverify',
584 ['secret' => $reCAPTCHA->secretKey, 'response' => $token]
585 );
586 $isgReCaptchaVerified = false;
587 if (!is_wp_error($gRecaptchaResponse)) {
588 if (
589 $captchaV3Settings
590 && !empty($gRecaptchaResponse->score)
591 && ((float) $gRecaptchaResponse->score < (float) $captchaV3Settings->score)
592 ) {
593 wp_send_json_error(
594 __(
595 // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
596 $captchaV3Settings->message,
597 'bit-form'
598 )
599 );
600 }
601
602 $isgReCaptchaVerified = $gRecaptchaResponse->success;
603 }
604 if (!$isgReCaptchaVerified) {
605 return new WP_Error('spam_detection', __('Please verify reCAPTCHA', 'bit-form'));
606 }
607 }
608 }
609 }
610
611 private function verifyHCaptcha()
612 {
613 $hCaptchaExist = $this->isFieldTypeExist('hcaptcha'); // You can rename this to getHCaptchaSettings() if needed
614 if ($hCaptchaExist) {
615 if (!isset($_POST['h-captcha-response'])) {
616 return new WP_Error('spam_detection', __('Please verify hCaptcha', 'bit-form'));
617 }
618
619 $token = sanitize_text_field(wp_unslash($_POST['h-captcha-response']));
620
621 $integrationHandler = new IntegrationHandler(0);
622 $allFormIntegrations = $integrationHandler->getAllIntegration('app', 'hcaptcha');
623
624 if (!is_wp_error($allFormIntegrations)) {
625 foreach ($allFormIntegrations as $integration) {
626 if (!is_null($integration->integration_type) && 'hcaptcha' === $integration->integration_type) {
627 $integrationDetails = json_decode($integration->integration_details);
628 $integrationDetails->id = $integration->id;
629 $hCaptcha = $integrationDetails;
630 }
631 }
632 }
633
634 if (!empty($hCaptcha->secretKey)) {
635 $hCaptchaResponse = HttpHelper::post(
636 'https://api.hcaptcha.com/siteverify',
637 [
638 'secret' => $hCaptcha->secretKey,
639 'response' => $token,
640 'remoteip' => (isset($_SERVER['REMOTE_ADDR']) ? sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR'])) : '')
641 ]
642 );
643
644 $isVerified = false;
645 if (!is_wp_error($hCaptchaResponse)) {
646 $isVerified = $hCaptchaResponse->success;
647 }
648
649 if (!$isVerified) {
650 return new WP_Error('spam_detection', __('hCaptcha verification failed', 'bit-form'));
651 }
652 }
653 }
654 }
655
656 private function verifyTurnstileCaptcha()
657 {
658 $turnstileExist = $this->isFieldTypeExist('turnstile');
659 if ($turnstileExist) {
660 if (!isset($_POST['cf-turnstile-response'])) {
661 return new WP_Error('spam_detection', __('Please verify Cloudflare Turnstile Captcha', 'bit-form'));
662 }
663 $token = sanitize_text_field(wp_unslash($_POST['cf-turnstile-response']));
664 $turnstileCaptcha = null;
665 $integrationHandler = new IntegrationHandler(0);
666 $turnstileIntegration = $integrationHandler->getAllIntegration('app', 'turnstileCaptcha')[0];
667 if (!is_wp_error($turnstileIntegration && !is_null($turnstileIntegration->integration_type))) {
668 $turnstileCaptcha = json_decode($turnstileIntegration->integration_details);
669 // $integrationDetails->id = $turnstileIntegration->id;
670 // $turnstileCaptcha = $integrationDetails;
671 }
672 if (!is_null($turnstileCaptcha)) {
673 $isTurnstileCaptchaVerified = false;
674 $turnstileRecaptchaResponse = HttpHelper::post(
675 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
676 ['secret' => $turnstileCaptcha->secretKey, 'response' => $token]
677 );
678 if (!is_wp_error($turnstileRecaptchaResponse)) {
679 if (!$turnstileRecaptchaResponse->success) {
680 wp_send_json_error(
681 __(
682 // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText
683 'Cloudflare Turnstile Validation Error: ' . implode(', ', $turnstileRecaptchaResponse->{'error-codes'}),
684 'bit-form'
685 )
686 );
687 }
688
689 $isTurnstileCaptchaVerified = $turnstileRecaptchaResponse->success;
690 }
691 if (!$isTurnstileCaptchaVerified) {
692 return new WP_Error('spam_detection', __('Please verify Cloudflare Turnstile Captcha', 'bit-form'));
693 }
694 }
695 }
696 }
697
698 public function verifySubmissionNonce()
699 {
700 if (!isset($_POST['t_identity']) && !isset($_POST['csrf'])) {
701 return false;
702 }
703 $tIdenty = sanitize_text_field(wp_unslash($_POST['t_identity']));
704 $csrf = sanitize_text_field(wp_unslash($_POST['csrf']));
705 unset($_POST['t_identity'], $_POST['action'], $_POST['bitforms_id'], $_POST['csrf']);
706 return Helpers::csrfDecrypted($tIdenty, $csrf);
707 }
708
709 public function setViewCount()
710 {
711 if (!current_user_can('manage_options')) {
712 $update_status = $this->formModel->update(
713 [
714 'views' => intval(static::$form[0]->views) + 1
715 ],
716 [
717 'id' => $this->form_id
718 ]
719 );
720 }
721 }
722
723 public function checkSubmissionRestriction($checkedEmptySubmitted = true)
724 {
725 $formContents = $this->getFormContent();
726 $fromRestrictionSetitingsEnabled = empty($formContents->additional->enabled) ? [] : $formContents->additional->enabled;
727 $fromRestrictionSetitings = empty($formContents->additional->settings) ? null : $formContents->additional->settings;
728 if (is_null($formContents->additional->enabled) || is_null($formContents->additional->settings)) {
729 return false;
730 }
731 $restrictionMessage = [];
732 $ipTool = new IpTool();
733 $ipAddress = $ipTool->getIP();
734 $currentUserId = get_current_user_id();
735 // error_log(print_r(['ip address', $ipAddress, ip2long($ipAddress)], true));
736 // error_log(print_r(['restrictions', $fromRestrictionSetitings], true));
737 foreach ($fromRestrictionSetitingsEnabled as $restrictionKey => $isEnabled) {
738 if ($isEnabled) {
739 if (('entry_limit' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) || ('entry_limit_by_user' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey}))) {
740 $entryLimitHelper = new EntryLimitHelper($this->form_id, $fromRestrictionSetitings, $fromRestrictionSetitingsEnabled);
741 $advancedLimitMessages = $entryLimitHelper->checkAllLimits($ipAddress, $currentUserId);
742 $restrictionMessage = array_merge($restrictionMessage, $advancedLimitMessages);
743 }
744
745 if ('onePerIp' === $restrictionKey) {
746 $formEntry = new FormEntryModel();
747
748 $getResult = $formEntry->get(
749 ['user_ip', 'status'],
750 [
751 'form_id' => $this->form_id,
752 'user_ip' => ip2long($ipAddress)
753 ],
754 );
755
756 $count = 0;
757 $status = 0;
758
759 if (!is_wp_error($getResult) && count($getResult) > 0) {
760 $count = count($getResult);
761
762 foreach ($getResult as $row) {
763 if (9 === (int) $row->status) {
764 $status = 9;
765 break;
766 }
767 }
768 }
769
770 if ($count > 0 && 9 !== (int) $status) {
771 $onePerIp = __('Sorry!! You have already submitted from this IP address', 'bit-form');
772
773 $onePerIp = apply_filters(
774 'bitform_filter_restriction_one_per_ip_message',
775 $onePerIp,
776 $this->form_id
777 );
778
779 $restrictionMessage[] = $onePerIp;
780 }
781 }
782 if ('is_login' === $restrictionKey && 0 === get_current_user_id()) {
783 $is_login_messages = $fromRestrictionSetitings->is_login->message;
784
785 $is_login_messages = apply_filters(
786 'bitform_filter_restriction_is_login_message',
787 $is_login_messages,
788 $this->form_id
789 );
790
791 $restrictionMessage[] = $is_login_messages;
792 }
793 if ($checkedEmptySubmitted && 'empty_submission' === $restrictionKey) {
794 $isEmpty = $this->checkEmptySubmission($_POST, $_FILES);
795 if ($isEmpty) {
796 $restriction = $fromRestrictionSetitings->empty_submission->message;
797
798 $restriction = apply_filters(
799 'bitform_filter_restriction_empty_submission_message',
800 $restriction,
801 $this->form_id
802 );
803
804 $restrictionMessage[] = $restriction;
805 }
806 }
807 if ('restrict_form' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) {
808 $day = empty($fromRestrictionSetitings->{$restrictionKey}->day) ? null : $fromRestrictionSetitings->{$restrictionKey}->day;
809 $date = empty($fromRestrictionSetitings->{$restrictionKey}->date) ? null : $fromRestrictionSetitings->{$restrictionKey}->date;
810 $time = empty($fromRestrictionSetitings->{$restrictionKey}->time) ? null : $fromRestrictionSetitings->{$restrictionKey}->time;
811
812 $isdayOk = $isdateOk = $istimeOk = true;
813 $dayNotOkMsg = $dateNotOkMsg = $timeNotOkMsg = '';
814 $dateTimeHelper = new DateTimeHelper();
815 if (
816 !empty($day)
817 && is_array($day)
818 && (in_array('Friday', $day)
819 || in_array('Saturday', $day)
820 || in_array('Sunday', $day)
821 || in_array('Monday', $day)
822 || in_array('Tuesday', $day)
823 || in_array('Wednesday', $day)
824 || in_array('Thursday', $day))
825 && (!in_array($dateTimeHelper->getDay('full-name'), $day))
826 ) {
827 $isdayOk = false;
828 $dayMsgVarsFormat = '';
829 foreach ($day as $dayIndex => $dayValue) {
830 if ($dayIndex > 0) {
831 $dayMsgVarsFormat .= ', ';
832 }
833 $dayMsgVarsFormat .= '%s';
834 }
835 // phpcs:ignore WordPress.WP.I18n.InterpolatedVariableText
836 $dayNotOkMsg = vsprintf(__("in $dayMsgVarsFormat", 'bit-form'), $day);
837 }
838 if (
839 !empty($day)
840 && is_array($day)
841 && (in_array('Custom', $day))
842 ) {
843 $startDate = empty($date->from) ? '00-00-0000' : $date->from;
844 $endDate = empty($date->to) ? '00-00-0000' : $date->to;
845 $dateFormat = preg_match('/^[0-9]{4}-[0-9]{2}-[0-9]{2}$/', $startDate) ? 'Y-m-d' : 'm-d-Y';
846 if (!empty($date->from) && false !== strpos($startDate, 'T')) {
847 $startDate = $dateTimeHelper->getDate($startDate, false, null, $dateFormat);
848 }
849 if (!empty($date->to) && false !== strpos($endDate, 'T')) {
850 $endDate = $dateTimeHelper->getDate($endDate, false, null, $dateFormat);
851 }
852 $currentDate = $dateTimeHelper->getDate(null, null, null, $dateFormat);
853 if (!($currentDate >= $startDate && $currentDate <= $endDate)) {
854 $isdateOk = false;
855 /* translators: %1$s: start date, %2$s: end date */
856 $dateNotOkMsg = sprintf(__('within %1$s to %2$s', 'bit-form'), $startDate, $endDate);
857 }
858 }
859
860 if (!empty($time)) {
861 $startTime = empty($time->from) ? '00:00' : $time->from;
862 $endTime = empty($time->to) ? '23:59.999' : $time->to;
863 $currentTime = $dateTimeHelper->getTime(null, null, null, 'H:i');
864 if (!($currentTime >= $startTime && $currentTime <= $endTime)) {
865 $istimeOk = false;
866 $startTime = $dateTimeHelper->getTime($startTime, 'H:i', null);
867 $endTime = $dateTimeHelper->getTime($endTime, 'H:i', null);
868 $isTimeOk = false;
869 /* translators: %1$s: start time, %2$s: end time */
870 $timeNotOkMsg = sprintf(__('%1$s to %2$s', 'bit-form'), $startTime, $endTime);
871 }
872 }
873
874 if (!($isdateOk && $isdayOk && $istimeOk)) {
875 $restrict_form_message = null;
876 if (!$isdayOk) {
877 /* translators: %1$s: day restriction message, %2$s: time restriction message */
878 $restrict_form_message = !empty($timeNotOkMsg) ? sprintf(__('Form is available %1$s From %2$s', 'bit-form'), $dayNotOkMsg, $timeNotOkMsg) :
879 /* translators: %1$s: day restriction message */
880 sprintf(__('Form is available %1$s', 'bit-form'), $dayNotOkMsg, $timeNotOkMsg);
881 } elseif (!$isdateOk) {
882 /* translators: %1$s: date restriction message, %2$s: time restriction message */
883 $restrict_form_message = !empty($timeNotOkMsg) ? sprintf(__('Form is available %1$s From %2$s', 'bit-form'), $dateNotOkMsg, $timeNotOkMsg) :
884 /* translators: %1$s: date restriction message */
885 sprintf(__('Form is available %1$s', 'bit-form'), $dateNotOkMsg, $timeNotOkMsg);
886 } elseif (!$istimeOk) {
887 /* translators: %s: time restriction message */
888 $restrict_form_message = sprintf(__('Form is available on %s', 'bit-form'), $timeNotOkMsg);
889 }
890
891 if ($restrict_form_message) {
892 $restrict_form_message = apply_filters(
893 'bitform_filter_restrict_form_message',
894 $restrict_form_message,
895 $this->form_id
896 );
897
898 $restrictionMessage[] = $restrict_form_message;
899 }
900 }
901 }
902 if ('blocked_ip' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) {
903 $isIpBlocked = false;
904 foreach ($fromRestrictionSetitings->{$restrictionKey} as $ipIndex => $ipDetails) {
905 if (!empty($ipDetails->status) && $ipDetails->status && !empty($ipDetails->ip) && $ipDetails->ip === $ipAddress) {
906 $isIpBlocked = true;
907 break;
908 }
909 }
910 if ($isIpBlocked) {
911 $blocked_ip_message = sprintf(
912 /* translators: %s: IP address */
913 __('Sorry!! Your IP address is %s, Blocked from submitting the form', 'bit-form'),
914 $ipAddress
915 );
916
917 $blocked_ip_message = apply_filters(
918 'bitform_filter_restricted_ip_message',
919 $blocked_ip_message,
920 $this->form_id
921 );
922
923 $restrictionMessage[] = $blocked_ip_message;
924 }
925 }
926 if ('private_ip' === $restrictionKey && isset($fromRestrictionSetitings->{$restrictionKey})) {
927 $isIpWhiteListed = false;
928 foreach ($fromRestrictionSetitings->{$restrictionKey} as $ipIndex => $ipDetails) {
929 if (!empty($ipDetails->status) && $ipDetails->status && !empty($ipDetails->ip) && $ipDetails->ip === $ipAddress) {
930 $isIpWhiteListed = true;
931 break;
932 }
933 }
934 if (!$isIpWhiteListed) {
935 $private_ip = sprintf(
936 /* translators: %s: IP address */
937 __('Sorry!! Your IP address is %s, Blocked from submitting the form', 'bit-form'),
938 $ipAddress
939 );
940
941 $private_ip = apply_filters(
942 'bitform_filter_private_ip_message',
943 $private_ip,
944 $this->form_id
945 );
946
947 $restrictionMessage[] = $private_ip;
948 }
949 }
950 }
951 }
952 return $restrictionMessage;
953 }
954
955 /**
956 * Will check if form is submitted by a bot
957 *
958 * @return Boolean true - if submitted by bot else false
959 */
960 public function isTrappedInHoneypot()
961 {
962 $isHoneyPot = false;
963
964 if (!$this->isHoneypotActive()) {
965 return false;
966 }
967
968 $token = isset($_POST['b_h_t']) ? sanitize_text_field(wp_unslash($_POST['b_h_t'])) : '';
969 $pattern = '/^([a-zA-Z0-9]*_[a-zA-Z0-9]*){4}$/';
970 $decryptedToken = base64_decode(base64_decode($token));
971
972 preg_match($pattern, $decryptedToken, $validToken);
973
974 if ($validToken) {
975 if (isset($_POST[$token]) && empty($_POST[$token])) {
976 $isHoneyPot = false;
977 } else {
978 $isHoneyPot = true;
979 }
980 } else {
981 $isHoneyPot = true;
982 }
983
984 if (isset($_POST[$token])) {
985 unset($_POST[$token]);
986 }
987 unset($_POST['b_h_t']);
988 return $isHoneyPot;
989 }
990
991 public function isHoneypotActive()
992 {
993 $formContents = $this->getFormContent();
994 $enabled = empty($formContents->additional->enabled) ? null : $formContents->additional->enabled;
995 if (!empty($enabled->honeypot) && $enabled->honeypot) {
996 return true;
997 }
998 return false;
999 }
1000
1001 public function checkPaymentFields()
1002 {
1003 $formContents = $this->getFormContent();
1004 $fields = $formContents->fields;
1005
1006 $payments = [];
1007 foreach ($fields as $fldData) {
1008 if ('paypal' === $fldData->typ && property_exists($fldData, 'payIntegID')) {
1009 $payments['paypalKey'] = $this->getClientKey($fldData->payIntegID, 'clientID');
1010 } elseif ('razorpay' === $fldData->typ && property_exists($fldData->options, 'payIntegID')) {
1011 $payments['razorpayKey'] = $this->getClientKey($fldData->options->payIntegID, 'apiKey');
1012 }
1013 }
1014
1015 return $payments;
1016 }
1017
1018 private function getClientKey($integID, $keyName)
1019 {
1020 $client = '';
1021 if (!empty($integID)) {
1022 $integrationHandler = new IntegrationHandler(0);
1023 $integration = $integrationHandler->getAIntegration($integID, 'app', 'payments');
1024 if (!is_wp_error($integration)) {
1025 $integration_details = json_decode($integration[0]->integration_details);
1026 $client = base64_encode($integration_details->{$keyName});
1027 }
1028 }
1029 return $client;
1030 }
1031
1032 public function getSuccessMessageMarkups()
1033 {
1034 if (is_null($this->_work_flows)) {
1035 $workFlowManager = new WorkFlowHandler($this->form_id);
1036 $this->_work_flows = $workFlowManager->getAllworkFlow();
1037 }
1038
1039 $ids = [];
1040 foreach ($this->_work_flows as $msgItem) {
1041 foreach ($msgItem['conditions'] as $condition) {
1042 if (isset($condition->actions->success)) {
1043 foreach ($condition->actions->success as $msg) {
1044 if ('successMsg' === $msg->type && isset($msg->details->id)) {
1045 $idObj = json_decode(stripslashes($msg->details->id));
1046 if (is_object($idObj) && !empty($idObj->id)) {
1047 array_push($ids, $idObj->id);
1048 }
1049 }
1050 }
1051 }
1052 if (isset($condition->actions->failure)) {
1053 $idObj = json_decode(stripslashes($condition->actions->failure));
1054 if (is_object($idObj) && !empty($idObj->id)) {
1055 array_push($ids, $idObj->id);
1056 }
1057 }
1058 }
1059 }
1060 $ids = array_unique($ids);
1061 if (is_null($this->_conf_messages)) {
1062 $successMsgHandler = new SuccessMessageHandler($this->form_id);
1063 $this->_conf_messages = $successMsgHandler->getMessages($ids);
1064 }
1065
1066 $messageMarkups = '';
1067 if (is_wp_error($this->_conf_messages)) {
1068 return $messageMarkups;
1069 }
1070
1071 foreach ($this->_conf_messages as $msgItem) {
1072 $messageMarkups .= $this->messageMarkup($msgItem);
1073 }
1074
1075 return $messageMarkups;
1076 }
1077
1078 public function getFormAbandonmentMessage()
1079 {
1080 if (class_exists('\BitCode\BitFormPro\Admin\FormSettings\FormAbandonment')) {
1081 $formAbandonmentSettings = FormAbandonment::getFormAbandonmentSettings($this->form_id);
1082 $msg = '';
1083 if (isset($formAbandonmentSettings->showWarningMsg) && $formAbandonmentSettings->showWarningMsg && !empty($formAbandonmentSettings->warningMsg)) {
1084 $msg = $formAbandonmentSettings->warningMsg;
1085 $msg = '<div class="bf-form-msg active warning">' . wp_kses_post($msg) . '</div>';
1086 }
1087 return $msg;
1088 }
1089 }
1090
1091 public function getFormAbandonmentSettings()
1092 {
1093 if (class_exists('\BitCode\BitFormPro\Admin\FormSettings\FormAbandonment')) {
1094 $formAbandonmentSettings = FormAbandonment::getFormAbandonmentSettings($this->form_id);
1095 return $formAbandonmentSettings;
1096 }
1097 return null;
1098 }
1099
1100 private function messageMarkup($msg)
1101 {
1102 $msgId = $msg->id;
1103 $msgConfig = json_decode($msg->message_config);
1104 $scrollClass = 'below' === $msgConfig->msgType ? 'scroll' : '';
1105
1106 return '<div
1107 role="dialog"
1108 aria-hidden="true"
1109 data-modal-backdrop="true"
1110 class="' . $this->getAtomicCls("msg-container-{$msgId}") . ' deactive ' . $scrollClass . '">
1111 <div
1112 data-contentid="' . $this->getFormIdentifier() . '"
1113 data-msgid="' . $msgId . '"
1114 role="button"
1115 class="' . $this->getAtomicCls("msg-background-{$msgId}") . ' msg-backdrop">
1116 <div class="bf-msg-content ' . $this->getAtomicCls("msg-content-{$msgId}") . '">
1117 <button
1118 data-contentid="' . $this->getFormIdentifier() . '"
1119 data-msgid="' . $msgId . '"
1120 class="' . $this->getAtomicCls("close-{$msgId}") . ' bf-msg-close"
1121 type="button">
1122 <svg class="' . $this->getAtomicCls("close-icn-{$msgId}") . '" viewBox="0 0 30 30">
1123 <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="4" y1="3.88" x2="26" y2="26.12"></line>
1124 <line fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" x1="26" y1="3.88" x2="4" y2="26.12"></line>
1125 </svg>
1126 </button>
1127 <div class="msg-content"></div>
1128 </div>
1129 </div>
1130 </div>';
1131 }
1132 }
1133