From 2fdfd48a98064a96e6b02fd584e31d15153bc5b1 Mon Sep 17 00:00:00 2001 From: svfcode Date: Thu, 18 Sep 2025 07:35:45 +0300 Subject: [PATCH 1/4] New. Protect. Init external form protection. --- .gitignore | 2 + cleantalk.antispam/assets/js/external.js | 384 +++++++++++++++++++++++ cleantalk.antispam/assets/js/public.js | 149 +++++++++ cleantalk.antispam/include.php | 156 +-------- cleantalk/ajax/.parameters.php | 14 + cleantalk/ajax/ajax.php | 10 + cleantalk/ajax/class.php | 132 ++++++++ 7 files changed, 696 insertions(+), 151 deletions(-) create mode 100644 cleantalk.antispam/assets/js/external.js create mode 100644 cleantalk.antispam/assets/js/public.js create mode 100644 cleantalk/ajax/.parameters.php create mode 100644 cleantalk/ajax/ajax.php create mode 100644 cleantalk/ajax/class.php diff --git a/.gitignore b/.gitignore index 7681e02..b9ac0c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /* !/cleantalk.antispam !/cleantalk.antispam/* +!/cleantalk +!/cleantalk/* !/.github !/.github/* \ No newline at end of file diff --git a/cleantalk.antispam/assets/js/external.js b/cleantalk.antispam/assets/js/external.js new file mode 100644 index 0000000..816e6a2 --- /dev/null +++ b/cleantalk.antispam/assets/js/external.js @@ -0,0 +1,384 @@ + +window.addEventListener('load', function() { + // if ( ! +ctPublic.settings__forms__check_external ) { + // return; + // } + + setTimeout(function() { + ctProtectExternal(); + }, 2000); +}); + +/** + * Handle external forms + */ +function ctProtectExternal() { + for (let i = 0; i < document.forms.length; i++) { + const currentForm = document.forms[i]; + + // skip excluded forms + // if (formIsExclusion(currentForm)) { + // continue; + // } + + // Ajax checking for the integrated forms - will be changed the whole form object to make protection + if (isIntegratedForm(currentForm)) { + apbctProcessExternalForm(currentForm, i, document); + } + } +} + +/** + * Check if the form is integrated + * @param {HTMLFormElement} form + * @returns {boolean} + */ +function isIntegratedForm(form) { + // if form contains input with class b24-form-control + if (form.querySelector('input[class*="b24-form-control"]')) { + console.log('Form is integrated'); + return true; + } + console.log('Form is not integrated'); + return false; +} + +/** + * Process external form + * @param {HTMLFormElement} form + * @param {number} iterator + * @param {HTMLDocument} document + */ +function apbctProcessExternalForm(currentForm, iterator, documentObject) { + console.log('Processing external form'); + + const cleantalkPlaceholder = document.createElement('i'); + cleantalkPlaceholder.className = 'cleantalk_placeholder'; + cleantalkPlaceholder.style = 'display: none'; + + currentForm.parentElement.insertBefore(cleantalkPlaceholder, currentForm); + + // Deleting form to prevent submit event + const prev = currentForm.previousSibling; + const formHtml = currentForm.outerHTML; + const formOriginal = currentForm; + const formContent = currentForm.querySelectorAll('input, textarea, select'); + + // Remove the original form + currentForm.parentElement.removeChild(currentForm); + + // Insert a clone + const placeholder = document.createElement('div'); + placeholder.innerHTML = formHtml; + const clonedForm = placeholder.firstElementChild; + prev.after(clonedForm); + + if (formContent && formContent.length > 0) { + formContent.forEach(function(content) { + if (content && content.name && content.type !== 'submit' && content.type !== 'button') { + if (content.type === 'checkbox') { + const checkboxInput = clonedForm.querySelector(`input[name="${content.name}"]`); + if (checkboxInput) { + checkboxInput.checked = content.checked; + } + } else { + const input = clonedForm.querySelector( + `input[name="${content.name}"], ` + + `textarea[name="${content.name}"], ` + + `select[name="${content.name}"]`, + ); + if (input) { + input.value = content.value; + } + } + } + }); + } + + const forceAction = document.createElement('input'); + forceAction.name = 'action'; + forceAction.value = 'cleantalk_force_ajax_check'; + forceAction.type = 'hidden'; + + const reUseCurrentForm = documentObject.forms[iterator]; + + reUseCurrentForm.appendChild(forceAction); + reUseCurrentForm.apbctPrev = prev; + reUseCurrentForm.apbctFormOriginal = formOriginal; + + documentObject.forms[iterator].onsubmit = function(event) { + event.preventDefault(); + sendAjaxCheckingFormData(event.currentTarget); + }; +} + +/** + * Replace input values from one form to another + * @param {HTMLElement} sourceForm + * @param {HTMLElement} targetForm + */ +function apbctReplaceInputsValuesFromOtherForm(sourceForm, targetForm) { + if (!sourceForm || !targetForm) return; + + const sourceInputs = sourceForm.querySelectorAll('input, textarea, select'); + sourceInputs.forEach(function(sourceInput) { + if (sourceInput.name && sourceInput.type !== 'submit' && sourceInput.type !== 'button') { + const targetInput = targetForm.querySelector(`[name="${sourceInput.name}"]`); + if (targetInput) { + if (sourceInput.type === 'checkbox' || sourceInput.type === 'radio') { + targetInput.checked = sourceInput.checked; + } else { + targetInput.value = sourceInput.value; + } + } + } + }); +} + +/** + * ApbctHandler class for handling block messages + */ +function ApbctHandler() { + this.parseBlockMessage = function(result) { + let message = ''; + if (result.apbct && result.apbct.comment) { + message = result.apbct.comment; + } else if (result.error && result.error.msg) { + message = result.error.msg; + } else if (result.data && result.data.message) { + message = result.data.message; + } + + if (message) { + alert(message); + if (result.apbct && result.apbct.stop_script == 1) { + window.stop(); + } + } + }; +} + +/** + * Sending Ajax for checking form data + * @param {HTMLElement} form + * @param {HTMLElement} prev + * @param {HTMLElement} formOriginal + */ +function sendAjaxCheckingFormData(form) { + let botDetectorEventToken = localStorage.getItem('bot_detector_event_token'); + if (typeof botDetectorEventToken === 'string') { + botDetectorEventToken = JSON.parse(botDetectorEventToken); + if (typeof botDetectorEventToken === 'object') { + botDetectorEventToken = botDetectorEventToken.value; + } + } + const data = { + 'ct_bot_detector_event_token': botDetectorEventToken, + }; + let elems = form.elements; + elems = Array.prototype.slice.call(elems); + + elems.forEach(function(elem, y) { + elem.name === '' ? data['input_' + y] = elem.value : data[elem.name] = elem.value; + }); + + // Use Bitrix AJAX framework + if (typeof BX !== 'undefined' && BX.ajax) { + BX.ajax({ + url: '/bitrix/components/cleantalk/ajax/ajax.php', + method: 'POST', + data: data, + dataType: 'json', + async: false, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + onsuccess: function(result) { + console.log('CleanTalk AJAX success:', result); + if ((result.apbct === undefined && result.data === undefined) || + (result.apbct !== undefined && ! +result.apbct.blocked) + ) { + // Clear service fields + // for (const el of form.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { + // el.remove(); + // } + + const formNew = form; + form.parentElement.removeChild(form); + const prev = form.apbctPrev; + const formOriginal = form.apbctFormOriginal; + // let isExternalFormsRestartRequired = apbctExternalFormsRestartRequired(formOriginal); + apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); + + prev.after(formOriginal); + + for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { + el.remove(); + } + for (const el of formOriginal.querySelectorAll('input[name="ct_bot_detector_event_token"]')) { + el.remove(); + } + + // Common click event + let submButton = formOriginal.querySelectorAll('button[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX success 1:', submButton); + // submButton[0].click(); + // if (isExternalFormsRestartRequired) { + // setTimeout(function() { + // ctProtectExternal(); + // }, 1500); + // } + return; + } + + submButton = formOriginal.querySelectorAll('input[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX success 2:', submButton); + // submButton[0].click(); + // if (isExternalFormsRestartRequired) { + // setTimeout(function() { + // ctProtectExternal(); + // }, 1500); + // } + return; + } + } + // if ((result.apbct !== undefined && +result.apbct.blocked) || + // (result.data !== undefined && result.data.message !== undefined) + // ) { + // new ApbctHandler().parseBlockMessage(result); + // } + }, + onfailure: function(result) { + console.error('CleanTalk AJAX error:', result); + // Allow form submission on error + const formNew = form; + form.parentElement.removeChild(form); + const prev = form.apbctPrev; + const formOriginal = form.apbctFormOriginal; + apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); + prev.after(formOriginal); + + for (const el of formOriginal.querySelectorAll('input[name="cleantalk_force_ajax_check"]')) { + el.remove(); + } + for (const el of formOriginal.querySelectorAll('input[name="ct_bot_detector_event_token"]')) { + el.remove(); + } + + let submButton = formOriginal.querySelectorAll('button[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX error 1:', submButton); + // submButton[0].click(); + return; + } + submButton = formOriginal.querySelectorAll('input[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX error 2:', submButton); + // submButton[0].click(); + } + } + }); + } else { + // Fallback to native XMLHttpRequest if BX is not available + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/bitrix/components/cleantalk/ajax/ajax.php', false); // synchronous + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + const formData = new URLSearchParams(); + for (const key in data) { + formData.append(key, data[key]); + } + + xhr.send(formData); + + if (xhr.status === 200) { + try { + const result = JSON.parse(xhr.responseText); + if ((result.apbct === undefined && result.data === undefined) || + (result.apbct !== undefined && ! +result.apbct.blocked) + ) { + // Allow form submission + const formNew = form; + form.parentElement.removeChild(form); + const prev = form.apbctPrev; + const formOriginal = form.apbctFormOriginal; + apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); + prev.after(formOriginal); + + for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { + el.remove(); + } + + let submButton = formOriginal.querySelectorAll('button[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX success 1:', submButton); + // submButton[0].click(); + return; + } + submButton = formOriginal.querySelectorAll('input[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX success 2:', submButton); + // submButton[0].click(); + } + } else if ((result.apbct !== undefined && +result.apbct.blocked) || + (result.data !== undefined && result.data.message !== undefined) + ) { + new ApbctHandler().parseBlockMessage(result); + } + } catch (e) { + console.error('CleanTalk JSON parse error:', e); + // Allow form submission on error + const formNew = form; + form.parentElement.removeChild(form); + const prev = form.apbctPrev; + const formOriginal = form.apbctFormOriginal; + apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); + prev.after(formOriginal); + + for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { + el.remove(); + } + + let submButton = formOriginal.querySelectorAll('button[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX error 1:', submButton); + // submButton[0].click(); + return; + } + submButton = formOriginal.querySelectorAll('input[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX error 2:', submButton); + // submButton[0].click(); + } + } + } else { + console.error('CleanTalk AJAX request failed:', xhr.status); + // Allow form submission on error + const formNew = form; + form.parentElement.removeChild(form); + const prev = form.apbctPrev; + const formOriginal = form.apbctFormOriginal; + apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); + prev.after(formOriginal); + + for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { + el.remove(); + } + + let submButton = formOriginal.querySelectorAll('button[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX success 1:', submButton); + // submButton[0].click(); + return; + } + submButton = formOriginal.querySelectorAll('input[type=submit]'); + if ( submButton.length !== 0 ) { + console.log('CleanTalk AJAX success 2:', submButton); + // submButton[0].click(); + } + } + } +} diff --git a/cleantalk.antispam/assets/js/public.js b/cleantalk.antispam/assets/js/public.js new file mode 100644 index 0000000..0acb5bd --- /dev/null +++ b/cleantalk.antispam/assets/js/public.js @@ -0,0 +1,149 @@ +var ct_date = new Date(); + +function ctSetCookie(c_name, value) { + document.cookie = c_name + '=' + encodeURIComponent(value) + '; path=/'; +} + +ctSetCookie('ct_ps_timestamp', Math.floor(new Date().getTime()/1000)); +ctSetCookie('ct_fkp_timestamp', '0'); +ctSetCookie('ct_timezone', '0'); + +ct_attach_event_handler(window, 'DOMContentLoaded', ct_ready); + +setTimeout(function(){ + ctSetCookie('ct_timezone', ct_date.getTimezoneOffset()/60*(-1)); + ctSetCookie('ct_checkjs', ct_checkjs_val); +},1000); + +/* Writing first key press timestamp */ +var ctFunctionFirstKey = function output(event){ + var KeyTimestamp = Math.floor(new Date().getTime()/1000); + ctSetCookie('ct_fkp_timestamp', KeyTimestamp); + ctKeyStopStopListening(); +} + +/* Stop key listening function */ +function ctKeyStopStopListening(){ + if(typeof window.addEventListener == 'function'){ + window.removeEventListener('mousedown', ctFunctionFirstKey); + window.removeEventListener('keydown', ctFunctionFirstKey); + }else{ + window.detachEvent('mousedown', ctFunctionFirstKey); + window.detachEvent('keydown', ctFunctionFirstKey); + } +} + +if(typeof window.addEventListener == 'function'){ + window.addEventListener('mousedown', ctFunctionFirstKey); + window.addEventListener('keydown', ctFunctionFirstKey); +}else{ + window.attachEvent('mousedown', ctFunctionFirstKey); + window.attachEvent('keydown', ctFunctionFirstKey); +} +/* Ready function */ +function ct_ready(){ + ctSetCookie('ct_visible_fields', 0); + ctSetCookie('ct_visible_fields_count', 0); + setTimeout(function(){ + for(var i = 0; i < document.forms.length; i++){ + var form = document.forms[i]; + if (form.action.toString().indexOf('/auth/?forgot_password') !== -1) { + continue; + } + form.onsubmit_prev = form.onsubmit; + form.onsubmit = function(event){ + + /* Get only fields */ + var elements = []; + for(var key in this.elements){ + if(!isNaN(+key)) + elements[key] = this.elements[key]; + } + + /* Filter fields */ + elements = elements.filter(function(elem){ + + var pass = true; + + /* Filter fields */ + if( getComputedStyle(elem).display === 'none' || // hidden + getComputedStyle(elem).visibility === 'hidden' || // hidden + getComputedStyle(elem).opacity === '0' || // hidden + elem.getAttribute('type') === 'hidden' || // type == hidden + elem.getAttribute('type') === 'submit' || // type == submit + elem.value === '' || // empty value + elem.getAttribute('name') === null + ){ + return false; + } + + /* Filter elements with same names for type == radio */ + if(elem.getAttribute('type') === 'radio'){ + elements.forEach(function(el, j, els){ + if(elem.getAttribute('name') === el.getAttribute('name')){ + pass = false; + return; + } + }); + } + + return true; + }); + + /* Visible fields count */ + var visible_fields_count = elements.length; + + /* Visible fields */ + var visible_fields = ''; + elements.forEach(function(elem, i, elements){ + visible_fields += ' ' + elem.getAttribute('name'); + }); + visible_fields = visible_fields.trim(); + + ctSetCookie('ct_visible_fields', visible_fields); + ctSetCookie('ct_visible_fields_count', visible_fields_count); + + /* Call previous submit action */ + if(event.target.onsubmit_prev instanceof Function){ + setTimeout(function(){ + event.target.onsubmit_prev.call(event.target, event); + }, 500); + } + }; + } + }, 1000); +} + +function ct_attach_event_handler(elem, event, callback){ + if(typeof window.addEventListener === 'function') elem.addEventListener(event, callback); + else elem.attachEvent(event, callback); +} + +function ct_remove_event_handler(elem, event, callback){ + if(typeof window.removeEventListener === 'function') elem.removeEventListener(event, callback); + else elem.detachEvent(event, callback); +} + +if(typeof jQuery !== 'undefined') { + +/* Capturing responses and output block message for unknown AJAX forms */ +jQuery(document).ajaxComplete(function (event, xhr, settings) { + if (xhr.responseText && xhr.responseText.indexOf('\"apbct') !== -1) { + try { + var response = JSON.parse(xhr.responseText); + if (typeof response.apbct !== 'undefined') { + response = response.apbct; + if (response.blocked) { + alert(response.comment); + if(+response.stop_script == 1) + window.stop(); + } + } + } catch (e) { + return; + } + + } +}); + +} \ No newline at end of file diff --git a/cleantalk.antispam/include.php b/cleantalk.antispam/include.php index 1fa9c82..1e0327e 100644 --- a/cleantalk.antispam/include.php +++ b/cleantalk.antispam/include.php @@ -1131,157 +1131,11 @@ static function FormAddon() { $ct_check_values = self::SetCheckJSValues(); - $js_template = ""; + $js_template = ""; return $js_template; } diff --git a/cleantalk/ajax/.parameters.php b/cleantalk/ajax/.parameters.php new file mode 100644 index 0000000..97701e0 --- /dev/null +++ b/cleantalk/ajax/.parameters.php @@ -0,0 +1,14 @@ + array(), + "PARAMETERS" => array( + "ACTION" => array( + "PARENT" => "BASE", + "NAME" => "AJAX Action", + "TYPE" => "STRING", + "DEFAULT" => "cleantalk_force_ajax_check", + ), + ), +); diff --git a/cleantalk/ajax/ajax.php b/cleantalk/ajax/ajax.php new file mode 100644 index 0000000..fe657e4 --- /dev/null +++ b/cleantalk/ajax/ajax.php @@ -0,0 +1,10 @@ +IncludeComponent( + "cleantalk:ajax", + "", + array( + "ACTION" => $_POST['action'] ?? $_GET['action'] ?? 'cleantalk_force_ajax_check' + ) +); diff --git a/cleantalk/ajax/class.php b/cleantalk/ajax/class.php new file mode 100644 index 0000000..886e39b --- /dev/null +++ b/cleantalk/ajax/class.php @@ -0,0 +1,132 @@ +showError('Module not installed'); + return; + } + + // Check if this is an AJAX request (relaxed check for compatibility) + $isAjax = ( + (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') || + (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) || + (isset($_POST['action']) || isset($_GET['action'])) + ); + + if (!$isAjax) { + // Debug: Log what headers we received + $debug_info = [ + 'headers' => function_exists('getallheaders') ? getallheaders() : 'getallheaders not available', + 'server_vars' => [ + 'HTTP_X_REQUESTED_WITH' => $_SERVER['HTTP_X_REQUESTED_WITH'] ?? 'not set', + 'HTTP_ACCEPT' => $_SERVER['HTTP_ACCEPT'] ?? 'not set', + 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? 'not set', + 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'] ?? 'not set' + ] + ]; + $this->showError('Not an AJAX request. Debug: ' . json_encode($debug_info)); + return; + } + + $action = $this->arParams['ACTION'] ?? $_POST['action'] ?? $_GET['action'] ?? ''; + + switch ($action) { + case 'cleantalk_force_ajax_check': + $this->handleCleantalkAjaxCheck(); + break; + default: + $this->showError('Unknown action'); + } + } + + private function handleCleantalkAjaxCheck() + { + global $APPLICATION; + + // Get form data + $formData = $_POST; + + // Remove the action parameter + unset($formData['action']); + + // Prepare user data for checking + $arUser = array(); + $arUser["type"] = "feedback_general_contact_form"; + $arUser["sender_email"] = $formData['email'] ?? ''; + $arUser["sender_nickname"] = $formData['name'] ?? $formData['nickname'] ?? ''; + $arUser["subject"] = $formData['subject'] ?? ''; + $arUser["message"] = array_filter($formData, function($key) { + return !in_array($key, ['email', 'name', 'nickname', 'subject', 'action', 'ct_bot_detector_event_token']); + }, ARRAY_FILTER_USE_KEY); + + // Check for spam + $aResult = CleantalkAntispam::CheckAllBefore($arUser, false); + + if (isset($aResult) && is_array($aResult)) { + if ($aResult['errno'] == 0) { + if ($aResult['allow'] == 1) { + // Not spam - allow + $this->showSuccess([ + 'apbct' => [ + 'blocked' => false, + 'comment' => '' + ], + 'success' => true + ]); + } else { + // Spam detected - block + $this->showSuccess([ + 'apbct' => [ + 'blocked' => true, + 'comment' => $aResult['ct_result_comment'] + ], + 'error' => [ + 'msg' => $aResult['ct_result_comment'] + ] + ]); + } + } else { + // Error occurred + $this->showSuccess([ + 'apbct' => [ + 'blocked' => false, + 'comment' => '' + ], + 'error' => [ + 'msg' => $aResult['errstr'] ?? 'Unknown error' + ] + ]); + } + } else { + // No result - allow by default + $this->showSuccess([ + 'apbct' => [ + 'blocked' => false, + 'comment' => '' + ], + 'success' => true + ]); + } + } + + private function showSuccess($data) + { + header('Content-Type: application/json'); + echo Json::encode($data); + die(); + } + + private function showError($message) + { + header('Content-Type: application/json'); + echo Json::encode(['error' => $message]); + die(); + } +} From 2337068c8bb14f4d962316bb1b9913b6df20963c Mon Sep 17 00:00:00 2001 From: alexandergull Date: Wed, 1 Oct 2025 23:27:46 +0500 Subject: [PATCH 2/4] Upd. No component needs to check external AJAX. --- cleantalk.antispam/include.php | 26 ++++ cleantalk.antispam/install/index.php | 119 +++++++++------- .../ApbctBitrix/CleantalkAjaxCatcher.php | 104 ++++++++++++++ cleantalk/ajax/.parameters.php | 14 -- cleantalk/ajax/ajax.php | 10 -- cleantalk/ajax/class.php | 132 ------------------ 6 files changed, 202 insertions(+), 203 deletions(-) create mode 100644 cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkAjaxCatcher.php delete mode 100644 cleantalk/ajax/.parameters.php delete mode 100644 cleantalk/ajax/ajax.php delete mode 100644 cleantalk/ajax/class.php diff --git a/cleantalk.antispam/include.php b/cleantalk.antispam/include.php index 1e0327e..b142e1a 100644 --- a/cleantalk.antispam/include.php +++ b/cleantalk.antispam/include.php @@ -8,6 +8,7 @@ //Antispam classes use Bitrix\Main\Page\Asset; +use Bitrix\Main\Context; use Cleantalk\Antispam\Cleantalk; use Cleantalk\Antispam\CleantalkRequest; use Cleantalk\Antispam\CleantalkResponse; @@ -19,6 +20,7 @@ use Cleantalk\ApbctBitrix\RemoteCalls; use Cleantalk\ApbctBitrix\Cron; use Cleantalk\ApbctBitrix\DB; +use Cleantalk\ApbctBitrix\CleantalkAjaxCatcher; use Cleantalk\Common\Variables\Server; use Cleantalk\ApbctBitrix\SFW; @@ -1187,6 +1189,7 @@ static function CheckAllBefore(&$arEntity, $bSendEmail = FALSE, $form_errors = n 'register', 'order', 'feedback_general_contact_form', + 'feedback_ajax', 'private_message', ); @@ -1388,6 +1391,19 @@ static function CheckAllBefore(&$arEntity, $bSendEmail = FALSE, $form_errors = n $request_params['post_info']['comment_type'] = 'private_message'; $ct_request = new CleantalkRequest($request_params); $ct_result = $ct->isAllowMessage($ct_request); + break; + + case 'feedback_ajax': + + $timelabels_key = 'mail_error_comment'; + if (is_array($arEntity['message'])) { + $arEntity['message'] = json_encode($arEntity['message']); + } + $request_params['message'] = $arEntity['message']; + $request_params['tz'] = isset($arEntity['user_timezone']) ? $arEntity['user_timezone'] : NULL; + $request_params['post_info']['comment_type'] = 'feedback_ajax'; + $ct_request = new CleantalkRequest($request_params); + $ct_result = $ct->isAllowMessage($ct_request); } $ret_val = array(); @@ -1960,4 +1976,14 @@ private static function urlIsExclusion(){ } return false; } + + public static function onProlog() + { + $request = Context::getCurrent()->getRequest(); + + // Проверяем, что это AJAX запрос + if ($request->isAjaxRequest()) { + CleantalkAjaxCatcher::handleAjax($request); + } + } } diff --git a/cleantalk.antispam/install/index.php b/cleantalk.antispam/install/index.php index a0ccfc3..74c9c24 100644 --- a/cleantalk.antispam/install/index.php +++ b/cleantalk.antispam/install/index.php @@ -7,6 +7,7 @@ use Cleantalk\Common\API as CleantalkAPI; use Bitrix\Main\Config\Option; +use Bitrix\Main\EventManager; /** * Installer for CleanTalk module @@ -52,7 +53,7 @@ function __construct() { } $this->MODULE_NAME = GetMessage('CLEANTALK_MODULE_NAME'); $this->MODULE_DESCRIPTION = GetMessage('CLEANTALK_MODULE_DESCRIPTION'); - $this->PARTNER_NAME = "CleanTalk"; + $this->PARTNER_NAME = "CleanTalk"; $this->PARTNER_URI = "http://www.cleantalk.org"; // Values for all templates @@ -71,6 +72,30 @@ function __construct() { $this->template_messages = array(); } + public function InstallEvents() + { + $eventManager = EventManager::getInstance(); + $eventManager->registerEventHandler( + 'main', + 'OnProlog', + 'cleantalk.antispam', + 'CleantalkAntispam', + 'OnProlog' + ); + } + + public function UnInstallEvents() + { + $eventManager = EventManager::getInstance(); + $eventManager->unRegisterEventHandler( + 'main', + 'OnProlog', + 'cleantalk.antispam', + 'CleantalkAntispam', + 'OnProlog' + ); + } + function DoInstall() { global $DOCUMENT_ROOT, $APPLICATION; @@ -112,11 +137,11 @@ function DoInstall() { if (IsModuleInstalled('bitrix.eshop')) { RegisterModuleDependences('sale', 'OnBeforeOrderAdd', 'cleantalk.antispam', 'CleantalkAntispam', 'OnBeforeOrderAddHandler'); - } + } if (IsModuleInstalled('form')) { RegisterModuleDependences('form', 'OnBeforeResultAdd', 'cleantalk.antispam', 'CleantalkAntispam', 'OnBeforeResultAddHandler'); - } + } } //init default options if no options set @@ -143,7 +168,7 @@ function DoInstall() { //Checking API key if already set $api_key = COption::GetOptionString( 'cleantalk.antispam', 'key', ''); $form_sfw = COption::GetOptionInt( 'cleantalk.antispam', 'form_sfw', 0 ); //TODO For what is it? - + $result = CleantalkAPI::method__notice_paid_till($api_key, preg_replace('/http[s]?:\/\//', '', $_SERVER['HTTP_HOST'], 1)); COption::SetOptionInt( 'cleantalk.antispam', 'key_is_ok', isset($result['valid']) && $result['valid'] == '1' ? 1 : 0); @@ -155,7 +180,7 @@ function DoInstall() { } $GLOBALS["errors"] = $this->errors; $GLOBALS["messages"] = $this->messages; - $APPLICATION->IncludeAdminFile(GetMessage('CLEANTALK_INSTALL_TITLE'), $DOCUMENT_ROOT.'/bitrix/modules/cleantalk.antispam/install/step.php'); + $APPLICATION->IncludeAdminFile(GetMessage('CLEANTALK_INSTALL_TITLE'), $DOCUMENT_ROOT.'/bitrix/modules/cleantalk.antispam/install/step.php'); } function DoUninstall() { @@ -187,7 +212,7 @@ function DoUninstall() { UnRegisterModuleDependences('main', 'OnEndBufferContent', 'cleantalk.antispam', 'CleantalkAntispam', 'OnEndBufferContentHandler'); if (IsModuleInstalled('form')){ UnRegisterModuleDependences('form', 'OnBeforeResultAdd', 'cleantalk.antispam', 'CleantalkAntispam', 'OnBeforeResultAddHandler'); - } + } UnRegisterModule('cleantalk.antispam'); $this->UnInstallDB(); $this->UnInstallFiles(); @@ -203,7 +228,7 @@ function DoUninstall() { } function InstallFiles() { - + $results = $this->install_ct_template__in_dirs( $this->SAR_template_file, array( @@ -216,18 +241,18 @@ function InstallFiles() { $this->ct_template_addon_tag, $this->ct_template_addon_body ); - + foreach ($results as $dir => $result){ if(is_dir($dir) && $result != 0){ error_log('CLEANTALK_ERROR: INSTALLING_IN_TEMPLATE_FILES: ' . $dir . sprintf('%02d', $result )); } } - + return true; } function UnInstallFiles() { - + $results = $this->uninstall_ct_template__in_dirs( $this->SAR_template_file, array( @@ -248,9 +273,9 @@ function UnInstallFiles() { } function InstallDB() { - + global $DB; - + // Creating SFW DATA $result = $DB->Query( 'CREATE TABLE IF NOT EXISTS `cleantalk_sfw` ( @@ -266,7 +291,7 @@ function InstallDB() { $this->errors[] = GetMessage('CLEANTALK_ERROR_CREATE_SFW_DATA'); return FALSE; } - + // Creating SFW LOGS $result = $DB->Query( 'CREATE TABLE IF NOT EXISTS `cleantalk_sfw_logs` ( @@ -284,7 +309,7 @@ function InstallDB() { $this->errors[] = GetMessage('CLEANTALK_ERROR_CREATE_SFW_LOGS'); return FALSE; } - + // Creating TIMELABELS $result = $DB->Query( 'CREATE @@ -298,7 +323,7 @@ function InstallDB() { $this->errors[] = GetMessage('CLEANTALK_ERROR_CREATE_TIMELABELS'); return FALSE; } - + // Creating CIDS $result = $DB->Query( 'CREATE @@ -314,7 +339,7 @@ function InstallDB() { $this->errors[] = GetMessage('CLEANTALK_ERROR_CREATE_CIDS'); return FALSE; } - + // Creating SERVER $result = $DB->Query( 'CREATE @@ -329,7 +354,7 @@ function InstallDB() { $this->errors[] = GetMessage('CLEANTALK_ERROR_CREATE_SERVER'); return FALSE; } - + // Creating CHECKJS $result = $DB->Query( 'CREATE @@ -356,7 +381,7 @@ function UnInstallDB($arParams = Array()) { $DB->Query('DROP TABLE IF EXISTS cleantalk_checkjs'); return TRUE; } - + /** * Wrapper for cleantalk_antispam::install_ct_template__in_dir() * Allows to pass an array into it @@ -376,7 +401,7 @@ function install_ct_template__in_dirs($template_file, $template_dirs, $pattern, } return $out; } - + /** * Copies needed template from system dir to local dir and inserts CleanTalk addon into it * @@ -389,19 +414,19 @@ function install_ct_template__in_dirs($template_file, $template_dirs, $pattern, * @return int Returns error code or 0 when success */ function install_ct_template__in_dir( $template_file, $template_dir, $pattern, $ct_template_addon_tag, $ct_template_addon_body ){ - + // Check system folders if(!file_exists($template_dir)){ // No required system folders return 0; } $all_templates_folder = glob( $template_dir . '*' , GLOB_ONLYDIR); - + if (file_exists( $template_dir . '.default')) $all_templates_folder[] = $template_dir . '.default'; - + foreach ($all_templates_folder as $current_template){ - + // Exception for template mail templates // By type $description_file = $current_template . '/description.php'; @@ -414,26 +439,26 @@ function install_ct_template__in_dir( $template_file, $template_dir, $pattern, $ // Deleting mail template if( in_array( $current_template, array( 'mail_user' ) ) ) continue; - + $template_file_path = $current_template.'/'.$template_file; - + $start_pattern = ''; // don't change this! $end_pattern = ''; // don't change this! - + $result = $this->ct_file__clean_up( $template_file_path, $start_pattern, $end_pattern ); if( $result === true ){ - + // Check is it parsable $template_content = file_get_contents( $template_file_path ); if( $template_content ){ - + if( preg_match( $pattern, $template_content ) === 1 ){ - + $ct_template_addon = $start_pattern . $ct_template_addon_body . $end_pattern . "\n"; $template_content = preg_replace($pattern, $ct_template_addon . '${1}', $template_content, 1); - + if( file_put_contents( $template_file_path, $template_content ) ){ - + }else return 9; // Cannot write new content to template PHP file }else @@ -446,7 +471,7 @@ function install_ct_template__in_dir( $template_file, $template_dir, $pattern, $ // Here all is OK - new template PHP file with CLEANTALK addon inserted is ready return 0; } - + /** * Wrapper for cleantalk_antispam::install_ct_template__in_dir() * Allows to pass an array into it @@ -464,7 +489,7 @@ function uninstall_ct_template__in_dirs( $template_file, $template_dirs, $ct_tem } return $out; } - + /** * Remove addon from needed local component template * @@ -475,7 +500,7 @@ function uninstall_ct_template__in_dirs( $template_file, $template_dirs, $ct_tem * @return int Returns error code or 0 when success */ function uninstall_ct_template__in_dir( $template_file, $template_dir, $ct_template_addon_tag ){ - + // Check system folders if(!file_exists($template_dir)){ // No required system folders @@ -486,12 +511,12 @@ function uninstall_ct_template__in_dir( $template_file, $template_dir, $ct_templ if (file_exists( $template_dir . '.default')) $all_templates_folder[] = $template_dir . '.default'; foreach ($all_templates_folder as $current_template){ - + $template_file_path = $current_template.'/'.$template_file; - + $start_pattern = ''; // don't change this! $end_pattern = ''; // don't change this! - + $result = $this->ct_file__clean_up( $template_file_path, $start_pattern, $end_pattern ); if($result !== true ) return $result; @@ -500,7 +525,7 @@ function uninstall_ct_template__in_dir( $template_file, $template_dir, $ct_templ // Here all is OK - new template PHP file with CLEANTALK addon inserted is ready return 0; } - + /** * @param $file_path * @param $start_pattern @@ -509,31 +534,31 @@ function uninstall_ct_template__in_dir( $template_file, $template_dir, $ct_templ * @return bool|int */ function ct_file__clean_up( $file_path, $start_pattern, $end_pattern ){ - + // Last check - template PHP file if( is_file( $file_path ) || is_writable( $file_path ) ){ - + // Try to get template PHP file content $file_content = file_get_contents( $file_path ); - + if( $file_content ){ - + // Clean all CLEANTALK template addons $pos_begin = strpos( $file_content, $start_pattern ); $pos_end = strpos( $file_content, $end_pattern ); - + // Nothing to clean if($pos_begin === false && $pos_end === false) return true; - + if( $pos_begin !== false ){ if( $pos_end !== false ){ if( $pos_begin < $pos_end ){ - + // Cleaning up $file_content = substr( $file_content, 0, $pos_begin ) . substr( $file_content, $pos_end + strlen( $end_pattern ) ); // $file_content = substr( $file_content, 0, $pos_begin ) . substr( $file_content, $pos_end + strlen( '' ) ); - + if( file_put_contents( $file_path, $file_content ) ){ return true; }else @@ -549,5 +574,5 @@ function ct_file__clean_up( $file_path, $start_pattern, $end_pattern ){ }else return 4; // No template PHP file } - + } diff --git a/cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkAjaxCatcher.php b/cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkAjaxCatcher.php new file mode 100644 index 0000000..d6c09aa --- /dev/null +++ b/cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkAjaxCatcher.php @@ -0,0 +1,104 @@ +getPostList()->toArray(); + + if ( + empty($formData) || + !isset($formData['action']) || + $formData['action'] + !== 'cleantalk_force_ajax_check' + ) { + return; + } + // Remove the action parameter + unset($formData['action']); + + // Prepare user data for checking + $arUser = array(); + $arUser["type"] = "feedback_ajax"; + $arUser["sender_email"] = $formData['email'] ?? ''; + $arUser["sender_nickname"] = $formData['name'] ?? $formData['nickname'] ?? ''; + $arUser["subject"] = $formData['subject'] ?? ''; + $arUser["message"] = array_filter($formData, function($key) { + return !in_array($key, ['email', 'name', 'nickname', 'subject', 'action', 'ct_bot_detector_event_token']); + }, ARRAY_FILTER_USE_KEY); + + // Check for spam + $aResult = CleantalkAntispam::CheckAllBefore($arUser, false); + + if (isset($aResult) && is_array($aResult)) { + if ($aResult['errno'] == 0) { + if ($aResult['allow'] == 1) { + // Not spam - allow + self::showSuccess([ + 'apbct' => [ + 'blocked' => false, + 'comment' => '' + ], + 'success' => true + ]); + } else { + // Spam detected - block + self::showSuccess([ + 'apbct' => [ + 'blocked' => true, + 'comment' => $aResult['ct_result_comment'] + ], + 'error' => [ + 'msg' => $aResult['ct_result_comment'] + ] + ]); + } + } else { + // Error occurred + self::showSuccess([ + 'apbct' => [ + 'blocked' => false, + 'comment' => '' + ], + 'error' => [ + 'msg' => $aResult['errstr'] ?? 'Unknown error' + ] + ]); + } + } else { + // No result - allow by default + self::showSuccess([ + 'apbct' => [ + 'blocked' => false, + 'comment' => '' + ], + 'success' => true + ]); + } + } + + private static function showSuccess($data) + { + header('Content-Type: application/json'); + echo Json::encode($data); + die(); + } + + private static function showError($message) + { + header('Content-Type: application/json'); + echo Json::encode(['error' => $message]); + die(); + } +} diff --git a/cleantalk/ajax/.parameters.php b/cleantalk/ajax/.parameters.php deleted file mode 100644 index 97701e0..0000000 --- a/cleantalk/ajax/.parameters.php +++ /dev/null @@ -1,14 +0,0 @@ - array(), - "PARAMETERS" => array( - "ACTION" => array( - "PARENT" => "BASE", - "NAME" => "AJAX Action", - "TYPE" => "STRING", - "DEFAULT" => "cleantalk_force_ajax_check", - ), - ), -); diff --git a/cleantalk/ajax/ajax.php b/cleantalk/ajax/ajax.php deleted file mode 100644 index fe657e4..0000000 --- a/cleantalk/ajax/ajax.php +++ /dev/null @@ -1,10 +0,0 @@ -IncludeComponent( - "cleantalk:ajax", - "", - array( - "ACTION" => $_POST['action'] ?? $_GET['action'] ?? 'cleantalk_force_ajax_check' - ) -); diff --git a/cleantalk/ajax/class.php b/cleantalk/ajax/class.php deleted file mode 100644 index 886e39b..0000000 --- a/cleantalk/ajax/class.php +++ /dev/null @@ -1,132 +0,0 @@ -showError('Module not installed'); - return; - } - - // Check if this is an AJAX request (relaxed check for compatibility) - $isAjax = ( - (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest') || - (isset($_SERVER['HTTP_ACCEPT']) && strpos($_SERVER['HTTP_ACCEPT'], 'application/json') !== false) || - (isset($_POST['action']) || isset($_GET['action'])) - ); - - if (!$isAjax) { - // Debug: Log what headers we received - $debug_info = [ - 'headers' => function_exists('getallheaders') ? getallheaders() : 'getallheaders not available', - 'server_vars' => [ - 'HTTP_X_REQUESTED_WITH' => $_SERVER['HTTP_X_REQUESTED_WITH'] ?? 'not set', - 'HTTP_ACCEPT' => $_SERVER['HTTP_ACCEPT'] ?? 'not set', - 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'] ?? 'not set', - 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'] ?? 'not set' - ] - ]; - $this->showError('Not an AJAX request. Debug: ' . json_encode($debug_info)); - return; - } - - $action = $this->arParams['ACTION'] ?? $_POST['action'] ?? $_GET['action'] ?? ''; - - switch ($action) { - case 'cleantalk_force_ajax_check': - $this->handleCleantalkAjaxCheck(); - break; - default: - $this->showError('Unknown action'); - } - } - - private function handleCleantalkAjaxCheck() - { - global $APPLICATION; - - // Get form data - $formData = $_POST; - - // Remove the action parameter - unset($formData['action']); - - // Prepare user data for checking - $arUser = array(); - $arUser["type"] = "feedback_general_contact_form"; - $arUser["sender_email"] = $formData['email'] ?? ''; - $arUser["sender_nickname"] = $formData['name'] ?? $formData['nickname'] ?? ''; - $arUser["subject"] = $formData['subject'] ?? ''; - $arUser["message"] = array_filter($formData, function($key) { - return !in_array($key, ['email', 'name', 'nickname', 'subject', 'action', 'ct_bot_detector_event_token']); - }, ARRAY_FILTER_USE_KEY); - - // Check for spam - $aResult = CleantalkAntispam::CheckAllBefore($arUser, false); - - if (isset($aResult) && is_array($aResult)) { - if ($aResult['errno'] == 0) { - if ($aResult['allow'] == 1) { - // Not spam - allow - $this->showSuccess([ - 'apbct' => [ - 'blocked' => false, - 'comment' => '' - ], - 'success' => true - ]); - } else { - // Spam detected - block - $this->showSuccess([ - 'apbct' => [ - 'blocked' => true, - 'comment' => $aResult['ct_result_comment'] - ], - 'error' => [ - 'msg' => $aResult['ct_result_comment'] - ] - ]); - } - } else { - // Error occurred - $this->showSuccess([ - 'apbct' => [ - 'blocked' => false, - 'comment' => '' - ], - 'error' => [ - 'msg' => $aResult['errstr'] ?? 'Unknown error' - ] - ]); - } - } else { - // No result - allow by default - $this->showSuccess([ - 'apbct' => [ - 'blocked' => false, - 'comment' => '' - ], - 'success' => true - ]); - } - } - - private function showSuccess($data) - { - header('Content-Type: application/json'); - echo Json::encode($data); - die(); - } - - private function showError($message) - { - header('Content-Type: application/json'); - echo Json::encode(['error' => $message]); - die(); - } -} From eeb40c0b9a643988d387511e51734516423e25a5 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Wed, 1 Oct 2025 23:55:12 +0500 Subject: [PATCH 3/4] Upd. JS. External forms. Refactored. --- cleantalk.antispam/assets/js/external.js | 408 ++++++++++------------- 1 file changed, 175 insertions(+), 233 deletions(-) diff --git a/cleantalk.antispam/assets/js/external.js b/cleantalk.antispam/assets/js/external.js index 816e6a2..a6f85c4 100644 --- a/cleantalk.antispam/assets/js/external.js +++ b/cleantalk.antispam/assets/js/external.js @@ -16,11 +16,6 @@ function ctProtectExternal() { for (let i = 0; i < document.forms.length; i++) { const currentForm = document.forms[i]; - // skip excluded forms - // if (formIsExclusion(currentForm)) { - // continue; - // } - // Ajax checking for the integrated forms - will be changed the whole form object to make protection if (isIntegratedForm(currentForm)) { apbctProcessExternalForm(currentForm, i, document); @@ -119,7 +114,7 @@ function apbctProcessExternalForm(currentForm, iterator, documentObject) { */ function apbctReplaceInputsValuesFromOtherForm(sourceForm, targetForm) { if (!sourceForm || !targetForm) return; - + const sourceInputs = sourceForm.querySelectorAll('input, textarea, select'); sourceInputs.forEach(function(sourceInput) { if (sourceInput.name && sourceInput.type !== 'submit' && sourceInput.type !== 'button') { @@ -135,250 +130,197 @@ function apbctReplaceInputsValuesFromOtherForm(sourceForm, targetForm) { }); } -/** - * ApbctHandler class for handling block messages - */ -function ApbctHandler() { - this.parseBlockMessage = function(result) { - let message = ''; - if (result.apbct && result.apbct.comment) { - message = result.apbct.comment; - } else if (result.error && result.error.msg) { - message = result.error.msg; - } else if (result.data && result.data.message) { - message = result.data.message; - } - - if (message) { - alert(message); - if (result.apbct && result.apbct.stop_script == 1) { - window.stop(); - } +function apbctParseBlockMessageForAajax(result) { + let message = ''; + console.table('result',result) + if (result.apbct && result.apbct.comment) { + message = result.apbct.comment; + } else if (result.error && result.error.msg) { + message = result.error.msg; + } else if (result.data && result.data.message) { + message = result.data.message; + } + console.table('message',message) + if (message) { + alert(message); + if (result.apbct && result.apbct.stop_script == 1) { + window.stop(); } - }; + } } - /** * Sending Ajax for checking form data * @param {HTMLElement} form - * @param {HTMLElement} prev - * @param {HTMLElement} formOriginal */ function sendAjaxCheckingFormData(form) { - let botDetectorEventToken = localStorage.getItem('bot_detector_event_token'); - if (typeof botDetectorEventToken === 'string') { - botDetectorEventToken = JSON.parse(botDetectorEventToken); - if (typeof botDetectorEventToken === 'object') { - botDetectorEventToken = botDetectorEventToken.value; + const botDetectorEventToken = getBotDetectorToken(); + const data = prepareFormData(form, botDetectorEventToken); + + if (typeof BX !== 'undefined' && BX.ajax) { + sendBitrixAjax(form, data); + } else { + sendNativeAjax(form, data); + } +} + +/** + * Get bot detector token from localStorage + */ +function getBotDetectorToken() { + let token = localStorage.getItem('bot_detector_event_token'); + if (typeof token === 'string') { + token = JSON.parse(token); + if (typeof token === 'object') { + return token.value; } } + return token; +} + +/** + * Prepare form data for submission + */ +function prepareFormData(form, botDetectorToken) { const data = { - 'ct_bot_detector_event_token': botDetectorEventToken, + 'ct_bot_detector_event_token': botDetectorToken, }; - let elems = form.elements; - elems = Array.prototype.slice.call(elems); - elems.forEach(function(elem, y) { - elem.name === '' ? data['input_' + y] = elem.value : data[elem.name] = elem.value; + const elements = Array.from(form.elements); + elements.forEach((elem, index) => { + const key = elem.name || `input_${index}`; + data[key] = elem.value; }); - // Use Bitrix AJAX framework - if (typeof BX !== 'undefined' && BX.ajax) { - BX.ajax({ - url: '/bitrix/components/cleantalk/ajax/ajax.php', - method: 'POST', - data: data, - dataType: 'json', - async: false, - headers: { - 'X-Requested-With': 'XMLHttpRequest' - }, - onsuccess: function(result) { - console.log('CleanTalk AJAX success:', result); - if ((result.apbct === undefined && result.data === undefined) || - (result.apbct !== undefined && ! +result.apbct.blocked) - ) { - // Clear service fields - // for (const el of form.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { - // el.remove(); - // } - - const formNew = form; - form.parentElement.removeChild(form); - const prev = form.apbctPrev; - const formOriginal = form.apbctFormOriginal; - // let isExternalFormsRestartRequired = apbctExternalFormsRestartRequired(formOriginal); - apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); - - prev.after(formOriginal); - - for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { - el.remove(); - } - for (const el of formOriginal.querySelectorAll('input[name="ct_bot_detector_event_token"]')) { - el.remove(); - } + return data; +} - // Common click event - let submButton = formOriginal.querySelectorAll('button[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX success 1:', submButton); - // submButton[0].click(); - // if (isExternalFormsRestartRequired) { - // setTimeout(function() { - // ctProtectExternal(); - // }, 1500); - // } - return; - } +/** + * Send AJAX using Bitrix framework + */ +function sendBitrixAjax(form, data) { + BX.ajax({ + url: '/bitrix/components/cleantalk/ajax/ajax.php', + method: 'POST', + data: data, + dataType: 'json', + async: false, + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + onsuccess: (result) => handleSuccess(form, result), + onfailure: (result) => handleFailure(form, result) + }); +} - submButton = formOriginal.querySelectorAll('input[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX success 2:', submButton); - // submButton[0].click(); - // if (isExternalFormsRestartRequired) { - // setTimeout(function() { - // ctProtectExternal(); - // }, 1500); - // } - return; - } - } - // if ((result.apbct !== undefined && +result.apbct.blocked) || - // (result.data !== undefined && result.data.message !== undefined) - // ) { - // new ApbctHandler().parseBlockMessage(result); - // } - }, - onfailure: function(result) { - console.error('CleanTalk AJAX error:', result); - // Allow form submission on error - const formNew = form; - form.parentElement.removeChild(form); - const prev = form.apbctPrev; - const formOriginal = form.apbctFormOriginal; - apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); - prev.after(formOriginal); - - for (const el of formOriginal.querySelectorAll('input[name="cleantalk_force_ajax_check"]')) { - el.remove(); - } - for (const el of formOriginal.querySelectorAll('input[name="ct_bot_detector_event_token"]')) { - el.remove(); - } - - let submButton = formOriginal.querySelectorAll('button[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX error 1:', submButton); - // submButton[0].click(); - return; - } - submButton = formOriginal.querySelectorAll('input[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX error 2:', submButton); - // submButton[0].click(); - } - } - }); - } else { - // Fallback to native XMLHttpRequest if BX is not available - const xhr = new XMLHttpRequest(); - xhr.open('POST', '/bitrix/components/cleantalk/ajax/ajax.php', false); // synchronous - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); - - const formData = new URLSearchParams(); - for (const key in data) { - formData.append(key, data[key]); +/** + * Send AJAX using native XMLHttpRequest + */ +function sendNativeAjax(form, data) { + const xhr = new XMLHttpRequest(); + xhr.open('POST', '/bitrix/components/cleantalk/ajax/ajax.php', false); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + const formData = new URLSearchParams(); + for (const key in data) { + formData.append(key, data[key]); + } + + xhr.send(formData); + + if (xhr.status === 200) { + try { + const result = JSON.parse(xhr.responseText); + handleSuccess(form, result); + } catch (e) { + console.error('CleanTalk JSON parse error:', e); + restoreAndSubmitForm(form); } - - xhr.send(formData); - - if (xhr.status === 200) { - try { - const result = JSON.parse(xhr.responseText); - if ((result.apbct === undefined && result.data === undefined) || - (result.apbct !== undefined && ! +result.apbct.blocked) - ) { - // Allow form submission - const formNew = form; - form.parentElement.removeChild(form); - const prev = form.apbctPrev; - const formOriginal = form.apbctFormOriginal; - apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); - prev.after(formOriginal); - - for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { - el.remove(); - } - - let submButton = formOriginal.querySelectorAll('button[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX success 1:', submButton); - // submButton[0].click(); - return; - } - submButton = formOriginal.querySelectorAll('input[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX success 2:', submButton); - // submButton[0].click(); - } - } else if ((result.apbct !== undefined && +result.apbct.blocked) || - (result.data !== undefined && result.data.message !== undefined) - ) { - new ApbctHandler().parseBlockMessage(result); - } - } catch (e) { - console.error('CleanTalk JSON parse error:', e); - // Allow form submission on error - const formNew = form; - form.parentElement.removeChild(form); - const prev = form.apbctPrev; - const formOriginal = form.apbctFormOriginal; - apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); - prev.after(formOriginal); - - for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { - el.remove(); - } - - let submButton = formOriginal.querySelectorAll('button[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX error 1:', submButton); - // submButton[0].click(); - return; - } - submButton = formOriginal.querySelectorAll('input[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX error 2:', submButton); - // submButton[0].click(); - } - } - } else { - console.error('CleanTalk AJAX request failed:', xhr.status); - // Allow form submission on error - const formNew = form; - form.parentElement.removeChild(form); - const prev = form.apbctPrev; - const formOriginal = form.apbctFormOriginal; - apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); - prev.after(formOriginal); - - for (const el of formOriginal.querySelectorAll('input[value="cleantalk_force_ajax_check"]')) { - el.remove(); - } - - let submButton = formOriginal.querySelectorAll('button[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX success 1:', submButton); - // submButton[0].click(); - return; - } - submButton = formOriginal.querySelectorAll('input[type=submit]'); - if ( submButton.length !== 0 ) { - console.log('CleanTalk AJAX success 2:', submButton); - // submButton[0].click(); - } + } else { + console.error('CleanTalk AJAX request failed:', xhr.status); + restoreAndSubmitForm(form); + } +} + +/** + * Handle successful AJAX response + */ +function handleSuccess(form, result) { + console.log('CleanTalk AJAX success:', result); + + const isAllowed = (result.apbct === undefined && result.data === undefined) || + (result.apbct !== undefined && !+result.apbct.blocked); + + const isBlocked = (result.apbct !== undefined && +result.apbct.blocked) || + (result.data !== undefined && result.data.message !== undefined); + + if (isAllowed) { + restoreAndSubmitForm(form, true); + } else if (isBlocked) { + apbctParseBlockMessageForAajax(result); + } +} + +/** + * Handle AJAX failure + */ +function handleFailure(form, result) { + console.error('CleanTalk AJAX error:', result); + restoreAndSubmitForm(form); +} + +/** + * Restore original form and trigger submission + */ +function restoreAndSubmitForm(form, isSuccess = false) { + const formNew = form; + const prev = form.apbctPrev; + const formOriginal = form.apbctFormOriginal; + + // Remove current form and restore original + form.parentElement.removeChild(form); + apbctReplaceInputsValuesFromOtherForm(formNew, formOriginal); + prev.after(formOriginal); + + // Clean up service fields + removeServiceFields(formOriginal); + + // Find and trigger submit button + const submitButton = findSubmitButton(formOriginal); + if (submitButton) { + console.log(`CleanTalk AJAX ${isSuccess ? 'success' : 'error'}:`, submitButton); + submitButton.click(); + + // Schedule external forms protection for successful cases + if (isSuccess) { + setTimeout(() => { + ctProtectExternal(); + }, 1500); } } } + +/** + * Remove service fields from form + */ +function removeServiceFields(form) { + const selectors = [ + 'input[value="cleantalk_force_ajax_check"]', + 'input[name="ct_bot_detector_event_token"]' + ]; + + selectors.forEach(selector => { + form.querySelectorAll(selector).forEach(el => el.remove()); + }); +} + +/** + * Find submit button in form + */ +function findSubmitButton(form) { + const button = form.querySelector('button[type="submit"]'); + if (button) return button; + + const input = form.querySelector('input[type="submit"]'); + return input || null; +} From f159842d55da99581920827169eccec0b3eabbea Mon Sep 17 00:00:00 2001 From: alexandergull Date: Thu, 2 Oct 2025 00:24:42 +0500 Subject: [PATCH 4/4] Upd. External forms. Option added. Exclusions applied. --- cleantalk.antispam/default_option.php | 1 + cleantalk.antispam/include.php | 24 ++- cleantalk.antispam/lang/en/options.php | 4 +- cleantalk.antispam/lang/ru/options.php | 4 +- .../lib/Cleantalk/Antispam/Cleantalk.php | 167 +++++++++--------- ... => CleantalkExternalFormsAjaxHandler.php} | 5 +- cleantalk.antispam/options.php | 13 ++ 7 files changed, 130 insertions(+), 88 deletions(-) rename cleantalk.antispam/lib/Cleantalk/ApbctBitrix/{CleantalkAjaxCatcher.php => CleantalkExternalFormsAjaxHandler.php} (95%) diff --git a/cleantalk.antispam/default_option.php b/cleantalk.antispam/default_option.php index 1ef1f12..185d7ab 100644 --- a/cleantalk.antispam/default_option.php +++ b/cleantalk.antispam/default_option.php @@ -11,6 +11,7 @@ 'web_form' => 1, 'form_global_check' => 0, 'form_global_check_without_email' => 0, + 'form_external_ajax' => 0, 'bot_detector' => 1, 'form_sfw' => 1, 'form_sfw_uniq_get_option' => 1, diff --git a/cleantalk.antispam/include.php b/cleantalk.antispam/include.php index b142e1a..62a9ca9 100644 --- a/cleantalk.antispam/include.php +++ b/cleantalk.antispam/include.php @@ -20,7 +20,7 @@ use Cleantalk\ApbctBitrix\RemoteCalls; use Cleantalk\ApbctBitrix\Cron; use Cleantalk\ApbctBitrix\DB; -use Cleantalk\ApbctBitrix\CleantalkAjaxCatcher; +use Cleantalk\ApbctBitrix\CleantalkExternalFormsAjaxHandler; use Cleantalk\Common\Variables\Server; use Cleantalk\ApbctBitrix\SFW; @@ -1133,10 +1133,16 @@ static function FormAddon() { $ct_check_values = self::SetCheckJSValues(); + $external_forms_enabled = COption::GetOptionString( 'cleantalk.antispam', 'form_external_ajax', '' ); + + $external_forms_snippet = $external_forms_enabled + ? file_get_contents(dirname(__FILE__) . '/assets/js/external.js') + : ''; + $js_template = ""; return $js_template; @@ -1852,7 +1858,7 @@ private static function ct_cookies_test() } - private static function apbct__filter_form_data($form_data) + public static function apbct__filter_form_data($form_data) { // It is a service field. Need to be deleted before the processing. if ( isset($form_data['apbct_visible_fields']) ) { @@ -1979,11 +1985,21 @@ private static function urlIsExclusion(){ public static function onProlog() { + if (self::siteIsExclusion() || self::urlIsExclusion()) { + return; + } + + $external_forms_enabled = COption::GetOptionString( 'cleantalk.antispam', 'form_external_ajax', '' ); + + if (empty($external_forms_enabled)) { + return; + } + $request = Context::getCurrent()->getRequest(); // Проверяем, что это AJAX запрос if ($request->isAjaxRequest()) { - CleantalkAjaxCatcher::handleAjax($request); + CleantalkExternalFormsAjaxHandler::handleAjax($request); } } } diff --git a/cleantalk.antispam/lang/en/options.php b/cleantalk.antispam/lang/en/options.php index 99a5314..b9ab79c 100644 --- a/cleantalk.antispam/lang/en/options.php +++ b/cleantalk.antispam/lang/en/options.php @@ -9,6 +9,8 @@ $MESS['CLEANTALK_LABEL_SEND_EXAMPLE'] = 'Send texts for off-top analysis'; $MESS['CLEANTALK_LABEL_ORDER'] = 'Order form protection'; $MESS['CLEANTALK_LABEL_WEB_FORMS'] = 'Web forms protection'; +$MESS['CLEANTALK_LABEL_FORM_EXTERNAL_AJAX'] = 'Protect forms with external AJAX handler'; +$MESS['CLEANTALK_DESCRIPTION_EXTERNAL_AJAX'] = 'At the moment only Bitrix24 external forms are supported'; $MESS['CLEANTALK_BUTTON_SAVE'] = 'Save'; $MESS['CLEANTALK_GET_AUTO_KEY'] = 'Get access key automatically'; $MESS['CLEANTALK_GET_MANUAL_KEY'] = 'Get access key manually'; @@ -56,4 +58,4 @@ $MESS['CLEANTALK_RESET_OPTIONS_FAILED'] = 'Can not reset options to defaults.'; $MESS['CLEANTALK_MULTISITE_LABEL_KEY'] = 'If you want to use specific Access Key for this website paste it here. Otherwise, leave it empty.'; $MESS['CLEANTALK_USE_CUSTOM_SERVER'] = 'Use custom server'; -$MESS['CLEANTALK_USE_CUSTOM_SERVER_DESCRIPTION'] = 'Use custom server for spam checking. You can enter cleantalk.ru and the plugin will use only ru servers. This option is for critical situations when it is impossible to choose the server automatically.'; \ No newline at end of file +$MESS['CLEANTALK_USE_CUSTOM_SERVER_DESCRIPTION'] = 'Use custom server for spam checking. You can enter cleantalk.ru and the plugin will use only ru servers. This option is for critical situations when it is impossible to choose the server automatically.'; diff --git a/cleantalk.antispam/lang/ru/options.php b/cleantalk.antispam/lang/ru/options.php index 47adc3e..e2f8610 100644 --- a/cleantalk.antispam/lang/ru/options.php +++ b/cleantalk.antispam/lang/ru/options.php @@ -9,6 +9,8 @@ $MESS['CLEANTALK_LABEL_SEND_EXAMPLE'] = 'Отсылать тексты для офф-топ анализа'; $MESS['CLEANTALK_LABEL_ORDER'] = 'Проверять формы заказов'; $MESS['CLEANTALK_LABEL_WEB_FORMS'] = 'Защита Веб-форм'; +$MESS['CLEANTALK_LABEL_FORM_EXTERNAL_AJAX'] = 'Защита внешних AJAX форм'; +$MESS['CLEANTALK_DESCRIPTION_EXTERNAL_AJAX'] = 'На данный момент поддерживаются только внешние формы для Битрикс24'; $MESS['CLEANTALK_BUTTON_SAVE'] = 'Сохранить'; $MESS['CLEANTALK_GET_AUTO_KEY'] = 'Получить ключ автоматически'; $MESS['CLEANTALK_GET_MANUAL_KEY'] = 'Получить ключ вручную'; @@ -57,4 +59,4 @@ $MESS['CLEANTALK_MULTISITE_LABEL_KEY'] = 'Если вы хотите использовать отдельный ключ доступа для этого сайта, вставьте его здесь. В противном случае оставьте поле пустым.'; $MESS['CLEANTALK_USE_CUSTOM_SERVER'] = 'Использовать кастомный сервер'; $MESS['CLEANTALK_USE_CUSTOM_SERVER_DESCRIPTION'] = 'Использовать кастомный сервер для проверки спама. Можете ввести cleantalk.ru и плагин будет использовать только ru сервера. Опция для критических ситуаций, когда невозможен выбор сервера автоматически.'; -$MESS['CLEANTALK_SERVER_NOT_AVAILABLE'] = 'Указанные сервера не доступны, обратитесь в тех поддержку https://cleantalk.org/my/support/open'; \ No newline at end of file +$MESS['CLEANTALK_SERVER_NOT_AVAILABLE'] = 'Указанные сервера не доступны, обратитесь в тех поддержку https://cleantalk.org/my/support/open'; diff --git a/cleantalk.antispam/lib/Cleantalk/Antispam/Cleantalk.php b/cleantalk.antispam/lib/Cleantalk/Antispam/Cleantalk.php index 5cff41f..e70baf0 100644 --- a/cleantalk.antispam/lib/Cleantalk/Antispam/Cleantalk.php +++ b/cleantalk.antispam/lib/Cleantalk/Antispam/Cleantalk.php @@ -11,7 +11,7 @@ * @author Cleantalk team (welcome@cleantalk.org) * @copyright (C) 2014 CleanTalk team (http://cleantalk.org) * @license GNU/GPL: http://www.gnu.org/copyleft/gpl.html - * @see https://github.com/CleanTalk/php-antispam + * @see https://github.com/CleanTalk/php-antispam * */ @@ -27,15 +27,15 @@ class Cleantalk { */ public $debug = 0; - + /** - * Data compression rate + * Data compression rate * @var int */ private $compressRate = 6; - + /** - * Server connection timeout in seconds + * Server connection timeout in seconds * @var int */ private $server_timeout = 15; @@ -75,27 +75,27 @@ class Cleantalk { * @var bool */ public $stay_on_server = false; - + /** - * Codepage of the data + * Codepage of the data * @var bool */ public $data_codepage = null; - + /** - * API version to use + * API version to use * @var string */ public $api_version = '/api2.0'; - + /** - * Use https connection to servers - * @var bool + * Use https connection to servers + * @var bool */ public $ssl_on = false; - + /** - * Path to SSL certificate + * Path to SSL certificate * @var string */ public $ssl_path = ''; @@ -160,7 +160,7 @@ private function filterRequest(CleantalkRequest $request) { $request->$param = NULL; } } - + if (in_array($param, array('js_on')) && !empty($value)) { if (!is_integer($value)) { $request->$param = NULL; @@ -217,33 +217,33 @@ private function createMsg($method, CleantalkRequest $request) { } break; } - + $request->method_name = $method; - + // // Removing non UTF8 characters from request, because non UTF8 or malformed characters break json_encode(). // foreach ($request as $param => $value) { if (!preg_match('//u', $value)) { - $request->{$param} = 'Nulled. Not UTF8 encoded or malformed.'; + $request->{$param} = 'Nulled. Not UTF8 encoded or malformed.'; } } - + return $request; } - + /** - * Send JSON request to servers + * Send JSON request to servers * @param $msg * @return boolean|\CleantalkResponse */ private function sendRequest($data = null, $url, $server_timeout = 15) { // Convert to array $data = (array)json_decode(json_encode($data), true); - + $original_url = $url; $original_data = $data; - + //Cleaning from 'null' values $tmp_data = array(); foreach($data as $key => $value){ @@ -253,19 +253,19 @@ private function sendRequest($data = null, $url, $server_timeout = 15) { } $data = $tmp_data; unset($key, $value, $tmp_data); - + // Convert to JSON $data = json_encode($data); - + if (isset($this->api_version)) { $url = $url . $this->api_version; } - + // Switching to secure connection if ($this->ssl_on && !preg_match("/^https:/", $url)) { $url = preg_replace("/^(http)/i", "$1s", $url); } - + $result = false; $curl_error = null; if(function_exists('curl_init')){ @@ -317,17 +317,17 @@ private function sendRequest($data = null, $url, $server_timeout = 15) { $result = @file_get_contents($url, false, $context); } } - + if (!$result || !cleantalk_is_JSON($result)) { $response = null; $response['errno'] = 1; $response['errstr'] = true; $response['curl_err'] = isset($curl_error) ? $curl_error : false; $response = json_decode(json_encode($response)); - + return $response; } - + $errstr = null; $response = json_decode($result); if ($result !== false && is_object($response)) { @@ -335,45 +335,45 @@ private function sendRequest($data = null, $url, $server_timeout = 15) { $response->errstr = $errstr; } else { $errstr = 'Unknown response from ' . $url . '.' . ' ' . $result; - + $response = null; $response['errno'] = 1; $response['errstr'] = $errstr; $response = json_decode(json_encode($response)); - } - - + } + + return $response; } /** - * httpRequest + * httpRequest * @param $msg * @return boolean|\CleantalkResponse */ private function httpRequest($msg) { $result = false; - + if($msg->method_name != 'send_feedback'){ $ct_tmp = apache_request_headers(); - + if(isset($ct_tmp['Cookie'])) $cookie_name = 'Cookie'; elseif(isset($ct_tmp['cookie'])) $cookie_name = 'cookie'; else $cookie_name = 'COOKIE'; - + if(isset($tmp[$cookie_name])) $ct_tmp[$cookie_name] = preg_replace(array( '/\s{0,1}ct_checkjs=[a-z0-9]*[;|$]{0,1}/', '/\s{0,1}ct_timezone=.{0,1}\d{1,2}[;|$]/', '/;{0,1}\s{0,3}$/' ), '', $ct_tmp[$cookie_name]); - + $msg->all_headers=json_encode($ct_tmp); } - + //$msg->remote_addr=$_SERVER['REMOTE_ADDR']; //$msg->sender_info['remote_addr']=$_SERVER['REMOTE_ADDR']; $si=(array)json_decode($msg->sender_info,true); @@ -381,13 +381,13 @@ private function httpRequest($msg) { $si['remote_addr']=$_SERVER['REMOTE_ADDR']; $msg->x_forwarded_for=@$_SERVER['X_FORWARDED_FOR']; $msg->x_real_ip=@$_SERVER['X_REAL_IP']; - + $msg->sender_info=json_encode($si); if (((isset($this->work_url) && $this->work_url !== '') && ($this->server_changed + $this->server_ttl > time())) || $this->stay_on_server == true) { - + $url = (!empty($this->work_url)) ? $this->work_url : $this->server_url; - + $result = $this->sendRequest($msg, $url, $this->server_timeout); } @@ -401,11 +401,11 @@ private function httpRequest($msg) { $pool = null; if (isset($matches[2])) $pool = $matches[2]; - + $url_suffix = ''; if (isset($matches[3])) $url_suffix = $matches[3]; - + if ($url_prefix === '') $url_prefix = 'http://'; @@ -421,13 +421,13 @@ private function httpRequest($msg) { $work_url = $server_host; } $host = filter_var($work_url,FILTER_VALIDATE_IP) ? gethostbyaddr($work_url) : $work_url; - $work_url = $url_prefix . $host; - if (isset($url_suffix)) + $work_url = $url_prefix . $host; + if (isset($url_suffix)) $work_url = $work_url . $url_suffix; - + $this->work_url = $work_url; $this->server_ttl = $server['ttl']; - + $result = $this->sendRequest($msg, $this->work_url, $this->server_timeout); if ($result !== false && $result->errno === 0) { @@ -437,9 +437,9 @@ private function httpRequest($msg) { } } } - + $response = new CleantalkResponse(null, $result); - + if (!empty($this->data_codepage) && $this->data_codepage !== 'UTF-8') { if (!empty($response->comment)) $response->comment = $this->stringFromUTF8($response->comment, $this->data_codepage); @@ -448,10 +448,10 @@ private function httpRequest($msg) { if (!empty($response->sms_error_text)) $response->sms_error_text = $this->stringFromUTF8($response->sms_error_text, $this->data_codepage); } - + return $response; } - + /** * Function DNS request * @param $host @@ -471,6 +471,11 @@ public function get_servers_ip($host) { } } } + + if (is_null($response)) { + $response = array(); + } + if (count($response) == 0 && function_exists('gethostbynamel')) { $records = gethostbynamel($host); @@ -495,21 +500,21 @@ public function get_servers_ip($host) { $r_temp = null; $fast_server_found = false; foreach ($response as $server) { - + // Do not test servers because fast work server found if ($fast_server_found) { - $ping = $this->min_server_timeout; + $ping = $this->min_server_timeout; } else { $ping = $this->httpPing($server['ip']); $ping = $ping * 1000; } - + // -1 server is down, skips not reachable server if ($ping != -1) { $r_temp[$ping + $i] = $server; } $i++; - + if ($ping < $this->min_server_timeout) { $fast_server_found = true; } @@ -561,7 +566,7 @@ public function delCleantalkComment($message) { $message = preg_replace('/\*\*\*.+\*\*\*$/', '', $message); $message = preg_replace('/\[\n]{0,1}\[\n]{0,1}\*\*\*.+\*\*\*$/', '', $message); - + return $message; } @@ -573,7 +578,7 @@ public function ct_session_ip( $data_ip ) { return $data_ip; } /*if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - + $forwarded_ip = explode(",", $_SERVER['HTTP_X_FORWARDED_FOR']); // Looking for first value in the list, it should be sender real IP address @@ -595,14 +600,14 @@ public function ct_session_ip( $data_ip ) { if ($private_src_ip) { continue; } - + if ($this->net_match($v, $data_ip)) { $private_src_ip = true; } } if ($private_src_ip) { - // Taking first IP from the list HTTP_X_FORWARDED_FOR - $data_ip = $forwarded_ip[0]; + // Taking first IP from the list HTTP_X_FORWARDED_FOR + $data_ip = $forwarded_ip[0]; } } @@ -613,11 +618,11 @@ public function ct_session_ip( $data_ip ) { /** * From http://php.net/manual/en/function.ip2long.php#82397 */ - public function net_match($CIDR,$IP) { - list ($net, $mask) = explode ('/', $CIDR); - return ( ip2long ($IP) & ~((1 << (32 - $mask)) - 1) ) == ip2long ($net); - } - + public function net_match($CIDR,$IP) { + list ($net, $mask) = explode ('/', $CIDR); + return ( ip2long ($IP) & ~((1 << (32 - $mask)) - 1) ) == ip2long ($net); + } + /** * Function to check response time * param string @@ -626,7 +631,7 @@ public function net_match($CIDR,$IP) { function httpPing($host){ // Skip localhost ping cause it raise error at fsockopen. - // And return minimun value + // And return minimun value if ($host == 'localhost') return 0.001; @@ -641,19 +646,19 @@ function httpPing($host){ $status = ($stoptime - $starttime); $status = round($status, 4); } - + return $status; } - + /** - * Function convert string to UTF8 and removes non UTF8 characters + * Function convert string to UTF8 and removes non UTF8 characters * param string * param string * @return string */ function stringToUTF8($str, $data_codepage = null){ if (!preg_match('//u', $str) && function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding')) { - + if ($data_codepage !== null) return mb_convert_encoding($str, 'UTF-8', $data_codepage); @@ -661,12 +666,12 @@ function stringToUTF8($str, $data_codepage = null){ if ($encoding) return mb_convert_encoding($str, 'UTF-8', $encoding); } - + return $str; } - + /** - * Function convert string from UTF8 + * Function convert string from UTF8 * param string * param string * @return string @@ -675,19 +680,19 @@ function stringFromUTF8($str, $data_codepage = null){ if (preg_match('//u', $str) && function_exists('mb_convert_encoding') && $data_codepage !== null) { return mb_convert_encoding($str, $data_codepage, 'UTF-8'); } - + return $str; } - + /** - * Function gets information about spam active networks + * Function gets information about spam active networks * * @param string api_key - * @return JSON/array + * @return JSON/array */ public function get_2s_blacklists_db ($api_key) { $request=array(); - $request['method_name'] = '2s_blacklists_db'; + $request['method_name'] = '2s_blacklists_db'; $request['auth_key'] = $api_key; $url='https://api.cleantalk.org'; $result=CleantalkHelper::sendRawRequest($url,$request); @@ -755,7 +760,7 @@ function cleantalk_is_JSON($string) } // Patch for locale_get_display_region() for old PHP versions -if( !function_exists('locale_get_display_region') ){ +if( !function_exists('locale_get_display_region') ){ function locale_get_display_region($locale, $curr_lcoale='en'){ return $locale; } diff --git a/cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkAjaxCatcher.php b/cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkExternalFormsAjaxHandler.php similarity index 95% rename from cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkAjaxCatcher.php rename to cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkExternalFormsAjaxHandler.php index d6c09aa..87f5cda 100644 --- a/cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkAjaxCatcher.php +++ b/cleantalk.antispam/lib/Cleantalk/ApbctBitrix/CleantalkExternalFormsAjaxHandler.php @@ -6,7 +6,7 @@ use Bitrix\Main\Web\Json; use CleantalkAntispam; -class CleantalkAjaxCatcher +class CleantalkExternalFormsAjaxHandler { public static function handleAjax($request) { @@ -28,6 +28,9 @@ public static function handleAjax($request) // Remove the action parameter unset($formData['action']); + // Sanitize and filter form data + $formData = CleantalkAntispam::apbct__filter_form_data($formData); + // Prepare user data for checking $arUser = array(); $arUser["type"] = "feedback_ajax"; diff --git a/cleantalk.antispam/options.php b/cleantalk.antispam/options.php index e221eb5..b95a831 100644 --- a/cleantalk.antispam/options.php +++ b/cleantalk.antispam/options.php @@ -138,6 +138,7 @@ Option::set( $sModuleId, 'form_comment_treelike', $_POST['form_comment_treelike'] == '1' ? 1 : 0 ); Option::set( $sModuleId, 'form_send_example', $_POST['form_send_example'] == '1' ? 1 : 0 ); Option::set( $sModuleId, 'form_order', $_POST['form_order'] == '1' ? 1 : 0 ); + Option::set( $sModuleId, 'form_external_ajax', $_POST['form_external_ajax'] == '1' ? 1 : 0 ); Option::set( $sModuleId, 'web_form', $_POST['web_form'] == '1' ? 1 : 0 ); Option::set( $sModuleId, 'is_paid', $_POST['is_paid'] == '1' ? 1 : 0 ); Option::set( $sModuleId, 'last_checked', $_POST['last_checked'] == '1' ? 1 : 0 ); @@ -554,6 +555,18 @@ function ctDisableInputLine(ct_input_line){ checked="checked"value="1" /> + + +