Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions cleantalk.antispam/ajax_handler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/modules/main/include/prolog_before.php");


if (!CModule::IncludeModule('cleantalk.antispam')) {
echo json_encode(['error' => 'Module not loaded']);
exit;
}

$fields = $_POST;
$response = \Cleantalk\Integrations\IntegrationFactory::handle($fields);
echo json_encode($response);
1 change: 1 addition & 0 deletions cleantalk.antispam/default_option.php
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
14 changes: 14 additions & 0 deletions cleantalk.antispam/include.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@
define('APBCT_SELECT_LIMIT', 5000); // Select limit for logs.
define('APBCT_WRITE_LIMIT', 5000); // Write limit for firewall data.

// Register IntegrationFactory for autoload
\Bitrix\Main\Loader::registerAutoLoadClasses(
'cleantalk.antispam',
[
'Cleantalk\\Antispam\\Integrations\\IntegrationFactory' => 'lib/Cleantalk/Integrations/IntegrationFactory.php',
]
);


/**
* CleanTalk module class
Expand Down Expand Up @@ -256,6 +264,7 @@ public static function OnPageStartHandler()
$last_checked = COption::GetOptionInt( 'cleantalk.antispam', 'last_checked', 0 );
$show_review = COption::GetOptionInt( 'cleantalk.antispam', 'show_review', 0 );
$bot_detector = COption::GetOptionInt( 'cleantalk.antispam', 'bot_detector', 0 );
$form_external_ajax = COption::GetOptionInt( 'cleantalk.antispam', 'form_external_ajax', 0 );
$is_sfw = COption::GetOptionInt( 'cleantalk.antispam', 'form_sfw', 0 );
$sfw_last_update = COption::GetOptionInt( 'cleantalk.antispam', 'sfw_last_update', 0);
$sfw_last_send_log = COption::GetOptionInt( 'cleantalk.antispam', 'sfw_last_send_log', 0);
Expand All @@ -273,6 +282,11 @@ public static function OnPageStartHandler()

if( ! $USER->IsAdmin() ){

// JS External protection
if ($form_external_ajax) {
Asset::getInstance()->addJs('/bitrix/js/cleantalk.antispam/cleantalk-antispam-external-protection.js');
}

if ( $bot_detector ) {
if (class_exists('COption')) {
$use_custom_server = \COption::GetOptionString( 'cleantalk.antispam', 'use_custom_server', '' );
Expand Down
117 changes: 82 additions & 35 deletions cleantalk.antispam/install/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,50 +203,97 @@ function DoUninstall() {
}

function InstallFiles() {

$results = $this->install_ct_template__in_dirs(
$this->SAR_template_file,
array(
$this->SAR_bitrix_template_dir,
$this->SAR_local_template_dir,
/** @todo (.../bitrix/templates/.default/components/bitrix/system.auth.registration/) research it */
// Copy system.auth.registration default template from system dir to local dir and insert addon into
),
$this->SAR_pattern,
$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 ));
}
}
// Copy JS to public Bitrix directory
global $DOCUMENT_ROOT;
$publicJsDir = $DOCUMENT_ROOT . '/bitrix/js/cleantalk.antispam';
if (!is_dir($publicJsDir)) {
mkdir($publicJsDir, 0755, true);
}
$srcJs = dirname(__FILE__, 2) . '/js/cleantalk-antispam-external-protection.js';
$dstJs = $publicJsDir . '/cleantalk-antispam-external-protection.js';
if (file_exists($srcJs)) {
copy($srcJs, $dstJs);
}

// Copy ajax_handler.php to /bitrix/tools/cleantalk.antispam/
$toolsDir = $DOCUMENT_ROOT . '/bitrix/tools/cleantalk.antispam';
if (!is_dir($toolsDir)) {
mkdir($toolsDir, 0755, true);
}
$srcAjax = dirname(__FILE__, 2) . '/ajax_handler.php';
$dstAjax = $toolsDir . '/ajax_handler.php';
if (file_exists($srcAjax)) {
copy($srcAjax, $dstAjax);
}

$results = $this->install_ct_template__in_dirs(
$this->SAR_template_file,
array(
$this->SAR_bitrix_template_dir,
$this->SAR_local_template_dir,
/** @todo (.../bitrix/templates/.default/components/bitrix/system.auth.registration/) research it */
// Copy system.auth.registration default template from system dir to local dir and insert addon into
),
$this->SAR_pattern,
$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(
$this->SAR_bitrix_template_dir,
$this->SAR_local_template_dir,
/** @todo (.../bitrix/templates/.default/components/bitrix/system.auth.registration/) research it */
// Copy system.auth.registration default template from system dir to local dir and insert addon into
),
$this->ct_template_addon_tag
);
foreach ($results as $dir => $result){
if(is_dir($dir) && $result != 0){
error_log('CLEANTALK_ERROR: UNINSTALLING_IN_TEMPLATE_FILES: ' . $dir . sprintf('%02d', $result ));
}
}
$results = $this->uninstall_ct_template__in_dirs(
$this->SAR_template_file,
array(
$this->SAR_bitrix_template_dir,
$this->SAR_local_template_dir,
/** @todo (.../bitrix/templates/.default/components/bitrix/system.auth.registration/) research it */
// Copy system.auth.registration default template from system dir to local dir and insert addon into
),
$this->ct_template_addon_tag
);
foreach ($results as $dir => $result){
if(is_dir($dir) && $result != 0){
error_log('CLEANTALK_ERROR: UNINSTALLING_IN_TEMPLATE_FILES: ' . $dir . sprintf('%02d', $result ));
}
}

return true;
// Remove JS directory
global $DOCUMENT_ROOT;
$publicJsDir = $DOCUMENT_ROOT . '/bitrix/js/cleantalk.antispam';
if (is_dir($publicJsDir)) {
$this->removeDirWithFiles($publicJsDir);
}

// Remove ajax_handler.php directory
$toolsDir = $DOCUMENT_ROOT . '/bitrix/tools/cleantalk.antispam';
if (is_dir($toolsDir)) {
$this->removeDirWithFiles($toolsDir);
}

return true;
}

function removeDirWithFiles($dir) {
if (is_dir($dir)) {
$files = glob($dir . '/*');
foreach ($files as $file) {
if (is_file($file)) {
@unlink($file);
}
}
@rmdir($dir);
}
}

function InstallDB() {

global $DB;
Expand Down
175 changes: 175 additions & 0 deletions cleantalk.antispam/js/cleantalk-antispam-external-protection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/**
* Returns FormData with cleantalk_check and ct_bot_detector_event_token
* @param {HTMLFormElement} form
* @returns {FormData}
*/
function getFormDataWithToken(form) {
const formData = new FormData(form);
formData.append('cleantalk_check', '1');
let ctToken = '';
const ctTokenInput = form.querySelector('[name="ct_bot_detector_event_token"]');
if (ctTokenInput && ctTokenInput.value) {
ctToken = ctTokenInput.value;
} else {
try {
const lsToken = localStorage.getItem('bot_detector_event_token');
if (lsToken) {
const parsed = JSON.parse(lsToken);
if (parsed && parsed.value) {
ctToken = parsed.value;
}
}
} catch (e) {}
}
formData.set('ct_bot_detector_event_token', ctToken);
return formData;
}

/**
* Shows a full-page blocking message
* @param {string} msg
*/
function cleantalkShowForbiddenMessage(msg) {
let blockDiv = document.getElementById('cleantalk-forbidden-block');
if (!blockDiv) {
blockDiv = document.createElement('div');
blockDiv.id = 'cleantalk-forbidden-block';
blockDiv.style.position = 'fixed';
blockDiv.style.top = '0';
blockDiv.style.left = '0';
blockDiv.style.width = '100vw';
blockDiv.style.height = '100vh';
blockDiv.style.background = 'rgba(0,0,0,0.7)';
blockDiv.style.zIndex = '99999';
blockDiv.style.display = 'flex';
blockDiv.style.alignItems = 'center';
blockDiv.style.justifyContent = 'center';
blockDiv.style.color = '#fff';
blockDiv.style.fontSize = '2em';
blockDiv.innerHTML = '<div style="background:#222;padding:2em;border-radius:1em;max-width:90vw;text-align:center;">' + msg + '<br><br><button id="cleantalk-forbidden-close" style="margin-top:1em;padding:0.5em 2em;">OK</button></div>';
document.body.appendChild(blockDiv);
document.getElementById('cleantalk-forbidden-close').onclick = function() {
blockDiv.remove();
location.reload();
};
}
}

/**
* Overrides fetch to intercept target form submissions
*/
(function() {
// Save original fetch
const defaultFetch = window.fetch;
let fetchOverridden = false;

// Form selectors for different integrations to identify target forms
const FORM_SELECTORS = {
'Bitrix24Integration': '.b24-form-content form',
};

// List of URL patterns for fetch interception
const FETCH_URL_PATTERNS = [
'/bitrix/'
];

/**
* Checks if fetch call is for target forms
* @param {Array} args
* @returns {boolean}
*/
function isTargetFormFetch(args) {
if (!args || !args[0]) return false;
const url = typeof args[0] === 'string' ? args[0] : (args[0].url || '');
return FETCH_URL_PATTERNS.some(pattern => url.includes(pattern));
}

/**
* Gets the first found form and its integration class
* @returns {{form: HTMLFormElement, integration: string}|null}
*/
function getTargetFormAndIntegration() {
for (const integration in FORM_SELECTORS) {
const selector = FORM_SELECTORS[integration];
const form = document.querySelector(selector);
if (form) return { form, integration };
}
return null;
}

/**
* Overrides fetch for target forms
* @returns {void}
*/
function overrideFetchForTargetForms() {
if (fetchOverridden) return;
fetchOverridden = true;
window.fetch = async function(...args) {
if (isTargetFormFetch(args)) {
const found = getTargetFormAndIntegration();
if (found && found.form && found.integration) {
const formData = getFormDataWithToken(found.form);
formData.append('integration', found.integration);
let blocked = false;
let blockMsg = '';
try {
const checkResult = await defaultFetch('/bitrix/tools/cleantalk.antispam/ajax_handler.php', {
method: 'POST',
body: formData,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
const json = await checkResult.json();
if (json && json.apbct && +json.apbct.blocked) {
blocked = true;
blockMsg = json.apbct.comment || 'Your submission has been blocked as Cleantalk antispam.';
}
} catch (err) {
blocked = true;
blockMsg = 'Error during spam check. Please try again later.';
}
if (blocked) {
cleantalkShowForbiddenMessage(blockMsg);
return new Promise(() => {});
}
}
}
return defaultFetch.apply(window, args);
};
}

// Override fetch only after the page is fully loaded
if (document.readyState === 'complete' || document.readyState === 'interactive') {
overrideFetchForTargetForms();
} else {
document.addEventListener('DOMContentLoaded', overrideFetchForTargetForms);
}

// MutationObserver for dynamically added forms
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1) {
for (const integration in FORM_SELECTORS) {
const selector = FORM_SELECTORS[integration];
if (
(node.matches && node.matches(selector)) ||
(node.querySelector && node.querySelector(selector))
) {
overrideFetchForTargetForms();
break;
}
}
}
}
}
}
});
if (document.body) {
observer.observe(document.body, { childList: true, subtree: true });
} else {
document.addEventListener('DOMContentLoaded', function() {
observer.observe(document.body, { childList: true, subtree: true });
});
}
})();
4 changes: 3 additions & 1 deletion cleantalk.antispam/lang/en/options.php
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -19,7 +21,7 @@
$MESS['CLEANTALK_LABEL_GLOBAL_CHECK_WITHOUT_EMAIL'] = 'Check all POST data';
$MESS['CLEANTALK_WARNING_GLOBAL_CHECK_WITHOUT_EMAIL'] = '- Warning, conflict possibility!';
$MESS['CLEANTALK_LABEL_BOT_DETECTOR'] = 'Use Anti-Spam by CleanTalk JavaScript library';
$MESS['CLEANTALK_DESCRIPTION_BOT_DETECTOR'] = 'This option includes external Anti-Spam by CleanTalk JavaScript library to getting visitors info data';
$MESS['CLEANTALK_DESCRIPTION_BOT_DETECTOR'] = '- This option includes external Anti-Spam by CleanTalk JavaScript library to getting visitors info data';
$MESS['CLEANTALK_LABEL_SFW'] = 'Spam FireWall';
$MESS['CLEANTALK_LABEL_UNIQ_GET_OPTION'] = 'Uniq GET option';
$MESS['CLEANTALK_LABEL_UNIQ_GET_OPTION_DESC'] = 'If a visitor gets the SpamFireWall page, the plugin will put a unique GET variable in the URL to avoid issues with caching plugins.';
Expand Down
Loading
Loading