From 3990425c7c1f072db71635fe4111b55e5848aecd Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Fri, 1 Apr 2022 11:17:02 +0400 Subject: [PATCH 01/18] Fix: Excluded com_civicrm --- cleantalkantispam.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index 66c1e87..acc9f58 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -513,7 +513,8 @@ private function exceptionList() ($option_cmd == 'com_virtuemart' && $task_cmd == 'addJS') || ($option_cmd == 'com_virtuemart' && $task_cmd == 'cart') || ($option_cmd == 'com_rsform' && $task_cmd == 'ajaxValidate') || // RSFrom ajax validation on multipage form - ($option_cmd == 'com_virtuemart' && !empty($ctask_cmd) && ($ctask_cmd !== 'savebtaddress' || empty($post_field_stage) || $post_field_stage !== 'final')) + ($option_cmd == 'com_virtuemart' && !empty($ctask_cmd) && ($ctask_cmd !== 'savebtaddress' || empty($post_field_stage) || $post_field_stage !== 'final')) || + $option_cmd === 'com_civicrm' ) return true; From bd762f2a21581fa910b9ee80fd1c7aa0bab2bb5d Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Wed, 11 May 2022 11:04:17 +0400 Subject: [PATCH 02/18] Fix: Fixed settings.js: settings_title --- js/ct-settings.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/js/ct-settings.js b/js/ct-settings.js index 5771bbd..8fe8e9f 100644 --- a/js/ct-settings.js +++ b/js/ct-settings.js @@ -51,7 +51,8 @@ jQuery(document).ready(function(){ ct_notice_cookie = ct_getCookie('ct_notice_cookie'); jQuery('#attrib-checkuserscomments,#options-checkuserscomments').append("
   

"+ct_spamcheck_notice+"

"+ct_impspamcheck_label+"


") jQuery('#attrib-connectionreports,#options-connectionreports').append("
"); - jQuery('

'+ct_form_settings_title+'

').insertBefore(jQuery('.control-group')[2]); + jQuery('

'+ct_form_settings_title+'

') + .insertBefore(jQuery('#jform_params_apikey').closest('.control-group').next().next()); jQuery('#attrib-checkuserscomments,#options-checkuserscomments').append("
"); jQuery('#attrib-checkuserscomments,#options-checkuserscomments,#attrib-connectionreports,#options-connectionreports').append(""); //dev From 4a252880fd4315c89d583d0f4b3feec57935214d Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Tue, 31 May 2022 12:29:10 +0400 Subject: [PATCH 03/18] Mod: BreezingForm - validate email --- cleantalkantispam.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index acc9f58..d54b24c 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -724,6 +724,7 @@ public function onAfterRoute() $task_cmd = $app->input->get('task'); $ctask_cmd = $app->input->get('ctask'); $page_cmd = $app->input->get('page'); + $ff_task = $app->input->get('ff_task'); // Breezingform Integration /** * Integration with JotCache Plugin @@ -929,6 +930,19 @@ public function onAfterRoute() } + /** + * Empty email in the Breezingform + */ + if ( + empty($sender_email) && + $option_cmd === 'com_breezingforms' && + $ff_task === 'submit' + ) { + $error_tpl = file_get_contents(dirname(__FILE__) . "/lib/Cleantalk/Common/error.html"); + print str_replace('%ERROR_TEXT%', 'Please, fill email.', $error_tpl); + die(); + } + if ( ! empty( $_POST ) && ! $this->exceptionList() && From 28ecd57ce7317209da8c47ada239344089625af2 Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Wed, 1 Jun 2022 11:43:15 +0400 Subject: [PATCH 04/18] Changed ERROR_TEXT --- cleantalkantispam.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index d54b24c..a54d3e7 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -939,7 +939,7 @@ public function onAfterRoute() $ff_task === 'submit' ) { $error_tpl = file_get_contents(dirname(__FILE__) . "/lib/Cleantalk/Common/error.html"); - print str_replace('%ERROR_TEXT%', 'Please, fill email.', $error_tpl); + print str_replace('%ERROR_TEXT%', 'Please, fill the email field.', $error_tpl); die(); } From 2a3e21dd9cd0db761226222c12cb424a5d6d831b Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Mon, 6 Jun 2022 10:17:55 +0400 Subject: [PATCH 05/18] Fixed FirewallUpdated::unpackData() method --- lib/Cleantalk/Common/Firewall/FirewallUpdater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cleantalk/Common/Firewall/FirewallUpdater.php b/lib/Cleantalk/Common/Firewall/FirewallUpdater.php index f771895..1e7b644 100644 --- a/lib/Cleantalk/Common/Firewall/FirewallUpdater.php +++ b/lib/Cleantalk/Common/Firewall/FirewallUpdater.php @@ -263,7 +263,7 @@ private function unpackData( $file_url ) $gz_data = $helper::http__request__get_content( $file_url ); - if( empty( $gz_data['error'] ) ){ + if( is_string($gz_data) ){ if( Helper::get_mime_type( $gz_data, 'application/x-gzip' ) ){ From a1f83ee426f3da68897577a82921a9fa3d62961e Mon Sep 17 00:00:00 2001 From: alexandergull Date: Mon, 11 Jul 2022 14:47:55 +0500 Subject: [PATCH 06/18] New. Integrations. PWEB Ajax Contact Form integration. --- cleantalkantispam.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index a54d3e7..8e88312 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -1087,6 +1087,20 @@ public function onAfterRoute() ); die(); } + //PWEB AJAX CONTACT FORMS integration + elseif ( + JFactory::getApplication()->input->get('module') == 'pwebcontact' && + JFactory::getApplication()->input->get('method') == 'sendEmail' && + JFactory::getApplication()->input->get('option') == 'com_ajax' + ) { + $json_msg = array( + 'debug' => 'CAPTCHA check failed', + 'message' => $ctResponse['comment'], + 'success' => false + ); + print json_encode($json_msg); + die(); + } else { $error_tpl = file_get_contents(dirname(__FILE__) . "/lib/Cleantalk/Common/error.html"); From ba4e5bfd78f4675ee2752e5220c2f533be3576e3 Mon Sep 17 00:00:00 2001 From: alexandergull Date: Mon, 18 Jul 2022 16:51:54 +0500 Subject: [PATCH 07/18] New. Settings. Fields exclusions rules notice. --- cleantalkantispam.php | 1 + js/ct-settings.js | 3 +++ language/en-GB/en-GB.plg_system_cleantalkantispam.ini | 1 + language/ru-RU/ru-RU.plg_system_cleantalkantispam.ini | 1 + 4 files changed, 6 insertions(+) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index 8e88312..9968cde 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -658,6 +658,7 @@ public function onBeforeCompileHead() ct_register_message="' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_REGISTER_MESSAGE') . $adminmail . '", ct_key_is_bad_notice = "' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_KEY_IS_BAD') . '", ct_register_error="' . addslashes(JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_ERROR_AUTO_GET_KEY')) . '", + ct_exclusions_common_notice = "' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_COMMON_NOTICE') . '", ct_spamcheck_checksusers = "' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_CHECKUSERS_LABEL') . '", ct_spamcheck_checkscomments = "' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_CHECKCOMMENTS_LABEL') . '", ct_spamcheck_notice = "' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_SPAMCHECK_NOTICE') . '", diff --git a/js/ct-settings.js b/js/ct-settings.js index 8fe8e9f..d1b243d 100644 --- a/js/ct-settings.js +++ b/js/ct-settings.js @@ -49,6 +49,9 @@ function banner_check() { jQuery(document).ready(function(){ var ct_auth_key = jQuery('.cleantalk_auth_key').prop('value'), ct_notice_cookie = ct_getCookie('ct_notice_cookie'); + //notice about exclusion rules + jQuery('#attrib-exclusions,#options-exclusions').append("

" + ct_exclusions_common_notice + "

") + // misc notices jQuery('#attrib-checkuserscomments,#options-checkuserscomments').append("
   

"+ct_spamcheck_notice+"

"+ct_impspamcheck_label+"


") jQuery('#attrib-connectionreports,#options-connectionreports').append("
"); jQuery('

'+ct_form_settings_title+'

') diff --git a/language/en-GB/en-GB.plg_system_cleantalkantispam.ini b/language/en-GB/en-GB.plg_system_cleantalkantispam.ini index 51d34ea..bc1c332 100644 --- a/language/en-GB/en-GB.plg_system_cleantalkantispam.ini +++ b/language/en-GB/en-GB.plg_system_cleantalkantispam.ini @@ -10,6 +10,7 @@ PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_FIELDS="Fields exclusions" PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_FIELDS_DESC="Exclude fields from spam check. List them separated by commas. Works on forms except for registration and comment forms." PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_ROLES="User's groups exclusions" PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_ROLES_DESC="Exclude user's groups from spam check. List them separated by commas." +PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_COMMON_NOTICE="Important: Fields exclusion works on forms except for comment and registration forms!" PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_AUTOKEY_LABEL="Get the access key automatically!" PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_NOTICE1="Admin e-mail (" PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_NOTICE2=") will be used for registration" diff --git a/language/ru-RU/ru-RU.plg_system_cleantalkantispam.ini b/language/ru-RU/ru-RU.plg_system_cleantalkantispam.ini index ba3614d..d063e65 100644 --- a/language/ru-RU/ru-RU.plg_system_cleantalkantispam.ini +++ b/language/ru-RU/ru-RU.plg_system_cleantalkantispam.ini @@ -8,6 +8,7 @@ PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_FIELDS="Исключения по пол PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_FIELDS_DESC="Указанные поля (по атрибуту name) будут исключены из проверки. Перечислите через запятую." PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_ROLES="Исключения по группам пользователей" PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_ROLES_DESC="Указанные группы пользователей будут исключены из проверки. Перечислите через запятую." +PLG_SYSTEM_CLEANTALKANTISPAM_EXCLUSIONS_COMMON_NOTICE="Внимание! Исключения по полям не работают для формы регистрации и комментариев." PLG_SYSTEM_CLEANTALKANTISPAM_PARAM_APIKEY_HINT = "Введите ключ" PLG_SYSTEM_CLEANTALKANTISPAM_PARAM_APIKEY_DESC="Ключ доступа к сервису модерации и защиты от спама. Чтобы получить новый ключ зарегистрируйтесь на сайте http://cleantalk.org" PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_AUTOKEY_LABEL="Получить ключ автоматически" From efa7b6b0cff9f980e05214f1667791251459b342 Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Thu, 4 Aug 2022 11:38:21 +0400 Subject: [PATCH 08/18] Fix: fixed Helper::1323 error --- lib/Cleantalk/Common/Helper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Cleantalk/Common/Helper.php b/lib/Cleantalk/Common/Helper.php index dfdf940..f06b3e7 100644 --- a/lib/Cleantalk/Common/Helper.php +++ b/lib/Cleantalk/Common/Helper.php @@ -1320,7 +1320,7 @@ public static function http__request__rc_to_host($rc_action, $request_params, $p if( empty( $result__rc_check_website['error'] ) ){ - if( preg_match( '@^.*?OK$@', $result__rc_check_website) ){ + if (is_string($result__rc_check_website) && preg_match('@^.*?OK$@', $result__rc_check_website)) { static::http__request( static::getSiteUrl(), From db747db137e891a7f09a7ef5bacd1872be08d479 Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Thu, 15 Sep 2022 11:50:18 +0400 Subject: [PATCH 09/18] Mod: moved the jquery connection from the frontend to the admin part, refactoring ct-external.js --- cleantalkantispam.php | 3 +-- js/ct-external.js | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index 9968cde..cbf0c86 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -576,8 +576,6 @@ public function onBeforeCompileHead() $app = JFactory::getApplication(); $document = JFactory::getDocument(); - JHtml::_('jquery.framework'); - if ($this->isSite() && ! $this->jot_cache_enabled()) { $this->sfw_check(); @@ -684,6 +682,7 @@ public function onBeforeCompileHead() ct_form_settings_title = "' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_SETTINGS_TITLE') . '"; '); //Admin JS and CSS + JHtml::_('jquery.framework'); $document->addScript(JURI::root(true) . "/plugins/system/cleantalkantispam/js/ct-settings.js?" . time()); $document->addStyleSheet(JURI::root(true) . "/plugins/system/cleantalkantispam/css/ct-settings.css?" . time()); diff --git a/js/ct-external.js b/js/ct-external.js index 1b91cfb..ec883d3 100644 --- a/js/ct-external.js +++ b/js/ct-external.js @@ -37,7 +37,7 @@ function ct_check_external(){ } } -jQuery(document).ready( function(){ +document.addEventListener('DOMContentLoaded', function() { setTimeout(function () { ct_check_external(); }, 1500); From 97588a2436bd25cb74fb2e0503e7834d3ce61cf0 Mon Sep 17 00:00:00 2001 From: Viktor Date: Tue, 20 Sep 2022 08:44:02 +0300 Subject: [PATCH 10/18] Upd. Admin notices. Show admin banners logic updated. (#8) * Upd. Admin notices. JS logic added to dispatch notices behavior. * Upd. Admin notices. Show admin banners logic updated. * Upd. Admin notices. Set dismiss flag and check it methods added. * Upd. Admin notices. New method - checking plugin setting page is the current page. * Upd. Admin notices. Ajax handler added. * Upd. Admin notices. Banner displaying fixed. * Upd. Admin notices. Old logic for the checking renew banner deleted. * Upd. Admin notices. J3 notices compatibility implemented. --- cleantalkantispam.php | 134 +++++++++++++++++++++++++++++++++++++----- js/ct-settings.js | 95 ++++++++++++++++++++++++------ 2 files changed, 196 insertions(+), 33 deletions(-) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index 9968cde..8b72814 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -40,6 +40,10 @@ use Cleantalk\ApbctJoomla\RemoteCalls as RemoteCalls; use Cleantalk\Common\Variables\Server; use Cleantalk\Common\Variables\ServerVariables; +use Joomla\CMS\Factory; +use Joomla\CMS\Language\Text; +use Joomla\CMS\Session\Session; +use Joomla\CMS\Uri\Uri; define('APBCT_TBL_FIREWALL_DATA', 'cleantalk_sfw'); // Table with firewall data. define('APBCT_TBL_FIREWALL_LOG', 'cleantalk_sfw_logs'); // Table with firewall logs. @@ -58,11 +62,16 @@ class plgSystemCleantalkantispam extends JPlugin */ const ENGINE = 'joomla34-23'; - /* - * Flag marked JComments form initilization. - * @since 1.0 - */ - private $JCReady = false; + /** + * Flag marked JComments form initilization. + * @since 1.0 + */ + private $JCReady = false; + + /** + * Days to hide trial notice banner + */ + const DAYS_INTERVAL_HIDING_NOTICE = 30; /** * Form submited without page load @@ -392,11 +401,6 @@ public function onAfterInitialise() $save_params['connection_reports'] = array('success' => 0, 'negative' => 0, 'negative_report' => null); } - if (isset($_POST['check_renew_banner'])) { - $output['result'] = 'success'; - $output['close_renew_banner'] = $this->params->get('show_notice') == 0 ? 1 : 0; - } - if (isset($_POST['dev_insert_spam_users']) && $_POST['dev_insert_spam_users'] === 'yes') // @ToDo This code block not used! $output = self::dev_insert_spam_users(); @@ -617,11 +621,16 @@ public function onBeforeCompileHead() if (!$ct_key_is_ok) $notice = JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_NOTICE_APIKEY'); - if ($show_notice == 1 && $trial == 1) - $notice = JText::sprintf('PLG_SYSTEM_CLEANTALKANTISPAM_NOTICE_TRIAL', $config->get('user_token')); + if ($show_notice == 1 && $trial == 1) { + if ( ! $this->isDismissedNotice('trial_' . $user->id) || $this->isPluginSettingsPage() ) { + $notice = JText::sprintf('PLG_SYSTEM_CLEANTALKANTISPAM_NOTICE_TRIAL', $config->get('user_token')); + } + } if ($show_notice == 1 && $renew == 1) - $notice = JText::sprintf('PLG_SYSTEM_CLEANTALKANTISPAM_NOTICE_RENEW', $config->get('user_token')); + if ( ! $this->isDismissedNotice('renew_' . $user->id) || $this->isPluginSettingsPage() ) { + $notice = JText::sprintf('PLG_SYSTEM_CLEANTALKANTISPAM_NOTICE_RENEW', $config->get('user_token')); + } if (!$ct_curl_aufopen_availability) { $notice = JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_NOTICE_CURL_AUFOPEN_UNAVAILABLE'); @@ -645,6 +654,7 @@ public function onBeforeCompileHead() ct_connection_reports_negative ="' . (isset($connection_reports['negative']) ? $connection_reports['negative'] : 0) . '", ct_connection_reports_negative_report = "' . (isset($connection_reports['negative_report']) ? addslashes(json_encode($connection_reports['negative_report'])) : null) . '", ct_notice_review_done ='.(($config->get('show_review_done') && $config->get('show_review_done') === 1)?'true':'false').', + ct_extension_id = ' . $this->_id . ', //Translation ct_autokey_label = "' . JText::_('PLG_SYSTEM_CLEANTALKANTISPAM_JS_PARAM_AUTOKEY_LABEL') . '", @@ -697,8 +707,16 @@ public function onBeforeCompileHead() } if (isset($notice)) { + $notice_type = ''; + if ( $trial == 1 ) { + $notice_type = 'data-notice-type="trial"'; + } + if ( $renew == 1 ) { + $notice_type = 'data-notice-type="renew"'; + } + $notice = '
' . $notice . '
'; if(version_compare($this->cms_version, '4.0.0') >= 0) { - JFactory::getDocument()->addScriptOptions('joomla.messages', array('info' => array(array($notice)))); + $app->getDocument()->addScriptOptions('joomla.messages', array('info' => array(array($notice)))); } else { JFactory::getApplication()->enqueueMessage($notice, 'notice'); } @@ -1332,9 +1350,81 @@ public function onJCommentsCommentBeforeAdd(&$comment) return true; } + /** + * The spot to handle all ajax request for the plugin + * + * @return string[]|void + * + * @throws Exception + * @since version + */ + public function onAjaxCleantalkantispam() { + Session::checkToken('get') or die(Text::_('JINVALID_TOKEN')); + $data = Factory::getApplication()->input->json->getArray(); + if ( isset($data['action']) ) { + switch ($data['action']) { + case 'dismiss_notice' : + $this->setNoticeDismissed($data['data']); + // @ToDo add an error handling here + return ['success' => 'The notice dismissing was remembered']; + default : + return ['error' => 'Wrong action was provided']; + } + } + return ['error' => 'No action was provided']; + } + //////////////////////////// // Private methods + /** + * Store the notice dismissed flag + * @param array $notice_info + */ + private function setNoticeDismissed($notice_info) + { + if ( ! isset($notice_info['notice_type']) ) { + // @ToDo add an error throwing here + return; + } + + $filter = JFilterInput::getInstance(); + + $user = Factory::getUser(); + $notice = $filter->clean($notice_info['notice_type']); + $uid = $user->id; + $notice_uid = $notice . '_' . $uid; + $current_date = time(); + + $this->saveCTConfig(['cleantalk_' . $notice_uid . '_dismissed' => $current_date]); + } + + /** + * Check dismiss status of the notice + * + * @param string $notice_uid + * + * @return bool + */ + private function isDismissedNotice($notice_uid) + { + $option_name = 'cleantalk_' . $notice_uid . '_dismissed'; + $notice_date_option = $this->params->get($option_name, false); + + if ( $notice_date_option === false ) { + return false; + } + + $current_time = time(); + $notice_time = (int) $notice_date_option; + + if ( $current_time - $notice_time <= self::DAYS_INTERVAL_HIDING_NOTICE * 24 * 60 * 60 ) { + return true; + } + + return false; + } + /** * Include in head adn fill form * @@ -2448,4 +2538,20 @@ public function setDocumentBody($body) { } } } + + /** + * Check is the current page is the plugin settings page + * @return bool + * + * @since version + */ + private function isPluginSettingsPage() { + $uri = Uri::getInstance(); + $layout = $uri->getVar('layout'); + $ext_id = $uri->getVar('extension_id'); + if ( isset($layout, $ext_id) && $layout === 'edit' && $ext_id == $this->_id ) { + return true; + } + return false; + } } diff --git a/js/ct-settings.js b/js/ct-settings.js index d1b243d..0641222 100644 --- a/js/ct-settings.js +++ b/js/ct-settings.js @@ -29,23 +29,84 @@ function animate_banner(to){ jQuery('#feedback_notice').fadeTo(300,to); } } -function banner_check() { - var bannerChecker = setInterval( function() { - jQuery.ajax({ - type: "POST", - url: location.href, - data: {'check_renew_banner' : 1}, - // dataType: 'json', - success: function(msg){ - msg=jQuery.parseJSON(msg); - if (msg.close_renew_banner == 1) { - jQuery('.alert-info').hide('slow'); - clearInterval(bannerChecker); - } + +// Get system messages and handle these +document.addEventListener('DOMContentLoaded', () => { + setTimeout(dispatchJoomlaNotices, 0); +}); +function dispatchJoomlaNotices() { + const joomlaAlertWrapper = document.getElementById("system-message-container"); + if ( joomlaAlertWrapper !== null ) { + let joomlaAlerts = joomlaAlertWrapper.getElementsByTagName('joomla-alert'); + if ( joomlaAlerts.length === 0 ) { + joomlaAlerts = joomlaAlertWrapper.getElementsByClassName('alert'); + } + if ( joomlaAlerts.length > 0 ) { + for ( let i = 0; i < joomlaAlerts.length; i++ ) { + dispatchApbctJoomlaNotice(joomlaAlerts[i]); } - }); - }, 60000); + } + } +} +function dispatchApbctJoomlaNotice(element) { + const apbctNotice = element.querySelector("#apbct_joomla_notice"); + let oldWay = element.tagName !== 'JOOMLA-ALERT'; + + if ( apbctNotice !== null ) { + // Disable notice dismissing on the plugin settings page + const currentUrl = new URL(location.href); + if ( currentUrl.searchParams.get('layout') === 'edit' && +currentUrl.searchParams.get('extension_id') === ct_extension_id ) { + if ( typeof element.destroyCloseButton === "function" ) { + element.destroyCloseButton(); + } else { + element.getElementsByClassName('close')[0].remove(); + } + } + + // Listen close event only on the TRIAL of RENEW banner + if ( apbctNotice.dataset.noticeType === 'trial' || apbctNotice.dataset.noticeType === 'renew' ) { + const dispatchedElement = oldWay ? element.getElementsByClassName('close')[0] : element; + const dispatchedEvent = oldWay ? 'click' : 'joomla.alert.close'; + + if ( typeof dispatchedElement === 'undefined' ) { + return; + } + + dispatchedElement.addEventListener(dispatchedEvent, (event) => { + let data = { + 'action' : 'dismiss_notice', + 'data': { + 'notice_type' : apbctNotice.dataset.noticeType + } + }; + Joomla.request({ + url: 'index.php?option=com_ajax&plugin=cleantalkantispam&format=json', + method: 'POST', + data: JSON.stringify(data), + headers: { + 'Cache-Control' : 'no-cache' + }, + onSuccess: function (response, xhr){ + try { + let responseData = JSON.parse(response); + responseData = responseData.data[0]; + if ( responseData.error ) { + // Do something with the error + } else { + // Do something with the regular result + } + } catch (e) { + console.log(e.toString()); + console.log(e.fileName); + console.log(e.lineNumber); + } + } + }) + }); + } + } } + jQuery(document).ready(function(){ var ct_auth_key = jQuery('.cleantalk_auth_key').prop('value'), ct_notice_cookie = ct_getCookie('ct_notice_cookie'); @@ -145,10 +206,6 @@ jQuery(document).ready(function(){ if(ct_moderate_ip == 1) jQuery('#jform_params_apikey').parent().parent().append("

The anti-spam service is paid by your hosting provider. License #"+ct_ip_license+"

"); - //Check banner - if (jQuery('.alert').length && jQuery('.alert').hasClass('alert-info')) - banner_check(); - // Handler for review banner jQuery('#ct_review_link').click(function(){ var data = { From 34b21d844224a2c21e4d332fb4ae0bc350fab945 Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Tue, 25 Oct 2022 10:32:20 +0400 Subject: [PATCH 11/18] Mod: Removed cookies from request --- lib/Cleantalk/Antispam/Cleantalk.php | 38 +++++++++++++--------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/Cleantalk/Antispam/Cleantalk.php b/lib/Cleantalk/Antispam/Cleantalk.php index d306ca3..d8e9a67 100644 --- a/lib/Cleantalk/Antispam/Cleantalk.php +++ b/lib/Cleantalk/Antispam/Cleantalk.php @@ -385,27 +385,23 @@ private function sendRequest($data, $url, $server_timeout = 15) { */ 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}[;|$]/', - '/\s{0,1}ct_pointer_data=.*5D[;|$]{0,1}/', - '/;{0,1}\s{0,3}$/' - ), '', $ct_tmp[$cookie_name]); - - $msg->all_headers=json_encode($ct_tmp); - } + + // Wiping session cookies from request + $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($ct_tmp[$cookie_name])) { + unset($ct_tmp[$cookie_name]); + } + + $msg->all_headers = !empty($ct_tmp) ? json_encode($ct_tmp) : ''; //$msg->remote_addr=$_SERVER['REMOTE_ADDR']; //$msg->sender_info['remote_addr']=$_SERVER['REMOTE_ADDR']; From 96eeb0582451eae15349682f4c3b63b454b5134d Mon Sep 17 00:00:00 2001 From: Artem Anoshin Date: Tue, 25 Oct 2022 12:45:31 +0400 Subject: [PATCH 12/18] Added pageExcluded() --- cleantalkantispam.php | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index 72473f1..a19fa03 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -579,8 +579,9 @@ public function onBeforeCompileHead() $user = JFactory::getUser(); $app = JFactory::getApplication(); $document = JFactory::getDocument(); + $urls = $config->get('url_exclusions'); - if ($this->isSite() && ! $this->jot_cache_enabled()) + if ($this->isSite() && ! $this->jot_cache_enabled() && !$this->pageExcluded($urls)) { $this->sfw_check(); $this->ct_cookie(); @@ -743,11 +744,12 @@ public function onAfterRoute() $ctask_cmd = $app->input->get('ctask'); $page_cmd = $app->input->get('page'); $ff_task = $app->input->get('ff_task'); // Breezingform Integration + $urls = $this->params->get('url_exclusions'); /** * Integration with JotCache Plugin */ - if ($this->jot_cache_enabled()) + if ($this->jot_cache_enabled() && !$this->pageExcluded($urls)) { $document = JFactory::getDocument(); $config = $this->params; @@ -2553,4 +2555,32 @@ private function isPluginSettingsPage() { } return false; } + + /** + * Checking the page in the exception + * + * @param string $urls + * @return boolean + */ + public function pageExcluded($urls) { + if (empty($urls)) { + return false; + } + + $urls = explode(',', $urls); + + foreach ($urls as $url) { + // @ToDo need to detect ajax request + // @ToDo implement support for a regexp + $current_page_url = ((!empty($_SERVER['HTTPS'])) ? 'https' : 'http') . '://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + $current_page_url = explode('?', $current_page_url); + $current_page_url = $current_page_url[0]; + + if( strpos($current_page_url, $url) !== false) { + return true; + } + } + + return false; + } } From c9139c606390239a28d13089db2bfe88342d76a7 Mon Sep 17 00:00:00 2001 From: Viktor Date: Thu, 8 Dec 2022 09:58:01 +0300 Subject: [PATCH 13/18] New. Common lib. New composer packets loaded instead of the old library files. (#11) * Upd. Gitignore rules updated. * New. Common lib. Dependencies activated by composer.json. * New. Common lib. New composer packets loaded instead of the old library files. * New. Common lib. Client code has been adapted to the new library packets. * Upd. Common lib. Firewall updated. * Fix. ApbctJoomla\Helper some methods added. * Fix. Common lib. Client code has been fixed. * Upd. Common lib. Firewall updated. * Upd. Common lib. Firewall updated. * Fix. ApbctJoomla\Helper some methods added. * New. Big update. * New. Version 3.0. * New. Version 3.0 agent updated. * Common lib updated. * Fix. Antispam. Blocking page fixed. * Fix. Antispam. Allowed user registration fixed. * Fix. RC. Custom RC - remover unused method. * Upd. Common lib updated. * Upd. Common lib updated. * Fix. Updater fixed. --- .gitignore | 9 + cleantalkantispam.php | 314 ++-- cleantalkantispam.xml | 29 +- composer.json | 16 + lib/Cleantalk/Antispam/Cleantalk.php | 791 ---------- lib/Cleantalk/Antispam/CleantalkRequest.php | 172 -- lib/Cleantalk/Antispam/CleantalkResponse.php | 158 -- lib/Cleantalk/Antispam/SFW.php | 326 ---- lib/Cleantalk/ApbctJoomla/Cron.php | 99 -- lib/Cleantalk/ApbctJoomla/Helper.php | 89 -- lib/Cleantalk/Common/API.php | 793 ---------- lib/Cleantalk/Common/Antispam/Cleantalk.php | 513 ++++++ .../Common/Antispam/CleantalkRequest.php | 269 ++++ .../Common/Antispam/CleantalkResponse.php | 161 ++ .../Common/Antispam/lock-page-ct-die.html | 150 ++ lib/Cleantalk/Common/Api/Api.php | 908 +++++++++++ lib/Cleantalk/Common/Cleaner/Escape.php | 88 ++ lib/Cleantalk/Common/Cleaner/Sanitize.php | 206 +++ lib/Cleantalk/Common/Cleaner/Validate.php | 112 ++ lib/Cleantalk/Common/Cron.php | 255 --- lib/Cleantalk/Common/Cron/Cron.php | 336 ++++ lib/Cleantalk/Common/DB.php | 101 -- lib/Cleantalk/Common/Db/Db.php | 160 ++ lib/Cleantalk/Common/Db/DbTablesCreator.php | 79 + lib/Cleantalk/Common/Db/Schema.php | 104 ++ lib/Cleantalk/Common/Dns/Dns.php | 140 ++ .../Exceptions/SfwUpdateException.php | 8 + lib/Cleantalk/Common/Firewall/Firewall.php | 298 ++-- .../Common/Firewall/FirewallModule.php | 41 +- .../Common/Firewall/FirewallUpdater.php | 1231 +++++++++++---- lib/Cleantalk/Common/Firewall/FwStats.php | 19 + .../Common/Firewall/Modules/AntiCrawler.php | 635 ++++++++ .../Common/Firewall/Modules/AntiFlood.php | 345 ++++ lib/Cleantalk/Common/Firewall/Modules/SFW.php | 281 ---- lib/Cleantalk/Common/Firewall/Modules/Sfw.php | 809 ++++++++++ .../Modules/die_page_anticrawler.html | 278 ++++ .../Firewall/Modules/die_page_antiflood.html | 272 ++++ .../Common/Firewall/Modules/die_page_sfw.html | 136 +- lib/Cleantalk/Common/Helper.php | 1397 ----------------- lib/Cleantalk/Common/Helper/Helper.php | 1177 ++++++++++++++ lib/Cleantalk/Common/Http/Request.php | 568 +++++++ lib/Cleantalk/Common/Http/Response.php | 79 + lib/Cleantalk/Common/Mloader/Mloader.php | 30 + lib/Cleantalk/Common/Queue/Queue.php | 239 +++ lib/Cleantalk/Common/RemoteCalls.php | 130 -- .../Common/RemoteCalls/RemoteCalls.php | 248 +++ lib/Cleantalk/Common/Schema.php | 63 - .../Common/StorageHandler/StorageHandler.php | 16 + lib/Cleantalk/Common/Templates/Multiton.php | 28 + lib/Cleantalk/Common/Templates/Singleton.php | 42 + lib/Cleantalk/Common/Variables/Cookie.php | 130 +- lib/Cleantalk/Common/Variables/Get.php | 72 +- lib/Cleantalk/Common/Variables/Post.php | 72 +- lib/Cleantalk/Common/Variables/Request.php | 81 +- lib/Cleantalk/Common/Variables/Server.php | 208 ++- .../Common/Variables/ServerVariables.php | 201 ++- lib/Cleantalk/Common/error.html | 54 - .../{ApbctJoomla/DB.php => Custom/Db/Db.php} | 48 +- lib/Cleantalk/Custom/Helper/Helper.php | 473 ++++++ .../RemoteCalls}/RemoteCalls.php | 38 +- .../Custom/StorageHandler/StorageHandler.php | 76 + sql/mariadb/install.mariadb.utf8.sql | 29 +- sql/mariadb/updates/1.8.sql | 29 + sql/mariadb/updates/3.0.sql | 17 + sql/mysql/install.mysql.utf8.sql | 7 +- sql/mysql/updates/3.0.sql | 17 + sql/sqlsrv/install.mysql.utf8.sql | 29 +- sql/sqlsrv/install.sqlsrv.utf8.sql | 35 +- sql/sqlsrv/updates/3.0.sql | 17 + updater.php | 4 + 70 files changed, 10652 insertions(+), 5733 deletions(-) create mode 100644 composer.json delete mode 100644 lib/Cleantalk/Antispam/Cleantalk.php delete mode 100644 lib/Cleantalk/Antispam/CleantalkRequest.php delete mode 100644 lib/Cleantalk/Antispam/CleantalkResponse.php delete mode 100644 lib/Cleantalk/Antispam/SFW.php delete mode 100644 lib/Cleantalk/ApbctJoomla/Cron.php delete mode 100644 lib/Cleantalk/ApbctJoomla/Helper.php delete mode 100644 lib/Cleantalk/Common/API.php create mode 100644 lib/Cleantalk/Common/Antispam/Cleantalk.php create mode 100644 lib/Cleantalk/Common/Antispam/CleantalkRequest.php create mode 100644 lib/Cleantalk/Common/Antispam/CleantalkResponse.php create mode 100644 lib/Cleantalk/Common/Antispam/lock-page-ct-die.html create mode 100644 lib/Cleantalk/Common/Api/Api.php create mode 100644 lib/Cleantalk/Common/Cleaner/Escape.php create mode 100644 lib/Cleantalk/Common/Cleaner/Sanitize.php create mode 100644 lib/Cleantalk/Common/Cleaner/Validate.php delete mode 100644 lib/Cleantalk/Common/Cron.php create mode 100644 lib/Cleantalk/Common/Cron/Cron.php delete mode 100644 lib/Cleantalk/Common/DB.php create mode 100644 lib/Cleantalk/Common/Db/Db.php create mode 100644 lib/Cleantalk/Common/Db/DbTablesCreator.php create mode 100644 lib/Cleantalk/Common/Db/Schema.php create mode 100644 lib/Cleantalk/Common/Dns/Dns.php create mode 100644 lib/Cleantalk/Common/Firewall/Exceptions/SfwUpdateException.php create mode 100644 lib/Cleantalk/Common/Firewall/FwStats.php create mode 100644 lib/Cleantalk/Common/Firewall/Modules/AntiCrawler.php create mode 100644 lib/Cleantalk/Common/Firewall/Modules/AntiFlood.php delete mode 100644 lib/Cleantalk/Common/Firewall/Modules/SFW.php create mode 100644 lib/Cleantalk/Common/Firewall/Modules/Sfw.php create mode 100644 lib/Cleantalk/Common/Firewall/Modules/die_page_anticrawler.html create mode 100644 lib/Cleantalk/Common/Firewall/Modules/die_page_antiflood.html delete mode 100644 lib/Cleantalk/Common/Helper.php create mode 100644 lib/Cleantalk/Common/Helper/Helper.php create mode 100644 lib/Cleantalk/Common/Http/Request.php create mode 100644 lib/Cleantalk/Common/Http/Response.php create mode 100644 lib/Cleantalk/Common/Mloader/Mloader.php create mode 100644 lib/Cleantalk/Common/Queue/Queue.php delete mode 100644 lib/Cleantalk/Common/RemoteCalls.php create mode 100644 lib/Cleantalk/Common/RemoteCalls/RemoteCalls.php delete mode 100644 lib/Cleantalk/Common/Schema.php create mode 100644 lib/Cleantalk/Common/StorageHandler/StorageHandler.php create mode 100644 lib/Cleantalk/Common/Templates/Multiton.php create mode 100644 lib/Cleantalk/Common/Templates/Singleton.php delete mode 100644 lib/Cleantalk/Common/error.html rename lib/Cleantalk/{ApbctJoomla/DB.php => Custom/Db/Db.php} (60%) create mode 100644 lib/Cleantalk/Custom/Helper/Helper.php rename lib/Cleantalk/{ApbctJoomla => Custom/RemoteCalls}/RemoteCalls.php (81%) create mode 100644 lib/Cleantalk/Custom/StorageHandler/StorageHandler.php create mode 100644 sql/mariadb/updates/1.8.sql create mode 100644 sql/mariadb/updates/3.0.sql create mode 100644 sql/mysql/updates/3.0.sql create mode 100644 sql/sqlsrv/updates/3.0.sql diff --git a/.gitignore b/.gitignore index 85e7c1d..e25d880 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,10 @@ /.idea/ +/vendor +/node_modules +/lib/**/composer.json +/lib/**/.gitignore +/lib/**/LICENSE +/lib/**/README.MD +/lib/**/tests +composer.lock +package-lock.json \ No newline at end of file diff --git a/cleantalkantispam.php b/cleantalkantispam.php index a19fa03..90386f4 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -26,20 +26,13 @@ require_once(dirname(__FILE__) . '/lib/autoload.php'); //Antispam classes -use Cleantalk\Antispam\Cleantalk as Cleantalk; -use Cleantalk\Antispam\CleantalkRequest as CleantalkRequest; -use Cleantalk\Antispam\CleantalkRequest as CleantalkResponse; - -use Cleantalk\ApbctJoomla\DB; -use Cleantalk\Common\API as CleantalkAPI; -use Cleantalk\ApbctJoomla\Helper as CleantalkHelper; -use Cleantalk\ApbctJoomla\Cron; -use Cleantalk\Common\Schema; -use Cleantalk\Common\Firewall\Firewall; -use Cleantalk\Common\Firewall\Modules\SFW; -use Cleantalk\ApbctJoomla\RemoteCalls as RemoteCalls; +use Cleantalk\Common\Antispam\Cleantalk; +use Cleantalk\Common\Antispam\CleantalkRequest; + +use Cleantalk\Common\Cleaner\Sanitize; +use Cleantalk\Common\Mloader\Mloader; + use Cleantalk\Common\Variables\Server; -use Cleantalk\Common\Variables\ServerVariables; use Joomla\CMS\Factory; use Joomla\CMS\Language\Text; use Joomla\CMS\Session\Session; @@ -50,9 +43,11 @@ define('APBCT_TBL_AC_LOG', 'cleantalk_ac_log'); // Table with firewall logs. define('APBCT_TBL_AC_UA_BL', 'cleantalk_ua_bl'); // Table with User-Agents blacklist. define('APBCT_TBL_SESSIONS', 'cleantalk_sessions'); // Table with session data. +define('APBCT_SFW_SEND_LOGS_LIMIT', 1000); define('APBCT_SPAMSCAN_LOGS', 'cleantalk_spamscan_logs'); // Table with session data. define('APBCT_SELECT_LIMIT', 5000); // Select limit for logs. define('APBCT_WRITE_LIMIT', 5000); // Write limit for firewall data. +define('APBCT_DIR_PATH', __DIR__); class plgSystemCleantalkantispam extends JPlugin { @@ -60,10 +55,10 @@ class plgSystemCleantalkantispam extends JPlugin * Plugin version string for server * @since 1.0 */ - const ENGINE = 'joomla34-23'; + const ENGINE = 'joomla34-30'; /** - * Flag marked JComments form initilization. + * Flag marked JComments form initialization. * @since 1.0 */ private $JCReady = false; @@ -144,6 +139,7 @@ public function __construct(&$subject, $config) $this->loadLanguage(); $this->cms_version = $this->getCmsVersion(); + } private function getId() { @@ -214,16 +210,18 @@ private function cleantalk_get_checkjs_code() private function checkIsPaid($ct_api_key = '', $force_check = false) { + $helper = Mloader::get('Helper'); + $api_key = trim($ct_api_key); if (($this->params->get('acc_status_last_check') && ($this->params->get('acc_status_last_check') < time() - 86400)) || $force_check || !$this->params->get('ct_key_is_ok')) { $ct_key_is_ok = 0; - $key_is_valid = CleantalkHelper::key_is_correct($api_key); + $key_is_valid = $helper::isApikeyCorrect($api_key); $save_params = array(); $result = null; if ($key_is_valid){ - $result = CleantalkAPI::method__notice_paid_till($api_key, preg_replace('/http[s]?:\/\//', '', $_SERVER['HTTP_HOST'], 1)); + $result = Mloader::get('Api')::methodNoticePaidTill($api_key, preg_replace('/http[s]?:\/\//', '', $_SERVER['HTTP_HOST'], 1)); $ct_key_is_ok = (empty($result['error']) && $result['valid']) ? 1 : 0; } @@ -276,20 +274,20 @@ public function onAfterInitialise() if (!$this->isAdmin()) { // Remote calls - if (isset($_GET['spbc_remote_call_token'], $_GET['spbc_remote_call_action'], $_GET['plugin_name']) && in_array($_GET['plugin_name'], array('antispam', 'anti-spam', 'apbct'))) - { - // Remote calls - if( RemoteCalls::check() ) { - $rc = new RemoteCalls( $this->params->get('apikey') ); - $rc->perform(); - } - } + /** @var \Cleantalk\Common\RemoteCalls\RemoteCalls $remote_calls_class */ + $remote_calls_class = Mloader::get('RemoteCalls'); + + if( $remote_calls_class::check() ) { + $rc = new $remote_calls_class( $this->params->get('apikey') ); + $rc->process(); + } } if ($this->isAdmin() && $app->input->get('layout') == 'edit' && $app->input->get('extension_id') == $this->_id) { $output = null; $save_params = array(); + $api_class = Mloader::get('Api'); // Close review banner if (isset($_POST['ct_delete_notice']) && $_POST['ct_delete_notice'] === 'yes') @@ -298,11 +296,11 @@ public function onAfterInitialise() // Getting key automatically if (isset($_POST['get_auto_key']) && $_POST['get_auto_key'] === 'yes') { - $output = CleantalkAPI::method__get_api_key('antispam', JFactory::getConfig()->get('mailfrom'), $_SERVER['HTTP_HOST'], 'joomla3'); + $output = $api_class::methodGetApiKey('antispam', JFactory::getConfig()->get('mailfrom'), $_SERVER['HTTP_HOST'], 'joomla3'); // Checks if the user token is empty, then get user token by notice_paid_till() if( empty( $output['user_token'] ) && ! empty( $output['auth_key'] ) ){ - $result_tmp = CleantalkAPI::method__notice_paid_till($output['auth_key'], preg_replace('/http[s]?:\/\//', '', $_SERVER['HTTP_HOST'], 1)); + $result_tmp = $api_class::methodNoticePaidTill($output['auth_key'], preg_replace('/http[s]?:\/\//', '', $_SERVER['HTTP_HOST'], 1)); if( empty( $result_tmp['error'] ) ) $output['user_token'] = $result_tmp['user_token']; @@ -314,18 +312,18 @@ public function onAfterInitialise() // Check spam users if (isset($_POST['check_type']) && $_POST['check_type'] === 'users') { - $improved_check = ($_POST['improved_check'] == 'true') ? true : false; + $improved_check = ($_POST['improved_check'] === 'true') ? true : false; $offset = isset($_POST['offset']) ? $_POST['offset'] : 0; $on_page = isset($_POST['amount']) ? $_POST['amount'] : 2; - $output = self::get_spam_users($offset, $on_page, $improved_check); + $output = $this->get_spam_users($offset, $on_page, $improved_check); } // Check spam comments if (isset($_POST['check_type']) && $_POST['check_type'] === 'comments') { - $improved_check = ($_POST['improved_check'] == 'true') ? true : false; + $improved_check = ($_POST['improved_check'] === 'true') ? true : false; $offset = isset($_POST['offset']) ? $_POST['offset'] : 0; $on_page = isset($_POST['amount']) ? $_POST['amount'] : 2; - $output = self::get_spam_comments($offset, $on_page, $improved_check); + $output = $this->get_spam_comments($offset, $on_page, $improved_check); } if (isset($_POST['ct_del_user_ids'])) { @@ -401,10 +399,6 @@ public function onAfterInitialise() $save_params['connection_reports'] = array('success' => 0, 'negative' => 0, 'negative_report' => null); } - if (isset($_POST['dev_insert_spam_users']) && $_POST['dev_insert_spam_users'] === 'yes') - // @ToDo This code block not used! - $output = self::dev_insert_spam_users(); - $this->saveCTConfig($save_params); if ($output !== null) @@ -805,13 +799,14 @@ public function onAfterRoute() $sender_email = JFactory::getUser()->email; $sender_nickname = JFactory::getUser()->username; $message = trim($_GET['searchword']); - $ctResponse = self::ctSendRequest( - 'check_message', array( - 'sender_nickname' => $sender_nickname, - 'sender_email' => $sender_email, - 'message' => trim(preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $message)), - 'post_info' => json_encode($post_info), - ) + $ctResponse = $this->ctSendRequest( + 'check_message', + array( + 'sender_nickname' => $sender_nickname, + 'sender_email' => $sender_email, + 'message' => trim(preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $message)), + 'post_info' => json_encode($post_info), + ) ); if ($ctResponse) { @@ -823,8 +818,27 @@ public function onAfterRoute() { if ($ctResponse['allow'] == 0) { - $error_tpl = file_get_contents(dirname(__FILE__) . "/lib/Cleantalk/Common/error.html"); - print str_replace('%ERROR_TEXT%', $ctResponse['comment'], $error_tpl); + $ct_die_page = file_get_contents(Cleantalk::getLockPageFile()); + + $message_title = 'CleanTalk. Spam protection'; + $back_script = ''; + $back_link = ''; + if ( isset($_SERVER['HTTP_REFERER']) ) { + $back_link = 'Back'; + } + + // Translation + $replaces = array( + '{MESSAGE_TITLE}' => $message_title, + '{MESSAGE}' => $ctResponse['comment'], + '{BACK_LINK}' => $back_link, + '{BACK_SCRIPT}' => $back_script + ); + + foreach ( $replaces as $place_holder => $replace ) { + $ct_die_page = str_replace($place_holder, $replace, $ct_die_page); + } + print $ct_die_page; die(); } @@ -887,8 +901,7 @@ public function onAfterRoute() //BreezingForms }elseif (isset($_POST['ff_task']) && $_POST['ff_task'] == 'submit'){ - - $ct_temp_msg_data = CleantalkHelper::get_fields_any($_POST, $this->params->get('fields_exclusions')); + $ct_temp_msg_data = Mloader::get('Helper')::get_fields_any($_POST, $this->params->get('fields_exclusions')); $sender_email = ($ct_temp_msg_data['email'] ? $ct_temp_msg_data['email'] : ''); $sender_nickname = ($ct_temp_msg_data['nickname'] ? $ct_temp_msg_data['nickname'] : ''); @@ -923,7 +936,7 @@ public function onAfterRoute() } else { $post_processed = $_POST; } - $ct_temp_msg_data = CleantalkHelper::get_fields_any($post_processed, $this->params->get('fields_exclusions')); + $ct_temp_msg_data = Mloader::get('Helper')::get_fields_any($post_processed, $this->params->get('fields_exclusions')); $sender_email = ($ct_temp_msg_data['email'] ? $ct_temp_msg_data['email'] : ''); $sender_nickname = ($ct_temp_msg_data['nickname'] ? $ct_temp_msg_data['nickname'] : ''); $subject = ($ct_temp_msg_data['subject'] ? $ct_temp_msg_data['subject'] : ''); @@ -937,7 +950,7 @@ public function onAfterRoute() // General test for any forms or form with custom fields else { - $ct_temp_msg_data = CleantalkHelper::get_fields_any($_POST, $this->params->get('fields_exclusions')); + $ct_temp_msg_data = Mloader::get('Helper')::get_fields_any($_POST, $this->params->get('fields_exclusions')); $sender_email = ($ct_temp_msg_data['email'] ? $ct_temp_msg_data['email'] : ''); $sender_nickname = ($ct_temp_msg_data['nickname'] ? $ct_temp_msg_data['nickname'] : ''); $subject = ($ct_temp_msg_data['subject'] ? $ct_temp_msg_data['subject'] : ''); @@ -958,8 +971,27 @@ public function onAfterRoute() $option_cmd === 'com_breezingforms' && $ff_task === 'submit' ) { - $error_tpl = file_get_contents(dirname(__FILE__) . "/lib/Cleantalk/Common/error.html"); - print str_replace('%ERROR_TEXT%', 'Please, fill the email field.', $error_tpl); + $ct_die_page = file_get_contents(Cleantalk::getLockPageFile()); + + $message_title = 'CleanTalk. Spam protection'; + $back_script = ''; + $back_link = ''; + if ( isset($_SERVER['HTTP_REFERER']) ) { + $back_link = 'Back'; + } + + // Translation + $replaces = array( + '{MESSAGE_TITLE}' => $message_title, + '{MESSAGE}' => $ctResponse['comment'], + '{BACK_LINK}' => $back_link, + '{BACK_SCRIPT}' => $back_script + ); + + foreach ( $replaces as $place_holder => $replace ) { + $ct_die_page = str_replace($place_holder, $replace, $ct_die_page); + } + print $ct_die_page; die(); } @@ -1123,8 +1155,27 @@ public function onAfterRoute() } else { - $error_tpl = file_get_contents(dirname(__FILE__) . "/lib/Cleantalk/Common/error.html"); - print str_replace('%ERROR_TEXT%', $ctResponse['comment'], $error_tpl); + $ct_die_page = file_get_contents(Cleantalk::getLockPageFile()); + + $message_title = 'CleanTalk. Spam protection'; + $back_script = ''; + $back_link = ''; + if ( isset($_SERVER['HTTP_REFERER']) ) { + $back_link = 'Back'; + } + + // Translation + $replaces = array( + '{MESSAGE_TITLE}' => $message_title, + '{MESSAGE}' => $ctResponse['comment'], + '{BACK_LINK}' => $back_link, + '{BACK_SCRIPT}' => $back_script + ); + + foreach ( $replaces as $place_holder => $replace ) { + $ct_die_page = str_replace($place_holder, $replace, $ct_die_page); + } + print $ct_die_page; die(); } } @@ -1135,7 +1186,7 @@ public function onAfterRoute() unset($_POST['ct_action']); unset($_POST['ct_method']); print "
"; - CleantalkHelper::print_form($_POST, ''); + Mloader::get('Helper')::print_form($_POST, ''); print "
"; print "'; + $back_link = ''; + if ( isset($_SERVER['HTTP_REFERER']) ) { + $back_link = 'Back'; + } + + // Translation + $replaces = array( + '{MESSAGE_TITLE}' => $message_title, + '{MESSAGE}' => $ctResponse['comment'], + '{BACK_LINK}' => $back_link, + '{BACK_SCRIPT}' => $back_script + ); + + foreach ( $replaces as $place_holder => $replace ) { + $ct_die_page = str_replace($place_holder, $replace, $ct_die_page); + } + print $ct_die_page; die(); } } @@ -1312,14 +1383,15 @@ public function onJCommentsCommentBeforeAdd(&$comment) $example = $baseText . "\n\n\n\n" . $prevComments; } - $ctResponse = self::ctSendRequest( - 'check_message', array( - 'example' => $example, - 'message' => preg_replace('/\s+/', ' ', str_replace("
", " ", $comment->comment)), - 'sender_nickname' => $comment->name, - 'sender_email' => $comment->email, - 'post_info' => $post_info, - ) + $ctResponse = $this->ctSendRequest( + 'check_message', + array( + 'example' => $example, + 'message' => preg_replace('/\s+/', ' ', str_replace("
", " ", $comment->comment)), + 'sender_nickname' => $comment->name, + 'sender_email' => $comment->email, + 'post_info' => $post_info, + ) ); if ($ctResponse) { @@ -1519,11 +1591,12 @@ private function moderateUser() $session = JFactory::getSession(); - $ctResponse = self::ctSendRequest( - 'check_newuser', array( - 'sender_email' => $post_email, - 'sender_nickname' => $post_username, - ) + $ctResponse = $this->ctSendRequest( + 'check_newuser', + array( + 'sender_email' => $post_email, + 'sender_nickname' => $post_username, + ) ); if ($ctResponse) { @@ -1576,13 +1649,9 @@ private function moderateUser() } else { - $ct = new Cleantalk(); - $comment = $ct->addCleantalkComment("", $ctResponse['comment']); - $hash = $ct->getCleantalkCommentHash($comment); - $session->set('register_username', $post_username); $session->set('register_email', $post_email); - $session->set('ct_request_id', $hash); + $session->set('ct_request_id', $ctResponse['id']); } } } @@ -1723,9 +1792,9 @@ private function ctSendRequest($method, $params) $ct_request->auth_key = $this->params->get('apikey'); $ct_request->agent = self::ENGINE; $ct_request->submit_time = $this->submit_time_test(); - $ct_request->sender_ip = CleantalkHelper::ip__get(array('real'), false); - $ct_request->x_forwarded_for = CleantalkHelper::ip__get(array('x_forwarded_for'), false); - $ct_request->x_real_ip = CleantalkHelper::ip__get(array('x_real_ip'), false); + $ct_request->sender_ip = Mloader::get('Helper')::ipGet('real', false); + $ct_request->x_forwarded_for = Mloader::get('Helper')::ipGet('x_forwarded_for', false); + $ct_request->x_real_ip = Mloader::get('Helper')::ipGet('x_real_ip', false); $ct_request->sender_info = $this->get_sender_info(); $ct_request->js_on = $this->get_ct_checkjs($_COOKIE); @@ -1813,7 +1882,9 @@ private function get_ct_checkjs($data) { if (!$data) - return; + { + return; + } $checkjs = null; $js_post_value = null; @@ -1976,14 +2047,14 @@ private function ct_cookies_test() return 1; } if (isset($_COOKIE['apbct_cookies_test'])) { - $cookie_test = json_decode(stripslashes(self::ct_getcookie('apbct_cookies_test')), true); + $cookie_test = json_decode(stripslashes($this->ct_getcookie('apbct_cookies_test')), true); if (is_null($cookie_test)) { return null; } $check_string = trim($this->params->get('apikey')); foreach ($cookie_test['cookies_names'] as $cookie_name) { - $check_string .= self::ct_getcookie($cookie_name); + $check_string .= $this->ct_getcookie($cookie_name); } unset($cokie_name); @@ -2080,7 +2151,7 @@ static private function _apbct_alt_sessions__remove_old() */ static private function _apbct_alt_session__id__get() { - $id = CleantalkHelper::ip__get(array('real')) + $id = Mloader::get('Helper')::ipGet('real') . filter_input(INPUT_SERVER, 'HTTP_USER_AGENT') . filter_input(INPUT_SERVER, 'HTTP_ACCEPT_LANGUAGE'); return hash('sha256', $id); @@ -2143,13 +2214,13 @@ private function get_spam_comments($offset = 0, $on_page = 20, $improved_check = foreach ($data as $date => $values) { $values = implode(',', $values); - $result = CleantalkAPI::method__spam_check_cms($this->params->get('apikey'), $values, $date); + $result = Mloader::get('Api')::methodSpamCheckCms($this->params->get('apikey'), $values, $date); } } else { $values = implode(',', $data); - $result = CleantalkAPI::method__spam_check_cms($this->params->get('apikey'), $values); + $result = Mloader::get('Api')::methodSpamCheckCms($this->params->get('apikey'), $values); } if ($result) { @@ -2250,13 +2321,13 @@ private function get_spam_users($offset = 0, $on_page = 20, $improved_check = fa foreach ($data as $date => $values) { $values = implode(',', $values); - $result = CleantalkAPI::method__spam_check_cms($this->params->get('apikey'), $values, $date); + $result = Mloader::get('Api')::methodSpamCheckCms($this->params->get('apikey'), $values, $date); } } else { $values = implode(',', $data); - $result = CleantalkAPI::method__spam_check_cms($this->params->get('apikey'), $values); + $result = Mloader::get('Api')::methodSpamCheckCms($this->params->get('apikey'), $values); } if ($result) { @@ -2317,23 +2388,25 @@ private function get_spam_users($offset = 0, $on_page = 20, $improved_check = fa private function sfw_check() { - $app = JFactory::getApplication(); - if (!$this->isAdmin() && $this->params->get('other_settings') && in_array('sfw_enable', $this->params->get('other_settings')) && $_SERVER["REQUEST_METHOD"] == 'GET') { - $firewall = new Firewall( + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $firewall = new \Cleantalk\Common\Firewall\Firewall( $this->params->get('apikey'), - DB::getInstance(), - APBCT_TBL_FIREWALL_LOG + $db_obj->prefix . APBCT_TBL_FIREWALL_LOG ); - $firewall->loadFwModule( new SFW( - APBCT_TBL_FIREWALL_DATA, - array( - 'sfw_counter' => 0, - 'cookie_domain' => Server::get('HTTP_HOST'), - 'set_cookies' => $this->params->get('cookies'), - ) - ) ); + $firewall->loadFwModule( new \Cleantalk\Common\Firewall\Modules\Sfw( + $db_obj->prefix . APBCT_TBL_FIREWALL_LOG, + $db_obj->prefix . APBCT_TBL_FIREWALL_DATA, + array( + 'sfw_counter' => 0, + 'cookie_domain' => \Cleantalk\Common\Variables\Server::get('HTTP_HOST'), + 'set_cookies' => $this->params->get('cookies'), + ) + ) ); $firewall->run(); @@ -2342,7 +2415,9 @@ private function sfw_check() } private function apbct_run_cron() { - $cron = new Cron(); + $cron_class = Mloader::get('Cron'); + + $cron = new $cron_class(); if (!$this->params->get($cron->getCronOptionName())) { $cron->addTask( 'sfw_update', 'apbct_sfw_update', 86400, time() + 60 ); $cron->addTask( 'sfw_send_logs', 'apbct_sfw_send_logs', 3600 ); @@ -2350,7 +2425,7 @@ private function apbct_run_cron() $tasks_to_run = $cron->checkTasks(); // Check for current tasks. Drop tasks inner counters. if( ! empty( $tasks_to_run ) && // There is tasks to run - ! RemoteCalls::check() && // Do not doing CRON in remote call action + ! Mloader::get('RemoteCalls')::check() && // Do not doing CRON in remote call action ( ! defined( 'DOING_CRON' ) || ( defined( 'DOING_CRON' ) && DOING_CRON !== true ) @@ -2361,7 +2436,7 @@ private function apbct_run_cron() } } - static public function apbct_sfw_update($access_key = '') { + public static function apbct_sfw_update($access_key = '') { if( empty( $access_key ) ){ $plugin = \JPluginHelper::getPlugin('system', 'cleantalkantispam'); $params = new \JRegistry($plugin->params); @@ -2370,17 +2445,21 @@ static public function apbct_sfw_update($access_key = '') { return false; } } - $firewall = new Firewall( + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $firewall = new \Cleantalk\Common\Firewall\Firewall( $access_key, - DB::getInstance(), - APBCT_TBL_FIREWALL_LOG + $db_obj->prefix . APBCT_TBL_FIREWALL_LOG ); - $firewall->setSpecificHelper( new CleantalkHelper() ); - $fw_updater = $firewall->getUpdater( APBCT_TBL_FIREWALL_DATA ); - $fw_updater->update(); + + return $firewall->getUpdater()->update(); } - static public function apbct_sfw_send_logs($access_key = '') { + + public static function apbct_sfw_send_logs($access_key = '') { if( empty( $access_key ) ){ $plugin = \JPluginHelper::getPlugin('system', 'cleantalkantispam'); $params = new \JRegistry($plugin->params); @@ -2390,8 +2469,11 @@ static public function apbct_sfw_send_logs($access_key = '') { } } - $firewall = new Firewall( $access_key, DB::getInstance(), APBCT_TBL_FIREWALL_LOG ); - $firewall->setSpecificHelper( new CleantalkHelper() ); + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $firewall = new \Cleantalk\Common\Firewall\Firewall( $access_key, $db_obj->prefix . APBCT_TBL_FIREWALL_LOG ); $result = $firewall->sendLogs(); return true; diff --git a/cleantalkantispam.xml b/cleantalkantispam.xml index 0513a7c..a6165bb 100644 --- a/cleantalkantispam.xml +++ b/cleantalkantispam.xml @@ -7,44 +7,19 @@ GNU/GPLv2 welcome@cleantalk.org cleantalk.org - 2.3 + 3.0 PLG_SYSTEM_CLEANTALKANTISPAM_DESCRIPTION updater.php cleantalkantispam.php updater.php - lib/autoload.php - lib/Cleantalk/Antispam/Cleantalk.php - lib/Cleantalk/Antispam/CleantalkRequest.php - lib/Cleantalk/Antispam/CleantalkResponse.php - lib/Cleantalk/Common/API.php - lib/Cleantalk/Common/Cron.php - lib/Cleantalk/Common/DB.php - lib/Cleantalk/Common/Helper.php - lib/Cleantalk/Common/RemoteCalls.php - lib/Cleantalk/Common/Schema.php - lib/Cleantalk/Common/Variables/Cookie.php - lib/Cleantalk/Common/Variables/Get.php - lib/Cleantalk/Common/Variables/Post.php - lib/Cleantalk/Common/Variables/Request.php - lib/Cleantalk/Common/Variables/Server.php - lib/Cleantalk/Common/Variables/ServerVariables.php - lib/Cleantalk/Common/Firewall/Firewall.php - lib/Cleantalk/Common/Firewall/FirewallModule.php - lib/Cleantalk/Common/Firewall/FirewallUpdater.php - lib/Cleantalk/Common/Firewall/Modules/die_page_sfw.html - lib/Cleantalk/Common/Firewall/Modules/SFW.php - lib/Cleantalk/Common/error.html - lib/Cleantalk/ApbctJoomla/Cron.php - lib/Cleantalk/ApbctJoomla/DB.php - lib/Cleantalk/ApbctJoomla/Helper.php - lib/Cleantalk/ApbctJoomla/RemoteCalls.php js/ct-settings.js js/ct-functions.js js/ct-external.js css/ct-settings.css img/preloader.gif sql/mysql + lib/ en-GB/en-GB.plg_system_cleantalkantispam.ini diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..aac75d8 --- /dev/null +++ b/composer.json @@ -0,0 +1,16 @@ +{ + "require": { + "cleantalk/antispam": "^1.0", + "cleantalk/firewall": "^1.0", + "cleantalk/dns": "^1.0", + "cleantalk/api": "^1.0", + "cleantalk/cron": "^1.0", + "cleantalk/remote-calls": "^1.0", + "cleantalk/storage-handler": "^1.0" + }, + "config": { + "allow-plugins": { + "cleantalk/apbct-installer": true + } + } +} diff --git a/lib/Cleantalk/Antispam/Cleantalk.php b/lib/Cleantalk/Antispam/Cleantalk.php deleted file mode 100644 index d8e9a67..0000000 --- a/lib/Cleantalk/Antispam/Cleantalk.php +++ /dev/null @@ -1,791 +0,0 @@ -filterRequest($request); - $msg = $this->createMsg('check_message', $request); - return $this->httpRequest($msg); - } - - /** - * Function checks whether it is possible to publish the message - * @param CleantalkRequest $request - * @return type - */ - public function isAllowUser(CleantalkRequest $request) { - $request = $this->filterRequest($request); - $msg = $this->createMsg('check_newuser', $request); - return $this->httpRequest($msg); - } - - /** - * Function sends the results of manual moderation - * - * @param CleantalkRequest $request - * @return type - */ - public function sendFeedback(CleantalkRequest $request) { - $request = $this->filterRequest($request); - $msg = $this->createMsg('send_feedback', $request); - return $this->httpRequest($msg); - } - - /** - * Filter request params - * @param CleantalkRequest $request - * @return type - */ - private function filterRequest(CleantalkRequest $request) { - // general and optional - foreach ($request as $param => $value) { - if (in_array($param, array('message', 'example', 'agent', - 'sender_info', 'sender_nickname', 'post_info', 'phone')) && !empty($value)) { - if (!is_string($value) && !is_integer($value)) { - $request->$param = NULL; - } - } - - if (in_array($param, array('stoplist_check', 'allow_links')) && !empty($value)) { - if (!in_array($value, array(1, 2))) { - $request->$param = NULL; - } - } - - if (in_array($param, array('js_on')) && !empty($value)) { - if (!is_integer($value)) { - $request->$param = NULL; - } - } - - if ($param == 'sender_ip' && !empty($value)) { - if (!is_string($value)) { - $request->$param = NULL; - } - } - - if ($param == 'sender_email' && !empty($value)) { - if (!is_string($value)) { - $request->$param = NULL; - } - } - - if ($param == 'submit_time' && !empty($value)) { - if (!is_int($value)) { - $request->$param = NULL; - } - } - } - return $request; - } - - /** - * Compress data and encode to base64 - * @param type string - * @return string - */ - private function compressData($data = null){ - - if (strlen($data) > $this->dataMaxSise && function_exists('gzencode') && function_exists('base64_encode')){ - - $localData = gzencode($data, $this->compressRate, FORCE_GZIP); - - if ($localData === false) - return $data; - - $localData = base64_encode($localData); - - if ($localData === false) - return $data; - - return $localData; - } - - return $data; - } - - /** - * Create msg for cleantalk server - * @param type $method - * @param CleantalkRequest $request - * @return \xmlrpcmsg - */ - private function createMsg($method, CleantalkRequest $request) { - switch ($method) { - case 'check_message': - // Convert strings to UTF8 - $request->message = $this->stringToUTF8($request->message, $this->data_codepage); - $request->example = $this->stringToUTF8($request->example, $this->data_codepage); - $request->sender_email = $this->stringToUTF8($request->sender_email, $this->data_codepage); - $request->sender_nickname = $this->stringToUTF8($request->sender_nickname, $this->data_codepage); - - $request->message = $this->compressData($request->message); - $request->example = $this->compressData($request->example); - break; - - case 'check_newuser': - // Convert strings to UTF8 - $request->sender_email = $this->stringToUTF8($request->sender_email, $this->data_codepage); - $request->sender_nickname = $this->stringToUTF8($request->sender_nickname, $this->data_codepage); - break; - - case 'send_feedback': - if (is_array($request->feedback)) { - $request->feedback = implode(';', $request->feedback); - } - 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.'; - } - } - - return $request; - } - - /** - * Send JSON request to servers - * @param $msg - * @return boolean|\CleantalkResponse - */ - private function sendRequest($data, $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){ - if($value !== null){ - $tmp_data[$key] = $value; - } - } - $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')){ - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_TIMEOUT, $server_timeout); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - // receive server response ... - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - // resolve 'Expect: 100-continue' issue - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); - // see http://stackoverflow.com/a/23322368 - curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); - - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disabling CA cert verivication and - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // Disabling common name verification - - if ($this->ssl_on && $this->ssl_path != '') { - curl_setopt($ch, CURLOPT_CAINFO, $this->ssl_path); - } - - $result = curl_exec($ch); - if (!$result) { - $curl_error = curl_error($ch); - // Use SSL next time, if error occurs. - if(!$this->ssl_on){ - $this->ssl_on = true; - return $this->sendRequest($original_data, $original_url, $server_timeout); - } - } - - curl_close($ch); - } - - if (!$result) { - $allow_url_fopen = ini_get('allow_url_fopen'); - if (function_exists('file_get_contents') && isset($allow_url_fopen) && $allow_url_fopen == '1') { - $opts = array('http' => - array( - 'method' => 'POST', - 'header' => "Content-Type: text/html\r\n", - 'content' => $data, - 'timeout' => $server_timeout - ) - ); - - $context = stream_context_create($opts); - $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)) { - $response->errno = 0; - $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 - * @param $msg - * @return boolean|\CleantalkResponse - */ - private function httpRequest($msg) { - $result = false; - - // Wiping session cookies from request - $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($ct_tmp[$cookie_name])) { - unset($ct_tmp[$cookie_name]); - } - - $msg->all_headers = !empty($ct_tmp) ? 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); - - $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); - } - - if (($result === false || $result->errno != 0) && $this->stay_on_server == false) { - // Split server url to parts - preg_match("@^(https?://)([^/:]+)(.*)@i", $this->server_url, $matches); - $url_prefix = ''; - if (isset($matches[1])) - $url_prefix = $matches[1]; - - $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://'; - - if (empty($pool)) { - return false; - } else { - // Loop until find work server - foreach ($this->get_servers_ip($pool) as $server) { - if ($server['host'] === 'localhost' || $server['ip'] === null) { - $work_url = $server['host']; - } else { - $server_host = $server['ip']; - $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 = $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) { - $this->server_change = true; - break; - } - } - } - } - - $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); - if (!empty($response->errstr)) - $response->errstr = $this->stringFromUTF8($response->errstr, $this->data_codepage); - 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 - * @return array - */ - public function get_servers_ip($host) { - $response = null; - if (!isset($host)) - return $response; - - if (function_exists('dns_get_record')) { - $records = @dns_get_record($host, DNS_A); - - if ($records !== FALSE) { - foreach ($records as $server) { - $response[] = $server; - } - } - } - if (count($response) == 0 && function_exists('gethostbynamel')) { - $records = gethostbynamel($host); - - if ($records !== FALSE) { - foreach ($records as $server) { - $response[] = array("ip" => $server, - "host" => $host, - "ttl" => $this->server_ttl - ); - } - } - } - - if (count($response) == 0) { - $response[] = array("ip" => null, - "host" => $host, - "ttl" => $this->server_ttl - ); - } else { - // $i - to resolve collisions with localhost - $i = 0; - $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; - } 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; - } - } - if (count($r_temp)){ - ksort($r_temp); - $response = $r_temp; - } - } - - return $response; - } - - /** - * Function to get the message hash from Cleantalk.org comment - * @param $message - * @return null - */ - public function getCleantalkCommentHash($message) { - $matches = array(); - if (preg_match('/\n\n\*\*\*.+([a-z0-9]{32}).+\*\*\*$/', $message, $matches)) - return $matches[1]; - else if (preg_match('/\[\n]{0,1}\[\n]{0,1}\*\*\*.+([a-z0-9]{32}).+\*\*\*$/', $message, $matches)) - return $matches[1]; - - return NULL; - } - - /** - * Function adds to the post comment Cleantalk.org - * @param $message - * @param $comment - * @return string - */ - public function addCleantalkComment($message, $comment) { - $comment = preg_match('/\*\*\*(.+)\*\*\*/', $comment, $matches) ? $comment : '*** ' . $comment . ' ***'; - return $message . "\n\n" . $comment; - } - - /** - * Function deletes the comment Cleantalk.org - * @param $message - * @return mixed - */ - public function delCleantalkComment($message) { - $message = preg_replace('/\n\n\*\*\*.+\*\*\*$/', '', $message); - - // DLE sign cut - $message = preg_replace('/\*\*\*.+\*\*\*$/', '', $message); - - $message = preg_replace('/\[\n]{0,1}\[\n]{0,1}\*\*\*.+\*\*\*$/', '', $message); - - return $message; - } - - /** - * Get user IP behind proxy server - */ - public function ct_session_ip( $data_ip ) { - if (!$data_ip || !preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/", $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 - if (!preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/", $forwarded_ip[0])) { - return $data_ip; - } - - $private_src_ip = false; - $private_nets = array( - '10.0.0.0/8', - '127.0.0.0/8', - '176.16.0.0/12', - '192.168.0.0/16', - ); - - foreach ($private_nets as $v) { - - // Private IP found - 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]; - } - } - - return $data_ip;*/ - return cleantalk_get_real_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); - } - - /** - * Function to check response time - * param string - * @return int - */ - function httpPing($host){ - - // Skip localhost ping cause it raise error at fsockopen. - // And return minimun value - if ($host == 'localhost') - return 0.001; - - $starttime = microtime(true); - $file = @fsockopen ($host, 80, $errno, $errstr, $this->server_timeout); - $stoptime = microtime(true); - $status = 0; - if (!$file) { - $status = -1; // Site is down - } else { - fclose($file); - $status = ($stoptime - $starttime); - $status = round($status, 4); - } - - return $status; - } - - /** - * 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); - - $encoding = mb_detect_encoding($str); - if ($encoding) - return mb_convert_encoding($str, 'UTF-8', $encoding); - } - - return $str; - } - - /** - * Function convert string from UTF8 - * param string - * param string - * @return string - */ - 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 - * - * @param string api_key - * @return JSON/array - */ - public function get_2s_blacklists_db ($api_key) { - $request=array(); - $request['method_name'] = '2s_blacklists_db'; - $request['auth_key'] = $api_key; - $url='https://api.cleantalk.org'; - $result=CleantalkHelper::sendRawRequest($url,$request); - return $result; - } -} -if( !function_exists('apache_request_headers') ) -{ - function apache_request_headers() - { - $arh = array(); - $rx_http = '/\AHTTP_/'; - foreach($_SERVER as $key => $val) - { - if( preg_match($rx_http, $key) ) - { - $arh_key = preg_replace($rx_http, '', $key); - $rx_matches = array(); - $rx_matches = explode('_', $arh_key); - if( count($rx_matches) > 0 and strlen($arh_key) > 2 ) - { - foreach($rx_matches as $ak_key => $ak_val) $rx_matches[$ak_key] = ucfirst($ak_val); - $arh_key = implode('-', $rx_matches); - } - $arh[$arh_key] = $val; - } - } - return( $arh ); - } -} - -function cleantalk_get_real_ip() -{ - // Getting headers - $headers = function_exists('apache_request_headers') ? apache_request_headers() : $_SERVER; - - // Getting IP for validating - if (array_key_exists( 'X-Forwarded-For', $headers )){ - $ip = explode(",", trim($headers['X-Forwarded-For'])); - $ip = trim($ip[0]); - }elseif(array_key_exists( 'HTTP_X_FORWARDED_FOR', $headers)){ - $ip = explode(",", trim($headers['HTTP_X_FORWARDED_FOR'])); - $ip = trim($ip[0]); - }else{ - $ip = $_SERVER['REMOTE_ADDR']; - } - - // Validating IP - // IPv4 - if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){ - $the_ip = $ip; - // IPv6 - }elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){ - $the_ip = $ip; - // Unknown - }else{ - $the_ip = null; - } - return $the_ip; -} - -function cleantalk_is_JSON($string) -{ - return ((is_string($string) && (is_object(json_decode($string)) || is_array(json_decode($string))))) ? true : false; -} - -// Patch for locale_get_display_region() for old PHP versions -if( !function_exists('locale_get_display_region') ){ - function locale_get_display_region($locale, $curr_lcoale='en'){ - return $locale; - } -} diff --git a/lib/Cleantalk/Antispam/CleantalkRequest.php b/lib/Cleantalk/Antispam/CleantalkRequest.php deleted file mode 100644 index b480dfc..0000000 --- a/lib/Cleantalk/Antispam/CleantalkRequest.php +++ /dev/null @@ -1,172 +0,0 @@ - 0) { - foreach ($params as $param => $value) { - $this->{$param} = $value; - } - } - } - -} diff --git a/lib/Cleantalk/Antispam/CleantalkResponse.php b/lib/Cleantalk/Antispam/CleantalkResponse.php deleted file mode 100644 index c56ed12..0000000 --- a/lib/Cleantalk/Antispam/CleantalkResponse.php +++ /dev/null @@ -1,158 +0,0 @@ - 0) { - foreach ($response as $param => $value) { - $this->{$param} = $value; - } - } else { - $this->errno = $obj->errno; - $this->errstr = $obj->errstr; - - $this->errstr = preg_replace("/.+(\*\*\*.+\*\*\*).+/", "$1", $this->errstr); - - $this->stop_words = isset($obj->stop_words) ? utf8_decode($obj->stop_words) : null; - $this->comment = isset($obj->comment) ? utf8_decode($obj->comment) : null; - $this->blacklisted = (isset($obj->blacklisted)) ? $obj->blacklisted : null; - $this->allow = (isset($obj->allow)) ? $obj->allow : 0; - $this->id = (isset($obj->id)) ? $obj->id : null; - $this->fast_submit = (isset($obj->fast_submit)) ? $obj->fast_submit : 0; - $this->spam = (isset($obj->spam)) ? $obj->spam : 0; - $this->js_disabled = (isset($obj->js_disabled)) ? $obj->js_disabled : 0; - $this->sms_allow = (isset($obj->sms_allow)) ? $obj->sms_allow : null; - $this->sms = (isset($obj->sms)) ? $obj->sms : null; - $this->sms_error_code = (isset($obj->sms_error_code)) ? $obj->sms_error_code : null; - $this->sms_error_text = (isset($obj->sms_error_text)) ? $obj->sms_error_text : null; - $this->stop_queue = (isset($obj->stop_queue)) ? $obj->stop_queue : 0; - $this->inactive = (isset($obj->inactive)) ? $obj->inactive : 0; - $this->account_status = (isset($obj->account_status)) ? $obj->account_status : -1; - $this->received = (isset($obj->received)) ? $obj->received : -1; - - if ($this->errno !== 0 && $this->errstr !== null && $this->comment === null) - $this->comment = '*** ' . $this->errstr . ' Antispam service cleantalk.org ***'; - } - } - -} -?> \ No newline at end of file diff --git a/lib/Cleantalk/Antispam/SFW.php b/lib/Cleantalk/Antispam/SFW.php deleted file mode 100644 index 3f3d564..0000000 --- a/lib/Cleantalk/Antispam/SFW.php +++ /dev/null @@ -1,326 +0,0 @@ -table_prefix = $table_prefix; - $this->api_key = $api_key; - $this->db = $db; - } - - abstract protected function universal_query($query); - - abstract protected function universal_fetch(); - - abstract protected function universal_fetch_all(); - - /* - * Getting arrays of IP (REMOTE_ADDR, X-Forwarded-For, X-Real-Ip, Cf_Connecting_Ip) - * reutrns array('remote_addr' => 'val', ['x_forwarded_for' => 'val', ['x_real_ip' => 'val', ['cloud_flare' => 'val']]]) - */ - private function ip_get($v4_only = true) { - - $real_ip = (array)CleantalkHelper::ip_get(array('real'), $v4_only); - $real_ip = !empty($real_ip) ? $real_ip[0] : ''; - - $this->ips_array['real'] = array('ip' => $real_ip, 'in_list' => false); - - if(isset($_GET['sfw_test_ip'])) { - if(CleantalkHelper::ip_validate($_GET['sfw_test_ip']) !== false) { - $this->ips_array['test'] = array('ip' => $_GET['sfw_test_ip'], 'in_list' => false); - } - } - } - - /* - * Checks IP via Database - */ - public function check_ip() { - - $this->ip_get(); - - if (isset($_COOKIE['ct_sfw_pass_key']) && $_COOKIE['ct_sfw_pass_key'] == md5($this->ips_array['real']['ip'] . $this->api_key)) { - if (isset($_COOKIE['ct_sfw_passed'])) { - @setcookie('ct_sfw_passed'); //Deleting cookie - $this->sfw_update_logs($this->ips_array['real']['ip'], false); - } - return; - } - - foreach ($this->ips_array as $type => $ip) { - - $current_ip_v4 = sprintf("%u", ip2long($ip['ip'])); - for ( $needles = array(), $m = 6; $m <= 32; $m ++ ) { - $mask = sprintf( "%u", ip2long( long2ip( - 1 << ( 32 - (int) $m ) ) ) ); - $needles[] = bindec( decbin( $mask ) & decbin( $current_ip_v4 ) ); - } - $needles = array_unique( $needles ); - - $query = "SELECT - network, mask, status - FROM `".$this->table_prefix."cleantalk_sfw` - WHERE network IN (". implode( ',', $needles ) .") - AND network = " . $current_ip_v4 . " & mask - ORDER BY status DESC LIMIT 1;"; - - $this->universal_query($query); - $result = $this->universal_fetch(); - - if ($result) { - if ($result['status'] == 0) { - $this->ips_array[$type]['in_list'] = true; - $this->sfw_update_logs($this->ips_array[$type]['ip'], true); - } - } - } - - if (isset($this->ips_array['test']) || $this->ips_array['real']['in_list']) { - $this->sfw_die(); - } - - } - - /* - * Add entry to SFW log - */ - protected function sfw_update_logs($ip, $desicion) { - - if($ip === NULL || $desicion === NULL) { - return; - } - - $blocked = $desicion ? ' + 1' : ''; - $time = time(); - - $query = "INSERT INTO `".$this->table_prefix."cleantalk_sfw_logs` - SET - ip = '$ip', - all_entries = 1, - blocked_entries = 1, - entries_timestamp = '".intval($time)."' - ON DUPLICATE KEY - UPDATE - all_entries = all_entries + 1, - blocked_entries = blocked_entries".strval($blocked).", - entries_timestamp = '".intval($time)."'"; - - $this->universal_query($query); - } - - /* - * Updates SFW local base - * - * return mixed true || array('error' => true, 'error_string' => STRING) - */ - public function sfw_update($file_url = null) { - - if(!$file_url) { - - $result = CleantalkAPI::method__get_2s_blacklists_db($this->api_key, 'multifiles'); - - if(empty($result['error'])) { - - if( !empty($result['file_url']) ){ - - if(CleantalkHelper::http__request($result['file_url'], array(), 'get_code') === 200) { - - if(ini_get('allow_url_fopen')) { - - $pattenrs = array(); - $pattenrs = array('get', 'async'); - $base_host_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://".$_SERVER['HTTP_HOST']; - - $this->universal_query("TRUNCATE TABLE `".$this->table_prefix."cleantalk_sfw`"); - - if (preg_match('/multifiles/', $result['file_url'])) { - - $gf = gzopen($result['file_url'], 'rb'); - - if ($gf) { - - $file_urls = array(); - - while(!gzeof($gf)) - $file_urls[] = trim(gzgets($gf, 1024)); - - gzclose($gf); - - return CleantalkHelper::http__request( - $base_host_url, - array( - 'spbc_remote_call_token' => md5($this->api_key), - 'spbc_remote_call_action' => 'sfw_update', - 'plugin_name' => 'apbct', - 'file_urls' => implode(',', $file_urls), - ), - $pattenrs - ); - } - }else { - return CleantalkHelper::http__request( - $base_host_url, - array( - 'spbc_remote_call_token' => md5($this->api_key), - 'spbc_remote_call_action' => 'sfw_update', - 'plugin_name' => 'apbct', - 'file_urls' => $result['file_url'], - ), - $pattenrs - ); - } - } else - return array('error' => 'ERROR_ALLOW_URL_FOPEN_DISABLED'); - } - } else - return array('error' => 'BAD_RESPONSE'); - } else - return $result; - } else { - - if(CleantalkHelper::http__request($file_url, array(), 'get_code') === 200) { // Check if it's there - - $gf = gzopen($file_url, 'rb'); - - if($gf){ - - if(!gzeof($gf)) { - - for($count_result = 0; !gzeof($gf); ) { - - $query = "INSERT INTO `".$this->table_prefix."cleantalk_sfw` VALUES %s"; - - for($i=0, $values = array(); 5000 !== $i && !gzeof($gf); $i++, $count_result++) { - - $entry = trim(gzgets($gf, 1024)); - - if(empty($entry)) continue; - - $entry = explode(',', $entry); - - // Cast result to int - $ip = preg_replace('/[^\d]*/', '', $entry[0]); - $mask = preg_replace('/[^\d]*/', '', $entry[1]); - $private = isset($entry[2]) ? $entry[2] : 0; - - if(!$ip || !$mask) continue; - - $values[] = '('. $ip .','. $mask .', '. $private .')'; - - } - - if(!empty($values)) { - $query = sprintf($query, implode(',', $values).';'); - $this->universal_query($query); - } - - } - - gzclose($gf); - return $count_result; - - } else - return array('error' => 'ERROR_GZ_EMPTY'); - } else - return array('error' => 'ERROR_OPEN_GZ_FILE'); - } else - return array('error' => 'NO_REMOTE_FILE_FOUND'); - } - } - - /* - * Sends and wipe SFW log - * - * returns mixed true || array('error' => true, 'error_string' => STRING) - */ - public function send_logs() { - - //Getting logs - $query = "SELECT * FROM `".$this->table_prefix."cleantalk_sfw_logs`"; - $this->universal_query($query); - $result = $this->universal_fetch_all(); - - if(count($result)) { - - //Compile logs - $data = array(); - foreach($result as $key => $value) { - $data[] = array(trim($value['ip']), $value['all_entries'], $value['all_entries']-$value['blocked_entries'], $value['entries_timestamp']); - } - unset($key, $value); - - //Sending the request - $result = CleantalkAPI::method__sfw_logs($this->api_key, $data); - - //Checking answer and deleting all lines from the table - if(empty($result['error'])) { - if($result['rows'] == count($data)){ - $this->universal_query("TRUNCATE TABLE `".$this->table_prefix."cleantalk_sfw_logs`"); - return true; - } - } else { - return $result; - } - - } else { - return array('error' => true, 'error_string' => 'NO_LOGS_TO_SEND'); - } - } - - /* - * Shows DIE page - * - * Stops script executing - */ - private function sfw_die($cookie_prefix = '', $cookie_domain = '') { - - // File exists? - if(file_exists(dirname(__FILE__).'/../../sfw_die_page.html')) { - $sfw_die_page = file_get_contents(dirname(__FILE__).'/../../sfw_die_page.html'); - } else { - die("IP BLACKLISTED"); - } - - // Service info - $sfw_die_page = str_replace('{REMOTE_ADDRESS}', $this->ips_array['real']['ip'], $sfw_die_page); - $sfw_die_page = str_replace('{REQUEST_URI}', $_SERVER['REQUEST_URI'], $sfw_die_page); - $sfw_die_page = str_replace('{SFW_COOKIE}', md5($this->ips_array['real']['ip'].$this->api_key), $sfw_die_page); - if (isset($this->ips_array['test'])) { - $sfw_die_page = str_replace('{TEST_IP}', 'Tested IP ' . $this->ips_array['test']['ip'] . ' - ' . ($this->ips_array['test']['in_list'] ? 'In list': 'Not in list'), $sfw_die_page); - } - - // Headers - if(headers_sent() === false) { - header('Expires: '.date(DATE_RFC822, mktime(0, 0, 0, 1, 1, 1971))); - header('Cache-Control: no-store, no-cache, must-revalidate'); - header('Cache-Control: post-check=0, pre-check=0', FALSE); - header('Pragma: no-cache'); - header("HTTP/1.0 403 Forbidden"); - $sfw_die_page = str_replace('{GENERATED}', "", $sfw_die_page); - } else { - $sfw_die_page = str_replace('{GENERATED}', "

The page was generated at ".date("D, d M Y H:i:s")."

",$sfw_die_page); - } - - die($sfw_die_page); - } -} diff --git a/lib/Cleantalk/ApbctJoomla/Cron.php b/lib/Cleantalk/ApbctJoomla/Cron.php deleted file mode 100644 index 49efc61..0000000 --- a/lib/Cleantalk/ApbctJoomla/Cron.php +++ /dev/null @@ -1,99 +0,0 @@ -getQuery(true); - $query - ->select($db->quoteName('extension_id')) - ->from($db->quoteName('#__extensions')) - ->where($db->quoteName('element') . ' = ' . $db->quote('cleantalkantispam')) - ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); - $db->setQuery($query); - $db->execute(); - - if ($plg = $db->loadObject()) { - $table = \JTable::getInstance('extension'); - $table->load((int) $plg->extension_id); - $jparams = new \JRegistry($table->params); - $jparams->set($this->cron_option_name, array('last_start' => time() , 'tasks' => $tasks)); - $table->params = $jparams->toString(); - $table->store(); - } - } - - /** - * Getting all tasks - * - * @return array - */ - public function getTasks() - { - // TODO: Implement getTasks() method. - $plugin = \JPluginHelper::getPlugin('system', 'cleantalkantispam'); - $params = new \JRegistry($plugin->params); - $cron_option_name = $params->get($this->cron_option_name); - - if(is_object($cron_option_name) && $cron_option_name->tasks) { - return json_decode(json_encode($cron_option_name->tasks),true); - } - - return null; - } - - /** - * Save option with tasks - * - * @return int timestamp - */ - public function getCronLastStart() - { - // TODO: Implement getCronLastStart() method. - $plugin = \JPluginHelper::getPlugin('system', 'cleantalkantispam'); - $params = new \JRegistry($plugin->params); - $cron_option_name = $params->get($this->cron_option_name); - - if(is_object($cron_option_name) && $cron_option_name->last_start) { - return $cron_option_name->last_start; - } - - return 0; - } - - /** - * Save timestamp of running Cron. - * - * @return bool - */ - public function setCronLastStart() - { - // TODO: Implement setCronLastStart() method. - $db = \JFactory::getDBO(); - - $query = $db->getQuery(true); - $query - ->select($db->quoteName('extension_id')) - ->from($db->quoteName('#__extensions')) - ->where($db->quoteName('element') . ' = ' . $db->quote('cleantalkantispam')) - ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); - $db->setQuery($query); - $db->execute(); - - if ($plg = $db->loadObject()) { - $table = \JTable::getInstance('extension'); - $table->load((int) $plg->extension_id); - $jparams = new \JRegistry($table->params); - $jparams->set($this->cron_option_name, array('last_start' => time() , 'tasks' => $this->getTasks())); - $table->params = $jparams->toString(); - $table->store(); - return true; - } - return false; - } -} \ No newline at end of file diff --git a/lib/Cleantalk/ApbctJoomla/Helper.php b/lib/Cleantalk/ApbctJoomla/Helper.php deleted file mode 100644 index 7626f30..0000000 --- a/lib/Cleantalk/ApbctJoomla/Helper.php +++ /dev/null @@ -1,89 +0,0 @@ - false, 'firewall_updating_id' => md5(), 'firewall_update_percent' => 0, 'firewall_updating_last_start' => 0 ) - * @important This method must be overloaded in the CMS-based Helper class. - */ - public static function getFwStats() - { - //die( __METHOD__ . ' method must be overloaded in the CMS-based Helper class' ); - $plugin = \JPluginHelper::getPlugin('system', 'cleantalkantispam'); - $params = new \JRegistry($plugin->params); - - return array( - 'firewall_updating_id' => $params->get('firewall_updating_id'), - 'firewall_updating_last_start' => $params->get('firewall_updating_last_start', 0), - 'firewall_update_percent' => $params->get('firewall_update_percent', 0) - ); - } - - /** - * Save fw stats on the storage. - * - * @param array $fw_stats - * @return bool - * @important This method must be overloaded in the CMS-based Helper class. - */ - public static function setFwStats( $fw_stats ) - { - $db = \JFactory::getDBO(); - - $query = $db->getQuery(true); - $query - ->select($db->quoteName('extension_id')) - ->from($db->quoteName('#__extensions')) - ->where($db->quoteName('element') . ' = ' . $db->quote('cleantalkantispam')) - ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); - $db->setQuery($query); - $db->execute(); - - if ($plg = $db->loadObject()) { - $table = \JTable::getInstance('extension'); - $table->load((int) $plg->extension_id); - $params = array(); - $params['firewall_updating_id'] = $fw_stats['firewall_updating_id']; - $params['firewall_updating_last_start'] = $fw_stats['firewall_updating_last_start']; - $params['firewall_update_percent'] = $fw_stats['firewall_update_percent']; - $jparams = new \JRegistry($table->params); - foreach ($params as $k => $v) - $jparams->set($k, $v); - $table->params = $jparams->toString(); - $table->store(); - } - } - - /** - * Implement here any actions after SFW updating finished. - * - * @return void - */ - public static function SfwUpdate_DoFinisnAction() - { - $db = \JFactory::getDBO(); - - $query = $db->getQuery(true); - $query - ->select($db->quoteName('extension_id')) - ->from($db->quoteName('#__extensions')) - ->where($db->quoteName('element') . ' = ' . $db->quote('cleantalkantispam')) - ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); - $db->setQuery($query); - $db->execute(); - - if ($plg = $db->loadObject()) { - $table = \JTable::getInstance('extension'); - $table->load((int) $plg->extension_id); - $jparams = new \JRegistry($table->params); - $jparams->set('sfw_last_check', time()); - $table->params = $jparams->toString(); - $table->store(); - } - } -} \ No newline at end of file diff --git a/lib/Cleantalk/Common/API.php b/lib/Cleantalk/Common/API.php deleted file mode 100644 index e2b29a2..0000000 --- a/lib/Cleantalk/Common/API.php +++ /dev/null @@ -1,793 +0,0 @@ - STRING) - */ - public static function method__get_2s_blacklists_db($api_key, $out = null, $version = '1_0', $do_check = true) - { - $request = array( - 'method_name' => '2s_blacklists_db', - 'auth_key' => $api_key, - 'out' => $out, - 'version' => $version, - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, '2s_blacklists_db') : $result; - - return $result; - } - - /** - * Wrapper for get_api_key API method. - * Gets access key automatically. - * - * @param string $product_name Type of product - * @param string $email Website admin email - * @param string $website Website host - * @param string $platform Website platform - * @param string|null $timezone - * @param string|null $language - * @param string|null $user_ip - * @param bool $wpms - * @param bool $white_label - * @param string $hoster_api_key - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__get_api_key($product_name, $email, $website, $platform, $timezone = null, $language = null, $user_ip = null, $wpms = false, $white_label = false, $hoster_api_key = '', $do_check = true) - { - $request = array( - 'method_name' => 'get_api_key', - 'product_name' => $product_name, - 'email' => $email, - 'website' => $website, - 'platform' => $platform, - 'timezone' => $timezone, - 'http_accept_language' => $language, - 'user_ip' => $user_ip, - 'wpms_setup' => $wpms, - 'hoster_whitelabel' => $white_label, - 'hoster_api_key' => $hoster_api_key, - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'get_api_key') : $result; - - return $result; - } - - /** - * Wrapper for get_antispam_report API method. - * Gets spam report. - * - * @param string $host website host - * @param integer $period report days - * @param boolean $do_check - * - * @return array|bool|mixed - */ - public static function method__get_antispam_report($host, $period = 1, $do_check = true) - { - $request = Array( - 'method_name' => 'get_antispam_report', - 'hostname' => $host, - 'period' => $period - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'get_antispam_report') : $result; - - return $result; - } - - /** - * Wrapper for get_antispam_report_breif API method. - * Ggets spam statistics. - * - * @param string $api_key - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__get_antispam_report_breif($api_key, $do_check = true) - { - $request = array( - 'method_name' => 'get_antispam_report_breif', - 'auth_key' => $api_key, - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'get_antispam_report_breif') : $result; - - return $result; - } - - /** - * Wrapper for notice_paid_till API method. - * Gets information about renew notice. - * - * @param string $api_key API key - * @param string $path_to_cms Website URL - * @param string $product_name - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__notice_paid_till($api_key, $path_to_cms, $product_name = 'antispam', $do_check = true) - { - $request = array( - 'method_name' => 'notice_paid_till', - 'path_to_cms' => $path_to_cms, - 'auth_key' => $api_key, - ); - - $product_id = null; - $product_id = $product_name === 'antispam' ? 1 : $product_id; - $product_id = $product_name === 'anti-spam-hosting' ? 3 : $product_id; - $product_id = $product_name === 'security' ? 4 : $product_id; - if($product_id) - $request['product_id'] = $product_id; - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'notice_paid_till') : $result; - - return $result; - } - - /** - * Wrapper for ip_info API method. - * Gets IP country. - * - * @param string $data - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__ip_info($data, $do_check = true) - { - $request = array( - 'method_name' => 'ip_info', - 'data' => $data - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'ip_info') : $result; - return $result; - } - - /** - * Wrapper for spam_check_cms API method. - * Checks IP|email via CleanTalk's database. - * - * @param string $api_key - * @param array $data - * @param null|string $date - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__spam_check_cms($api_key, $data, $date = null, $do_check = true) - { - $request = Array( - 'method_name' => 'spam_check_cms', - 'auth_key' => $api_key, - 'data' => is_array($data) ? implode(',', $data) : $data, - ); - - if($date) $request['date'] = $date; - - $result = static::send_request($request, self::URL, 20); - $result = $do_check ? static::check_response($result, 'spam_check_cms') : $result; - - return $result; - } - - /** - * Wrapper for spam_check API method. - * Checks IP|email via CleanTalk's database. - * - * @param string $api_key - * @param array $data - * @param null|string $date - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__spam_check($api_key, $data, $date = null, $do_check = true) - { - $request = Array( - 'method_name' => 'spam_check', - 'auth_key' => $api_key, - 'data' => is_array($data) ? implode(',', $data) : $data, - ); - - if( $date ) { - $request['date'] = $date; - } - - $result = static::send_request($request, self::URL, 10); - $result = $do_check ? static::check_response($result, 'spam_check') : $result; - - return $result; - } - - /** - * Wrapper for sfw_logs API method. - * Sends SpamFireWall logs to the cloud. - * - * @param string $api_key - * @param array $data - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__sfw_logs($api_key, $data, $do_check = true) - { - - $request = array( - 'auth_key' => $api_key, - 'method_name' => 'sfw_logs', - 'data' => json_encode($data), - 'rows' => count($data), - 'timestamp' => time() - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'sfw_logs') : $result; - - return $result; - } - - /** - * Wrapper for security_logs API method. - * Sends security logs to the cloud. - * - * @param string $api_key - * @param array $data - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_logs($api_key, $data, $do_check = true) - { - $request = array( - 'auth_key' => $api_key, - 'method_name' => 'security_logs', - 'timestamp' => static::getCurrentTimestamp(), - 'data' => json_encode($data), - 'rows' => count($data), - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_logs') : $result; - - return $result; - } - - /** - * Wrapper for security_logs API method. - * Sends Securitty Firewall logs to the cloud. - * - * @param string $api_key - * @param array $data - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_logs__sendFWData($api_key, $data, $do_check = true) - { - - $request = array( - 'auth_key' => $api_key, - 'method_name' => 'security_logs', - 'timestamp' => static::getCurrentTimestamp(), - 'data_fw' => json_encode($data), - 'rows_fw' => count($data), - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_logs') : $result; - - return $result; - } - - /** - * Wrapper for security_logs API method. - * Sends empty data to the cloud to syncronize version. - * - * @param string $api_key - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_logs__feedback($api_key, $do_check = true) - { - $request = array( - 'auth_key' => $api_key, - 'method_name' => 'security_logs', - 'data' => '0', - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_logs') : $result; - - return $result; - } - - /** - * Wrapper for security_firewall_data API method. - * Gets Securitty Firewall data to write to the local database. - * - * @param string $api_key - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_firewall_data($api_key, $do_check = true) - { - - $request = array( - 'auth_key' => $api_key, - 'method_name' => 'security_firewall_data', - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_firewall_data') : $result; - - return $result; - } - - /** - * Wrapper for security_firewall_data_file API method. - * Gets URI with security firewall data in .csv.gz file to write to the local database. - * - * @param string $api_key - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_firewall_data_file($api_key, $do_check = true) - { - - $request = array( - 'auth_key' => $api_key, - 'method_name' => 'security_firewall_data_file', - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_firewall_data_file') : $result; - - return $result; - } - - /** - * Wrapper for security_linksscan_logs API method. - * Send data to the cloud about scanned links. - * - * @param string $api_key - * @param string $scan_time Datetime of scan - * @param bool $scan_result - * @param int $links_total - * @param array $links_list - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_linksscan_logs($api_key, $scan_time, $scan_result, $links_total, $links_list, $do_check = true) - { - $request = array( - 'auth_key' => $api_key, - 'method_name' => 'security_linksscan_logs', - 'started' => $scan_time, - 'result' => $scan_result, - 'total_links_found' => $links_total, - 'links_list' => $links_list, - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_linksscan_logs') : $result; - - return $result; - } - - /** - * Wrapper for security_mscan_logs API method. - * Sends result of file scan to the cloud. - * - * @param string $api_key - * @param int $service_id - * @param string $scan_time Datetime of scan - * @param bool $scan_result - * @param int $scanned_total - * @param array $modified List of modified files with details - * @param array $unknown List of modified files with details - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_mscan_logs($api_key, $service_id, $scan_time, $scan_result, $scanned_total, $modified, $unknown, $do_check = true) - { - $request = array( - 'method_name' => 'security_mscan_logs', - 'auth_key' => $api_key, - 'service_id' => $service_id, - 'started' => $scan_time, - 'result' => $scan_result, - 'total_core_files' => $scanned_total, - ); - - if(!empty($modified)){ - $request['failed_files'] = json_encode($modified); - $request['failed_files_rows'] = count($modified); - } - if(!empty($unknown)){ - $request['unknown_files'] = json_encode($unknown); - $request['unknown_files_rows'] = count($unknown); - } - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_mscan_logs') : $result; - - return $result; - } - - /** - * Wrapper for security_mscan_files API method. - * Sends file to the cloud for analysis. - * - * @param string $api_key - * @param string $file_path Path to the file - * @param array $file File itself - * @param string $file_md5 MD5 hash of file - * @param array $weak_spots List of weak spots found in file - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_mscan_files($api_key, $file_path, $file, $file_md5, $weak_spots, $do_check = true) - { - $request = array( - 'method_name' => 'security_mscan_files', - 'auth_key' => $api_key, - 'path_to_sfile' => $file_path, - 'attached_sfile' => $file, - 'md5sum_sfile' => $file_md5, - 'dangerous_code' => $weak_spots, - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_mscan_files') : $result; - - return $result; - } - - /** - * Wrapper for get_antispam_report API method. - * Function gets spam domains report. - * - * @param string $api_key - * @param array|string|mixed $data - * @param string $date - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__backlinks_check_cms($api_key, $data, $date = null, $do_check = true) - { - $request = array( - 'method_name' => 'backlinks_check_cms', - 'auth_key' => $api_key, - 'data' => is_array($data) ? implode(',', $data) : $data, - ); - - if($date) $request['date'] = $date; - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'backlinks_check_cms') : $result; - - return $result; - } - - /** - * Wrapper for get_antispam_report API method. - * Function gets spam domains report - * - * @param string $api_key - * @param array $logs - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_backend_logs($api_key, $logs, $do_check = true) - { - $request = array( - 'method_name' => 'security_backend_logs', - 'auth_key' => $api_key, - 'logs' => json_encode($logs), - 'total_logs' => count($logs), - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_backend_logs') : $result; - - return $result; - } - - /** - * Wrapper for get_antispam_report API method. - * Sends data about auto repairs - * - * @param string $api_key - * @param bool $repair_result - * @param string $repair_comment - * @param $repaired_processed_files - * @param $repaired_total_files_proccessed - * @param $backup_id - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__security_mscan_repairs($api_key, $repair_result, $repair_comment, $repaired_processed_files, $repaired_total_files_proccessed, $backup_id, $do_check = true) - { - $request = array( - 'method_name' => 'security_mscan_repairs', - 'auth_key' => $api_key, - 'repair_result' => $repair_result, - 'repair_comment' => $repair_comment, - 'repair_processed_files' => json_encode($repaired_processed_files), - 'repair_total_files_processed' => $repaired_total_files_proccessed, - 'backup_id' => $backup_id, - 'mscan_log_id' => 1, - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'security_mscan_repairs') : $result; - - return $result; - } - - /** - * Wrapper for get_antispam_report API method. - * Force server to update checksums for specific plugin\theme - * - * @param string $api_key - * @param string $plugins_and_themes_to_refresh - * @param bool $do_check - * - * @return array|bool|mixed - */ - public static function method__request_checksums($api_key, $plugins_and_themes_to_refresh, $do_check = true) - { - $request = array( - 'method_name' => 'request_checksums', - 'auth_key' => $api_key, - 'data' => $plugins_and_themes_to_refresh - ); - - $result = static::send_request($request); - $result = $do_check ? static::check_response($result, 'request_checksums') : $result; - - return $result; - } - - /** - * Function sends raw request to API server - * - * @param array $data to send - * @param string $url of API server - * @param integer $timeout timeout in seconds - * @param boolean $ssl use ssl on not - * - * @return array|bool - */ - public static function send_request($data, $url = self::URL, $timeout = 10, $ssl = false, $ssl_path = '') - { - // Possibility to switch agent vaersion - $data['agent'] = !empty($data['agent']) - ? $data['agent'] - : (defined('CLEANTALK_AGENT') ? CLEANTALK_AGENT : self::AGENT); - - // Make URL string - $data_string = http_build_query($data); - $data_string = str_replace("&", "&", $data_string); - - // For debug purposes - if(defined('CLEANTALK_DEBUG') && CLEANTALK_DEBUG){ - global $apbct_debug; - $apbct_debug['sent_data'] = $data; - $apbct_debug['request_string'] = $data_string; - } - - // Possibility to switch API url - $url = defined('CLEANTALK_API_URL') ? CLEANTALK_API_URL : $url; - - if(function_exists('curl_init')){ - - $ch = curl_init(); - - // Set diff options - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); - - $ssl_path = $ssl_path - ? $ssl_path - : (defined('CLEANTALK_CASERT_PATH') ? CLEANTALK_CASERT_PATH : ''); - - // Switch on/off SSL - if($ssl && $ssl_path){ - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - curl_setopt($ch, CURLOPT_CAINFO, $ssl_path); - }else{ - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); - } - - // Make a request - $result = curl_exec($ch); - $errors = curl_error($ch); - curl_close($ch); - - }else{ - $errors = 'CURL_NOT_INSTALLED'; - } - - // Trying to use file_get_contents() to make a API call - if(!empty($errors)){ - if(ini_get('allow_url_fopen')){ - $opts = array( - 'http' => array( - 'method' => "POST", - 'timeout' => $timeout, - 'content' => $data_string, - ), - ); - $context = stream_context_create($opts); - $result = @file_get_contents($url, 0, $context); - - $errors = $result === false - ? $errors . '_FAILED_TO_USE_FILE_GET_CONTENTS' - : false; - - }else{ - $errors .= '_AND_ALLOW_URL_FOPEN_IS_DISABLED'; - } - } - - return empty($result) || !empty($errors) - ? array('error' => $errors) - : $result; - } - - /** - * Function checks server response - * - * @param array|string $result - * @param string $method_name - * - * @return mixed (array || array('error' => true)) - */ - public static function check_response($result, $method_name = null) - { - // Errors handling - // Bad connection - if(is_array($result) && isset($result['error'])){ - $last = error_get_last(); - $out = ! empty( $result['error'] ) - ? array( 'error' => 'CONNECTION_ERROR : "' . $result['error'] . '"' ) - : array( 'error' => 'CONNECTION_ERROR : "Unknown Error. Last error: ' . $last['message'] ); - return $out; - } - - // JSON decode errors - $result = json_decode($result, true); - if(empty($result)){ - return array( - 'error' => 'JSON_DECODE_ERROR', - ); - } - - // Server errors - if( $result && ( isset( $result['error_no'], $result['error_message'] ) ) ){ - - if( $result['error_no'] != 12 ){ - return array( - 'error' => "SERVER_ERROR NO: {$result['error_no']} MSG: {$result['error_message']}", - 'error_no' => $result['error_no'], - 'error_message' => $result['error_message'], - ); - } - } - - // Pathces for different methods - switch($method_name){ - - // notice_paid_till - case 'notice_paid_till': - - $result = isset($result['data']) ? $result['data'] : $result; - - if((isset($result['error_no']) && $result['error_no'] == 12) || - ( - !(isset($result['service_id']) && is_int($result['service_id'])) && - empty($result['moderate_ip']) - ) - ) - $result['valid'] = 0; - else - $result['valid'] = 1; - - return $result; - - break; - - // get_antispam_report_breif - case 'get_antispam_report_breif': - - $out = isset($result['data']) && is_array($result['data']) - ? $result['data'] - : array('error' => 'NO_DATA'); - - for($tmp = array(), $i = 0; $i < 7; $i++){ - $tmp[date('Y-m-d', time() - 86400 * 7 + 86400 * $i)] = 0; - } - $out['spam_stat'] = (array)array_merge($tmp, isset($out['spam_stat']) ? $out['spam_stat'] : array()); - $out['top5_spam_ip'] = isset($out['top5_spam_ip']) ? $out['top5_spam_ip'] : array(); - - return $out; - - break; - - default: - return isset($result['data']) && is_array($result['data']) - ? $result['data'] - : array('error' => 'NO_DATA'); - break; - } - } - - /** - * Get current timestamp for API calling. - * - * @return int - * @important This method can be overloaded in the CMS-based API class. - */ - private static function getCurrentTimestamp() - { - return time(); - } - -} \ No newline at end of file diff --git a/lib/Cleantalk/Common/Antispam/Cleantalk.php b/lib/Cleantalk/Common/Antispam/Cleantalk.php new file mode 100644 index 0000000..dda5a36 --- /dev/null +++ b/lib/Cleantalk/Common/Antispam/Cleantalk.php @@ -0,0 +1,513 @@ +createMsg('check_message', $request); + + return $this->httpRequest($msg); + } + + /** + * Function checks whether it is possible to publish the message + * + * @param CleantalkRequest $request + * + * @return bool|CleantalkResponse + */ + public function isAllowUser(CleantalkRequest $request) + { + $msg = $this->createMsg('check_newuser', $request); + + return $this->httpRequest($msg); + } + + /** + * Function sends the results of manual moderation + * + * @param CleantalkRequest $request + * + * @return bool|CleantalkResponse + */ + public function sendFeedback(CleantalkRequest $request) + { + $msg = $this->createMsg('send_feedback', $request); + + return $this->httpRequest($msg); + } + + /** + * Create msg for cleantalk server + * + * @param string $method + * @param CleantalkRequest $request + * + * @return CleantalkRequest + */ + private function createMsg($method, CleantalkRequest $request) + { + switch ( $method ) { + case 'check_message': + // Convert strings to UTF8 + $request->message = Helper::toUTF8($request->message, $this->data_codepage); + $request->example = Helper::toUTF8($request->example, $this->data_codepage); + $request->sender_email = Helper::toUTF8($request->sender_email, $this->data_codepage); + $request->sender_nickname = Helper::toUTF8($request->sender_nickname, $this->data_codepage); + $request->message = $this->compressData($request->message); + $request->example = $this->compressData($request->example); + break; + + case 'check_newuser': + // Convert strings to UTF8 + $request->sender_email = Helper::toUTF8($request->sender_email, $this->data_codepage); + $request->sender_nickname = Helper::toUTF8($request->sender_nickname, $this->data_codepage); + break; + + case 'send_feedback': + if ( is_array($request->feedback) ) { + $request->feedback = implode(';', $request->feedback); + } + break; + } + + // Removing non UTF8 characters from request, because non UTF8 or malformed characters break json_encode(). + foreach ( $request as $param => $value ) { + if ( is_array($request->$param) || is_string($request->$param) ) { + $request->$param = Helper::removeNonUTF8($value); + } + } + + $request->method_name = $method; + $request->message = is_array($request->message) ? json_encode($request->message) : $request->message; + + // Wiping cleantalk's headers but, not for send_feedback + if ( $request->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 ( $ct_tmp ) { + if ( isset($ct_tmp[$cookie_name]) ) { + $ct_tmp[$cookie_name] = preg_replace(array( + '/\s?ct_checkjs=[a-z0-9]*[^;]*;?/', + '/\s?ct_timezone=.{0,1}\d{1,2}[^;]*;?/', + '/\s?ct_pointer_data=.*5D[^;]*;?/', + '/\s?apbct_timestamp=\d*[^;]*;?/', + '/\s?apbct_site_landing_ts=\d*[^;]*;?/', + '/\s?apbct_cookies_test=%7B.*%7D[^;]*;?/', + '/\s?apbct_prev_referer=http.*?[^;]*;?/', + '/\s?ct_ps_timestamp=.*?[^;]*;?/', + '/\s?ct_fkp_timestamp=\d*?[^;]*;?/', + '/\s?wordpress_ct_sfw_pass_key=\d*?[^;]*;?/', + '/\s?apbct_page_hits=\d*?[^;]*;?/', + '/\s?apbct_visible_fields_count=\d*?[^;]*;?/', + '/\s?apbct_visible_fields=%7B.*%7D[^;]*;?/', + '/\s?apbct_visible_fields_\d=%7B.*%7D[^;]*;?/', + ), '', $ct_tmp[$cookie_name]); + } + $request->all_headers = json_encode($ct_tmp); + } + } + + return $request; + } + + /** + * Compress data and encode to base64 + * + * @param string $data + * + * @return null|string + */ + private function compressData($data = null) + { + if ( strlen($data) > $this->dataMaxSise && function_exists('\gzencode') && function_exists('base64_encode') ) { + $localData = \gzencode($data, $this->compressRate, FORCE_GZIP); + + if ( $localData === false ) { + return $data; + } + + return base64_encode($localData); + } + + return $data; + } + + /** + * httpRequest + * + * @param $msg + * + * @return CleantalkResponse + */ + private function httpRequest($msg) + { + // Using current server without changing it + $result = ! empty($this->work_url) && $this->server_changed + 86400 > time() + ? $this->sendRequest($msg, $this->work_url, $this->server_timeout) + : false; + + // Changing server if no work_url or request has an error + if ( $result === false || (is_object($result) && $result->errno != 0) ) { + if ( ! empty($this->work_url) ) { + $this->downServers[] = $this->work_url; + } + $this->rotateModerate(); + $result = $this->sendRequest($msg, $this->work_url, $this->server_timeout); + if ( $result !== false && $result->errno === 0 ) { + $this->server_change = true; + } + } + $response = new CleantalkResponse($result); + + if ( ! empty($this->data_codepage) && $this->data_codepage !== 'UTF-8' ) { + if ( ! empty($response->comment) ) { + $response->comment = Helper::fromUTF8($response->comment, $this->data_codepage); + } + if ( ! empty($response->errstr) ) { + $response->errstr = Helper::fromUTF8($response->errstr, $this->data_codepage); + } + if ( ! empty($response->sms_error_text) ) { + $response->sms_error_text = Helper::fromUTF8($response->sms_error_text, $this->data_codepage); + } + } + + return $response; + } + + /** + * * @todo Refactor / fix logic errors + */ + public function rotateModerate() + { + // Split server url to parts + preg_match("/^(https?:\/\/)([^\/:]+)(.*)/i", $this->server_url, $matches); + + $url_protocol = isset($matches[1]) ? $matches[1] : ''; + $url_host = isset($matches[2]) ? $matches[2] : ''; + $url_suffix = isset($matches[3]) ? $matches[3] : ''; + + $servers = $this->getServersIp($url_host); + + if ( ! $servers ) { + return; + } + + // Loop until find work server + foreach ( $servers as $server ) { + $dns = Helper::ipResolveCleantalks($server['ip']); + if ( ! $dns ) { + continue; + } + + $this->work_url = $url_protocol . $dns . $url_suffix; + + // Do not checking previous down server + if ( ! empty($this->downServers) && in_array($this->work_url, $this->downServers) ) { + continue; + } + + $this->server_ttl = $server['ttl']; + $this->server_change = true; + break; + } + } + + /** + * @param $host + * @return array|null + * @todo Refactor / fix logic errors + * + * Function DNS request + * + * @psalm-suppress RedundantCondition + */ + public function getServersIp($host) + { + if ( ! isset($host) ) { + return null; + } + + $servers = array(); + + // Get DNS records about URL + if ( function_exists('dns_get_record') ) { + $records = @dns_get_record($host, DNS_A); + if ( $records !== false ) { + foreach ( $records as $server ) { + $servers[] = $server; + } + } + } + + // Another try if first failed + if ( count($servers) === 0 && function_exists('gethostbynamel') ) { + $records = gethostbynamel($host); + if ( $records !== false ) { + foreach ( $records as $server ) { + $servers[] = array( + "ip" => $server, + "host" => $host, + "ttl" => $this->server_ttl + ); + } + } + } + + // If couldn't get records + if ( count($servers) === 0 ) { + $servers[] = array( + "ip" => null, + "host" => $host, + "ttl" => $this->server_ttl + ); + // If records received + } else { + $tmp = array(); + $fast_server_found = false; + + foreach ( $servers as $server ) { + if ( $fast_server_found ) { + $ping = $this->max_server_timeout; + } else { + $ping = $this->httpPing($server['ip']); + $ping *= 1000; + } + + $tmp[(int)$ping] = $server; + + $fast_server_found = $ping < $this->min_server_timeout; + } + + if ( count($tmp) ) { + ksort($tmp); + $response = $tmp; + } + } + + return empty($response) ? null : $response; + } + + /** + * Function to check response time + * + * @param string $host + * + * @return float|int + */ + public function httpPing($host) + { + // Skip localhost ping cause it raise error at fsockopen. + // And return minimum value + if ( $host === 'localhost' ) { + return 0.001; + } + + $starttime = microtime(true); + $file = @fsockopen($host, 443, $errno, $errstr, $this->max_server_timeout / 1000); + $stoptime = microtime(true); + + if ( ! $file ) { + $status = $this->max_server_timeout / 1000; // Site is down + } else { + fclose($file); + $status = ($stoptime - $starttime); + $status = round($status, 4); + } + + return $status; + } + + /** + * Send JSON request to servers + * + * @param string|array $data + * @param string $url + * @param int $server_timeout + * + * @return boolean|CleantalkResponse + */ + private function sendRequest($data, $url, $server_timeout = 3) + { + //Cleaning from 'null' values + $tmp_data = array(); + foreach ( $data as $key => $value ) { + if ( $value !== null ) { + $tmp_data[$key] = $value; + } + } + $data = $tmp_data; + unset($key, $value, $tmp_data); + + // Convert to JSON + $data = json_encode($data); + + if ( isset($this->api_version) ) { + $url .= $this->api_version; + } + + $http = new Request(); + + $result = $http->setUrl($url) + ->setData($data) + ->setOptions(['timeout' => $server_timeout]) + ->request(); + + $errstr = null; + $response = is_string($result) ? json_decode($result) : false; + if ( $result !== false && is_object($response) ) { + $response->errno = 0; + $response->errstr = $errstr; + } else { + if ( isset($result['error']) ) { + $error = $result['error']; + } else if ( is_string($result) ) { + $error = $result; + } else { + $error = ''; + } + + $errstr = 'Unknown response from ' . $url . ': ' . $error; + + $response = null; + $response['errno'] = 1; + $response['errstr'] = $errstr; + $response = json_decode(json_encode($response)); + } + + return $response; + } + + /** + * Call check_bot API method + * + * Make a decision if it's bot or not based on limited input JavaScript data + * + * @param CleantalkRequest $request + * + * @return CleantalkResponse + */ + public function checkBot(CleantalkRequest $request) + { + $msg = $this->createMsg('check_bot', $request); + + return $this->httpRequest($msg); + } + + public static function getLockPageFile() + { + return __DIR__ . '/lock-page-ct-die.html'; + } +} diff --git a/lib/Cleantalk/Common/Antispam/CleantalkRequest.php b/lib/Cleantalk/Common/Antispam/CleantalkRequest.php new file mode 100644 index 0000000..5301483 --- /dev/null +++ b/lib/Cleantalk/Common/Antispam/CleantalkRequest.php @@ -0,0 +1,269 @@ +sender_ip = isset($params['sender_ip']) ? (string)$params['sender_ip'] : null; + $this->x_forwarded_for = isset($params['x_forwarded_for']) ? (string)$params['x_forwarded_for'] : null; + $this->x_real_ip = isset($params['x_real_ip']) ? (string)$params['x_real_ip'] : null; + + // Misc + $this->agent = isset($params['agent']) ? (string)$params['agent'] : null; + $this->auth_key = isset($params['auth_key']) ? (string)$params['auth_key'] : null; + $this->sender_email = isset($params['sender_email']) ? (string)$params['sender_email'] : null; + + // crunch for "PHP Notice: Array to string conversion". Error appears only on Gravity forms + // @todo fix gat_fields_any + if ( isset($params['sender_nickname']) && is_array($params['sender_nickname']) ) { + $params['sender_nickname'] = current($params['sender_nickname']); + } + + $this->page_url = !empty($params['page_url']) ? (string)$params['page_url'] : null; + $this->referrer = !empty($params['referrer']) ? (string)$params['referrer'] : null; + $this->sender_nickname = !empty($params['sender_nickname']) ? (string)$params['sender_nickname'] : null; + $this->phone = !empty($params['phone']) ? (string)$params['phone'] : null; + $this->js_on = isset($params['js_on']) ? (int)$params['js_on'] : null; + $this->submit_time = isset($params['submit_time']) ? (int)$params['submit_time'] : null; + $this->post_info = isset($params['post_info']) ? (string)json_encode($params['post_info']) : null; + $this->sender_info = isset($params['sender_info']) ? (string)json_encode($params['sender_info']) : null; + $this->honeypot_field = isset($params['honeypot_field']) ? (int)$params['honeypot_field'] : null; + $this->exception_action = isset($params['exception_action']) ? (int)$params['exception_action'] : null; + + $this->event_token = isset($params['event_token']) ? (string)$params['event_token'] : null; + $this->event_javascript_data = isset($params['event_javascript_data']) ? (string)$params['event_javascript_data'] : null; + $this->browser_sign = isset($params['browser_sign']) ? (string)$params['browser_sign'] : null; + $this->event_type = isset($params['event_type']) ? (string)$params['event_type'] : null; + $this->message_to_log = isset($params['message_to_log']) ? (string)$params['message_to_log'] : null; + + $this->message = !empty($params['message']) + ? (!is_scalar($params['message']) + ? serialize($params['message']) + : $params['message']) + : null; + $this->example = !empty($params['example']) + ? (!is_scalar($params['example']) + ? serialize($params['example']) + : $params['example']) + : null; + + // Feedback + $this->feedback = !empty($params['feedback']) ? $params['feedback'] : null; + } +} diff --git a/lib/Cleantalk/Common/Antispam/CleantalkResponse.php b/lib/Cleantalk/Common/Antispam/CleantalkResponse.php new file mode 100644 index 0000000..8a7dce8 --- /dev/null +++ b/lib/Cleantalk/Common/Antispam/CleantalkResponse.php @@ -0,0 +1,161 @@ +errno = isset($obj->errno) ? $obj->errno : 0; + $this->errstr = isset($obj->errstr) ? preg_replace("/.+(\*\*\*.+\*\*\*).+/", "$1", htmlspecialchars($obj->errstr)) : null; + + $this->stop_words = isset($obj->stop_words) ? utf8_decode($obj->stop_words) : null; + $this->comment = isset($obj->comment) ? strip_tags(utf8_decode($obj->comment), '


') : null; + $this->blacklisted = isset($obj->blacklisted) ? $obj->blacklisted : null; + $this->allow = isset($obj->allow) ? $obj->allow : 1; + $this->id = isset($obj->id) ? $obj->id : null; + $this->fast_submit = isset($obj->fast_submit) ? $obj->fast_submit : 0; + $this->spam = isset($obj->spam) ? $obj->spam : 0; + $this->js_disabled = isset($obj->js_disabled) ? $obj->js_disabled : 0; + $this->sms_allow = isset($obj->sms_allow) ? $obj->sms_allow : null; + $this->sms = isset($obj->sms) ? $obj->sms : null; + $this->sms_error_code = isset($obj->sms_error_code) ? $obj->sms_error_code : null; + $this->sms_error_text = isset($obj->sms_error_text) ? htmlspecialchars($obj->sms_error_text) : null; + $this->stop_queue = isset($obj->stop_queue) ? $obj->stop_queue : 0; + $this->inactive = isset($obj->inactive) ? $obj->inactive : 0; + $this->account_status = isset($obj->account_status) ? $obj->account_status : -1; + $this->received = isset($obj->received) ? $obj->received : -1; + $this->codes = isset($obj->codes) ? explode(' ', $obj->codes) : array(); + + if ( $this->errno !== 0 && $this->errstr !== null && $this->comment === null ) { + $this->comment = '*** ' . $this->errstr . ' Anti-Spam service cleantalk.org ***'; + } + } +} diff --git a/lib/Cleantalk/Common/Antispam/lock-page-ct-die.html b/lib/Cleantalk/Common/Antispam/lock-page-ct-die.html new file mode 100644 index 0000000..a537db8 --- /dev/null +++ b/lib/Cleantalk/Common/Antispam/lock-page-ct-die.html @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + +

+{BACK_SCRIPT} + + \ No newline at end of file diff --git a/lib/Cleantalk/Common/Api/Api.php b/lib/Cleantalk/Common/Api/Api.php new file mode 100644 index 0000000..7c97bee --- /dev/null +++ b/lib/Cleantalk/Common/Api/Api.php @@ -0,0 +1,908 @@ + '2s_blacklists_db', + 'auth_key' => $api_key, + 'out' => $out, + 'version' => $version, + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for get_api_key API method. + * Gets Access key automatically. + * + * @param string $product_name Type of product + * @param string $email Website admin email + * @param string $website Website host + * @param string $platform Website platform + * @param string|null $timezone + * @param string|null $language + * @param string|null $user_ip + * @param bool $wpms + * @param bool $white_label + * @param string $hoster_api_key + * + * @return array|bool|mixed + */ + public static function methodGetApiKey( + $product_name, + $email, + $website, + $platform, + $timezone = null, + $language = null, + $user_ip = null, + $wpms = false, + $white_label = false, + $hoster_api_key = '', + $email_filtered = false + ) { + $request = array( + 'method_name' => 'get_api_key', + 'product_name' => $product_name, + 'email' => $email, + 'website' => $website, + 'platform' => $platform, + 'timezone' => $timezone, + 'http_accept_language' => $language, + 'user_ip' => $user_ip, + 'wpms_setup' => $wpms, + 'hoster_whitelabel' => $white_label, + 'hoster_api_key' => $hoster_api_key, + 'email_filtered' => $email_filtered + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for get_antispam_report API method. + * Gets spam report. + * + * @param string $host website host + * @param integer $period report days + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodGetAntispamReport($host, $period = 1) + { + $request = array( + 'method_name' => 'get_antispam_report', + 'hostname' => $host, + 'period' => $period + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for get_antispam_report_breif API method. + * Ggets spam statistics. + * + * @param string $api_key + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodGetAntispamReportBreif($api_key) + { + $request = array( + 'method_name' => 'get_antispam_report_breif', + 'auth_key' => $api_key, + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for notice_paid_till API method. + * Gets information about renew notice. + * + * @param string $api_key Access key + * @param string $path_to_cms Website URL + * @param string $product_name + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodNoticePaidTill( + $api_key, + $path_to_cms, + $product_name = 'antispam' + ) { + $request = array( + 'method_name' => 'notice_paid_till', + 'path_to_cms' => $path_to_cms, + 'auth_key' => $api_key, + ); + + if (self::getProductId($product_name)) { + $request['product_id'] = self::getProductId($product_name); + } + + return static::sendRequest($request); + } + + /** + * Wrapper for ip_info API method. + * Gets IP country. + * + * @param string $data + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodIpInfo($data) + { + $request = array( + 'method_name' => 'ip_info', + 'data' => $data + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for spam_check_cms API method. + * Checks IP|email via CleanTalk's database. + * + * @param string $api_key + * @param array $data + * @param null|string $date + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSpamCheckCms($api_key, $data, $date = null) + { + $request = array( + 'method_name' => 'spam_check_cms', + 'auth_key' => $api_key, + 'data' => is_array($data) ? implode(',', $data) : $data, + ); + + if ($date) { + $request['date'] = $date; + } + + return static::sendRequest($request, self::URL, 20); + } + + /** + * Wrapper for notice_paid_till API method. + * Gets information about renew notice. + * + * @param string $api_key Access key + * @param string $path_to_cms Website URL + * @param string $product_name + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodEmailCheck($email, $cache_only = true) + { + $request = array( + 'method_name' => 'email_check', + 'cache_only' => $cache_only ? '1' : '0', + 'email' => $email, + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for spam_check API method. + * Checks IP|email via CleanTalk's database. + * + * @param string $api_key + * @param array $data + * @param null|string $date + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSpamCheck($api_key, $data, $date = null) + { + $request = array( + 'method_name' => 'spam_check', + 'auth_key' => $api_key, + 'data' => is_array($data) ? implode(',', $data) : $data, + ); + + if ($date) { + $request['date'] = $date; + } + + return static::sendRequest($request); + } + + /** + * Wrapper for sfw_logs API method. + * Sends SpamFireWall logs to the cloud. + * + * @param string $api_key + * @param array $data + * + * @return array|bool + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSfwLogs($api_key, $data) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'sfw_logs', + 'data' => json_encode($data), + 'rows' => count($data), + 'timestamp' => time() + ); + + $request['data'] = str_replace('"EMPTY_ASSOCIATIVE_ARRAY"', '{}', $request['data']); + + return static::sendRequest($request); + } + + /** + * Wrapper for security_logs API method. + * Sends security logs to the cloud. + * + * @param string $api_key + * @param array $data + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityLogs($api_key, $data) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_logs', + 'timestamp' => current_time('timestamp'), + 'data' => json_encode($data), + 'rows' => count($data), + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for security_logs API method. + * Sends Securitty Firewall logs to the cloud. + * + * @param string $api_key + * @param array $data + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityLogsSendFWData($api_key, $data) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_logs', + 'timestamp' => current_time('timestamp'), + 'data_fw' => json_encode($data), + 'rows_fw' => count($data), + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for security_logs API method. + * Sends empty data to the cloud to syncronize version. + * + * @param string $api_key + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityLogsFeedback($api_key) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_logs', + 'data' => '0', + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for security_firewall_data API method. + * Gets Securitty Firewall data to write to the local database. + * + * @param string $api_key + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityFirewallData($api_key) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_firewall_data', + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for security_firewall_data_file API method. + * Gets URI with security firewall data in .csv.gz file to write to the local database. + * + * @param string $api_key + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityFirewallDataFile($api_key) + { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_firewall_data_file', + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for security_linksscan_logs API method. + * Send data to the cloud about scanned links. + * + * @param string $api_key + * @param string $scan_time Datetime of scan + * @param bool $scan_result + * @param int $links_total + * @param array $links_list + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityLinksscanLogs( + $api_key, + $scan_time, + $scan_result, + $links_total, + $links_list + ) { + $request = array( + 'auth_key' => $api_key, + 'method_name' => 'security_linksscan_logs', + 'started' => $scan_time, + 'result' => $scan_result, + 'total_links_found' => $links_total, + 'links_list' => $links_list, + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for security_mscan_logs API method. + * Sends result of file scan to the cloud. + * + * @param string $api_key + * @param int $service_id + * @param string $scan_time Datetime of scan + * @param bool $scan_result + * @param int $scanned_total + * @param array $modified List of modified files with details + * @param array $unknown List of modified files with details + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityMscanLogs( + $api_key, + $service_id, + $scan_time, + $scan_result, + $scanned_total, + $modified, + $unknown + ) { + $request = array( + 'method_name' => 'security_mscan_logs', + 'auth_key' => $api_key, + 'service_id' => $service_id, + 'started' => $scan_time, + 'result' => $scan_result, + 'total_core_files' => $scanned_total, + ); + + if ( ! empty($modified)) { + $request['failed_files'] = json_encode($modified); + $request['failed_files_rows'] = count($modified); + } + if ( ! empty($unknown)) { + $request['unknown_files'] = json_encode($unknown); + $request['unknown_files_rows'] = count($unknown); + } + + return static::sendRequest($request); + } + + /** + * Wrapper for security_mscan_files API method. + * Sends file to the cloud for analysis. + * + * @param string $api_key + * @param string $file_path Path to the file + * @param array $file File itself + * @param string $file_md5 MD5 hash of file + * @param array $weak_spots List of weak spots found in file + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityMscanFiles( + $api_key, + $file_path, + $file, + $file_md5, + $weak_spots + ) { + $request = array( + 'method_name' => 'security_mscan_files', + 'auth_key' => $api_key, + 'path_to_sfile' => $file_path, + 'attached_sfile' => $file, + 'md5sum_sfile' => $file_md5, + 'dangerous_code' => $weak_spots, + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for get_antispam_report API method. + * Function gets spam domains report. + * + * @param string $api_key + * @param array|string|mixed $data + * @param string $date + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodBacklinksCheckCms($api_key, $data, $date = null) + { + $request = array( + 'method_name' => 'backlinks_check_cms', + 'auth_key' => $api_key, + 'data' => is_array($data) ? implode(',', $data) : $data, + ); + + if ($date) { + $request['date'] = $date; + } + + return static::sendRequest($request); + } + + /** + * Wrapper for get_antispam_report API method. + * Function gets spam domains report + * + * @param string $api_key + * @param array $logs + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityBackendLogs($api_key, $logs) + { + $request = array( + 'method_name' => 'security_backend_logs', + 'auth_key' => $api_key, + 'logs' => json_encode($logs), + 'total_logs' => count($logs), + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for get_antispam_report API method. + * Sends data about auto repairs + * + * @param string $api_key + * @param bool $repair_result + * @param string $repair_comment + * @param $repaired_processed_files + * @param $repaired_total_files_proccessed + * @param $backup_id + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodSecurityMscanRepairs( + $api_key, + $repair_result, + $repair_comment, + $repaired_processed_files, + $repaired_total_files_proccessed, + $backup_id + ) { + $request = array( + 'method_name' => 'security_mscan_repairs', + 'auth_key' => $api_key, + 'repair_result' => $repair_result, + 'repair_comment' => $repair_comment, + 'repair_processed_files' => json_encode($repaired_processed_files), + 'repair_total_files_processed' => $repaired_total_files_proccessed, + 'backup_id' => $backup_id, + 'mscan_log_id' => 1, + ); + + return static::sendRequest($request); + } + + /** + * Wrapper for get_antispam_report API method. + * Force server to update checksums for specific plugin\theme + * + * @param string $api_key + * @param string $plugins_and_themes_to_refresh + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodRequestChecksums($api_key, $plugins_and_themes_to_refresh) + { + $request = array( + 'method_name' => 'request_checksums', + 'auth_key' => $api_key, + 'data' => $plugins_and_themes_to_refresh + ); + + return static::sendRequest($request); + } + + /** + * Settings templates get API method wrapper + * + * @param string $api_key + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodServicesTemplatesGet($api_key, $product_name = 'antispam') + { + $request = array( + 'method_name' => 'services_templates_get', + 'auth_key' => $api_key, + 'search[product_id]' => self::getProductId($product_name), + ); + + return static::sendRequest($request); + } + + /** + * Settings templates add API method wrapper + * + * @param string $api_key + * @param null|string $template_name + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodServicesTemplatesAdd( + $api_key, + $template_name = null, + $options = '', + $product_name = 'antispam' + ) { + $request = array( + 'method_name' => 'services_templates_add', + 'auth_key' => $api_key, + 'name' => $template_name, + 'options_site' => $options, + 'search[product_id]' => self::getProductId($product_name), + ); + + return static::sendRequest($request); + } + + /** + * Settings templates add API method wrapper + * + * @param string $api_key + * @param int $template_id + * @param string $options + * @param string $product_name + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodServicesTemplatesUpdate( + $api_key, + $template_id, + $options = '', + $product_name = 'antispam' + ) { + $request = array( + 'method_name' => 'services_templates_update', + 'auth_key' => $api_key, + 'template_id' => $template_id, + 'name' => null, + 'options_site' => $options, + 'search[product_id]' => self::getProductId($product_name), + ); + + return static::sendRequest($request); + } + + /** + * + * + * @param string $user_token + * @param string $service_id + * @param string $ip + * @param string $service_type + * @param int $product_id + * @param int $record_type + * @param string $note Description text + * @param string $status allow|deny + * @param string $expired Date Y-m-d H:i:s + * + * @return array|bool|bool[]|mixed|string[] + * + * @psalm-suppress PossiblyUnusedMethod + */ + public static function methodPrivateListAdd( + $user_token, + $service_id, + $ip, + $service_type, + $product_id, + $record_type, + $note, + $status, + $expired + ) { + $request = array( + 'method_name' => 'private_list_add', + 'user_token' => $user_token, + 'service_id' => $service_id, + 'records' => $ip, + 'service_type' => $service_type, + 'product_id' => $product_id, + 'record_type' => $record_type, + 'note' => $note, + 'status' => $status, + 'expired' => $expired, + ); + + return static::sendRequest($request); + } + + /** + * Sending of local settings API method wrapper + * + * @param string $api_key + * @param string $hostname + * @param string $settings + * + * @return array|bool|mixed + * + * @psalm-suppress PossiblyUnusedMethod + * @psalm-suppress PossiblyUnusedReturnValue + */ + public static function methodSendLocalSettings( + $api_key, + $hostname, + $settings + ) { + $request = array( + 'method_name' => 'service_update_local_settings', + 'auth_key' => $api_key, + 'hostname' => $hostname, + 'settings' => $settings + ); + + return static::sendRequest($request, self::URL, 0); + } + + private static function getProductId($product_name) + { + $product_id = null; + $product_id = $product_name === 'antispam' ? 1 : $product_id; + $product_id = $product_name === 'security' ? 4 : $product_id; + + return $product_id; + } + + /** + * Function sends raw request to API server + * + * @param array $data to send + * @param string $_url + * @param integer $timeout timeout in seconds + * @param boolean $ssl use ssl on not + * @param string $ssl_path + * + * @return array|bool + */ + public static function sendRequest($data, $_url = self::URL, $timeout = 10, $ssl = false, $ssl_path = '') + { + // Possibility to switch agent version + $data['agent'] = ! empty($data['agent']) + ? $data['agent'] + : (defined('CLEANTALK_AGENT') ? CLEANTALK_AGENT : self::AGENT); + + // Possibility to switch API url + $url = defined('CLEANTALK_API_URL') ? CLEANTALK_API_URL : $_url; + + $http = new Request(); + + return $http->setUrl($url) + ->setData($data) + ->setPresets(['retry_with_socket']) + ->addCallback( + __CLASS__ . '::checkResponse', + [$data['method_name']] + ) + ->request(); + } + + /** + * Function checks server response + * + * @param array|string $result + * @params null|string $_url + * @param string $method_name + * + * @return mixed (array || array('error' => true)) + */ + public static function checkResponse($result, $_url = null, $method_name = null) + { + // Errors handling + // Bad connection + if (is_array($result)) { + $last = error_get_last(); + + return (isset($result['error']) && ! empty($result['error'])) + ? array('error' => 'CONNECTION_ERROR : "' . $result['error'] . '"') + : array('error' => 'CONNECTION_ERROR : "Unknown Error. Last error: ' . $last['message']); + } + + // JSON decode errors + $result = json_decode($result, true); + if (empty($result)) { + return array( + 'error' => 'JSON_DECODE_ERROR', + ); + } + + // Server errors + if ($result && (isset($result['error_no'], $result['error_message']))) { + if ($result['error_no'] != 12) { + return array( + 'error' => "SERVER_ERROR NO: {$result['error_no']} MSG: {$result['error_message']}", + 'error_no' => $result['error_no'], + 'error_message' => $result['error_message'], + ); + } + } + + // Patches for different methods + switch ($method_name) { + // notice_paid_till + case 'notice_paid_till': + $result = isset($result['data']) ? $result['data'] : $result; + + if ((isset($result['error_no']) && $result['error_no'] == 12) || + ( + ! (isset($result['service_id']) && is_int($result['service_id'])) && + empty($result['moderate_ip']) + ) + ) { + $result['valid'] = 0; + } else { + $result['valid'] = 1; + } + + return $result; + + case 'email_check': + return isset($result['data']) ? $result : array('error' => 'NO_DATA'); + + // get_antispam_report_breif + case 'get_antispam_report_breif': + $out = isset($result['data']) && is_array($result['data']) + ? $result['data'] + : array('error' => 'NO_DATA'); + + for ($tmp = array(), $i = 0; $i < 7; $i++) { + $tmp[date('Y-m-d', time() - 86400 * 7 + 86400 * $i)] = 0; + } + $out['spam_stat'] = array_merge($tmp, isset($out['spam_stat']) ? $out['spam_stat'] : array()); + $out['top5_spam_ip'] = isset($out['top5_spam_ip']) ? array_slice($out['top5_spam_ip'], 0, 5) : array(); + + return $out; + + case 'services_templates_add': + case 'services_templates_update': + return isset($result['data']) && is_array($result['data']) && count($result['data']) === 1 + ? $result['data'][0] + : array('error' => 'NO_DATA'); + + case 'private_list_add': + return isset($result['records'][0]['operation_status']) && $result['records'][0]['operation_status'] === 'SUCCESS' + ? true + : array('error' => 'COULDNT_ADD_WL_IP'); + + case '2s_blacklists_db': + return isset($result['data']) && isset($result['data_user_agents']) + ? $result + : $result['data']; + + default: + return isset($result['data']) && is_array($result['data']) + ? $result['data'] + : array('error' => 'NO_DATA'); + } + } +} diff --git a/lib/Cleantalk/Common/Cleaner/Escape.php b/lib/Cleantalk/Common/Cleaner/Escape.php new file mode 100644 index 0000000..3e5008c --- /dev/null +++ b/lib/Cleantalk/Common/Cleaner/Escape.php @@ -0,0 +1,88 @@ +.*?<#i', '', $variable); + + return $variable === $variable_filtered + ? htmlspecialchars($variable_filtered) + : static::sanitize($variable_filtered, 'xss'); + } + + /** + * Simple method: clean url + */ + public static function cleanUrl($variable) + { + return preg_replace('#[^a-zA-Z0-9$\-_.+!*\'(),{}|\\^~\[\]`<>\#%";\/?:@&=.]#i', '', $variable); + } + + /** + * Simple method: clean word + */ + public static function cleanWord($variable) + { + return preg_replace('#[^a-zA-Z0-9_.\-,]#', '', $variable); + } + + /** + * Simple method: clean int + */ + public static function cleanInt($variable) + { + return preg_replace('#[^0-9.,]#', '', $variable); + } + + /** + * Simple method: clean email + */ + public static function cleanEmail($variable) + { + // TODO + return $variable; + } + + /** + * Simple method: clean file name + */ + public static function cleanFileName($variable) + { + // TODO + } + + /** + * Simple method: clean hex color + */ + public static function cleanHexColor($variable) + { + // TODO + } + + /** + * Simple method: clean hex color no hash + */ + public static function cleanHexColorNoHash($variable) + { + // TODO + } + + /** + * Simple method: clean html class + */ + public static function cleanHtmlClass($variable) + { + // TODO + } + + /** + * Simple method: clean key + */ + public static function cleanKey($variable) + { + // TODO + } + + /** + * Simple method: clean meta + */ + public static function cleanMeta($meta_key, $meta_value, $object_type) + { + // TODO + } + + /** + * Simple method: clean mime type + */ + public static function cleanMimeType($variable) + { + // TODO + } + + /** + * Simple method: clean option + */ + public static function cleanOption($option, $value) + { + // TODO + } + + /** + * Simple method: clean sql order by + */ + public static function cleanSqlOrderBy($variable) + { + // TODO + } + + /** + * Simple method: clean text field + */ + public static function cleanTextField($variable) + { + // TODO + } + + /** + * Simple method: clean textarea field + */ + public static function cleanTextareaField($variable) + { + // TODO + } + + /** + * Simple method: clean title + */ + public static function cleanTitle($variable) + { + // TODO + } + + /** + * Simple method: clean title for query + */ + public static function cleanTitleForQuery($variable) + { + // TODO + } + + /** + * Simple method: clean title with dashes + */ + public static function cleanTitleWithDashes($variable) + { + // TODO + } + + /** + * Simple method: clean user + */ + public static function cleanUser($variable) + { + // TODO + } +} diff --git a/lib/Cleantalk/Common/Cleaner/Validate.php b/lib/Cleantalk/Common/Cleaner/Validate.php new file mode 100644 index 0000000..c481fa5 --- /dev/null +++ b/lib/Cleantalk/Common/Cleaner/Validate.php @@ -0,0 +1,112 @@ +cron_option_name = $cron_option_name; - $this->task_execution_min_interval = $task_execution_min_interval; - $this->cron_execution_min_interval = $cron_execution_min_interval; - if( time() - $this->getCronLastStart() > $this->cron_execution_min_interval ) { - $this->tasks = $this->getTasks(); - } - } - - /** - * Get timestamp last Cron started. - * - * @return int timestamp - */ - abstract public function getCronLastStart(); - - /** - * Save timestamp of running Cron. - * - * @return bool - */ - abstract public function setCronLastStart(); - - /** - * Save option with tasks - * - * @param array $tasks - * @return bool - */ - abstract public function saveTasks( $tasks ); - - /** - * Getting all tasks - * - * @return array - */ - abstract public function getTasks(); - - /** - * Adding new cron task. - * - * @param string $task - * @param string $handler - * @param int $period - * @param null|int $first_call - * @param array $params - * - * @return bool - */ - public function addTask( $task, $handler, $period, $first_call = null, $params = array() ) - { - // First call time() + period - $first_call = ! $first_call ? time() + $period : $first_call; - - if( isset( $this->tasks[ $task ] ) ){ - return false; - } - - // Task entry - $this->tasks[$task] = array( - 'handler' => $handler, - 'next_call' => $first_call, - 'period' => $period, - 'params' => $params, - ); - - return $this->saveTasks( $this->tasks ); - } - - /** - * Removing cron task - * - * @param string $task - * - * @return bool - */ - public function removeTask( $task ) - { - if( ! isset( $this->tasks[ $task ] ) ){ - return false; - } - - unset( $this->tasks[ $task ] ); - - return $this->saveTasks( $this->tasks ); - } - - /** - * Get cron option name - * - * @return string - */ - public function getCronOptionName() { - return $this->cron_option_name; - } - - /** - * Updates cron task, create task if not exists. - * - * @param string $task - * @param string $handler - * @param int $period - * @param null|int $first_call - * @param array $params - * - * @return bool - */ - public function updateTask( $task, $handler, $period, $first_call = null, $params = array() ) - { - $this->removeTask( $task ); - return $this->addTask( $task, $handler, $period, $first_call, $params ); - } - - /** - * Getting tasks which should be run - * - * @return bool|array - */ - public function checkTasks() - { - // No tasks to run - if( empty( $this->tasks ) ){ - return false; - } - - $tasks_to_run = array(); - foreach( $this->tasks as $task => &$task_data ){ - - if( - ! isset( $task_data['processing'], $task_data['last_call'] ) || - ( $task_data['processing'] === true && time() - $task_data['last_call'] > $this->task_execution_min_interval ) - ){ - $task_data['processing'] = false; - $task_data['last_call'] = 0; - } - - if( - $task_data['processing'] === false && - $task_data['next_call'] <= time() // default condition - ){ - - $task_data['processing'] = true; - $task_data['last_call'] = time(); - - $tasks_to_run[] = $task; - } - - // Hard bug fix - if( ! isset( $task_data['params'] ) ) { - $task_data['params'] = array(); - } - - } unset( $task_data ); - - $this->saveTasks( $this->tasks ); - - return $tasks_to_run; - } - - /** - * Run all tasks from $this->tasks_to_run. - * Saving all results to (array) $this->tasks_completed - * - * @param $tasks - * @return void|array Array of completed and uncompleted tasks. - */ - public function runTasks( $tasks ) - { - if( empty( $tasks ) ) { - return; - } - - if( ! $this->setCronLastStart() ) { - return; - } - - foreach( $tasks as $task ){ - - if( method_exists( '\plgSystemCleantalkantispam',$this->tasks[$task]['handler'] ) ){ - - if( $this->debug ) { - error_log( var_export( 'Task ' . $task . ' will be run.', 1 ) ); - } - - $result = call_user_func_array( '\plgSystemCleantalkantispam::'.$this->tasks[$task]['handler'], isset( $this->tasks[$task]['params'] ) ? $this->tasks[$task]['params'] : array() ); - - if( $this->debug ) { - error_log( var_export( 'Result:', 1 ) ); - error_log( var_export( $result, 1 ) ); - } - - if( empty( $result['error'] ) ){ - - $this->tasks_completed[$task] = true; - - if( $this->tasks[$task]['period'] == 0 ) { - // One time scheduled event - unset( $this->tasks[$task] ); - } else { - // Multi time scheduled event - $this->tasks[$task]['next_call'] = time() + $this->tasks[$task]['period']; - } - - }else{ - $this->tasks_completed[$task] = false; - $this->tasks[$task]['next_call'] = time() + $this->tasks[$task]['period'] / 4; - } - - }else{ - $this->tasks_completed[$task] = false; - } - - } - - $this->saveTasks( $this->tasks ); - - return $this->tasks_completed; - } -} diff --git a/lib/Cleantalk/Common/Cron/Cron.php b/lib/Cleantalk/Common/Cron/Cron.php new file mode 100644 index 0000000..2fbd4e6 --- /dev/null +++ b/lib/Cleantalk/Common/Cron/Cron.php @@ -0,0 +1,336 @@ +$param_name is NULL new value won't be set + foreach( $params as $param_name => $param ){ + $this->$param_name = isset( $this->$param_name ) ? $param : null; + } + */ + + $this->cron_option_name = $cron_option_name; + $this->task_execution_min_interval = $task_execution_min_interval; + $this->cron_execution_min_interval = $cron_execution_min_interval; + if ( time() - $this->getCronLastStart() > $this->cron_execution_min_interval ) { + if ( ! $this->setCronLastStart() ) { + return; + } + + $this->tasks = $this->getTasks(); + + if ( ! empty($this->tasks) ) { + $this->createId(); + usleep(10000); // 10 ms + } + } + } + + /** + * Get timestamp last Cron started. + * + * @return int timestamp + */ + public function getCronLastStart() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + return (int) $storage_handler_class::getSetting('cleantalk_cron_last_start'); + } + + /** + * Save timestamp of running Cron. + * + * @return bool + */ + public function setCronLastStart() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + return $storage_handler_class::saveSetting('cleantalk_cron_last_start', time()); + } + + /** + * Save option with tasks + * + * @param array $tasks + * + * @return bool + */ + public function saveTasks($tasks) + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + return $storage_handler_class::saveSetting($this->cron_option_name, $tasks); + } + + /** + * Getting all tasks + * + * @return array + */ + public function getTasks() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + $tasks = $storage_handler_class::getSetting($this->cron_option_name); + + return empty($tasks) ? array() : $tasks; + } + + /** + * Adding new cron task. + * + * @param string $task + * @param string $handler + * @param int $period + * @param null|int $first_call + * @param array $params + * + * @return bool + */ + public function addTask($task, $handler, $period, $first_call = null, $params = array()) + { + // First call time() + period + $first_call = ! $first_call ? time() + $period : $first_call; + + $tasks = ! empty($this->tasks) ? $this->tasks : $this->getTasks(); + + if (isset($tasks[$task])) { + return false; + } + + // Task entry + $tasks[$task] = array( + 'handler' => $handler, + 'next_call' => $first_call, + 'period' => $period, + 'params' => $params, + ); + + return $this->saveTasks($tasks); + } + + /** + * Removing cron task + * + * @param string $task + * + * @return bool + * @psalm-suppress PossiblyUnusedReturnValue + */ + public function removeTask($task) + { + $tasks = ! empty($this->tasks) ? $this->tasks : $this->getTasks(); + if ( ! isset($tasks[$task])) { + return false; + } + + unset($tasks[$task]); + + return $this->saveTasks($tasks); + } + + /** + * Updates cron task, create task if not exists. + * + * @param string $task + * @param string $handler + * @param int $period + * @param null|int $first_call + * @param array $params + * + * @return bool + * @psalm-suppress PossiblyUnusedReturnValue + */ + public function updateTask($task, $handler, $period, $first_call = null, $params = array()) + { + $tasks = ! empty($this->tasks) ? $this->tasks : $this->getTasks(); + + if (isset($tasks[$task])) { + // Rewrite the task + $tasks[$task] = array( + 'handler' => $handler, + 'next_call' => is_null($first_call) ? time() + $period : $first_call, + 'period' => $period, + 'params' => $params, + ); + + return $this->saveTasks($tasks); + } + + // Add task if it's disappeared + return $this->addTask($task, $handler, $period, $first_call, $params); + } + + /** + * Get cron option name + * + * @return string + * @psalm-suppress PossiblyUnusedMethod + */ + public function getCronOptionName() + { + return $this->cron_option_name; + } + + /** + * Getting tasks which should be run + * + * @return bool|array + */ + public function checkTasks() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + + // No tasks to run + if ( empty($this->tasks) || $storage_handler_class::getSetting('cleantalk_cron_pid') !== $this->id ) { + return false; + } + + $tasks_to_run = array(); + foreach ($this->tasks as $task => &$task_data) { + if ( + ! isset($task_data['processing'], $task_data['last_call']) || + ($task_data['processing'] === true && + time() - $task_data['last_call'] > $this->task_execution_min_interval) + ) { + $task_data['processing'] = false; + $task_data['last_call'] = 0; + } + + if ( + $task_data['processing'] === false && + $task_data['next_call'] <= time() // default condition + ) { + $task_data['processing'] = true; + $task_data['last_call'] = time(); + + $tasks_to_run[] = $task; + } + + // Hard bug fix + if ( ! isset($task_data['params'])) { + $task_data['params'] = array(); + } + } + unset($task_data); + + $this->saveTasks($this->tasks); + + return $tasks_to_run; + } + + /** + * Run all tasks from $this->tasks_to_run. + * Saving all results to (array) $this->tasks_completed + * + * @param $tasks + * + * @return void|array Array of completed and uncompleted tasks. + */ + public function runTasks($tasks) + { + if ( empty($tasks) ) { + return; + } + + foreach ($tasks as $task) { + if (is_callable($this->tasks[$task]['handler'])) { + if ($this->debug) { + error_log(var_export('Task ' . $task . ' will be run.', true)); + } + + $result = call_user_func_array( + $this->tasks[$task]['handler'], + isset($this->tasks[$task]['params']) ? $this->tasks[$task]['params'] : array() + ); + + if ($this->debug) { + error_log(var_export('Result:', true)); + error_log(var_export($result, true)); + } + + if (empty($result['error'])) { + $this->tasks_completed[$task] = true; + + if ($this->tasks[$task]['period'] == 0) { + // One time scheduled event + unset($this->tasks[$task]); + } else { + // Multi time scheduled event + $this->tasks[$task]['next_call'] = time() + $this->tasks[$task]['period']; + } + } else { + $this->tasks_completed[$task] = $result['error']; + $this->tasks[$task]['next_call'] = time() + $this->tasks[$task]['period'] / 4; + } + } else { + $this->tasks_completed[$task] = $this->tasks[$task]['handler'] . '_IS_NOT_EXISTS'; + } + } + + $this->saveTasks($this->tasks); + + return $this->tasks_completed; + } + + /** + * Generates and save Cron ID to the base + */ + public function createId() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + + $this->id = mt_rand(0, mt_getrandmax()); + $storage_handler_class::saveSetting('cleantalk_cron_pid', $this->id); + } +} diff --git a/lib/Cleantalk/Common/DB.php b/lib/Cleantalk/Common/DB.php deleted file mode 100644 index 90af5c4..0000000 --- a/lib/Cleantalk/Common/DB.php +++ /dev/null @@ -1,101 +0,0 @@ -init(); - } - return self::$instance; - } - - /** - * Alternative constructor. - * Initilize Database object and write it to property. - * Set tables prefix. - */ - abstract protected function init(); - - /** - * Set $this->query string for next uses - * - * @param $query - * @return $this - */ - abstract public function set_query( $query ); - - /** - * Safely replace place holders - * - * @param string $query - * @param array $vars - * - * @return $this - */ - abstract public function prepare( $query, $vars = array() ); - - /** - * Run any raw request - * - * @param $query - * - * @return bool|int Raw result - */ - abstract public function execute( $query ); - - /** - * Fetchs first column from query. - * May receive raw or prepared query. - * - * @param bool $query - * @param bool $response_type - * - * @return array|object|void|null - */ - abstract public function fetch( $query = false, $response_type = false ); - - /** - * Fetchs all result from query. - * May receive raw or prepared query. - * - * @param bool $query - * @param bool $response_type - * - * @return array|object|null - */ - abstract public function fetch_all( $query = false, $response_type = false ); - -} \ No newline at end of file diff --git a/lib/Cleantalk/Common/Db/Db.php b/lib/Cleantalk/Common/Db/Db.php new file mode 100644 index 0000000..285db2f --- /dev/null +++ b/lib/Cleantalk/Common/Db/Db.php @@ -0,0 +1,160 @@ +query string for next uses + * + * @param $query + * + * @return $this|void + * @psalm-suppress PossiblyUnusedMethod + */ + public function setQuery($query) + { + $this->query = $query; + return $this; + } + + public function getQuery() + { + return $this->query; + } + + /** + * Safely replace placeholders + * + * @param string $query + * @param array $vars + * + * @return $this + * @psalm-suppress PossiblyUnusedMethod + */ + public function prepare($query, $vars = array()) + { + $query = $query ?: $this->query; + $vars = $vars ?: array(); + + $this->query = call_user_func($this->getPreparingMethod(), $query, $vars); + + return $this; + } + + /** + * @important This is very weak protection method + * @important Overload this method in CMS-based class + */ + public function getPreparingMethod() + { + return [$this, 'simplePreparingMethod']; + } + + private function simplePreparingMethod($query, $vars) + { + array_walk($vars, function (&$item) { + $item = '"' . addslashes($item) . '"'; + }); + return vsprintf($query, $vars); + } + + /** + * Run any raw request + * + * @param $query + * + * @return bool|int|void Raw result + * @psalm-suppress PossiblyUnusedParam + */ + abstract public function execute($query); + + /** + * Fetchs first column from query. + * May receive raw or prepared query. + * + * @param string $query + * @param bool|string $response_type + * + * @return array|object|void|null + * @psalm-suppress PossiblyUnusedMethod + */ + abstract public function fetch($query, $response_type = false); + + /** + * Fetchs all result from query. + * May receive raw or prepared query. + * + * @param string $query + * @param bool|string $response_type + * + * @return array|object|null|void + * @psalm-suppress PossiblyUnusedMethod + */ + abstract public function fetchAll($query = '', $response_type = false); + + public function getVar($query) + { + return array_values($this->fetch($query))[0]; + } + + abstract public function getAffectedRows(); + + /** + * Checks if the table exists + * + * @param $table_name + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public function isTableExists($table_name) + { + return (bool)$this->execute("SHOW TABLES LIKE '" . $table_name . "'"); + } + + public function getLastError() + { + return 'Not implemented'; + } +} diff --git a/lib/Cleantalk/Common/Db/DbTablesCreator.php b/lib/Cleantalk/Common/Db/DbTablesCreator.php new file mode 100644 index 0000000..b624a30 --- /dev/null +++ b/lib/Cleantalk/Common/Db/DbTablesCreator.php @@ -0,0 +1,79 @@ +prefix; + + foreach ($db_schema as $table_key => $table_schema) { + $sql = 'CREATE TABLE IF NOT EXISTS `%s' . $schema_prefix . $table_key . '` ('; + $sql = sprintf($sql, $prefix); + foreach ($table_schema as $column_name => $column_params) { + if ($column_name !== '__indexes' && $column_name !== '__createkey') { + $sql .= '`' . $column_name . '` ' . $column_params . ', '; + } elseif ($column_name === '__indexes') { + $sql .= $table_schema['__indexes']; + } + } + $sql .= ');'; + + $result = $db_obj->execute($sql); + if ($result === false) { + $errors[] = "Failed.\nQuery: $db_obj->last_query\nError: $db_obj->last_error"; + } + } + + // Logging errors + if (!empty($errors)) { + //@ToDo implement errors handling + } + } + + /** + * Create Table by table name + */ + public function createTable($table_name) + { + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $db_schema = Schema::getStructureSchemas(); + $schema_prefix = Schema::getSchemaTablePrefix(); + $table_key = explode($schema_prefix, $table_name)[1]; + + $sql = 'CREATE TABLE IF NOT EXISTS `' . $table_name . '` ('; + foreach ($db_schema[$table_key] as $column_name => $column_params) { + if ($column_name !== '__indexes' && $column_name !== '__createkey') { + $sql .= '`' . $column_name . '` ' . $column_params . ', '; + } elseif ($column_name === '__indexes') { + $sql .= $db_schema[$table_key]['__indexes']; + } + } + $sql .= ');'; + + $result = $db_obj->execute($sql); + if ($result === false) { + $errors[] = "Failed.\nQuery: $db_obj->last_query\nError: $db_obj->last_error"; + } + + // Logging errors + if (!empty($errors)) { + //@ToDo implement errors handling + } + } +} diff --git a/lib/Cleantalk/Common/Db/Schema.php b/lib/Cleantalk/Common/Db/Schema.php new file mode 100644 index 0000000..7991e45 --- /dev/null +++ b/lib/Cleantalk/Common/Db/Schema.php @@ -0,0 +1,104 @@ + array( + 'id' => 'INT NOT NULL AUTO_INCREMENT', + 'network' => 'INT unsigned NOT NULL', + 'mask' => 'INT unsigned NOT NULL', + 'status' => 'TINYINT NOT NULL DEFAULT 0', + 'source' => 'TINYINT NULL DEFAULT NULL', + '__indexes' => 'PRIMARY KEY (`id`), INDEX ( `network` , `mask` )', + '__createkey' => 'INT unsigned primary KEY AUTO_INCREMENT FIRST' + ), + 'ua_bl' => array( + 'id' => 'INT NOT NULL', + 'ua_template' => 'VARCHAR(255) NULL DEFAULT NULL', + 'ua_status' => 'TINYINT NULL DEFAULT NULL', + '__indexes' => 'PRIMARY KEY ( `id` ), INDEX ( `ua_template` )', + '__createkey' => 'INT unsigned primary KEY FIRST' + ), + 'sfw_logs' => array( + 'id' => 'VARCHAR(40) NOT NULL', + 'ip' => 'VARCHAR(15) NOT NULL', + 'status' => 'ENUM(\'PASS_SFW\',\'DENY_SFW\',\'PASS_SFW__BY_WHITELIST\',\'PASS_SFW__BY_COOKIE\',\'DENY_ANTICRAWLER\',\'PASS_ANTICRAWLER\',\'DENY_ANTICRAWLER_UA\',\'PASS_ANTICRAWLER_UA\',\'DENY_ANTIFLOOD\',\'PASS_ANTIFLOOD\',\'DENY_ANTIFLOOD_UA\',\'PASS_ANTIFLOOD_UA\') NULL DEFAULT NULL', + 'all_entries' => 'INT NOT NULL', + 'blocked_entries' => 'INT NOT NULL', + 'entries_timestamp' => 'INT NOT NULL', + 'ua_id' => 'INT NULL DEFAULT NULL', + 'ua_name' => 'VARCHAR(1024) NOT NULL', + 'source' => 'TINYINT NULL DEFAULT NULL', + 'network' => 'VARCHAR(20) NULL DEFAULT NULL', + 'first_url' => 'VARCHAR(100) NULL DEFAULT NULL', + 'last_url' => 'VARCHAR(100) NULL DEFAULT NULL', + '__indexes' => 'PRIMARY KEY (`id`)', + '__createkey' => 'VARCHAR(40) NOT NULL primary KEY FIRST' + ), + 'ac_log' => array( + 'id' => 'VARCHAR(40) NOT NULL', + 'ip' => 'VARCHAR(40) NOT NULL', + 'ua' => 'VARCHAR(40) NOT NULL', + 'entries' => 'INT DEFAULT 0', + 'interval_start' => 'INT NOT NULL', + '__indexes' => 'PRIMARY KEY (`id`)', + '__createkey' => 'VARCHAR(40) NOT NULL primary KEY FIRST' + ), + 'sessions' => array( + 'id' => 'VARCHAR(64) NOT NULL', + 'name' => 'VARCHAR(40) NOT NULL', + 'value' => 'TEXT NULL DEFAULT NULL', + 'last_update' => 'DATETIME NULL DEFAULT NULL', + '__indexes' => 'PRIMARY KEY (`name`(40), `id`(64))', + '__createkey' => 'VARCHAR(64) NOT NULL primary KEY FIRST' + ), + 'no_cookie_data' => array( + 'id' => 'VARCHAR(64) NOT NULL', + 'name' => 'VARCHAR(40) NOT NULL', + 'value' => 'TEXT NULL DEFAULT NULL', + 'last_update' => 'DATETIME NULL DEFAULT NULL', + 'prev_value' => 'TEXT NULL DEFAULT NULL', + '__indexes' => 'PRIMARY KEY (`name`(40), `id`(64))', + '__createkey' => 'VARCHAR(64) NOT NULL primary KEY FIRST' + ), + 'spamscan_logs' => array( + 'id' => 'INT NOT NULL AUTO_INCREMENT', + 'scan_type' => 'VARCHAR(11) NOT NULL', + 'start_time' => 'DATETIME NOT NULL', + 'finish_time' => 'DATETIME NOT NULL', + 'count_to_scan' => 'INT DEFAULT NULL', + 'found_spam' => 'INT DEFAULT NULL', + 'found_bad' => 'INT DEFAULT NULL', + '__indexes' => 'PRIMARY KEY (`id`)', + '__createkey' => 'INT unsigned primary KEY AUTO_INCREMENT FIRST' + ), + ); + + /** + * Return $schemaTablePrefix + */ + public static function getSchemaTablePrefix() + { + return self::$schemaTablePrefix; + } + + /** + * Return $structure_schemas + */ + public static function getStructureSchemas() + { + return self::$structureSchemas; + } +} diff --git a/lib/Cleantalk/Common/Dns/Dns.php b/lib/Cleantalk/Common/Dns/Dns.php new file mode 100644 index 0000000..012cadd --- /dev/null +++ b/lib/Cleantalk/Common/Dns/Dns.php @@ -0,0 +1,140 @@ + null, + "host" => $host, + "ttl" => static::$default_server_ttl + ); + + // Get DNS records about URL + if (function_exists('dns_get_record')) { + // Localhosts generates errors. block these by @ + $records = $type + ? @dns_get_record($host, $type) + : @dns_get_record($host); + if ($records !== false) { + $servers = array(); + foreach ($records as $server) { + $servers[] = $server; + } + } + } + + // Another try if first failed + if (function_exists('gethostbynamel') && empty($records)) { + $records = gethostbynamel($host); + if ($records !== false) { + $servers = array(); + foreach ($records as $server) { + $servers[] = array( + "ip" => $server, + "host" => $host, + "ttl" => static::$default_server_ttl + ); + } + } + } + + return $return_first + ? reset($servers) + : $servers; + } + + /** + * @param $servers + * + * @return array|null + * @psalm-suppress PossiblyUnusedMethod + */ + public static function findFastestServer($servers) + { + $tmp = array(); + $fast_server_found = false; + + foreach ($servers as $server) { + if ($fast_server_found) { + $ping = static::$max_server_timeout; + } else { + $ping = static::getResponseTime($server['ip']); + $ping *= 1000; + } + + $tmp[$ping] = $server; + + $fast_server_found = $ping < static::$min_server_timeout; + } + + if (count($tmp)) { + ksort($tmp); + $response = $tmp; + } + + return $response ?: null; + } + + /** + * Function to check response time + * + * @param string URL + * + * @return int|float Response time + */ + public static function getResponseTime($host) + { + // Skip localhost ping cause it raise error at fsockopen. + // And return minimum value + if ($host === 'localhost') { + return 0.001; + } + + $starttime = microtime(true); + $file = @fsockopen($host, 80, $errno, $errstr, static::$max_server_timeout / 1000); + $stoptime = microtime(true); + + if ( ! $file) { + $status = static::$max_server_timeout / 1000; // Site is down + } else { + fclose($file); + $status = ($stoptime - $starttime); + $status = round($status, 4); + } + + return $status; + } + + /** + * Get server TTL + * Wrapper for self::getRecord() + * + * @param $host + * + * @return int|false + * @psalm-suppress PossiblyUnusedMethod + */ + public static function getServerTTL($host) + { + $server = static::getRecord($host, true); + + return $server['ttl']; + } +} diff --git a/lib/Cleantalk/Common/Firewall/Exceptions/SfwUpdateException.php b/lib/Cleantalk/Common/Firewall/Exceptions/SfwUpdateException.php new file mode 100644 index 0000000..ce9fccb --- /dev/null +++ b/lib/Cleantalk/Common/Firewall/Exceptions/SfwUpdateException.php @@ -0,0 +1,8 @@ +api_key ) ? $apbct->api_key : false; $spbc_key = ! empty( $spbc->api_key ) ? $spbc->api_key : false; if( ( $apbct_key !== false && $_GET['access'] === $apbct_key ) || ( $spbc_key !== false && $_GET['access'] === $spbc_key ) ){ - Helper::apbct_cookie__set('spbc_firewall_pass_key', md5(Server::get( 'REMOTE_ADDR' ) . $spbc_key), time()+1200, '/', ''); - Helper::apbct_cookie__set('ct_sfw_pass_key', md5(Server::get( 'REMOTE_ADDR' ) . $apbct_key), time()+1200, '/', null); + Cookie::set('spbc_firewall_pass_key', md5(Server::get( 'REMOTE_ADDR' ) . $spbc_key), time()+1200, '/', ''); + Cookie::set('ct_sfw_pass_key', md5(Server::get( 'REMOTE_ADDR' ) . $apbct_key), time()+1200, '/', null); return true; } } return false; } - /** - * Creates Database driver instance. - * - * @param string $api_key - * @param DB $db - * @param string $log_table_name - */ - public function __construct( $api_key, DB $db, $log_table_name ) + /** + * Creates Database driver instance. + * + * @param string $api_key + * @param string $log_table_name + */ + public function __construct( $api_key, $log_table_name ) { + $this->helper = Mloader::get('Helper'); + $this->db = Mloader::get('Db')::getInstance(); + $this->api = Mloader::get('Api'); + $this->api_key = $api_key; - $this->db = $db; - $this->log_table_name = $db->prefix . $log_table_name; + $this->log_table_name = $log_table_name; $this->debug = (bool) Get::get('debug'); - $this->ip_array = $this->ipGet( 'real', true ); - $this->helper = new Helper(); - $this->api = new API(); + $this->ip_array = $this->ipGet(); } - /** - * Setting the specific extended Helper class - * - * @param Helper $helper - */ - public function setSpecificHelper( Helper $helper ) - { - $this->helper = $helper; - } - - /** - * Setting the specific extended API class - * - * @param API $api - */ - public function setSpecificApi( API $api ) - { - $this->api = $api; - } - /** * Loads the FireWall module to the array. * Factory method for configure instance of FirewallModule. @@ -163,19 +148,17 @@ public function loadFwModule( FirewallModule $module ) // Configure the Module Obj $module->setApiKey( $this->api_key ); - $module->setDb( $this->db ); $module->setLogTableName( $this->log_table_name ); - $module->setHelper( $this->helper ); + $module->ipAppendAdditional( $this->ip_array ); $module->setIpArray( $this->ip_array ); $module->setIsDebug( $this->debug ); - $module->ipAppendAdditional( $this->ip_array ); // Store the Module Obj $this->fw_modules[ $module->module_name ] = $module; } } - + /** * Do main logic of the module. * @@ -183,77 +166,93 @@ public function loadFwModule( FirewallModule $module ) */ public function run() { - $this->module_names = array_keys( $this->fw_modules ); - + $this->module_names = array_keys($this->fw_modules); + $results = array(); // Checking - foreach ( $this->fw_modules as $module ) { - - if( isset( $module->isExcluded ) && $module->isExcluded ) { - continue; - } + foreach ($this->fw_modules as $module) { + if (isset($module->isExcluded) && $module->isExcluded) { + continue; + } $module_results = $module->check(); - if( ! empty( $module_results ) ) { + if ( ! empty($module_results)) { $results[$module->module_name] = $module_results; } - if( $this->isWhitelisted( $results ) ) { + if ($this->isWhitelisted($results)) { // Break protection logic if it whitelisted or trusted network. break; } - } // Write Logs - foreach ( $this->fw_modules as $module ) { - if( array_key_exists( $module->module_name, $results ) ){ - foreach ( $results[$module->module_name] as $result ) { - if( in_array( $result['status'], array( 'PASS_SFW__BY_WHITELIST', 'PASS_SFW', 'PASS_ANTIFLOOD', 'PASS_ANTICRAWLER', 'PASS_ANTICRAWLER_UA' ) ) ){ - continue; - } - $module->update_log( $result['ip'], $result['status'] ); - } - } - } + foreach ($this->fw_modules as $module) { + if (array_key_exists($module->module_name, $results)) { + foreach ($results[$module->module_name] as $result) { + if ( + in_array( + $result['status'], + array( + 'PASS_SFW__BY_WHITELIST', + 'PASS_SFW__BY_STATUS', + 'PASS_SFW', + 'PASS_ANTIFLOOD', + 'PASS_ANTICRAWLER', + 'PASS_ANTICRAWLER_UA', + 'PASS_ANTIFLOOD_UA' + ) + ) + ) { + continue; + } + $module->updateLog( + $result['ip'], + $result['status'], + isset($result['network']) ? $result['network'] : null, + isset($result['is_personal']) ? $result['is_personal'] : 'NULL' + ); + } + } + } - // Get the primary result - $result = $this->prioritize( $results ); + // Get the primary result + $result = $this->prioritize($results); // Do finish action - die or set cookies - foreach( $this->module_names as $module_name ){ - - if( strpos( $result['status'], $module_name ) ){ + foreach ($this->module_names as $module_name) { + if (strpos($result['status'], $module_name)) { // Blocked - if( strpos( $result['status'], 'DENY' ) !== false ){ - $this->fw_modules[ $module_name ]->actionsForDenied( $result ); - $this->fw_modules[ $module_name ]->_die( $result ); - - // Allowed - }else{ - $this->fw_modules[ $module_name ]->actionsForPassed( $result ); + if (strpos($result['status'], 'DENY') !== false) { + $this->fw_modules[$module_name]->actionsForDenied($result); + $this->fw_modules[$module_name]->diePage($result); + // Allowed + } elseif ($result['status'] === 'PASS_SFW__BY_STATUS') { + $this->fw_modules[$module_name]->actionsForPassed($result); + $this->fw_modules[$module_name]->diePage($result); + } else { + $this->fw_modules[$module_name]->actionsForPassed($result); } } - } - } - /** - * Getting arrays of IP (REMOTE_ADDR, X-Forwarded-For, X-Real-Ip, Cf_Connecting_Ip) - * - * @param string $ips_input type of IP you want to receive - * @param bool $v4_only - * - * @return array - */ - private function ipGet( $ips_input, $v4_only = true ) - { - $result = Helper::ip__get( $ips_input, $v4_only ); - return ! empty( $result ) ? array( 'real' => $result ) : array(); - } - + /** + * Getting arrays of IP (REMOTE_ADDR, X-Forwarded-For, X-Real-Ip, Cf_Connecting_Ip) + * + * @param string $ips_input type of IP you want to receive + * @param bool $v4_only + * + * @return array + */ + public function ipGet($ips_input = 'real', $v4_only = true) + { + $result = $this->helper::ipGet($ips_input, $v4_only); + + return ! empty($result) ? array('real' => $result) : array(); + } + /** * Sets priorities for firewall results. * It generates one main result from multi-level results array. @@ -264,32 +263,33 @@ private function ipGet( $ips_input, $v4_only = true ) */ private function prioritize( $results ) { - $current_fw_result_priority = 0; - $result = array( 'status' => 'PASS', 'passed_ip' => '' ); - - if( is_array( $results ) ) { - foreach ( $this->fw_modules as $module ) { - if( array_key_exists( $module->module_name, $results ) ) { - foreach ( $results[$module->module_name] as $fw_result ) { - $priority = array_search( $fw_result['status'], $this->statuses_priority ) + ( isset($fw_result['is_personal']) && $fw_result['is_personal'] ? count ( $this->statuses_priority ) : 0 ); - if( $priority >= $current_fw_result_priority ){ - $current_fw_result_priority = $priority; - $result['status'] = $fw_result['status']; - $result['passed_ip'] = isset( $fw_result['ip'] ) ? $fw_result['ip'] : $fw_result['passed_ip']; - $result['blocked_ip'] = isset( $fw_result['ip'] ) ? $fw_result['ip'] : $fw_result['blocked_ip']; - $result['pattern'] = isset( $fw_result['pattern'] ) ? $fw_result['pattern'] : array(); - } - } - } - } - } - - $result['ip'] = strpos( $result['status'], 'PASS' ) !== false ? $result['passed_ip'] : $result['blocked_ip']; - $result['passed'] = strpos( $result['status'], 'PASS' ) !== false; - - return $result; + $current_fw_result_priority = 0; + $result = array('status' => 'PASS', 'passed_ip' => ''); + + if (is_array($results)) { + foreach ($this->fw_modules as $module) { + if (array_key_exists($module->module_name, $results)) { + foreach ($results[$module->module_name] as $fw_result) { + $priority = array_search($fw_result['status'], $this->statuses_priority) + + (isset($fw_result['is_personal']) && $fw_result['is_personal'] ? count($this->statuses_priority) : 0); + if ($priority >= $current_fw_result_priority) { + $current_fw_result_priority = $priority; + $result['status'] = $fw_result['status']; + $result['passed_ip'] = isset($fw_result['ip']) ? $fw_result['ip'] : $fw_result['passed_ip']; + $result['blocked_ip'] = isset($fw_result['ip']) ? $fw_result['ip'] : $fw_result['blocked_ip']; + $result['pattern'] = isset($fw_result['pattern']) ? $fw_result['pattern'] : array(); + } + } + } + } + } + + $result['ip'] = strpos($result['status'], 'PASS') !== false ? $result['passed_ip'] : $result['blocked_ip']; + $result['passed'] = strpos($result['status'], 'PASS') !== false; + + return $result; } - + /** * Check the result if it whitelisted or trusted network * @@ -324,7 +324,7 @@ public function sendLogs() { //Getting logs $query = "SELECT * FROM " . $this->log_table_name . ";"; - $this->db->fetch_all( $query ); + $this->db->fetchAll( $query ); if( count( $this->db->result ) ){ @@ -359,8 +359,7 @@ public function sendLogs() { unset( $key, $value ); //Sending the request - $api = $this->api; - $result = $api::method__sfw_logs( $this->api_key, $data ); + $result = $this->api::methodSfwLogs( $this->api_key, $data ); //Checking answer and deleting all lines from the table if( empty( $result['error'] ) ){ @@ -379,18 +378,39 @@ public function sendLogs() { return array( 'rows' => 0 ); } - /** - * Get and configure the FirewallUpdater object. - * - * @param string $data_table_name - * @return FirewallUpdater - */ - public function getUpdater( $data_table_name ) + /** + * Get and configure the FirewallUpdater object. + * + * @return FirewallUpdater + */ + public function getUpdater() { - $fw_updater = new FirewallUpdater( $this->api_key, $this->db, $data_table_name ); - $fw_updater->setSpecificHelper( $this->helper ); - $fw_updater->setSpecificApi( $this->api ); - return $fw_updater; + return new FirewallUpdater($this); } + public static function getFwStats() + { + $fw_stats = new FwStats(); + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + $stats = $storage_handler_class::getSetting(self::FW_STATS_SETTING_NAME); + if ( $stats !== null ) { + foreach ( $stats as $stat_key => $stat_val ) { + $fw_stats->$stat_key = $stat_val; + } + } + return $fw_stats; + } + + public static function saveFwStats(FwStats $fw_stats) + { + $stats = []; + foreach ( $fw_stats as $stat_key => $stat_val ) + { + $stats[$stat_key] = $stat_val; + } + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + return $storage_handler_class::saveSetting(self::FW_STATS_SETTING_NAME, $stats); + } } diff --git a/lib/Cleantalk/Common/Firewall/FirewallModule.php b/lib/Cleantalk/Common/Firewall/FirewallModule.php index 87a9e66..ad751f8 100644 --- a/lib/Cleantalk/Common/Firewall/FirewallModule.php +++ b/lib/Cleantalk/Common/Firewall/FirewallModule.php @@ -13,8 +13,7 @@ * @since 2.49 */ -use Cleantalk\Common\DB; -use Cleantalk\Common\Helper; +use Cleantalk\Common\Mloader\Mloader; use Cleantalk\Common\Variables\Get; abstract class FirewallModule { @@ -35,7 +34,7 @@ abstract class FirewallModule { protected $ip_array = array(); /** - * @var DB + * @var \Cleantalk\Common\Db\Db */ protected $db; @@ -50,7 +49,7 @@ abstract class FirewallModule { protected $db_data_table_name; /** - * @var Helper + * @var \Cleantalk\Common\Helper\Helper */ protected $helper; @@ -83,10 +82,15 @@ abstract class FirewallModule { * FirewallModule constructor. * Use this method to prepare any data for the module working. * + * @param string $log_table * @param string $data_table * @param array $params */ - abstract public function __construct( $data_table, $params = array() ); + public function __construct($log_table, $data_table, $params = array()) + { + $this->helper = Mloader::get('Helper'); + $this->db = Mloader::get('Db')::getInstance(); + } /** * Use this method to execute main logic of the module. @@ -117,26 +121,16 @@ abstract public function actionsForPassed( $result ); * @param array $ips * @return void */ - public function ipAppendAdditional( $ips ) + public function ipAppendAdditional(& $ips) { $this->real_ip = isset($ips['real']) ? $ips['real'] : null; - if( Get::get('sfw_test_ip') && Helper::ip__validate( Get::get('sfw_test_ip') ) !== false ) { + if( Get::get('sfw_test_ip') && $this->helper::ipValidate( Get::get('sfw_test_ip') ) !== false ) { $this->ip_array['sfw_test'] = Get::get( 'sfw_test_ip' ); $this->test_ip = Get::get( 'sfw_test_ip' ); $this->test = true; } } - - /** - * Set specify CMS based DB instance - * - * @param DB $db - */ - public function setDb( DB $db ) - { - $this->db = $db; - } /** * Set Log Table name @@ -145,19 +139,10 @@ public function setDb( DB $db ) */ public function setLogTableName( $log_table_name ) { + $this->db_data_table_name = $this->db->prefix . $this->db_data_table_name; $this->db_log_table_name = $log_table_name; } - /** - * Set specify CMS based Helper instance - * - * @param Helper $helper - */ - public function setHelper( Helper $helper ) - { - $this->helper = $helper; - } - /** * Set API KEY * @@ -193,7 +178,7 @@ public function setIpArray( $ip_array ) * * @param array $result */ - public function _die( $result ) + public function diePage( $result ) { // Headers if( headers_sent() === false ){ diff --git a/lib/Cleantalk/Common/Firewall/FirewallUpdater.php b/lib/Cleantalk/Common/Firewall/FirewallUpdater.php index 1e7b644..f5dd9b1 100644 --- a/lib/Cleantalk/Common/Firewall/FirewallUpdater.php +++ b/lib/Cleantalk/Common/Firewall/FirewallUpdater.php @@ -2,336 +2,931 @@ namespace Cleantalk\Common\Firewall; -use Cleantalk\Common\API; -use Cleantalk\Common\DB; -use Cleantalk\Common\Helper; -use Cleantalk\Common\RemoteCalls; -use Cleantalk\Common\Schema; -use Cleantalk\Common\Variables\Get; +use Cleantalk\Common\Db\DbTablesCreator; +use Cleantalk\Common\Db\Schema; +use Cleantalk\Common\Firewall\Exceptions\SfwUpdateException; +use Cleantalk\Common\Mloader\Mloader; +use Cleantalk\Common\Queue\Queue; +use Cleantalk\Common\Variables\Request; class FirewallUpdater { - /** - * @var int - */ - const WRITE_LIMIT = 5000; - /** * @var string */ private $api_key; - /** - * @var Helper - */ - private $helper; - - /** - * @var API - */ - private $api; - - /** - * @var DB - */ - private $db; - - /** - * @var string - */ - private $fw_data_table_name; - - - /** - * FirewallUpdater constructor. - * - * @param string $api_key - * @param DB $db - * @param string $fw_data_table_name - */ - public function __construct( $api_key, DB $db, $fw_data_table_name ) - { - $this->api_key = $api_key; - $this->db = $db; - $this->fw_data_table_name = $db->prefix . $fw_data_table_name; - $this->helper = new Helper(); - $this->api = new API(); - } - - /** - * Set specify CMS based Helper instance - * - * @param Helper $helper - */ - public function setSpecificHelper( Helper $helper ) + /** + * @var \Cleantalk\Common\RemoteCalls\RemoteCalls + */ + private $rc; + + /** + * @var \Cleantalk\Common\Queue\Queue + */ + private $queue; + + /** + * @var \Cleantalk\Common\Cron\Cron + */ + private $cron; + + /** + * @var Firewall + */ + private Firewall $fw; + + /** + * @var FwStats + */ + private $fwStats; + + /** + * FirewallUpdater constructor. + * + * @param Firewall $fw + */ + public function __construct($fw) { - $this->helper = $helper; + $this->rc = Mloader::get('RemoteCalls'); + $this->queue = Mloader::get('Queue'); + $this->fw = $fw; + $this->api_key = $this->fw->api_key; + $this->fwStats = $this->fw::getFwStats(); } - /** - * Set specify CMS based API instance - * - * @param API $api - */ - public function setSpecificApi( API $api ) - { - $this->api = $api; - } - - public function update() { - $helper = $this->helper; - $fw_stats = $helper::getFwStats(); - - // Prevent start another update at a time - if( ! Get::get('firewall_updating_id') && - $fw_stats['firewall_updating_id'] && - Get::get('spbc_remote_call_action') == 'sfw_update__write_base' && - time() - $fw_stats['firewall_updating_last_start'] < 60 ){ - return true; - } - - // Check if the update performs right now. Blocks remote calls with different ID - if( Get::get('spbc_remote_call_action') == 'sfw_update__write_base' && Get::get('firewall_updating_id') && - Get::get('firewall_updating_id') !== $fw_stats['firewall_updating_id'] - ) { - return array( 'error' => 'FIREWALL_IS_UPDATING' ); - } - // No updating without api key - if( empty( $this->api_key ) ){ - return true; - } - - // Set new update ID - if( ! $fw_stats['firewall_updating_id'] || time() - $fw_stats['firewall_updating_last_start'] > 300 ){ - $helper::setFwStats( - array( - 'firewall_updating_id' => md5( rand( 0, 100000 ) ), - 'firewall_updating_last_start' => time(), - 'firewall_update_percent' => 0 - ) - ); - } - - if( RemoteCalls::check() ) { - // Remote call is in process, do updating - - $file_urls = Get::get('file_urls'); - $url_count = Get::get('url_count'); - $current_url = Get::get('current_url'); - - // Getting blacklists file here. - if( ! $file_urls ){ - - // @todo We have to handle errors here - $this->createTempTables(); - - $blacklists = $this->getSfwBlacklists( $this->api_key ); - - if( empty( $blacklists['error'] ) ){ - if( ! empty( $blacklists['file_url'] ) ){ - $data = $this->unpackData( $blacklists['file_url'] ); - if( empty( $data['error'] ) ) { - return Helper::http__request__rc_to_host( - 'sfw_update__write_base', - array( - 'spbc_remote_call_token' => md5( $this->api_key ), - 'firewall_updating_id' => $fw_stats['firewall_updating_id'], - 'file_urls' => str_replace( array( 'http://', 'https://' ), '', $blacklists['file_url'] ), - 'url_count' => count( $data ), - 'current_url' => 0, - ), - array( 'get','async' ) - ); - } else { - return $data; - } - } else { - return array('error' => 'NO_REMOTE_MULTIFILE_FOUND: ' . $blacklists['file_url'] ); - } - } else { - // Error getting blacklists. - return $blacklists; - } - - // Doing updating here. - }elseif( $file_urls && $url_count > $current_url ){ - - $file_url = 'https://' . str_replace( 'multifiles', $current_url, $file_urls ); - - $lines = $this->unpackData( $file_url ); - if( empty( $lines['error'] ) ) { - - // Do writing to the DB - reset( $lines ); - for( $count_result = 0; current($lines) !== false; ) { - $query = "INSERT INTO ".$this->fw_data_table_name."_temp (network, mask, status) VALUES "; - for( $i = 0, $values = array(); self::WRITE_LIMIT !== $i && current( $lines ) !== false; $i ++, $count_result ++, next( $lines ) ){ - $entry = current($lines); - if(empty($entry)) { - continue; - } - if ( self::WRITE_LIMIT !== $i ) { - // Cast result to int - $ip = preg_replace('/[^\d]*/', '', $entry[0]); - $mask = preg_replace('/[^\d]*/', '', $entry[1]); - $private = isset($entry[2]) ? $entry[2] : 0; - } - $values[] = '('. $ip .','. $mask .','. $private .')'; - } - if( ! empty( $values ) ){ - $query = $query . implode( ',', $values ) . ';'; - $this->db->execute( $query ); - } - } - $current_url++; - $fw_stats['firewall_update_percent'] = round( ( ( (int) $current_url + 1 ) / (int) $url_count ), 2) * 100; - $helper::setFwStats( $fw_stats ); - - // Updating continue: Do next remote call. - if ( $url_count > $current_url ) { - return Helper::http__request__rc_to_host( - 'sfw_update__write_base', - array( - 'spbc_remote_call_token' => md5( $this->api_key ), - 'file_urls' => str_replace( array( 'http://', 'https://' ), '', $file_urls ), - 'url_count' => $url_count, - 'current_url' => $current_url, - // Additional params - 'firewall_updating_id' => $fw_stats['firewall_updating_id'], - ), - array('get', 'async') - ); - - // Updating end: Do finish actions. - } else { - - // @todo We have to handle errors here - $this->deleteMainDataTables(); - // @todo We have to handle errors here - $this->renameDataTables(); - - //Files array is empty update sfw stats - $helper::SfwUpdate_DoFinisnAction(); - - $fw_stats['firewall_update_percent'] = 0; - $fw_stats['firewall_updating_id'] = null; - $helper::setFwStats( $fw_stats ); - - return true; - - } - } else { - return $data; - } - }else { - return array('error' => 'SFW_UPDATE WRONG_FILE_URLS'); - } - } else { - // Go to init remote call - return $helper::http__request__rc_to_host( - 'sfw_update', - array( - 'spbc_remote_call_token' => md5( $this->api_key ), - 'firewall_updating_id' => $fw_stats['firewall_updating_id'], - ), - array( 'get','async' ) - ); - } - - } - - private function getSfwBlacklists( $api_key ) - { - $api = $this->api; - $result = $api::method__get_2s_blacklists_db( $api_key, 'multifiles', '3_0' ); - sleep(4); - return $result; - } - - private function unpackData( $file_url ) - { - $helper = $this->helper; - $file_url = trim( $file_url ); - - $response_code = $helper::http__request__get_response_code( $file_url ); - - if( empty( $response_code['error'] ) ){ - - if( $response_code == 200 || $response_code == 501 ){ - - $gz_data = $helper::http__request__get_content( $file_url ); - - if( is_string($gz_data) ){ - - if( Helper::get_mime_type( $gz_data, 'application/x-gzip' ) ){ - - if( function_exists( 'gzdecode' ) ){ - - $data = gzdecode( $gz_data ); - - if( $data !== false ){ - - return Helper::buffer__parse__csv( $data ); - - }else { - return array('error' => 'COULD_DECODE_FILE'); - } - }else { - return array('error' => 'FUNCTION_GZ_DECODE_DOES_NOT_EXIST'); - } - }else { - return array('error' => 'WRONG_FILE_MIME_TYPE'); - } - }else { - return array('error' => 'COULD_NOT_GET_IFILE: ' . $gz_data['error']); - } - }else { - return array('error' => 'FILE_BAD_RESPONSE_CODE: ' . (int)$response_code); - } - }else { - return array('error' => 'FILE_COULD_NOT_GET_RESPONSE_CODE: ' . $response_code['error']); - } - } - - /** - * Creatin a temporary updating table - * - * @param DB $db database handler - * @throws \Exception - */ - private function createTempTables() - { - $sql = 'SHOW TABLES LIKE "%scleantalk_sfw";'; - $sql = sprintf( $sql, $this->db->prefix ); // Adding current blog prefix - $result = $this->db->fetch( $sql ); - if( ! $result ){ - $sql = sprintf( Schema::getSchema('sfw'), $this->db->prefix ); - $this->db->execute( $sql ); - } - $this->db->execute( 'CREATE TABLE IF NOT EXISTS `' . $this->fw_data_table_name . '_temp` LIKE `' . $this->fw_data_table_name . '`;' ); - $this->db->execute( 'TRUNCATE TABLE `' . $this->fw_data_table_name . '_temp`;' ); - } - - /** - * Removing a temporary updating table - * - * @param DB $db database handler - */ - private function deleteMainDataTables() - { - $this->db->execute( 'DROP TABLE `' . $this->db->prefix . APBCT_TBL_FIREWALL_DATA .'`;' ); - } - - /** - * Renamin a temporary updating table into production table name - * - * @param DB $db database handler - */ - private function renameDataTables() - { - $this->db->execute( 'ALTER TABLE `' . $this->db->prefix . APBCT_TBL_FIREWALL_DATA .'_temp` RENAME `' . $this->db->prefix . APBCT_TBL_FIREWALL_DATA .'`;' ); + if ( Request::get('worker') ) { + return $this->updateWorker(); + } + return $this->updateInit(); } -} \ No newline at end of file + /** + * Called by sfw_update remote call + * Starts SFW update and could use a delay before start + * + * @param int $delay + * + * @return bool|string|string[] + */ + private function updateInit($delay = 0) + { + // Prevent start an update if update is already running and started less than 10 minutes ago + if ( + $this->fwStats->updating_id && + time() - $this->fwStats->updating_last_start < 600 && + $this->isUpdateInProgress() + ) { + return false; + } + + // The Access key is empty + if ( ! $this->api_key ) { + throw new SfwUpdateException('updateInit: API key is empty'); + } + + // Get update period for server + /** @var \Cleantalk\Common\Dns\Dns $dns_class */ + $dns_class = Mloader::get('Dns'); + $update_period = $dns_class::getRecord('spamfirewall-ttl-txt.cleantalk.org', true, DNS_TXT); + $update_period = isset($update_period['txt']) ? $update_period['txt'] : 0; + $update_period = (int)$update_period > 14400 ? (int)$update_period : 14400; + if ( $this->fwStats->update_period != $update_period ) { + $this->fwStats->update_period = $update_period; + $this->fw::saveFwStats($this->fwStats); + } + + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler */ + $storage_handler = Mloader::get('StorageHandler'); + $this->fwStats->updating_folder = $storage_handler::getUpdatingFolder(); + + $prepare_dir__result = $this->prepareUpdDir(); + + $test_rc_result = $this->rc::perform( + 'sfw_update', + 'apbct', + $this->api_key, + ['test' => 'test'] + ); + + if ( ! empty($prepare_dir__result['error']) || ! empty($test_rc_result['error']) ) { + return $this->directUpdate(); + } + + // Set a new update ID and an update time start + $this->fwStats->calls = 0; + $this->fwStats->updating_id = md5((string)rand(0, 100000)); + $this->fwStats->updating_last_start = time(); + $this->fw::saveFwStats($this->fwStats); + + Queue::clearQueue(); + + $queue = new Queue($this->api_key); + $queue->addStage([$this::class, 'getMultifiles']); + + $cron = new \Cleantalk\Common\Cron\Cron(); + $cron->addTask('sfw_update_checker', 'apbct_sfw_update__checker', 15, null. $this->api_key); + + return $this->rc::perform( + 'sfw_update', + 'apbct', + $this->api_key, + array( + 'firewall_updating_id' => $this->fwStats->updating_id, + 'delay' => $delay, + 'worker' => 1, + ), + ['async'] + ); + } + + /** + * Called by sfw_update__worker remote call + * gather all process about SFW updating + * + * @param bool $checker_work + * + * @return array|bool|int|string|string[] + * @throws SfwUpdateException + */ + private function updateWorker($checker_work = false) + { + if ( ! $checker_work ) { + if ( + Request::equal('firewall_updating_id', '') || + ! Request::equal('firewall_updating_id', $this->fwStats->updating_id) + ) { + throw new SfwUpdateException('updateWorker: Wrong update ID'); + } + } + + if ( ! isset($this->fwStats->calls) ) { + $this->fwStats->calls = 0; + } + + $this->fwStats->calls++; + $this->fw::saveFwStats($this->fwStats); + + if ( $this->fwStats->calls > 600 ) { + throw new SfwUpdateException('updateWorker: Worker call limit exceeded'); + } + + $queue = new $this->queue($this->api_key); + + if ( count($queue->queue['stages']) === 0 ) { + // Queue is already empty. Exit. + return true; + } + + $result = $queue->executeStage(); + + if ( $result === null ) { + // The stage is in progress, will try to wait up to 5 seconds to its complete + for ( $i = 0; $i < 5; $i++ ) { + sleep(1); + $queue->refreshQueue(); + if ( ! $queue->isQueueInProgress() ) { + // The stage executed, break waiting and continue sfw_update__worker process + break; + } + if ( $i >= 4 ) { + // The stage still not executed, exit from sfw_update__worker + return true; + } + } + } + + if ( isset($result['error'], $result['status']) && $result['status'] === 'FINISHED' ) { + $this->updateFallback(); + + $direct_upd_res = $this->directUpdate(); + + if ( $direct_upd_res['error'] ) { + throw new SfwUpdateException($direct_upd_res['error']); + } + + return true; + } + + if ( $queue->isQueueFinished() ) { + $queue->queue['finished'] = time(); + $queue->saveQueue($queue->queue); + foreach ( $queue->queue['stages'] as $stage ) { + if ( isset($stage['error'], $stage['status']) && $stage['status'] !== 'FINISHED' ) { + //there could be an array of errors of files processed + if ( is_array($stage['error']) ) { + $error = implode(" ", array_values($stage['error'])); + } else { + $error = $result['error']; + } + throw new SfwUpdateException($error); + } + } + + // Do logging the queue process here + return true; + } + + + // This is the repeat stage request, do not generate any new RC + if ( stripos(Request::get('stage'), 'Repeat') !== false ) { + return true; + } + + return $this->rc::perform( + 'sfw_update', + 'apbct', + $this->api_key, + array( + 'firewall_updating_id' => $this->fwStats->updating_id, + 'worker' => 1, + ), + array('async') + ); + } + + public static function getMultifiles($api_key) + { + // The Access key is empty + if ( ! $api_key ) { + throw new SfwUpdateException('getMultifiles: API key is empty'); + } + + $fw_stats = Firewall::getFwStats(); + /** @var \Cleantalk\Common\Api\Api $api_class */ + $api_class = Mloader::get('Api'); + /** @var \Cleantalk\Common\Helper\Helper $helper_class */ + $helper_class = Mloader::get('Helper'); + + // Getting remote file name + + $result = $api_class::methodGet2sBlacklistsDb($api_key, 'multifiles', '3_1'); + + if ( empty($result['error']) ) { + if ( ! empty($result['file_url']) ) { + $file_urls = $helper_class::httpGetDataFromRemoteGzAndParseCsv($result['file_url']); + if ( empty($file_urls['error']) ) { + if ( ! empty($result['file_ua_url']) ) { + $file_urls[][0] = $result['file_ua_url']; + } + if ( ! empty($result['file_ck_url']) ) { + $file_urls[][0] = $result['file_ck_url']; + } + $urls = array(); + foreach ( $file_urls as $value ) { + $urls[] = $value[0]; + } + + $fw_stats->update_percent = round(100 / count($urls), 2); + Firewall::saveFwStats($fw_stats); + + return array( + 'next_stage' => array( + 'name' => [self::class, 'downloadFiles'], + 'args' => $urls, + 'is_last' => '0' + ) + ); + } + + throw new SfwUpdateException('getMultifiles: ' . $file_urls['error']); + } + } else { + return $result; + } + return null; + } + + public static function downloadFiles($api_key, $urls) + { + // The Access key is empty + if ( ! $api_key ) { + throw new SfwUpdateException('downloadFiles: API key is empty'); + } + + $fw_stats = Firewall::getFwStats(); + /** @var \Cleantalk\Common\Helper\Helper $helper_class */ + $helper_class = Mloader::get('Helper'); + + sleep(3); + + //Reset keys + $urls = array_values($urls); + $results = $helper_class::httpMultiRequest($urls, $fw_stats->updating_folder); + $count_urls = count($urls); + $count_results = count($results); + + if ( empty($results['error']) && ($count_urls === $count_results) ) { + $download_again = array(); + $results = array_values($results); + for ( $i = 0; $i < $count_results; $i++ ) { + if ( $results[$i] === 'error' ) { + $download_again[] = $urls[$i]; + } + } + + if ( count($download_again) !== 0 ) { + return array( + 'error' => 'Files download not completed.', + 'update_args' => array( + 'args' => $download_again + ) + ); + } + + return array( + 'next_stage' => array( + 'name' => [self::class, 'createTables'] + ) + ); + } + + if ( ! empty($results['error']) ) { + throw new SfwUpdateException('downloadFiles: ' . $results['error']); + } + + throw new SfwUpdateException('downloadFiles: Files download not completed.'); + } + + public static function createTables($_api_key) + { + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + // Preparing database infrastructure + // Creating SFW tables to make sure that they are exists + $db_tables_creator = new DbTablesCreator(); + $table_name_sfw = $db_obj->prefix . Schema::getSchemaTablePrefix() . 'sfw'; + $db_tables_creator->createTable($table_name_sfw); + $table_name_ua = $db_obj->prefix . Schema::getSchemaTablePrefix() . 'ua_bl'; + $db_tables_creator->createTable($table_name_ua); + + return array( + 'next_stage' => array( + 'name' => [self::class, 'createTempTables'], + ) + ); + } + + public static function createTempTables($_api_key) + { + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + // Preparing temporary tables + $result = \Cleantalk\Common\Firewall\Modules\Sfw::createTempTables($db_obj, $db_obj->prefix . APBCT_TBL_FIREWALL_DATA); + if ( ! empty($result['error']) ) { + throw new SfwUpdateException('createTempTables: ' . $result['error']); + } + + return array( + 'next_stage' => array( + 'name' => [self::class, 'processFiles'], + ) + ); + } + + public static function processFiles($_api_key) + { + $fw_stats = Firewall::getFwStats(); + $files = glob($fw_stats->updating_folder . '/*csv.gz'); + $files = array_filter($files, static function ($element) { + return strpos($element, 'list') !== false; + }); + + if ( count($files) ) { + reset($files); + $concrete_file = current($files); + + if ( strpos($concrete_file, 'bl_list') !== false ) { + $result = self::processFile($concrete_file); + } + + if ( strpos($concrete_file, 'ua_list') !== false ) { + $result = self::processUa($concrete_file); + } + + if ( strpos($concrete_file, 'ck_list') !== false ) { + $result = self::processCk($concrete_file); + } + + if ( ! empty($result['error']) ) { + throw new SfwUpdateException('processFiles: ' . $concrete_file . ' -> ' . $result['error']); + } + + $fw_stats = Firewall::getFwStats(); + $fw_stats->update_percent = round(100 / count($files), 2); + Firewall::saveFwStats($fw_stats); + + return array( + 'next_stage' => array( + 'name' => [self::class, 'processFiles'], + ) + ); + } + + return array( + 'next_stage' => array( + 'name' => [self::class, 'processExclusions'], + ) + ); + } + + public static function processFile($file_path) + { + if ( ! file_exists($file_path) ) { + return array('error' => 'PROCESS FILE: ' . $file_path . ' is not exists.'); + } + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $result = \Cleantalk\Common\Firewall\Modules\Sfw::updateWriteToDb( + $db_obj, + $db_obj->prefix . APBCT_TBL_FIREWALL_DATA . '_temp', + $file_path + ); + + if ( ! empty($result['error']) ) { + throw new SfwUpdateException('processFile: ' . $file_path . ' -> ' . $result['error']); + } + + if ( ! is_int($result) ) { + throw new SfwUpdateException('processFiles: ' . $file_path . ' -> WRONG RESPONSE FROM update__write_to_db'); + } + + return $result; + } + + public static function processUa($file_path) + { + $result = \Cleantalk\Common\Firewall\Modules\AntiCrawler::update($file_path); + + if ( ! empty($result['error']) ) { + throw new SfwUpdateException('processUa: ' . $file_path . ' -> ' . $result['error']); + } + + if ( ! is_int($result) ) { + throw new SfwUpdateException('processUa: ' . $file_path . ' -> WRONG_RESPONSE AntiCrawler::update'); + } + + return $result; + } + + public static function processCk($file_path) + { + /** @var \Cleantalk\Common\Helper\Helper $helper_class */ + $helper_class = Mloader::get('Helper'); + + // Save expected_networks_count and expected_ua_count if exists + $file_content = file_get_contents($file_path); + + if ( ! function_exists('gzdecode') ) { + throw new SfwUpdateException('processCk: Function gzdecode not exists. Please update your PHP at least to version 5.4 '); + } + + $unzipped_content = gzdecode($file_content); + + if ( $unzipped_content === false ) { + throw new SfwUpdateException('processCk: Can not unpack datafile'); + } + + $fw_stats = Firewall::getFwStats(); + $file_ck_url__data = $helper_class::bufferParseCsv($unzipped_content); + + if ( ! empty($file_ck_url__data['error']) ) { + throw new SfwUpdateException('processCk: ' . $file_path . ' -> GET EXPECTED RECORDS COUNT DATA: ' . $file_ck_url__data['error']); + } + + $expected_networks_count = 0; + $expected_ua_count = 0; + + foreach ( $file_ck_url__data as $value ) { + if ( trim($value[0], '"') === 'networks_count' ) { + $expected_networks_count = $value[1]; + } + if ( trim($value[0], '"') === 'ua_count' ) { + $expected_ua_count = $value[1]; + } + } + + $fw_stats->expected_networks_count = $expected_networks_count; + $fw_stats->expected_ua_count = $expected_ua_count; + Firewall::saveFwStats($fw_stats); + + if ( file_exists($file_path) ) { + unlink($file_path); + } + } + + public static function processExclusions($_api_key) + { + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $result = \Cleantalk\Common\Firewall\Modules\Sfw::updateWriteToDbExclusions( + $db_obj, + $db_obj->prefix . APBCT_TBL_FIREWALL_DATA . '_temp' + ); + + if ( ! empty($result['error']) ) { + throw new SfwUpdateException('processExclusions: ' . $result['error']); + } + + if ( ! is_int($result) ) { + throw new SfwUpdateException('processExclusions: WRONG_RESPONSE update__write_to_db__exclusions'); + } + + /** + * Update expected_networks_count + */ + if ( $result > 0 ) { + $fw_stats->expected_networks_count += $result; + Firewall::saveFwStats($fw_stats); + } + + return array( + 'next_stage' => array( + 'name' => [self::class, 'endOfUpdate_renamingTables'], + 'accepted_tries' => 1 + ) + ); + } + + public static function endOfUpdate_renamingTables($_api_key) + { + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + if ( ! $db_obj->isTableExists($db_obj->prefix . APBCT_TBL_FIREWALL_DATA) ) { + throw new SfwUpdateException('endOfUpdate_renamingTables: SFW main table does not exist.'); + } + + if ( ! $db_obj->isTableExists($db_obj->prefix . APBCT_TBL_FIREWALL_DATA . '_temp') ) { + throw new SfwUpdateException('endOfUpdate_renamingTables: SFW temp table does not exist.'); + } + + $fw_stats->update_mode = 1; + Firewall::saveFwStats($fw_stats); + usleep(10000); + + // REMOVE AND RENAME + $result = \Cleantalk\Common\Firewall\Modules\Sfw::dataTablesDelete($db_obj, $db_obj->prefix . APBCT_TBL_FIREWALL_DATA); + if ( empty($result['error']) ) { + $result = \Cleantalk\Common\Firewall\Modules\Sfw::renameDataTablesFromTempToMain($db_obj, $db_obj->prefix . APBCT_TBL_FIREWALL_DATA); + } + + $fw_stats->update_mode = 0; + Firewall::saveFwStats($fw_stats); + + if ( ! empty($result['error']) ) { + throw new SfwUpdateException('endOfUpdate_renamingTables: '. $result['error']); + } + + return array( + 'next_stage' => array( + 'name' => [self::class, 'endOfUpdate_checkingData'], + 'accepted_tries' => 1 + ) + ); + } + + public static function endOfUpdate_checkingData($_api_key) + { + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + if ( ! $db_obj->isTableExists($db_obj->prefix . APBCT_TBL_FIREWALL_DATA) ) { + throw new SfwUpdateException('endOfUpdate_checkingData: SFW main table does not exist.'); + } + + $entries = $db_obj->setQuery('')->getVar('SELECT COUNT(*) FROM ' . $db_obj->prefix . APBCT_TBL_FIREWALL_DATA); + + /** + * Checking the integrity of the sfw database update + */ + if ( $entries != $fw_stats->expected_networks_count ) { + throw new SfwUpdateException('endOfUpdate_checkingData: ' + . 'The discrepancy between the amount of data received for the update and in the final table: ' + . $db_obj->prefix . APBCT_TBL_FIREWALL_DATA + . '. RECEIVED: ' . $fw_stats->expected_networks_count + . '. ADDED: ' . $entries); + } + + $fw_stats->entries = $entries; + Firewall::saveFwStats($fw_stats); + + return array( + 'next_stage' => array( + 'name' => [self::class, 'endOfUpdate_updatingStats'], + 'accepted_tries' => 1 + ) + ); + } + + public static function endOfUpdate_updatingStats($_api_key, $is_direct_update = false) + { + $fw_stats = Firewall::getFwStats(); + + $is_first_updating = ! $fw_stats->last_update_time; + $fw_stats->last_update_time = time(); + $fw_stats->last_update_way = $is_direct_update ? 'Direct update' : 'Queue update'; + Firewall::saveFwStats($fw_stats); + + return array( + 'next_stage' => array( + 'name' => [self::class, 'endOfUpdate'], + 'accepted_tries' => 1, + 'args' => $is_first_updating + ) + ); + } + + public static function endOfUpdate($_api_key, $is_first_updating = false) + { + // @ToDo implement errors handling + // Delete update errors + //$apbct->errorDelete('sfw_update', true); + + // @ToDo implement this! + // Running sfw update once again in 12 min if entries is < 4000 + /*if ( $is_first_updating && + $apbct->stats['sfw']['entries'] < 4000 + ) { + wp_schedule_single_event(time() + 720, 'apbct_sfw_update__init'); + }*/ + + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\Cron\Cron $cron_class */ + $cron_class = Mloader::get('Cron'); + + $cron = new $cron_class(); + $cron->updateTask('sfw_update', 'apbct_sfw_update__init', $fw_stats->update_period); + $cron->removeTask('sfw_update_checker'); + + self::removeUpdDir($fw_stats->updating_folder); + + // Reset all FW stats + $fw_stats->update_percent = 0; + $fw_stats->updating_id = null; + $fw_stats->expected_networks_count = false; + $fw_stats->expected_ua_count = false; + Firewall::saveFwStats($fw_stats); + + return true; + } + + private function prepareUpdDir() + { + $dir_name = $this->fwStats->updating_folder; + + if ( $dir_name === '' ) { + return array('error' => 'FW dir can not be blank.'); + } + + if ( ! is_dir($dir_name) ) { + if ( ! mkdir($dir_name) && ! is_dir($dir_name) ) { + return array('error' => 'Can not to make FW dir.'); + } + } else { + $files = glob($dir_name . '/*'); + if ( $files === false ) { + return array('error' => 'Can not find FW files.'); + } + if ( count($files) === 0 ) { + return (bool)file_put_contents($dir_name . 'index.php', ' 'Can not delete the FW file: ' . $file); + } + } + } + + return (bool)file_put_contents($dir_name . 'index.php', 'queue['stages']) ) { + foreach ( $queue->queue['stages'] as $stage ) { + if ( $stage['status'] === 'NULL' ) { + // @ToDo Have to be implemented this! + //return updateWorker(true); + } + } + } + + return true; + } + + function directUpdate() + { + // The Access key is empty + if ( empty($this->api_key) ) { + return array('error' => 'SFW DIRECT UPDATE: KEY_IS_EMPTY'); + } + + // Getting BL + $result = \Cleantalk\Common\Firewall\Modules\Sfw::directUpdateGetBlackLists($this->api_key); + + if ( empty($result['error']) ) { + + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $blacklists = $result['blacklist']; + $useragents = $result['useragents']; + $bl_count = $result['bl_count']; + $ua_count = $result['ua_count']; + + if ( isset($bl_count, $ua_count) ) { + $fw_stats->expected_networks_count = $bl_count; + $fw_stats->expected_ua_count = $ua_count; + Firewall::saveFwStats($fw_stats); + } + + // Preparing database infrastructure + // @ToDo need to implement returning result of the Activator::createTables work. + $db_tables_creator = new DbTablesCreator(); + $table_name = $db_obj->prefix . Schema::getSchemaTablePrefix() . 'sfw'; + $db_tables_creator->createTable($table_name); + + $result__creating_tmp_table = \Cleantalk\Common\Firewall\Modules\SFW::createTempTables($db_obj, $db_obj->prefix . APBCT_TBL_FIREWALL_DATA); + if ( ! empty($result__creating_tmp_table['error']) ) { + return array('error' => 'DIRECT UPDATING CREATE TMP TABLE: ' . $result__creating_tmp_table['error']); + } + + /** + * UPDATING UA LIST + */ + if ( $useragents ) { + $ua_result = \Cleantalk\Common\Firewall\Modules\AntiCrawler::directUpdate($useragents); + + if ( ! empty($ua_result['error']) ) { + return array('error' => 'DIRECT UPDATING UA LIST: ' . $result['error']); + } + + if ( ! is_int($ua_result) ) { + return array('error' => 'DIRECT UPDATING UA LIST: : WRONG_RESPONSE AntiCrawler::directUpdate'); + } + } + + /** + * UPDATING BLACK LIST + */ + $upd_result = \Cleantalk\Common\Firewall\Modules\SFW::directUpdate( + $db_obj, + $db_obj->prefix . APBCT_TBL_FIREWALL_DATA . '_temp', + $blacklists + ); + + if ( ! empty($upd_result['error']) ) { + return array('error' => 'DIRECT UPDATING BLACK LIST: ' . $upd_result['error']); + } + + if ( ! is_int($upd_result) ) { + return array('error' => 'DIRECT UPDATING BLACK LIST: WRONG RESPONSE FROM SFW::directUpdate'); + } + + /** + * UPDATING EXCLUSIONS LIST + */ + $excl_result = self::processExclusions(''); + + if ( ! empty($excl_result['error']) ) { + return array('error' => 'DIRECT UPDATING EXCLUSIONS: ' . $excl_result['error']); + } + + /** + * DELETING AND RENAMING THE TABLES + */ + $rename_tables_res = self::endOfUpdate_renamingTables(''); + if ( ! empty($rename_tables_res['error']) ) { + return array('error' => 'DIRECT UPDATING BLACK LIST: ' . $rename_tables_res['error']); + } + + /** + * CHECKING THE UPDATE + */ + $check_data_res = self::endOfUpdate_checkingData(''); + if ( ! empty($check_data_res['error']) ) { + return array('error' => 'DIRECT UPDATING BLACK LIST: ' . $check_data_res['error']); + } + + /** + * WRITE UPDATING STATS + */ + $update_stats_res = self::endOfUpdate_updatingStats('', true); + if ( ! empty($update_stats_res['error']) ) { + return array('error' => 'DIRECT UPDATING BLACK LIST: ' . $update_stats_res['error']); + } + + /** + * END OF UPDATE + */ + return self::endOfUpdate(''); + } + + return $result; + } + + public static function cleanData() + { + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + \Cleantalk\Common\Firewall\Modules\SFW::dataTablesDelete($db_obj, $db_obj->prefix . APBCT_TBL_FIREWALL_DATA . '_temp'); + + $fw_stats->firewall_update_percent = 0; + $fw_stats->firewall_updating_id = null; + Firewall::saveFwStats($fw_stats); + } + + public function updateFallback() + { + $fw_stats = Firewall::getFwStats(); + + /** + * Remove the upd folder + */ + if ( $fw_stats->updating_folder ) { + self::removeUpdDir($fw_stats->updating_folder); + } + + /** + * Remove SFW updating checker cron-task + */ + $cron = new \Cleantalk\Common\Cron\Cron(); + $cron->removeTask('sfw_update_checker'); + $cron->updateTask('sfw_update', 'apbct_sfw_update__init', $fw_stats->update_period); + + /** + * Remove _temp table + */ + self::cleanData(); + + /** + * Create SFW table if not exists + */ + self::createTables(''); + } + + private function isUpdateInProgress() + { + return (new $this->queue($this->api_key))->isQueueInProgress(); + } +} diff --git a/lib/Cleantalk/Common/Firewall/FwStats.php b/lib/Cleantalk/Common/Firewall/FwStats.php new file mode 100644 index 0000000..c57e81f --- /dev/null +++ b/lib/Cleantalk/Common/Firewall/FwStats.php @@ -0,0 +1,19 @@ +apbct = $apbct; + $this->db__table__logs = $log_table ?: null; + $this->db__table__ac_logs = $ac_logs_table ?: null; + $this->db__table__ac_ua_bl = defined('APBCT_TBL_AC_UA_BL') ? APBCT_TBL_AC_UA_BL : null; + $this->sign = md5( + Server::get('HTTP_USER_AGENT') . Server::get('HTTPS') . Server::get('HTTP_HOST') + ); + + foreach ( $params as $param_name => $param ) { + $this->$param_name = isset($this->$param_name) ? $param : false; + } + + $this->isExcluded = $this->checkExclusions(); + } + + public static function update($file_path_ua) + { + /** @var \Cleantalk\Common\Helper\Helper $helper_class */ + $helper_class = Mloader::get('Helper'); + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $ua_table = $db_obj->prefix . APBCT_TBL_AC_UA_BL; + + $file_content = file_get_contents($file_path_ua); + + if ( function_exists('gzdecode') ) { + $unzipped_content = gzdecode($file_content); + + if ( $unzipped_content !== false ) { + $lines = $helper_class::bufferParseCsv($unzipped_content); + + if ( empty($lines['errors']) ) { + $result__clear_db = self::clearDataTable($db_obj, $ua_table); + + if ( empty($result__clear_db['error']) ) { + for ( $count_result = 0; current($lines) !== false; ) { + $query = "INSERT INTO " . $ua_table . " (id, ua_template, ua_status) VALUES "; + + for ( + $i = 0, $values = array(); + APBCT_WRITE_LIMIT !== $i && current($lines) !== false; + $i++, $count_result++, next($lines) + ) { + $entry = current($lines); + + if ( empty($entry) || ! isset($entry[0], $entry[1]) ) { + continue; + } + + // Cast result to int + $ua_id = preg_replace('/[^\d]*/', '', $entry[0]); + $ua_template = isset($entry[1]) && Validate::isRegexp($entry[1]) + ? $helper_class::dbPrepareParam($entry[1]) + : 0; + $ua_status = isset($entry[2]) ? $entry[2] : 0; + + if ( ! $ua_template ) { + continue; + } + + $values[] = '(' . $ua_id . ',' . $ua_template . ',' . $ua_status . ')'; + } + + if ( ! empty($values) ) { + $query = $query . implode(',', $values) . ';'; + $db_obj->execute($query); + } + } + + if ( file_exists($file_path_ua) ) { + unlink($file_path_ua); + } + + return $count_result; + } else { + return $result__clear_db; + } + } else { + return array('error' => 'UAL_UPDATE_ERROR: ' . $lines['error']); + } + } else { + return array('error' => 'Can not unpack datafile'); + } + } else { + return array('error' => 'Function gzdecode not exists. Please update your PHP at least to version 5.4 '); + } + } + + public static function directUpdate($useragents) + { + /** @var \Cleantalk\Common\Helper\Helper $helper_class */ + $helper_class = Mloader::get('Helper'); + + /** @var \Cleantalk\Common\Db\Db $db_class */ + $db_class = Mloader::get('Db'); + $db_obj = $db_class::getInstance(); + + $ua_table = $db_obj->prefix . APBCT_TBL_AC_UA_BL; + + $result__clear_db = self::clearDataTable($db_obj, $ua_table); + + if ( empty($result__clear_db['error']) ) { + for ( $count_result = 0; current($useragents) !== false; ) { + $query = "INSERT INTO " . $ua_table . " (id, ua_template, ua_status) VALUES "; + + for ( + $i = 0, $values = array(); + APBCT_WRITE_LIMIT !== $i && current($useragents) !== false; + $i++, $count_result++, next($useragents) + ) { + $entry = current($useragents); + + if ( empty($entry) ) { + continue; + } + + // Cast result to int + // @ToDo check the output $entry + $ua_id = preg_replace('/[^\d]*/', '', $entry[0]); + $ua_template = isset($entry[1]) && Validate::isRegexp($entry[1]) ? $helper_class::dbPrepareParam($entry[1]) : 0; + $ua_status = isset($entry[2]) ? $entry[2] : 0; + + $values[] = '(' . $ua_id . ',' . $ua_template . ',' . $ua_status . ')'; + } + + if ( ! empty($values) ) { + $query = $query . implode(',', $values) . ';'; + $result = $db_obj->execute($query); + if ( $result === false ) { + return array( 'error' => $db_obj->getLastError() ); + } + } + } + + return $count_result; + } + + return $result__clear_db; + } + + private static function clearDataTable($db, $db__table__data) + { + $db->execute("TRUNCATE TABLE {$db__table__data};"); + $db->setQuery("SELECT COUNT(*) as cnt FROM {$db__table__data};")->fetch(); // Check if it is clear + if ( $db->result['cnt'] != 0 ) { + $db->execute("DELETE FROM {$db__table__data};"); // Truncate table + $db->setQuery("SELECT COUNT(*) as cnt FROM {$db__table__data};")->fetch(); // Check if it is clear + if ( $db->result['cnt'] != 0 ) { + return array('error' => 'COULD_NOT_CLEAR_UA_BL_TABLE'); // throw an error + } + } + $db->execute("ALTER TABLE {$db__table__data} AUTO_INCREMENT = 1;"); // Drop AUTO INCREMENT + } + + /** + * Use this method to execute main logic of the module. + * + * @return array Array of the check results + */ + public function check() + { + $results = array(); + + foreach ( $this->ip_array as $_ip_origin => $current_ip ) { + // Skip by 301 response code + if ( $this->isRedirected() ) { + $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_ANTICRAWLER',); + + return $results; + } + + // UA check + $ua_bl_results = $this->db->fetchAll( + "SELECT * FROM " . $this->db__table__ac_ua_bl . " ORDER BY `ua_status` DESC;" + ); + + if ( ! empty($ua_bl_results) ) { + $is_blocked = false; + + foreach ( $ua_bl_results as $ua_bl_result ) { + if ( + ! empty($ua_bl_result['ua_template']) && preg_match( + "%" . str_replace('"', '', $ua_bl_result['ua_template']) . "%i", + Server::get('HTTP_USER_AGENT') + ) + ) { + $this->ua_id = $ua_bl_result['id']; + + if ( $ua_bl_result['ua_status'] == 1 ) { + // Whitelisted + $results[] = array( + 'ip' => $current_ip, + 'is_personal' => false, + 'status' => 'PASS_ANTICRAWLER_UA', + ); + + return $results; + } else { + // Blacklisted + $results[] = array( + 'ip' => $current_ip, + 'is_personal' => false, + 'status' => 'DENY_ANTICRAWLER_UA', + ); + $is_blocked = true; + break; + } + } + } + + if ( ! $is_blocked ) { + $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_ANTICRAWLER_UA',); + } + } + + // Skip by cookie + if ( + Cookie::get('wordpress_apbct_antibot') == hash( + 'sha256', + $this->api_key . $this->apbct->data['salt'] + ) + ) { + if ( Cookie::get('apbct_anticrawler_passed') == 1 ) { + if ( ! headers_sent() ) { + Cookie::set('apbct_anticrawler_passed', '0', time() - 86400, '/', '', null, true, 'Lax'); + } + + // Do logging an one passed request + $this->updateLog($current_ip, 'PASS_ANTICRAWLER'); + } + + $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_ANTICRAWLER',); + + return $results; + } + } + + // Common check + foreach ( $this->ip_array as $_ip_origin => $current_ip ) { + // IP check + $result = $this->db->fetch( + "SELECT ip" + . ' FROM `' . $this->db__table__ac_logs . '`' + . " WHERE ip = '$current_ip'" + . " AND ua = '$this->sign' AND " . rand(1, 100000) . ";" + ); + if ( isset($result['ip']) ) { + if ( + Cookie::get('wordpress_apbct_antibot') !== hash( + 'sha256', + $this->api_key . $this->apbct->data['salt'] + ) + ) { + $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'DENY_ANTICRAWLER',); + } else { + if ( Cookie::get('apbct_anticrawler_passed') === '1' ) { + if ( ! headers_sent() ) { + \Cleantalk\ApbctWP\Variables\Cookie::set( + 'apbct_anticrawler_passed', + '0', + time() - 86400, + '/', + '', + false, + true, + 'Lax' + ); + } + + $results[] = array( + 'ip' => $current_ip, + 'is_personal' => false, + 'status' => 'PASS_ANTICRAWLER', + ); + + return $results; + } + } + } else { + if ( ! Cookie::get('wordpress_apbct_antibot') ) { + add_action('template_redirect', array(& $this, 'updateAcLog'), 999); + } + + add_action('wp_head', array('\Cleantalk\ApbctWP\Firewall\AntiCrawler', 'setCookie')); + add_action('login_head', array('\Cleantalk\ApbctWP\Firewall\AntiCrawler', 'setCookie')); + } + } + + return $results; + } + + public function updateAcLog() + { + $interval_time = Helper::timeGetIntervalStart($this->store_interval); + + foreach ( $this->ip_array as $_ip_origin => $current_ip ) { + $id = md5($current_ip . $this->sign . $interval_time); + $this->db->execute( + "INSERT INTO " . $this->db__table__ac_logs . " SET + id = '$id', + ip = '$current_ip', + ua = '$this->sign', + entries = 1, + interval_start = $interval_time + ON DUPLICATE KEY UPDATE + ip = ip, + entries = entries + 1, + interval_start = $interval_time;" + ); + } + } + + + public static function setCookie() + { + global $apbct; + + if ( $apbct->data['cookies_type'] === 'none' && ! is_admin() ) { + return; + } + + $script = + ""; + + echo $script; + } + + /** + * Add entry to SFW log. + * Writes to database. + * + * @param string $ip + * @param $status + */ + public function updateLog($ip, $status) + { + /** @psalm-suppress InvalidLiteralArgument */ + + if ( strpos('_UA', $status) !== false ) { + $id_str = $ip . $this->module_name . '_UA'; + } else { + $id_str = $ip . $this->module_name; + } + $id = md5($id_str); + $time = time(); + + $query = "INSERT INTO " . $this->db__table__logs . " + SET + id = '$id', + ip = '$ip', + status = '$status', + all_entries = 1, + blocked_entries = " . (strpos($status, 'DENY') !== false ? 1 : 0) . ", + entries_timestamp = '" . $time . "', + ua_id = " . $this->ua_id . ", + ua_name = %s, + first_url = %s, + last_url = %s + ON DUPLICATE KEY + UPDATE + status = '$status', + all_entries = all_entries + 1, + blocked_entries = blocked_entries" . (strpos($status, 'DENY') !== false ? ' + 1' : '') . ", + entries_timestamp = '" . $time . "', + ua_id = " . $this->ua_id . ", + ua_name = %s, + last_url = %s"; + + $this->db->prepare( + $query, + array( + Server::get('HTTP_USER_AGENT'), + substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), + substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), + + Server::get('HTTP_USER_AGENT'), + substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), + ) + ); + $this->db->execute($this->db->getQuery()); + } + + public function diePage($result) + { + global $apbct; + + // File exists? + if ( file_exists(CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_anticrawler.html") ) { + $this->sfw_die_page = file_get_contents( + CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_anticrawler.html" + ); + + $js_url = APBCT_URL_PATH . '/js/apbct-public-bundle.min.js?' . APBCT_VERSION; + + $net_count = $apbct->stats['sfw']['entries']; + + // Custom Logo + $custom_logo_img = ''; + $custom_logo_id = isset($apbct->settings['cleantalk_custom_logo']) ? $apbct->settings['cleantalk_custom_logo'] : false; + + if ($custom_logo_id && ($image_attributes = wp_get_attachment_image_src($custom_logo_id, array(150, 150)))) { + $custom_logo_img = ''; + } + + $block_message = sprintf( + esc_html__( + 'Anti-Crawler Protection is checking your browser and IP %s for spam bots', + 'cleantalk-spam-protect' + ), + '' . $result['ip'] . '' + ); + + // Translation + $replaces = array( + '{SFW_DIE_NOTICE_IP}' => $block_message, + '{SFW_DIE_MAKE_SURE_JS_ENABLED}' => __( + 'To continue working with the web site, please make sure that you have enabled JavaScript.', + 'cleantalk-spam-protect' + ), + '{SFW_DIE_YOU_WILL_BE_REDIRECTED}' => + sprintf( + __('You will be automatically redirected to the requested page after %d seconds.', 'cleantalk-spam-protect'), + 3 + ) . '
' + . __('Don\'t close this page. Please, wait for 3 seconds to pass to the page.', 'cleantalk-spam-protect'), + '{CLEANTALK_TITLE}' => __('Anti-Spam by CleanTalk', 'cleantalk-spam-protect'), + '{REMOTE_ADDRESS}' => $result['ip'], + '{SERVICE_ID}' => $this->apbct->data['service_id'] . ', ' . $net_count, + '{HOST}' => get_home_url() . ', ' . APBCT_VERSION, + '{COOKIE_ANTICRAWLER}' => hash('sha256', $apbct->api_key . $apbct->data['salt']), + '{COOKIE_ANTICRAWLER_PASSED}' => '1', + '{GENERATED}' => '

The page was generated at ' . date('D, d M Y H:i:s') . "

", + '{SCRIPT_URL}' => $js_url, + + // Custom Logo + '{CUSTOM_LOGO}' => $custom_logo_img + ); + + foreach ( $replaces as $place_holder => $replace ) { + $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); + } + + if ( Get::get('debug') ) { + $debug = '

Headers

' + . str_replace("\n", "
", print_r(\apache_request_headers(), true)) + . '

$_SERVER

' + . str_replace("\n", "
", print_r($_SERVER, true)) + . '

AC_LOG_RESULT

' + . str_replace("\n", "
", print_r($this->ac_log_result, true)) + . '

IPS

' + . str_replace("\n", "
", print_r($this->ip_array, true)); + } else { + $debug = ''; + } + $this->sfw_die_page = str_replace("{DEBUG}", $debug, $this->sfw_die_page); + } + + add_action('init', array($this, 'printDiePage')); + } + + public function printDiePage() + { + global $apbct; + + parent::diePage(''); + + $localize_js = array( + '_ajax_nonce' => wp_create_nonce('ct_secret_stuff'), + '_rest_nonce' => wp_create_nonce('wp_rest'), + '_ajax_url' => admin_url('admin-ajax.php', 'relative'), + '_rest_url' => esc_url(get_rest_url()), + 'data__cookies_type' => $apbct->data['cookies_type'], + 'data__ajax_type' => $apbct->data['ajax_type'], + 'sfw__random_get' => $apbct->settings['sfw__random_get'] === '1' || + ($apbct->settings['sfw__random_get'] === '-1' && apbct_is_cache_plugins_exists()), + 'cookiePrefix' => apbct__get_cookie_prefix(), + ); + + $localize_js_public = array( + 'pixel__setting' => $apbct->settings['data__pixel'], + 'pixel__enabled' => $apbct->settings['data__pixel'] === '2' || + ($apbct->settings['data__pixel'] === '3' && apbct_is_cache_plugins_exists()), + 'pixel__url' => $apbct->pixel_url, + 'data__email_check_before_post' => $apbct->settings['data__email_check_before_post'], + 'data__cookies_type' => $apbct->data['cookies_type'], + 'data__visible_fields_required' => ! apbct_is_user_logged_in() || $apbct->settings['data__protect_logged_in'] == 1, + ); + + $js_jquery_url = includes_url() . 'js/jquery/jquery.min.js'; + + $replaces = array( + '{JQUERY_SCRIPT_URL}' => $js_jquery_url, + '{LOCALIZE_SCRIPT}' => 'var ctPublicFunctions = ' . json_encode($localize_js) . ';' . + 'var ctPublic = ' . json_encode($localize_js_public) . ';', + ); + + foreach ( $replaces as $place_holder => $replace ) { + $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); + } + + http_response_code(403); + + // File exists? + if ( file_exists(CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_sfw.html") ) { + die($this->sfw_die_page); + } + + die("IP BLACKLISTED. Blocked by AntiCrawler " . $this->apbct->stats['last_sfw_block']['ip']); + } + + private function checkExclusions() + { + $allowed_roles = array('administrator', 'editor'); + $user = apbct_wp_get_current_user(); + + if ( ! $user ) { + return false; + } + + foreach ( $allowed_roles as $role ) { + if ( in_array($role, (array)$user->roles) ) { + return true; + } + } + + return false; + } + + private function isRedirected() + { + $is_redirect = false; + if ( Server::get('HTTP_REFERER') !== '' && Server::get('HTTP_HOST') !== '' && $this->isCloudflare() ) { + $parse_referer = parse_url(Server::get('HTTP_REFERER')); + if ( $parse_referer && isset($parse_referer['host']) ) { + $is_redirect = Server::get('HTTP_HOST') !== $parse_referer['host']; + } + } + + return http_response_code() === 301 || http_response_code() === 302 || $is_redirect; + } + + private function isCloudflare() + { + return Server::get('HTTP_CF_RAY') && Server::get('HTTP_CF_CONNECTING_IP') && Server::get('HTTP_CF_REQUEST_ID'); + } + + /** + * Clear table APBCT_TBL_AC_LOG + * once a day + */ + public function clearTable() + { + $interval_start = \Cleantalk\ApbctWP\Helper::timeGetIntervalStart($this->store_interval); + + $this->db->execute( + 'DELETE + FROM ' . $this->db__table__ac_logs . ' + WHERE interval_start < ' . $interval_start . ' + AND ua <> "" + LIMIT 100000;' + ); + } + + /** + * @inheritDoc + */ + public function actionsForDenied($result) + { + // TODO: Implement actionsForDenied() method. + } + + /** + * @inheritDoc + */ + public function actionsForPassed($result) + { + // TODO: Implement actionsForPassed() method. + } +} diff --git a/lib/Cleantalk/Common/Firewall/Modules/AntiFlood.php b/lib/Cleantalk/Common/Firewall/Modules/AntiFlood.php new file mode 100644 index 0000000..a8a4e0b --- /dev/null +++ b/lib/Cleantalk/Common/Firewall/Modules/AntiFlood.php @@ -0,0 +1,345 @@ +db__table__logs = $log_table ?: null; + $this->db__table__ac_logs = $ac_logs_table ?: null; + $this->db__table__ac_ua_bl = defined('APBCT_TBL_AC_UA_BL') ? APBCT_TBL_AC_UA_BL : null; + + foreach ($params as $param_name => $param) { + $this->$param_name = isset($this->$param_name) ? $param : false; + } + + $this->isExcluded = $this->checkExclusions(); + } + + /** + * Use this method to execute main logic of the module. + * @return array + */ + public function check() + { + $results = array(); + + $this->clearTable(); + + $time = time() - $this->store_interval; + + foreach ($this->ip_array as $_ip_origin => $current_ip) { + // UA check + $ua_bl_results = $this->db->fetchAll( + "SELECT * FROM " . $this->db__table__ac_ua_bl . " ORDER BY `ua_status` DESC;" + ); + + if ( ! empty($ua_bl_results)) { + foreach ($ua_bl_results as $ua_bl_result) { + if ( + ! empty($ua_bl_result['ua_template']) && + preg_match("%" . str_replace('"', '', $ua_bl_result['ua_template']) . "%i", Server::get('HTTP_USER_AGENT')) + ) { + if ($ua_bl_result['ua_status'] == 1) { + // Whitelisted + $results[] = array( + 'ip' => $current_ip, + 'is_personal' => false, + 'status' => 'PASS_ANTIFLOOD_UA', + ); + + return $results; + } + } + } + } + + // Passed + if (Cookie::get('apbct_antiflood_passed') === md5($current_ip . $this->api_key)) { + if ( ! headers_sent()) { + Cookie::set('apbct_antiflood_passed', '0', time() - 86400, '/', '', null, true); + } + + // Do logging an one passed request + $this->updateLog($current_ip, 'PASS_ANTIFLOOD'); + + $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_ANTIFLOOD',); + + return $results; + } + + + // @todo Rename ip column to sign. Use IP + UserAgent for it. + + $result = $this->db->fetchAll( + "SELECT SUM(entries) as total_count" + . ' FROM `' . $this->db__table__ac_logs . '`' + . " WHERE ip = '$current_ip' AND interval_start > '$time' AND " . rand(1, 100000) . ";" + ); + + if ( ! empty($result) && isset($result[0]['total_count']) && $result[0]['total_count'] >= $this->view_limit) { + $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'DENY_ANTIFLOOD',); + } + } + + if ( ! empty($results)) { + // Do block page + return $results; + } else { + // Do logging entries + add_action('template_redirect', array(& $this, 'updateAcLog'), 999); + } + + return $results; + } + + public function updateAcLog() + { + $interval_time = Helper::timeGetIntervalStart($this->store_interval); + + // @todo Rename ip column to sign. Use IP + UserAgent for it. + + foreach ($this->ip_array as $_ip_origin => $current_ip) { + $id = md5($current_ip . $interval_time); + $this->db->execute( + "INSERT INTO " . $this->db__table__ac_logs . " SET + id = '$id', + ip = '$current_ip', + entries = 1, + interval_start = $interval_time + ON DUPLICATE KEY UPDATE + ip = ip, + entries = entries + 1, + interval_start = $interval_time;" + ); + } + } + + public function clearTable() + { + if (rand(0, 100) < $this->chance_to_clean) { + $interval_start = \Cleantalk\ApbctWP\Helper::timeGetIntervalStart($this->store_interval); + $this->db->execute( + 'DELETE + FROM ' . $this->db__table__ac_logs . ' + WHERE interval_start < ' . $interval_start . ' + AND ua = "" + LIMIT 100000;' + ); + } + } + + /** + * Add entry to SFW log. + * Writes to database. + * + * @param string $ip + * @param $status + */ + public function updateLog($ip, $status) + { + $id = md5($ip . $this->module_name); + $time = time(); + + $query = "INSERT INTO " . $this->db__table__logs . " + SET + id = '$id', + ip = '$ip', + status = '$status', + all_entries = 1, + blocked_entries = " . (strpos($status, 'DENY') !== false ? 1 : 0) . ", + entries_timestamp = '" . $time . "', + ua_name = %s + ON DUPLICATE KEY + UPDATE + status = '$status', + all_entries = all_entries + 1, + blocked_entries = blocked_entries" . (strpos($status, 'DENY') !== false ? ' + 1' : '') . ", + entries_timestamp = '" . $time . "', + ua_name = %s"; + + $this->db->prepare($query, array(Server::get('HTTP_USER_AGENT'), Server::get('HTTP_USER_AGENT'))); + $this->db->execute($this->db->getQuery()); + } + + public function diePage($result) + { + global $apbct; + + // File exists? + if (file_exists(CLEANTALK_PLUGIN_DIR . 'lib/Cleantalk/ApbctWP/Firewall/die_page_antiflood.html')) { + $this->sfw_die_page = file_get_contents( + CLEANTALK_PLUGIN_DIR . 'lib/Cleantalk/ApbctWP/Firewall/die_page_antiflood.html' + ); + + $js_url = APBCT_URL_PATH . '/js/apbct-public-bundle.min.js?' . APBCT_VERSION; + + $net_count = $apbct->stats['sfw']['entries']; + + // Custom Logo + $custom_logo_img = ''; + $custom_logo_id = isset($apbct->settings['cleantalk_custom_logo']) ? $apbct->settings['cleantalk_custom_logo'] : false; + + if ($custom_logo_id && ($image_attributes = wp_get_attachment_image_src($custom_logo_id, array(150, 150)))) { + $custom_logo_img = ''; + } + + // Translation + $replaces = array( + '{SFW_DIE_NOTICE_IP}' => __( + 'Anti-Flood is activated for your IP', + 'cleantalk-spam-protect' + ), + '{SFW_DIE_MAKE_SURE_JS_ENABLED}' => __( + 'To continue working with the web site, please make sure that you have enabled JavaScript.', + 'cleantalk-spam-protect' + ), + '{SFW_DIE_YOU_WILL_BE_REDIRECTED}' => sprintf( + __( + 'You will be automatically redirected to the requested page after %d seconds.', + 'cleantalk-spam-protect' + ), + 30 + ), + '{CLEANTALK_TITLE}' => __('Anti-Spam by CleanTalk', 'cleantalk-spam-protect'), + '{REMOTE_ADDRESS}' => $result['ip'], + '{REQUEST_URI}' => Server::get('REQUEST_URI'), + '{SERVICE_ID}' => $this->apbct->data['service_id'] . ', ' . $net_count, + '{HOST}' => get_home_url() . ', ' . APBCT_VERSION, + '{GENERATED}' => '

The page was generated at ' . date('D, d M Y H:i:s') . "

", + '{COOKIE_ANTIFLOOD_PASSED}' => md5($this->api_key . $result['ip']), + '{SCRIPT_URL}' => $js_url, + + // Custom Logo + '{CUSTOM_LOGO}' => $custom_logo_img + ); + + foreach ($replaces as $place_holder => $replace) { + $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); + } + + add_action('init', array($this, 'printDiePage')); + } + } + + public function printDiePage() + { + global $apbct; + + parent::diePage(''); + + $localize_js = array( + '_ajax_nonce' => wp_create_nonce('ct_secret_stuff'), + '_rest_nonce' => wp_create_nonce('wp_rest'), + '_ajax_url' => admin_url('admin-ajax.php', 'relative'), + '_rest_url' => esc_url(get_rest_url()), + 'data__cookies_type' => $apbct->data['cookies_type'], + 'data__ajax_type' => $apbct->data['ajax_type'], + 'sfw__random_get' => $apbct->settings['sfw__random_get'] === '1' || + ($apbct->settings['sfw__random_get'] === '-1' && apbct_is_cache_plugins_exists()), + 'cookiePrefix' => apbct__get_cookie_prefix(), + ); + + $localize_js_public = array( + 'pixel__setting' => $apbct->settings['data__pixel'], + 'pixel__enabled' => $apbct->settings['data__pixel'] === '2' || + ($apbct->settings['data__pixel'] === '3' && apbct_is_cache_plugins_exists()), + 'pixel__url' => $apbct->pixel_url, + 'data__email_check_before_post' => $apbct->settings['data__email_check_before_post'], + 'data__cookies_type' => $apbct->data['cookies_type'], + 'data__visible_fields_required' => ! apbct_is_user_logged_in() || $apbct->settings['data__protect_logged_in'] == 1, + ); + + $js_jquery_url = includes_url() . 'js/jquery/jquery.min.js'; + + $replaces = array( + '{JQUERY_SCRIPT_URL}' => $js_jquery_url, + '{LOCALIZE_SCRIPT}' => 'var ctPublicFunctions = ' . json_encode($localize_js) . ';' . + 'var ctPublic = ' . json_encode($localize_js_public) . ';', + ); + + foreach ($replaces as $place_holder => $replace) { + $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); + } + + http_response_code(403); + + // File exists? + if (file_exists(CLEANTALK_PLUGIN_DIR . "lib/Cleantalk/ApbctWP/Firewall/die_page_sfw.html")) { + die($this->sfw_die_page); + } + + die("IP BLACKLISTED. Blocked by AntiFlood " . $this->apbct->stats['last_sfw_block']['ip']); + } + + private function checkExclusions() + { + $allowed_roles = array('administrator', 'editor'); + $user = apbct_wp_get_current_user(); + + if ( ! $user) { + return false; + } + + foreach ($allowed_roles as $role) { + if (in_array($role, (array)$user->roles)) { + return true; + } + } + + return false; + } + + /** + * @inheritDoc + */ + public function actionsForDenied($result) + { + // TODO: Implement actionsForDenied() method. + } + + /** + * @inheritDoc + */ + public function actionsForPassed($result) + { + // TODO: Implement actionsForPassed() method. + } +} diff --git a/lib/Cleantalk/Common/Firewall/Modules/SFW.php b/lib/Cleantalk/Common/Firewall/Modules/SFW.php deleted file mode 100644 index 989079b..0000000 --- a/lib/Cleantalk/Common/Firewall/Modules/SFW.php +++ /dev/null @@ -1,281 +0,0 @@ -db = DB::getInstance(); - $this->db_data_table_name = $this->db->prefix . $data_table ?: null; - - foreach( $params as $param_name => $param ){ - $this->$param_name = isset( $this->$param_name ) ? $param : false; - } - } - - /** - * Use this method to execute main logic of the module. - * - * @return array Array of the check results - */ - public function check() - { - $results = array(); - $status = 0; - - // @ToDo add counter check to avoid direct db request - if (!$this->db->is_table_exists($this->db_data_table_name)) { - return $results; - } - - // Skip by cookie - foreach( $this->ip_array as $current_ip ){ - - if( substr( Cookie::get( 'ct_sfw_pass_key' ), 0, 32 ) == md5( $current_ip . $this->api_key ) ){ - - if( Cookie::get( 'ct_sfw_passed' ) ){ - - if( ! headers_sent() ){ - \Cleantalk\Common\Helper::apbct_cookie__set( 'ct_sfw_passed', '0', time() + 86400 * 3, '/', null, false, true, 'Lax' ); - } else { - $results[] = array( 'ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_SFW__BY_COOKIE', ); - } - - // Do logging an one passed request - $this->update_log( $current_ip, 'PASS_SFW' ); - - if( $this->sfw_counter ){ - // @ToDo have to implement the logic of incrementing and saving count of all handled requests. - } - - } - - if( strlen( Cookie::get( 'ct_sfw_pass_key' ) ) > 32 ) { - $status = substr( Cookie::get( 'ct_sfw_pass_key' ), -1 ); - } - - if( $status ) { - $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_SFW__BY_WHITELIST',); - } - - return $results; - } - } - // Common check - - foreach( $this->ip_array as $origin => $current_ip ) - { - $current_ip_v4 = sprintf("%u", ip2long($current_ip)); - for ( $needles = array(), $m = 6; $m <= 32; $m ++ ) { - $mask = str_repeat( '1', $m ); - $mask = str_pad( $mask, 32, '0' ); - $needles[] = sprintf( "%u", bindec( $mask & base_convert( $current_ip_v4, 10, 2 ) ) ); - } - $needles = array_unique( $needles ); - $db_results = $this->db->fetch_all("SELECT - network, mask, status - FROM " . $this->db_data_table_name . " - WHERE network IN (". implode( ',', $needles ) .") - AND network = " . $current_ip_v4 . " & mask - AND " . rand( 1, 100000 ) . " - ORDER BY status DESC"); - - if( ! empty( $db_results ) ){ - - foreach( $db_results as $db_result ){ - - if( $db_result['status'] == 1 ) { - $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_SFW__BY_WHITELIST',); - break; - } - else - $results[] = array('ip' => $current_ip, 'is_personal' => false, 'status' => 'DENY_SFW',); - - } - - }else{ - - $results[] = array( 'ip' => $current_ip, 'is_personal' => false, 'status' => 'PASS_SFW' ); - - } - } - - return $results; - } - - /** - * Add entry to SFW log. - * Writes to database. - * - * @param string $ip - * @param string $status - */ - public function update_log( $ip, $status ) - { - $ip_version = \Cleantalk\Common\Helper::ip__validate( $ip ); - - if (!$ip_version || $ip_version === 'v6') { - return; - } - - $id = md5( $ip . $this->module_name ); - $time = time(); - - $query = "INSERT INTO " . $this->db_log_table_name . " - SET - id = '$id', - ip = '$ip', - status = '$status', - all_entries = 1, - blocked_entries = " . ( strpos( $status, 'DENY' ) !== false ? 1 : 0 ) . ", - entries_timestamp = '" . $time . "', - ua_name = '" . addslashes(Server::get('HTTP_USER_AGENT')) . "' - ON DUPLICATE KEY - UPDATE - status = '$status', - all_entries = all_entries + 1, - blocked_entries = blocked_entries" . ( strpos( $status, 'DENY' ) !== false ? ' + 1' : '' ) . ", - entries_timestamp = '" . intval( $time ) . "', - ua_name = '" . addslashes(Server::get('HTTP_USER_AGENT')) . "'"; - - $this->db->execute( $query ); - } - - /** - * @inheritdoc - */ - public function actionsForDenied( $result ) - { - if( $this->sfw_counter ){ - // @ToDo have to implement the logic of incrementing and saving count of blocked requests. - } - } - - /** - * @inheritdoc - */ - public function actionsForPassed( $result ) - { - if( $this->set_cookies && ! headers_sent() ) { - $status = $result['status'] === 'PASS_SFW__BY_WHITELIST' ? '1' : '0'; - $cookie_val = md5( $result['ip'] . $this->api_key ) . $status; - $helper = $this->helper; - $helper::apbct_cookie__set( 'ct_sfw_pass_key', $cookie_val, time() + 86400 * 30, '/', null, false ); - } - } - - /** - * @inheritdoc - */ - public function _die( $result ) - { - - parent::_die( $result ); - - // Statistics - if( ! empty( $this->blocked_ips ) ){ - reset($this->blocked_ips); - // @ToDo have to implement the logic of saving last_sfw_block info. - /* - $this->apbct->stats['last_sfw_block']['time'] = time(); - $this->apbct->stats['last_sfw_block']['ip'] = $result['ip']; - $this->apbct->save('stats'); - */ - } - - // File exists? - if( file_exists( __DIR__ . "/die_page_sfw.html" ) ){ - - $sfw_die_page = file_get_contents( __DIR__ . "/die_page_sfw.html" ); - - $net_count = $this->db->fetch( 'SELECT COUNT(*) as net_count FROM ' . $this->db_data_table_name )['net_count']; - - $status = $result['status'] === 'PASS_SFW__BY_WHITELIST' ? '1' : '0'; - $cookie_val = md5( $result['ip'] . $this->api_key ) . $status; - - // Translation - $replaces = array( - '{SFW_DIE_NOTICE_IP}' => $this->__('SpamFireWall is activated for your IP ', 'cleantalk-spam-protect'), - '{SFW_DIE_MAKE_SURE_JS_ENABLED}' => $this->__( 'To continue working with the web site, please make sure that you have enabled JavaScript.', 'cleantalk-spam-protect' ), - '{SFW_DIE_CLICK_TO_PASS}' => $this->__('Please click the link below to pass the protection,', 'cleantalk-spam-protect'), - '{SFW_DIE_YOU_WILL_BE_REDIRECTED}' => sprintf( $this->__('Or you will be automatically redirected to the requested page after %d seconds.', 'cleantalk-spam-protect'), 3), - '{CLEANTALK_TITLE}' => ($this->test ? $this->__('This is the testing page for SpamFireWall', 'cleantalk-spam-protect') : ''), - '{REMOTE_ADDRESS}' => $result['ip'], - '{SERVICE_ID}' => $net_count, - '{HOST}' => '', - '{GENERATED}' => '

The page was generated at ' . date( 'D, d M Y H:i:s' ) . "

", - '{REQUEST_URI}' => Server::get( 'REQUEST_URI' ), - - // Cookie - '{COOKIE_PREFIX}' => '', - '{COOKIE_DOMAIN}' => $this->cookie_domain, - '{COOKIE_SFW}' => $this->test ? $this->test_ip : $cookie_val, - - // Test - '{TEST_TITLE}' => '', - '{REAL_IP__HEADER}' => '', - '{TEST_IP__HEADER}' => '', - '{TEST_IP}' => '', - '{REAL_IP}' => '', - ); - - // Test - if($this->test){ - $replaces['{TEST_TITLE}'] = $this->__( 'This is the testing page for SpamFireWall', 'cleantalk-spam-protect' ); - $replaces['{REAL_IP__HEADER}'] = 'Real IP:'; - $replaces['{TEST_IP__HEADER}'] = 'Test IP:'; - $replaces['{TEST_IP}'] = $this->test_ip; - $replaces['{REAL_IP}'] = $this->real_ip; - } - - // Debug - if($this->debug){ - $debug = '

Headers

' - . var_export( apache_request_headers(), true ) - . '

REMOTE_ADDR

' - . Server::get( 'REMOTE_ADDR' ) - . '

SERVER_ADDR

' - . Server::get( 'REMOTE_ADDR' ) - . '

IP_ARRAY

' - . var_export( $this->ip_array, true ) - . '

ADDITIONAL

' - . var_export( $this->debug_data, true ); - } - $replaces['{DEBUG}'] = isset( $debug ) ? $debug : ''; - - foreach( $replaces as $place_holder => $replace ){ - $sfw_die_page = str_replace( $place_holder, $replace, $sfw_die_page ); - } - - die( $sfw_die_page ); - - } - - die( "IP BLACKLISTED. Blocked by SFW " . $result['ip'] ); - - } - -} \ No newline at end of file diff --git a/lib/Cleantalk/Common/Firewall/Modules/Sfw.php b/lib/Cleantalk/Common/Firewall/Modules/Sfw.php new file mode 100644 index 0000000..809d554 --- /dev/null +++ b/lib/Cleantalk/Common/Firewall/Modules/Sfw.php @@ -0,0 +1,809 @@ +db__table__data = $data_table ?: null; + $this->db__table__logs = $log_table ?: null; + + foreach ($params as $param_name => $param) { + $this->$param_name = isset($this->$param_name) ? $param : false; + } + + $this->debug = (bool)Get::get('debug'); + } + + /** + * @inheritDoc + */ + public function ipAppendAdditional(& $ips) + { + $this->real_ip = isset($ips['real']) ? $ips['real'] : null; + + if (Get::get('sfw_test_ip')) { + if ($this->helper::ipValidate(Get::get('sfw_test_ip')) !== false) { + $ips['sfw_test'] = Get::get('sfw_test_ip'); + $this->test_ip = Get::get('sfw_test_ip'); + $this->test = true; + } + } + } + + /** + * Use this method to execute main logic of the module. + * + * @return array Array of the check results + */ + public function check() + { + $results = array(); + $status = 0; + + if ( $this->test ) { + unset($_COOKIE['ct_sfw_pass_key']); + Cookie::set('ct_sfw_pass_key', '0'); + } + + // Skip by cookie + foreach ($this->ip_array as $current_ip) { + if ( + Cookie::get('ct_sfw_pass_key') + && strpos(Cookie::get('ct_sfw_pass_key'), md5($current_ip . $this->api_key)) === 0 + ) { + if (Cookie::get('ct_sfw_passed')) { + if ( ! headers_sent()) { + Cookie::set( + 'ct_sfw_passed', + '0', + time() + 86400 * 3, + '/', + '', + null, + true + ); + } else { + $results[] = array( + 'ip' => $current_ip, + 'is_personal' => false, + 'status' => 'PASS_SFW__BY_COOKIE' + ); + } + + // Do logging one passed request + $this->updateLog($current_ip, 'PASS_SFW'); + } + + if (strlen(Cookie::get('ct_sfw_pass_key')) > 32) { + $status = substr(Cookie::get('ct_sfw_pass_key'), -1); + } + + if ($status) { + $results[] = array( + 'ip' => $current_ip, + 'is_personal' => false, + 'status' => 'PASS_SFW__BY_WHITELIST' + ); + } + + return $results; + } + } + + // Common check + foreach ($this->ip_array as $_origin => $current_ip) { + $current_ip_v4 = sprintf("%u", ip2long($current_ip)); + for ($needles = array(), $m = 6; $m <= 32; $m++) { + $mask = str_repeat('1', $m); + $mask = str_pad($mask, 32, '0'); + $needles[] = sprintf("%u", bindec($mask & base_convert($current_ip_v4, 10, 2))); + } + $needles = array_unique($needles); + + $query = "SELECT + network, mask, status, source + FROM " . $this->db__table__data . " + WHERE network IN (" . implode(',', $needles) . ") + AND network = " . $current_ip_v4 . " & mask + AND " . rand(1, 100000) . " + ORDER BY status DESC LIMIT 1"; + + $db_results = $this->db->fetchAll($query); + + $test_status = 1; + if ( ! empty($db_results)) { + foreach ($db_results as $db_result) { + $result_entry = array( + 'ip' => $current_ip, + 'network' => $this->helper::ipLong2ip($db_result['network']) + . '/' + . $this->helper::ipMaskLongToNumber((int)$db_result['mask']), + 'is_personal' => $db_result['source'], + ); + + if ((int)$db_result['status'] === 1) { + $result_entry['status'] = 'PASS_SFW__BY_WHITELIST'; + break; + } + if ((int)$db_result['status'] === 0) { + $this->blocked_ips[] = $this->helper::ipLong2ip($db_result['network']); + $result_entry['status'] = 'DENY_SFW'; + } + + $test_status = (int)$db_result['status']; + } + } else { + $result_entry = array( + 'ip' => $current_ip, + 'is_personal' => null, + 'status' => 'PASS_SFW', + ); + } + + $results[] = $result_entry; + + if ( $this->test && $_origin === 'sfw_test' ) { + $this->test_status = $test_status; + } + } + + return $results; + } + + /** + * Add entry to SFW log. + * Writes to database. + * + * @param string $ip + * @param $status + * @param string $network + * @param string $source + */ + public function updateLog($ip, $status, $network = 'NULL', $source = 'NULL') + { + $id = md5($ip . $this->module_name); + $time = time(); + + $this->db->prepare( + "INSERT INTO " . $this->db__table__logs . " + SET + id = '$id', + ip = '$ip', + status = '$status', + all_entries = 1, + blocked_entries = " . (strpos($status, 'DENY') !== false ? 1 : 0) . ", + entries_timestamp = '" . $time . "', + ua_name = %s, + source = $source, + network = %s, + first_url = %s, + last_url = %s + ON DUPLICATE KEY + UPDATE + status = '$status', + source = $source, + all_entries = all_entries + 1, + blocked_entries = blocked_entries" . (strpos($status, 'DENY') !== false ? ' + 1' : '') . ", + entries_timestamp = '" . $time . "', + ua_name = %s, + network = %s, + last_url = %s", + array( + Server::get('HTTP_USER_AGENT'), + $network, + substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), + substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), + + Server::get('HTTP_USER_AGENT'), + $network, + substr(Server::get('HTTP_HOST') . Server::get('REQUEST_URI'), 0, 100), + ) + ); + $this->db->execute($this->db->getQuery()); + } + + public function actionsForDenied($result) + { + // Additional actions for the denied requests here + } + + public function actionsForPassed($result) + { + // Additional actions for the passed requests here + + /*if ($this->data__cookies_type === 'native' && ! headers_sent()) { + $status = $result['status'] === 'PASS_SFW__BY_WHITELIST' ? '1' : '0'; + $cookie_val = md5($result['ip'] . $this->api_key) . $status; + Cookie::setNativeCookie( + 'ct_sfw_pass_key', + $cookie_val, + time() + 86400 * 30, + '/' + ); + }*/ + } + + /** + * Shows DIE page. + * Stops script executing. + * + * @param array $result + */ + public function diePage($result) + { + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\RemoteCalls\Remotecalls $remote_calls_class */ + $remote_calls_class = Mloader::get('RemoteCalls'); + + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler */ + $storage_handler = Mloader::get('StorageHandler'); + + // File exists? + if (file_exists(__DIR__ . "/die_page_sfw.html")) { + $this->sfw_die_page = file_get_contents(__DIR__ . "/die_page_sfw.html"); + + $net_count = $fw_stats->entries; + + $status = $result['status'] === 'PASS_SFW__BY_WHITELIST' ? '1' : '0'; + $cookie_val = md5($result['ip'] . $this->api_key) . $status; + + $block_message = sprintf( + 'SpamFireWall is checking your browser and IP %s for spam bots', + '' . $result['ip'] . '' + ); + + $request_uri = Server::get('REQUEST_URI'); + if ( $this->test ) { + // Remove "sfw_test_ip" get parameter from the uri + $request_uri = preg_replace('%sfw_test_ip=\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}&?%', '', $request_uri); + } + + // @ToDo not implemented yet + // Custom Logo + //$custom_logo_img = ''; + + // Translation + $replaces = array( + '{SFW_DIE_NOTICE_IP}' => $block_message, + '{SFW_DIE_MAKE_SURE_JS_ENABLED}' => 'To continue working with the web site, please make sure that you have enabled JavaScript.', + '{SFW_DIE_CLICK_TO_PASS}' => 'Please click the link below to pass the protection,', + '{SFW_DIE_YOU_WILL_BE_REDIRECTED}' => sprintf( + 'Or you will be automatically redirected to the requested page after %d seconds.', + 3 + ), + '{CLEANTALK_TITLE}' => ($this->test ? 'This is the testing page for SpamFireWall' : ''), + '{REMOTE_ADDRESS}' => $result['ip'], + '{SERVICE_ID}' => $net_count, + '{HOST}' => $remote_calls_class::getSiteUrl(), + '{GENERATED}' => '

The page was generated at ' . date('D, d M Y H:i:s') . '

', + '{REQUEST_URI}' => $request_uri, + + // Cookie + '{COOKIE_PREFIX}' => '', + '{COOKIE_DOMAIN}' => '', + '{COOKIE_SFW}' => $cookie_val, + '{COOKIE_ANTICRAWLER}' => hash('sha256', $this->api_key . ''), + + // Test + '{TEST_TITLE}' => '', + '{REAL_IP__HEADER}' => '', + '{TEST_IP__HEADER}' => '', + '{TEST_IP}' => '', + '{REAL_IP}' => '', + '{SCRIPT_URL}' => $storage_handler::getJsLocation(), + + // Message about IP status + '{MESSAGE_IP_STATUS}' => '', + + // Custom Logo + '{CUSTOM_LOGO}' => '' + ); + + /** + * Message about IP status + */ + if ( $this->test ) { + $message_ip_status = 'IP in the common blacklist'; + $message_ip_status_color = 'red'; + + if ($this->test_status === 1) { + $message_ip_status = 'IP in the whitelist'; + $message_ip_status_color = 'green'; + } + + $replaces['{MESSAGE_IP_STATUS}'] = "

$message_ip_status

"; + } + + // Test + if ($this->test) { + $replaces['{TEST_TITLE}'] = 'This is the testing page for SpamFireWall'; + $replaces['{REAL_IP__HEADER}'] = 'Real IP:'; + $replaces['{TEST_IP__HEADER}'] = 'Test IP:'; + $replaces['{TEST_IP}'] = $this->test_ip; + $replaces['{REAL_IP}'] = $this->real_ip; + } + + // Debug + if ($this->debug) { + $debug = '

Headers

' + . var_export(apache_request_headers(), true) + . '

REMOTE_ADDR

' + . Server::get('REMOTE_ADDR') + . '

SERVER_ADDR

' + . Server::get('REMOTE_ADDR') + . '

IP_ARRAY

' + . var_export($this->ip_array, true) + . '

ADDITIONAL

' + . var_export($this->debug_data, true); + } + $replaces['{DEBUG}'] = isset($debug) ? $debug : ''; + + foreach ($replaces as $place_holder => $replace) { + $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); + } + } + + $this->printDiePage($result); + } + + public function printDiePage($result) + { + parent::diePage($result); + + http_response_code(403); + + $localize_js = array( + 'sfw__random_get' => '1', + ); + + $localize_js_public = array(); + + + $replaces = array( + '{JQUERY_SCRIPT_URL}' => '', + '{LOCALIZE_SCRIPT}' => 'var ctPublicFunctions = ' . json_encode($localize_js) . ';' . + 'var ctPublic = ' . json_encode($localize_js_public) . ';', + ); + + foreach ($replaces as $place_holder => $replace) { + $this->sfw_die_page = str_replace($place_holder, $replace, $this->sfw_die_page); + } + + // File exists? + if (file_exists(__DIR__ . "/die_page_sfw.html")) { + die($this->sfw_die_page); + } + + die("IP BLACKLISTED. Blocked by SFW " . $result['ip']); + } + + /** + * Sends and wipe SFW log + * + * @param $db + * @param $log_table + * @param string $ct_key Access key + * @param bool $_use_delete_command Determs whether use DELETE or TRUNCATE to delete the logs table data + * + * @return array|bool array('error' => STRING) + */ + public static function sendLog($db, $log_table, $ct_key, $_use_delete_command) + { + //Getting logs + $query = "SELECT * FROM $log_table ORDER BY entries_timestamp DESC LIMIT 0," . APBCT_SFW_SEND_LOGS_LIMIT . ";"; + $db->fetchAll($query); + + if (count($db->result)) { + $logs = $db->result; + + //Compile logs + $ids_to_delete = array(); + $data = array(); + foreach ($logs as $_key => &$value) { + $ids_to_delete[] = $value['id']; + + // Converting statuses to API format + $value['status'] = $value['status'] === 'DENY_ANTICRAWLER' ? 'BOT_PROTECTION' : $value['status']; + $value['status'] = $value['status'] === 'PASS_ANTICRAWLER' ? 'BOT_PROTECTION' : $value['status']; + $value['status'] = $value['status'] === 'DENY_ANTICRAWLER_UA' ? 'BOT_PROTECTION' : $value['status']; + $value['status'] = $value['status'] === 'PASS_ANTICRAWLER_UA' ? 'BOT_PROTECTION' : $value['status']; + + $value['status'] = $value['status'] === 'DENY_ANTIFLOOD' ? 'FLOOD_PROTECTION' : $value['status']; + $value['status'] = $value['status'] === 'PASS_ANTIFLOOD' ? 'FLOOD_PROTECTION' : $value['status']; + $value['status'] = $value['status'] === 'DENY_ANTIFLOOD_UA' ? 'FLOOD_PROTECTION' : $value['status']; + $value['status'] = $value['status'] === 'PASS_ANTIFLOOD_UA' ? 'FLOOD_PROTECTION' : $value['status']; + + $value['status'] = $value['status'] === 'PASS_SFW__BY_COOKIE' ? 'DB_MATCH' : $value['status']; + $value['status'] = $value['status'] === 'PASS_SFW' ? 'DB_MATCH' : $value['status']; + $value['status'] = $value['status'] === 'DENY_SFW' ? 'DB_MATCH' : $value['status']; + + $value['status'] = $value['source'] ? 'PERSONAL_LIST_MATCH' : $value['status']; + + $additional = array(); + if ($value['network']) { + $additional['nd'] = $value['network']; + } + if ($value['first_url']) { + $additional['fu'] = $value['first_url']; + } + if ($value['last_url']) { + $additional['lu'] = $value['last_url']; + } + $additional = $additional ?: 'EMPTY_ASSOCIATIVE_ARRAY'; + + $data[] = array( + trim($value['ip']), + // IP + $value['blocked_entries'], + // Count showing of block pages + $value['all_entries'] - $value['blocked_entries'], + // Count passed requests after block pages + $value['entries_timestamp'], + // Last timestamp + $value['status'], + // Status + $value['ua_name'], + // User-Agent name + $value['ua_id'], + // User-Agent ID + $additional + // Network, first URL, last URL + ); + } + unset($value); + + /** @var \Cleantalk\Common\Api\Api $api_class */ + $api_class = Mloader::get('Api'); + + //Sending the request + $result = $api_class::methodSfwLogs($ct_key, $data); + //Checking answer and deleting all lines from the table + if (empty($result['error'])) { + if ($result['rows'] == count($data)) { + $db->execute("BEGIN;"); + $db->execute("DELETE FROM $log_table WHERE id IN ( '" . implode('\',\'', $ids_to_delete) . "' );"); + $db->execute("COMMIT;"); + + return $result; + } + + return array('error' => 'SENT_AND_RECEIVED_LOGS_COUNT_DOESNT_MACH'); + } else { + return $result; + } + } else { + return array('rows' => 0); + } + } + + public static function directUpdateGetBlackLists($api_key) + { + /** @var \Cleantalk\Common\Api\Api $api_class */ + $api_class = Mloader::get('Api'); + + // Getting remote file name + $result = $api_class::methodGet2sBlacklistsDb($api_key, null, '3_1'); + + if ( empty($result['error']) ) { + return array( + 'blacklist' => isset($result['data']) ? $result['data'] : null, + 'useragents' => isset($result['data_user_agents']) ? $result['data_user_agents'] : null, + 'bl_count' => isset($result['networks_count']) ? $result['networks_count'] : null, + 'ua_count' => isset($result['ua_count']) ? $result['ua_count'] : null, + ); + } + + return $result; + } + + public static function directUpdate($db, $db__table__data, $blacklists) + { + if ( ! is_array($blacklists) ) { + return array('error' => 'BlackLists is not an array.'); + } + for ( $count_result = 0; current($blacklists) !== false; ) { + $query = "INSERT INTO " . $db__table__data . " (network, mask, status) VALUES "; + + for ( + $i = 0, $values = array(); + APBCT_WRITE_LIMIT !== $i && current($blacklists) !== false; + $i++, $count_result++, next($blacklists) + ) { + $entry = current($blacklists); + + if ( empty($entry) ) { + continue; + } + + // Cast result to int + $ip = preg_replace('/[^\d]*/', '', $entry[0]); + $mask = preg_replace('/[^\d]*/', '', $entry[1]); + $private = isset($entry[2]) ? $entry[2] : 0; + + $values[] = '(' . $ip . ',' . $mask . ',' . $private . ')'; + } + + if ( ! empty($values) ) { + $query .= implode(',', $values) . ';'; + $result = $db->execute($query); + if ( $result === false ) { + return array( 'error' => $db->getLastError() ); + } + } + } + + return $count_result; + } + + /** + * Updates SFW local base + * + * @param $db + * @param $db__table__data + * @param null|string $file_url File URL with SFW data. + * + * @return array|int array('error' => STRING) + */ + public static function updateWriteToDb($db, $db__table__data, $file_url = null) + { + $file_content = file_get_contents($file_url); + + if (function_exists('gzdecode')) { + $unzipped_content = @gzdecode($file_content); + + if ($unzipped_content !== false) { + + /** @var \Cleantalk\Common\Helper\Helper $helper_class */ + $helper_class = Mloader::get('Helper'); + $data = $helper_class::bufferParseCsv($unzipped_content); + + if (empty($data['errors'])) { + reset($data); + + for ($count_result = 0; current($data) !== false;) { + $query = "INSERT INTO " . $db__table__data . " (network, mask, status, source) VALUES "; + + for ( + $i = 0, $values = array(); + APBCT_WRITE_LIMIT !== $i && current($data) !== false; + $i++, $count_result++, next($data) + ) { + $entry = current($data); + + if (empty($entry) || empty($entry[0]) || empty($entry[1])) { + continue; + } + + // Cast result to int + $ip = preg_replace('/[^\d]*/', '', $entry[0]); + $mask = preg_replace('/[^\d]*/', '', $entry[1]); + $status = isset($entry[2]) ? $entry[2] : 0; + $source = isset($entry[3]) ? (int)$entry[3] : 'NULL'; + + $values[] = "($ip, $mask, $status, $source)"; + } + + if ( ! empty($values)) { + $query .= implode(',', $values) . ';'; + if ( ! $db->execute($query) ) { + return array( + 'error' => 'WRITE ERROR: FAILED TO INSERT DATA: ' . $db__table__data + . ' DB Error: ' . $db->getLastError() + ); + } + if (file_exists($file_url)) { + unlink($file_url); + } + } + } + + return $count_result; + } else { + return $data; + } + } else { + return array('error' => 'Can not unpack datafile'); + } + } else { + return array('error' => 'Function gzdecode not exists. Please update your PHP at least to version 5.4 '); + } + } + + /** + * @param $db + * @param $db__table__data + * @param $exclusions + * + * @return int|string[] + * + * @since version + */ + public static function updateWriteToDbExclusions($db, $db__table__data, $exclusions = array()) + { + $fw_stats = Firewall::getFwStats(); + + /** @var \Cleantalk\Common\Helper\Helper $helper_class */ + $helper_class = Mloader::get('Helper'); + + $query = 'INSERT INTO `' . $db__table__data . '` (network, mask, status) VALUES '; + + //Exclusion for servers IP (SERVER_ADDR) + if (Server::get('HTTP_HOST')) { + // Do not add exceptions for local hosts + if ( ! in_array(Server::getDomain(), array('lc', 'loc', 'lh'))) { + $exclusions[] = $helper_class::dnsResolve(Server::get('HTTP_HOST')); + $exclusions[] = '127.0.0.1'; + // And delete all 127.0.0.1 entries for local hosts + } else { + $delete_res = $db->execute('DELETE FROM ' . $db__table__data . ' WHERE network = ' . ip2long('127.0.0.1') . ';'); + if ($delete_res > 0) { + $fw_stats->expected_networks_count -= $delete_res; + Firewall::saveFwStats($fw_stats); + } + } + } + + foreach ($exclusions as $exclusion) { + if ($helper_class::ipValidate($exclusion) && sprintf('%u', ip2long($exclusion))) { + $query .= '(' . sprintf('%u', ip2long($exclusion)) . ', ' . sprintf( + '%u', + bindec(str_repeat('1', 32)) + ) . ', 1),'; + } + } + + if ($exclusions) { + $sql_result = $db->execute(substr($query, 0, -1) . ';'); + + return $sql_result === false + ? array('error' => 'COULD_NOT_WRITE_TO_DB 4: ' . $db->getLastError()) + : $db->getAffectedRows(); + } + + return 0; + } + + /** + * Creating a temporary updating table + * + * @param \Cleantalk\Common\Db\Db $db database handler + * @param array|string $table_names Array with table names to create + * + * @return bool|array + */ + public static function createTempTables($db, $table_names) + { + // Cast it to array for simple input + $table_names = (array)$table_names; + + foreach ($table_names as $table_name) { + $table_name__temp = $table_name . '_temp'; + + if ( ! $db->execute('CREATE TABLE IF NOT EXISTS `' . $table_name__temp . '` LIKE `' . $table_name . '`;')) { + return array('error' => 'CREATE TEMP TABLES: COULD NOT CREATE ' . $table_name__temp + . ' DB Error: ' . $db->getLastError() ); + } + + if ( ! $db->execute('TRUNCATE TABLE `' . $table_name__temp . '`;')) { + return array('error' => 'CREATE TEMP TABLES: COULD NOT TRUNCATE' . $table_name__temp + . ' DB Error: ' . $db->getLastError() ); + } + } + + return true; + } + + /** + * Delete tables with given names if they exists + * + * @param \Cleantalk\Common\Db\Db $db + * @param array|string $table_names Array with table names to delete + * + * @return bool|array + */ + public static function dataTablesDelete($db, $table_names) + { + // Cast it to array for simple input + $table_names = (array)$table_names; + + foreach ($table_names as $table_name) { + if ( $db->isTableExists($table_name) && ! $db->execute('DROP TABLE ' . $table_name . ';') ) { + return array( + 'error' => 'DELETE TABLE: FAILED TO DROP: ' . $table_name + . ' DB Error: ' . $db->getLastError() + ); + } + } + + return true; + } + + /** + * Renaming a temporary updating table into production table name + * + * @param \Cleantalk\Common\Db\Db $db database handler + * @param array|string $table_names Array with table names to rename + * + * @return bool|array + */ + public static function renameDataTablesFromTempToMain($db, $table_names) + { + // Cast it to array for simple input + $table_names = (array)$table_names; + + foreach ($table_names as $table_name) { + $table_name__temp = $table_name . '_temp'; + + if ( ! $db->isTableExists($table_name__temp)) { + return array('error' => 'RENAME TABLE: TEMPORARY TABLE IS NOT EXISTS: ' . $table_name__temp); + } + + if ($db->isTableExists($table_name)) { + //return array('error' => 'RENAME TABLE: MAIN TABLE IS STILL EXISTS: ' . $table_name); + } + + if ( ! $db->execute('ALTER TABLE `' . $table_name__temp . '` RENAME `' . $table_name . '`;') ) { + return array( + 'error' => 'RENAME TABLE: FAILED TO RENAME: ' . $table_name + . ' DB Error: ' . $db->getLastError() + ); + } + } + + return true; + } +} diff --git a/lib/Cleantalk/Common/Firewall/Modules/die_page_anticrawler.html b/lib/Cleantalk/Common/Firewall/Modules/die_page_anticrawler.html new file mode 100644 index 0000000..8ff16fd --- /dev/null +++ b/lib/Cleantalk/Common/Firewall/Modules/die_page_anticrawler.html @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+ {CUSTOM_LOGO} +
+

{SFW_DIE_NOTICE_IP}

+ +

{SFW_DIE_MAKE_SURE_JS_ENABLED}
+ +
+
+

{SFW_DIE_YOU_WILL_BE_REDIRECTED}

+
+
+
+
+
+
+
+ {GENERATED} +

Browser time

+
+ +
+
+

{SERVICE_ID},

+

{HOST}

+
+ + +
+ {DEBUG} +
+ + \ No newline at end of file diff --git a/lib/Cleantalk/Common/Firewall/Modules/die_page_antiflood.html b/lib/Cleantalk/Common/Firewall/Modules/die_page_antiflood.html new file mode 100644 index 0000000..73f63dc --- /dev/null +++ b/lib/Cleantalk/Common/Firewall/Modules/die_page_antiflood.html @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + +
+
+ {CUSTOM_LOGO} +
+

{SFW_DIE_NOTICE_IP} {REMOTE_ADDRESS}

+ +

{SFW_DIE_MAKE_SURE_JS_ENABLED}
+ +
+
+

{SFW_DIE_YOU_WILL_BE_REDIRECTED}

+
+
+
+
+
+
+
+ {GENERATED} +

Browser time

+
+ +
+
+

{SERVICE_ID},

+

{HOST}

+
+ + + + \ No newline at end of file diff --git a/lib/Cleantalk/Common/Firewall/Modules/die_page_sfw.html b/lib/Cleantalk/Common/Firewall/Modules/die_page_sfw.html index 221b0bb..fde2e25 100644 --- a/lib/Cleantalk/Common/Firewall/Modules/die_page_sfw.html +++ b/lib/Cleantalk/Common/Firewall/Modules/die_page_sfw.html @@ -70,19 +70,133 @@ transform: scale(1.0); } } + html { + background: #f1f1f1; + } + body { + background: #fff; + border: 1px solid #ccd0d4; + color: #444; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif; + margin: 2em auto; + padding: 1em 2em; + max-width: 700px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .04); + box-shadow: 0 1px 1px rgba(0, 0, 0, .04); + } + h1 { + border-bottom: 1px solid #dadada; + clear: both; + color: #666; + font-size: 24px; + margin: 30px 0 0 0; + padding: 0; + padding-bottom: 7px; + } + #error-page { + margin-top: 50px; + } + #error-page p, + #error-page .wp-die-message { + font-size: 14px; + line-height: 1.5; + margin: 25px 0 20px; + } + #error-page code { + font-family: Consolas, Monaco, monospace; + } + ul li { + margin-bottom: 10px; + font-size: 14px ; + } + a { + color: #0073aa; + } + a:hover, + a:active { + color: #006799; + } + a:focus { + color: #124964; + -webkit-box-shadow: + 0 0 0 1px #5b9dd9, + 0 0 2px 1px rgba(30, 140, 190, 0.8); + box-shadow: + 0 0 0 1px #5b9dd9, + 0 0 2px 1px rgba(30, 140, 190, 0.8); + outline: none; + } + .button { + background: #f3f5f6; + border: 1px solid #016087; + color: #016087; + display: inline-block; + text-decoration: none; + font-size: 13px; + line-height: 2; + height: 28px; + margin: 0; + padding: 0 10px 1px; + cursor: pointer; + -webkit-border-radius: 3px; + -webkit-appearance: none; + border-radius: 3px; + white-space: nowrap; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + + vertical-align: top; + } + + .button.button-large { + line-height: 2.30769231; + min-height: 32px; + padding: 0 12px; + } + + .button:hover, + .button:focus { + background: #f1f1f1; + } + + .button:focus { + background: #f3f5f6; + border-color: #007cba; + -webkit-box-shadow: 0 0 0 1px #007cba; + box-shadow: 0 0 0 1px #007cba; + color: #016087; + outline: 2px solid transparent; + outline-offset: 0; + } + + .button:active { + background: #f3f5f6; + border-color: #7e8993; + -webkit-box-shadow: none; + box-shadow: none; + } + + + @@ -90,12 +204,16 @@
-

{SFW_DIE_NOTICE_IP}{REMOTE_ADDRESS}

- +
+ {CUSTOM_LOGO} +
+

{SFW_DIE_NOTICE_IP}

{REAL_IP__HEADER} {REAL_IP}

{TEST_IP__HEADER} {TEST_IP}

{TEST_TITLE}

+ + {MESSAGE_IP_STATUS}

{SFW_DIE_MAKE_SURE_JS_ENABLED}
@@ -129,12 +247,16 @@

{SFW_DIE_CLICK_TO_PASS}

document.getElementById('js_passed').style.display = 'block'; document.getElementById('curr_date').innerHTML = ct_date.toGMTString(); set_spamFireWallCookie('{COOKIE_PREFIX}ct_sfw_pass_key','{COOKIE_SFW}'); - set_spamFireWallCookie('{COOKIE_PREFIX}apbct_antibot','{COOKIE_ANTICRAWLER}'); + set_spamFireWallCookie('{COOKIE_PREFIX}wordpress_apbct_antibot','{COOKIE_ANTICRAWLER}'); set_spamFireWallCookie('{COOKIE_PREFIX}ct_sfw_passed','1'); if(location.search.search('debug=1') === -1) { + var apbctSfwRandomGet = ''; + if( +ctPublicFunctions.sfw__random_get ) { + apbctSfwRandomGet = '?sfw=pass' + Math.round(ct_date.getTime()/1000); + } setTimeout(function(){ - window.location.href = window.location.origin + window.location.pathname + '?sfw=pass' + Math.round(ct_date.getTime()/1000); + window.location.href = window.location.origin + window.location.pathname + apbctSfwRandomGet; }, reload_timeout); } diff --git a/lib/Cleantalk/Common/Helper.php b/lib/Cleantalk/Common/Helper.php deleted file mode 100644 index f06b3e7..0000000 --- a/lib/Cleantalk/Common/Helper.php +++ /dev/null @@ -1,1397 +0,0 @@ - array( - '10.0.0.0/8', - '100.64.0.0/10', - '172.16.0.0/12', - '192.168.0.0/16', - '127.0.0.1/32', - ), - 'v6' => array( - '0:0:0:0:0:0:0:1/128', // localhost - '0:0:0:0:0:0:a:1/128', // ::ffff:127.0.0.1 - ), - ); - - /** - * @var array Set of CleanTalk servers - */ - public static $cleantalks_servers = array( - // MODERATE - 'moderate1.cleantalk.org' => '162.243.144.175', - 'moderate2.cleantalk.org' => '159.203.121.181', - 'moderate3.cleantalk.org' => '88.198.153.60', - 'moderate4.cleantalk.org' => '159.69.51.30', - 'moderate5.cleantalk.org' => '95.216.200.119', - 'moderate6.cleantalk.org' => '138.68.234.8', - // APIX - 'apix1.cleantalk.org' => '35.158.52.161', - 'apix2.cleantalk.org' => '18.206.49.217', - 'apix3.cleantalk.org' => '3.18.23.246', - 'apix4.cleantalk.org' => '44.227.90.42', - 'apix5.cleantalk.org' => '15.188.198.212', - 'apix6.cleantalk.org' => '54.219.94.72', - //ns - 'netserv2.cleantalk.org' => '178.63.60.214', - 'netserv3.cleantalk.org' => '188.40.14.173', - ); - - /** - * Getting arrays of IP (REMOTE_ADDR, X-Forwarded-For, X-Real-Ip, Cf_Connecting_Ip) - * - * @param string $ip_type_to_get Type of IP you want to receive - * @param bool $v4_only - * - * @return string|null - */ - public static function ip__get( $ip_type_to_get = 'real', $v4_only = true, $headers = array() ) - { - $out = null; - - switch( $ip_type_to_get ){ - - // Cloud Flare - case 'cloud_flare': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['Cf-Connecting-Ip'], $headers['Cf-Ipcountry'], $headers['Cf-Ray'] ) ){ - $tmp = strpos( $headers['Cf-Connecting-Ip'], ',' ) !== false - ? explode( ',', $headers['Cf-Connecting-Ip'] ) - : (array) $headers['Cf-Connecting-Ip']; - $ip_version = self::ip__validate( trim( $tmp[0] ) ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( trim( $tmp[0] ) ) : trim( $tmp[0] ); - } - } - break; - - // GTranslate - case 'gtranslate': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['X-Gt-Clientip'], $headers['X-Gt-Viewer-Ip'] ) ){ - $ip_version = self::ip__validate( $headers['X-Gt-Viewer-Ip'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['X-Gt-Viewer-Ip'] ) : $headers['X-Gt-Viewer-Ip']; - } - } - break; - - // ezoic - case 'ezoic': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['X-Middleton'], $headers['X-Middleton-Ip'] ) ){ - $ip_version = self::ip__validate( $headers['X-Middleton-Ip'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['X-Middleton-Ip'] ) : $headers['X-Middleton-Ip']; - } - } - break; - - // Sucury - case 'sucury': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['X-Sucuri-Clientip'] ) ){ - $ip_version = self::ip__validate( $headers['X-Sucuri-Clientip'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['X-Sucuri-Clientip'] ) : $headers['X-Sucuri-Clientip']; - } - } - break; - - // X-Forwarded-By - case 'x_forwarded_by': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['X-Forwarded-By'], $headers['X-Client-Ip'] ) ){ - $ip_version = self::ip__validate( $headers['X-Client-Ip'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['X-Client-Ip'] ) : $headers['X-Client-Ip']; - } - } - break; - - // Stackpath - case 'stackpath': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['X-Sp-Edge-Host'], $headers['X-Sp-Forwarded-Ip'] ) ){ - $ip_version = self::ip__validate( $headers['X-Sp-Forwarded-Ip'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['X-Sp-Forwarded-Ip'] ) : $headers['X-Sp-Forwarded-Ip']; - } - } - break; - - // Ico-X-Forwarded-For - case 'ico_x_forwarded_for': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['Ico-X-Forwarded-For'], $headers['X-Forwarded-Host'] ) ){ - $ip_version = self::ip__validate( $headers['Ico-X-Forwarded-For'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['Ico-X-Forwarded-For'] ) : $headers['Ico-X-Forwarded-For']; - } - } - break; - - // OVH - case 'ovh': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['X-Cdn-Any-Ip'], $headers['Remote-Ip'] ) ){ - $ip_version = self::ip__validate( $headers['Remote-Ip'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['Remote-Ip'] ) : $headers['Remote-Ip']; - } - } - break; - - // Incapsula proxy - case 'incapsula': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['Incap-Client-Ip'], $headers['X-Forwarded-For'] ) ){ - $ip_version = self::ip__validate( $headers['Incap-Client-Ip'] ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $headers['Incap-Client-Ip'] ) : $headers['Incap-Client-Ip']; - } - } - break; - - // Remote addr - case 'remote_addr': - $ip_version = self::ip__validate( Server::get( 'REMOTE_ADDR' ) ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( Server::get( 'REMOTE_ADDR' ) ) : Server::get( 'REMOTE_ADDR' ); - } - break; - - // X-Forwarded-For - case 'x_forwarded_for': - $headers = $headers ?: self::http__get_headers(); - if( isset( $headers['X-Forwarded-For'] ) ){ - $tmp = explode( ',', trim( $headers['X-Forwarded-For'] ) ); - $tmp = trim( $tmp[0] ); - $ip_version = self::ip__validate( $tmp ); - if( $ip_version ){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize( $tmp ) : $tmp; - } - } - break; - - // X-Real-Ip - case 'x_real_ip': - $headers = $headers ?: self::http__get_headers(); - if(isset($headers['X-Real-Ip'])){ - $tmp = explode(",", trim($headers['X-Real-Ip'])); - $tmp = trim($tmp[0]); - $ip_version = self::ip__validate($tmp); - if($ip_version){ - $out = $ip_version === 'v6' && ! $v4_only ? self::ip__v6_normalize($tmp) : $tmp; - } - } - break; - - // Real - // Getting real IP from REMOTE_ADDR or Cf_Connecting_Ip if set or from (X-Forwarded-For, X-Real-Ip) if REMOTE_ADDR is local. - case 'real': - - // Detect IP type - $out = self::ip__get( 'cloud_flare', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'sucury', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'gtranslate', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'ezoic', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'stackpath', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'x_forwarded_by', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'ico_x_forwarded_for', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'ovh', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'incapsula', $v4_only, $headers ); - - $ip_version = self::ip__validate( $out ); - - // Is private network - if( - ! $out || - ($out && - ( - self::ip__is_private_network( $out, $ip_version ) || - self::ip__mask_match( - $out, - Server::get( 'SERVER_ADDR' ) . '/24', - $ip_version - ) - )) - ){ - //@todo Remove local IP from x-forwarded-for and x-real-ip - $out = $out ?: self::ip__get( 'x_forwarded_for', $v4_only, $headers ); - $out = $out ?: self::ip__get( 'x_real_ip', $v4_only, $headers ); - } - - $out = $out ?: self::ip__get( 'remote_addr', $v4_only, $headers ); - - break; - - default: - $out = self::ip__get( 'real', $v4_only, $headers ); - } - - // Final validating IP - $ip_version = self::ip__validate( $out ); - - if( ! $ip_version ){ - return null; - - }elseif( $ip_version === 'v6' && $v4_only ){ - return null; - - }else{ - return $out; - } - } - - /** - * Checks if the IP is in private range - * - * @param string $ip - * @param string $ip_type - * - * @return bool - */ - static function ip__is_private_network($ip, $ip_type = 'v4') - { - return self::ip__mask_match($ip, self::$private_networks[$ip_type], $ip_type); - } - - /** - * Check if the IP belong to mask. Recursive. - * Octet by octet for IPv4 - * Hextet by hextet for IPv6 - * - * @param string $ip - * @param string $cidr work to compare with - * @param string $ip_type IPv6 or IPv4 - * @param int $xtet_count Recursive counter. Determs current part of address to check. - * - * @return bool - */ - static public function ip__mask_match($ip, $cidr, $ip_type = 'v4', $xtet_count = 0) - { - - if(is_array($cidr)){ - foreach($cidr as $curr_mask){ - if(self::ip__mask_match($ip, $curr_mask, $ip_type)){ - return true; - } - } - unset($curr_mask); - return false; - } - - if( ! self::ip__validate( $ip ) || ! self::cidr__validate( $cidr ) ){ - return false; - } - - $xtet_base = ($ip_type == 'v4') ? 8 : 16; - - // Calculate mask - $exploded = explode('/', $cidr); - $net_ip = $exploded[0]; - $mask = $exploded[1]; - - // Exit condition - $xtet_end = ceil($mask / $xtet_base); - if($xtet_count == $xtet_end) - return true; - - // Lenght of bits for comparsion - $mask = $mask - $xtet_base * $xtet_count >= $xtet_base ? $xtet_base : $mask - $xtet_base * $xtet_count; - - // Explode by octets/hextets from IP and Net - $net_ip_xtets = explode($ip_type == 'v4' ? '.' : ':', $net_ip); - $ip_xtets = explode($ip_type == 'v4' ? '.' : ':', $ip); - - // Standartizing. Getting current octets/hextets. Adding leading zeros. - $net_xtet = str_pad(decbin($ip_type == 'v4' ? $net_ip_xtets[$xtet_count] : @hexdec($net_ip_xtets[$xtet_count])), $xtet_base, 0, STR_PAD_LEFT); - $ip_xtet = str_pad(decbin($ip_type == 'v4' ? $ip_xtets[$xtet_count] : @hexdec($ip_xtets[$xtet_count])), $xtet_base, 0, STR_PAD_LEFT); - - // Comparing bit by bit - for($i = 0, $result = true; $mask != 0; $mask--, $i++){ - if($ip_xtet[$i] != $net_xtet[$i]){ - $result = false; - break; - } - } - - // Recursing. Moving to next octet/hextet. - if($result) - $result = self::ip__mask_match($ip, $cidr, $ip_type, $xtet_count + 1); - - return $result; - - } - - /** - * Converts long mask like 4294967295 to number like 32 - * - * @param int $long_mask - * - * @return int - */ - static function ip__mask__long_to_number($long_mask) - { - $num_mask = strpos((string)decbin($long_mask), '0'); - return $num_mask === false ? 32 : $num_mask; - } - - /** - * Validating IPv4, IPv6 - * - * @param string $ip - * - * @return string|bool - */ - static public function ip__validate($ip) - { - if(!$ip) return false; // NULL || FALSE || '' || so on... - if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && $ip != '0.0.0.0') return 'v4'; // IPv4 - if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && self::ip__v6_reduce($ip) != '0::0') return 'v6'; // IPv6 - return false; // Unknown - } - - /* - * Checking api_key - * returns (boolean) - */ - static function key_is_correct($api_key = '') { - - return preg_match('/^[a-z\d]{3,15}$|^$/', $api_key); - - } - - /** - * Validate CIDR - * - * @param string $cidr expects string like 1.1.1.1/32 - * - * @return bool - */ - public static function cidr__validate( $cidr ){ - $cidr = explode( '/', $cidr ); - return isset( $cidr[0], $cidr[1] ) && self::ip__validate( $cidr[0] ) && preg_match( '@\d{1,2}@', $cidr[1] ); - } - - /** - * Expand IPv6 - * - * @param string $ip - * - * @return string IPv6 - */ - static public function ip__v6_normalize($ip) - { - $ip = trim($ip); - // Searching for ::ffff:xx.xx.xx.xx patterns and turn it to IPv6 - if(preg_match('/^::ffff:([0-9]{1,3}\.?){4}$/', $ip)){ - $ip = dechex(sprintf("%u", ip2long(substr($ip, 7)))); - $ip = '0:0:0:0:0:0:' . (strlen($ip) > 4 ? substr('abcde', 0, -4) : '0') . ':' . substr($ip, -4, 4); - // Normalizing hextets number - }elseif(strpos($ip, '::') !== false){ - $ip = str_replace('::', str_repeat(':0', 8 - substr_count($ip, ':')) . ':', $ip); - $ip = strpos($ip, ':') === 0 ? '0' . $ip : $ip; - $ip = strpos(strrev($ip), ':') === 0 ? $ip . '0' : $ip; - } - // Simplifyng hextets - if(preg_match('/:0(?=[a-z0-9]+)/', $ip)){ - $ip = preg_replace('/:0(?=[a-z0-9]+)/', ':', strtolower($ip)); - $ip = self::ip__v6_normalize($ip); - } - return $ip; - } - - /** - * Reduce IPv6 - * - * @param string $ip - * - * @return string IPv6 - */ - static public function ip__v6_reduce($ip) - { - if(strpos($ip, ':') !== false){ - $ip = preg_replace('/:0{1,4}/', ':', $ip); - $ip = preg_replace('/:{2,}/', '::', $ip); - $ip = strpos($ip, '0') === 0 ? substr($ip, 1) : $ip; - } - return $ip; - } - - /** - * Get URL form IP. Check if it's belong to cleantalk. - * - * @param string $ip - * - * @return false|int|string - */ - static public function ip__is_cleantalks($ip) - { - if(self::ip__validate($ip)){ - $url = array_search($ip, self::$cleantalks_servers); - return $url - ? true - : false; - }else - return false; - } - - /** - * Get URL form IP. Check if it's belong to cleantalk. - * - * @param $ip - * - * @return false|int|string - */ - static public function ip__resolve__cleantalks($ip) - { - if(self::ip__validate($ip)){ - $url = array_search($ip, self::$cleantalks_servers); - return $url - ? $url - : self::ip__resolve($ip); - }else - return $ip; - } - - /* - * Get data from submit recursively - */ - static public function get_fields_any($arr, $fields_exclusions = '', $message = array(), $email = null, $nickname = array('nick' => '', 'first' => '', 'last' => ''), $subject = null, $contact = true, $prev_name = '') - { - - //Skip request if fields exists - $skip_params = array( - 'ipn_track_id', // PayPal IPN # - 'txn_type', // PayPal transaction type - 'payment_status', // PayPal payment status - 'ccbill_ipn', // CCBill IPN - 'ct_checkjs', // skip ct_checkjs field - 'api_mode', // DigiStore-API - 'loadLastCommentId', // Plugin: WP Discuz. ticket_id=5571 - ); - - // Fields to replace with **** - $obfuscate_params = array( - 'password', - 'pass', - 'pwd', - 'pswd' - ); - - // Skip feilds with these strings and known service fields - $skip_fields_with_strings = array( - // Common - 'ct_checkjs', //Do not send ct_checkjs - 'nonce', //nonce for strings such as 'rsvp_nonce_name' - 'security', - // 'action', - 'http_referer', - 'timestamp', - 'captcha', - // Formidable Form - 'form_key', - 'submit_entry', - // Custom Contact Forms - 'form_id', - 'ccf_form', - 'form_page', - // Qu Forms - 'iphorm_uid', - 'form_url', - 'post_id', - 'iphorm_ajax', - 'iphorm_id', - // Fast SecureContact Froms - 'fs_postonce_1', - 'fscf_submitted', - 'mailto_id', - 'si_contact_action', - // Ninja Forms - 'formData_id', - 'formData_settings', - 'formData_fields_\d+_id', - 'formData_fields_\d+_files.*', - // E_signature - 'recipient_signature', - 'output_\d+_\w{0,2}', - // Contact Form by Web-Settler protection - '_formId', - '_returnLink', - // Social login and more - '_save', - '_facebook', - '_social', - 'user_login-', - // Contact Form 7 - '_wpcf7', - 'avatar__file_image_data', - 'task', - 'page_url', - 'page_title', - 'Submit', - 'formId', - 'key', - 'id', - 'hiddenlists', - 'ctrl', - 'task', - 'option', - 'nextstep', - 'acy_source', - 'subid', - 'ct_action', - 'ct_method', - ); - // Reset $message if we have a sign-up data - $skip_message_post = array( - 'edd_action', // Easy Digital Downloads - ); - - foreach ($skip_params as $value) - { - if (@array_key_exists($value, $_GET) || @array_key_exists($value, $_POST)) - $contact = false; - } - unset($value); - - if (count($arr)) - { - foreach ($arr as $key => $value) - { - - if (gettype($value) == 'string') - { - $decoded_json_value = json_decode($value, true); - if ($decoded_json_value !== null) - $value = $decoded_json_value; - } - - if (!is_array($value) && !is_object($value)) - { - //Add custom exclusions - if( is_string($fields_exclusions) && !empty($fields_exclusions) ) { - $fields_exclusions = explode(",",$fields_exclusions); - if (is_array($fields_exclusions) && !empty($fields_exclusions)) { - foreach($fields_exclusions as &$fields_exclusion) { - if( preg_match('/\[*\]/', $fields_exclusion ) ) { - // I have to do this to support exclusions like 'submitted[name]' - $fields_exclusion = str_replace( array( '[', ']' ), array( '_', '' ), $fields_exclusion ); - } - } - $skip_fields_with_strings = array_merge($skip_fields_with_strings, $fields_exclusions); - } - } - if (in_array($key, $skip_params, true) && $key != 0 && $key != '' || preg_match("/^ct_checkjs/", $key)) - $contact = false; - - if ($value === '') - continue; - - // Skipping fields names with strings from (array)skip_fields_with_strings - foreach ($skip_fields_with_strings as $needle) - { - if (preg_match("/" . $needle . "/", $prev_name . $key) == 1) - { - continue(2); - } - } - unset($needle); - - // Obfuscating params - foreach ($obfuscate_params as $needle) - { - if (strpos($key, $needle) !== false) - { - $value = self::obfuscate_param($value); - continue(2); - } - } - unset($needle); - - - // Removes whitespaces - $value = urldecode( trim( $value ) ); // Fully cleaned message - $value_for_email = trim( $value ); // Removes shortcodes to do better spam filtration on server side. - - // Email - if ( ! $email && preg_match( "/^\S+@\S+\.\S+$/", $value_for_email ) ) { - $email = $value_for_email; - - // Names - } elseif (preg_match("/name/i", $key)) { - - preg_match("/((name.?)?(your|first|for)(.?name)?)$/", $key, $match_forename); - preg_match("/((name.?)?(last|family|second|sur)(.?name)?)$/", $key, $match_surname); - preg_match("/^(name.?)?(nick|user)(.?name)?$/", $key, $match_nickname); - - if (count($match_forename) > 1) - $nickname['first'] = $value; - elseif (count($match_surname) > 1) - $nickname['last'] = $value; - elseif (count($match_nickname) > 1) - $nickname['nick'] = $value; - else - $nickname[$prev_name . $key] = $value; - - // Subject - } - elseif ($subject === null && preg_match("/subject/i", $key)) - { - $subject = $value; - - // Message - } - else - { - $message[$prev_name . $key] = $value; - } - - } - elseif (!is_object($value)) - { - - $prev_name_original = $prev_name; - $prev_name = ($prev_name === '' ? $key . '_' : $prev_name . $key . '_'); - - $temp = self::get_fields_any($value, $fields_exclusions, $message, $email, $nickname, $subject, $contact, $prev_name); - - $message = $temp['message']; - $email = ($temp['email'] ? $temp['email'] : null); - $nickname = ($temp['nickname'] ? $temp['nickname'] : null); - $subject = ($temp['subject'] ? $temp['subject'] : null); - if ($contact === true) - $contact = ($temp['contact'] === false ? false : true); - $prev_name = $prev_name_original; - } - } - unset($key, $value); - } - - foreach ($skip_message_post as $v) - { - if (isset($_POST[$v])) - { - $message = null; - break; - } - } - unset($v); - - //If top iteration, returns compiled name field. Example: "Nickname Firtsname Lastname". - if ($prev_name === '') - { - if (!empty($nickname)) - { - $nickname_str = ''; - foreach ($nickname as $value) - { - $nickname_str .= ($value ? $value . " " : ""); - } - unset($value); - } - $nickname = $nickname_str; - } - - $return_param = array( - 'email' => $email, - 'nickname' => $nickname, - 'subject' => $subject, - 'contact' => $contact, - 'message' => $message - ); - - return $return_param; - } - - /** - * Masks a value with asterisks (*) Needed by the getFieldsAny() - * @return string - */ - static public function obfuscate_param($value = null) - { - if ($value && (!is_object($value) || !is_array($value))) - { - $length = strlen($value); - $value = str_repeat('*', $length); - } - - return $value; - } - - /** - * Print html form for external forms() - * @return string - */ - static public function print_form($arr, $k) - { - foreach ($arr as $key => $value) - { - if (!is_array($value)) - { - - if ($k == '') - print ''; - else - print ''; - } - } - } - - /** - * Get URL form IP - * - * @param $ip - * - * @return string - */ - static public function ip__resolve($ip) - { - if(self::ip__validate($ip)){ - $url = gethostbyaddr($ip); - if($url) - return $url; - } - return $ip; - } - - /** - * Resolve DNS to IP - * - * @param $host - * @param bool $out - * - * @return bool - */ - static public function dns__resolve($host, $out = false) - { - - // Get DNS records about URL - if(function_exists('dns_get_record')){ - $records = dns_get_record($host, DNS_A); - if($records !== false){ - $out = $records[0]['ip']; - } - } - - // Another try if first failed - if(!$out && function_exists('gethostbynamel')){ - $records = gethostbynamel($host); - if($records !== false){ - $out = $records[0]; - } - } - - return $out; - - } - - /** - * Function sends raw http request - * - * May use 4 presets(combining possible): - * get_code - getting only HTTP response code - * async - async requests - * get - GET-request - * ssl - use SSL - * - * @param string $url URL - * @param array $data POST|GET indexed array with data to send - * @param string|array $presets String or Array with presets: get_code, async, get, ssl, dont_split_to_array - * @param array $opts Optional option for CURL connection - * - * @return array|bool (array || array('error' => true)) - * @todo Have to replace this method to the new class like HttpHelper - */ - static public function http__request($url, $data = array(), $presets = null, $opts = array()) - { - if(function_exists('curl_init')){ - - $ch = curl_init(); - - if(!empty($data)){ - // If $data scalar converting it to array - $data = is_string($data) || is_int($data) ? array($data => 1) : $data; - // Build query - $opts[CURLOPT_POSTFIELDS] = $data; - } - - // Merging OBLIGATORY options with GIVEN options - $opts = self::array_merge__save_numeric_keys( - array( - CURLOPT_URL => $url, - CURLOPT_RETURNTRANSFER => true, - CURLOPT_CONNECTTIMEOUT_MS => 6000, - CURLOPT_FORBID_REUSE => true, - CURLOPT_USERAGENT => self::AGENT . '; ' . ( isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : 'UNKNOWN_HOST' ), - CURLOPT_POST => true, - CURLOPT_SSL_VERIFYPEER => false, - CURLOPT_SSL_VERIFYHOST => 0, - CURLOPT_HTTPHEADER => array('Expect:'), // Fix for large data and old servers http://php.net/manual/ru/function.curl-setopt.php#82418 - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_MAXREDIRS => 5, - ), - $opts - ); - - // Use presets - $presets = is_array($presets) ? $presets : explode(' ', $presets); - foreach($presets as $preset){ - - switch($preset){ - - // Do not follow redirects - case 'dont_follow_redirects': - $opts[CURLOPT_FOLLOWLOCATION] = false; - $opts[CURLOPT_MAXREDIRS] = 0; - break; - - // Get headers only - case 'get_code': - $opts[CURLOPT_HEADER] = true; - $opts[CURLOPT_NOBODY] = true; - break; - - // Make a request, don't wait for an answer - case 'async': - $opts[CURLOPT_CONNECTTIMEOUT_MS] = 1000; - $opts[CURLOPT_TIMEOUT_MS] = 1000; - break; - - case 'get': - $opts[CURLOPT_URL] .= $data ? '?' . str_replace("&", "&", http_build_query($data)) : ''; - $opts[CURLOPT_CUSTOMREQUEST] = 'GET'; - $opts[CURLOPT_POST] = false; - $opts[CURLOPT_POSTFIELDS] = null; - break; - - case 'ssl': - $opts[CURLOPT_SSL_VERIFYPEER] = true; - $opts[CURLOPT_SSL_VERIFYHOST] = 2; - if(defined('CLEANTALK_CASERT_PATH') && CLEANTALK_CASERT_PATH) - $opts[CURLOPT_CAINFO] = CLEANTALK_CASERT_PATH; - break; - - default: - - break; - } - - } - unset($preset); - - curl_setopt_array($ch, $opts); - $result = curl_exec($ch); - - // RETURN if async request - if(in_array('async', $presets)) - return true; - - if($result){ - - if(strpos($result, PHP_EOL) !== false && !in_array('dont_split_to_array', $presets)) - $result = explode(PHP_EOL, $result); - - // Get code crossPHP method - if(in_array('get_code', $presets)){ - $curl_info = curl_getinfo($ch); - $result = $curl_info['http_code']; - } - curl_close($ch); - $out = $result; - }else - $out = array('error' => curl_error($ch)); - }else - $out = array('error' => 'CURL_NOT_INSTALLED'); - - /** - * Getting HTTP-response code without cURL - */ - if($presets && ($presets == 'get_code' || (is_array($presets) && in_array('get_code', $presets))) - && isset($out['error']) && $out['error'] == 'CURL_NOT_INSTALLED' - ){ - $headers = get_headers($url); - $out = (int)preg_replace('/.*(\d{3}).*/', '$1', $headers[0]); - } - - return $out; - } - - /** - * Merging arrays without reseting numeric keys - * - * @param array $arr1 One-dimentional array - * @param array $arr2 One-dimentional array - * - * @return array Merged array - */ - public static function array_merge__save_numeric_keys($arr1, $arr2) - { - foreach($arr2 as $key => $val){ - $arr1[$key] = $val; - } - return $arr1; - } - - /** - * Merging arrays without reseting numeric keys recursive - * - * @param array $arr1 One-dimentional array - * @param array $arr2 One-dimentional array - * - * @return array Merged array - */ - public static function array_merge__save_numeric_keys__recursive($arr1, $arr2) - { - foreach($arr2 as $key => $val){ - - // Array | array => array - if(isset($arr1[$key]) && is_array($arr1[$key]) && is_array($val)){ - $arr1[$key] = self::array_merge__save_numeric_keys__recursive($arr1[$key], $val); - - // Scalar | array => array - }elseif(isset($arr1[$key]) && !is_array($arr1[$key]) && is_array($val)){ - $tmp = $arr1[$key] = - $arr1[$key] = $val; - $arr1[$key][] = $tmp; - - // array | scalar => array - }elseif(isset($arr1[$key]) && is_array($arr1[$key]) && !is_array($val)){ - $arr1[$key][] = $val; - - // scalar | scalar => scalar - }else{ - $arr1[$key] = $val; - } - } - return $arr1; - } - - /** - * Function removing non UTF8 characters from array|string|object - * - * @param array|object|string $data - * - * @return array|object|string - */ - public static function removeNonUTF8($data) - { - // Array || object - if(is_array($data) || is_object($data)){ - foreach($data as $key => &$val){ - $val = self::removeNonUTF8($val); - } - unset($key, $val); - - //String - }else{ - if(!preg_match('//u', $data)) - $data = 'Nulled. Not UTF8 encoded or malformed.'; - } - return $data; - } - - /** - * Function convert anything to UTF8 and removes non UTF8 characters - * - * @param array|object|string $obj - * @param string $data_codepage - * - * @return mixed(array|object|string) - */ - public static function toUTF8($obj, $data_codepage = null) - { - // Array || object - if(is_array($obj) || is_object($obj)){ - foreach($obj as $key => &$val){ - $val = self::toUTF8($val, $data_codepage); - } - unset($key, $val); - - //String - }else{ - if(!preg_match('//u', $obj) && function_exists('mb_detect_encoding') && function_exists('mb_convert_encoding')){ - $encoding = mb_detect_encoding($obj); - $encoding = $encoding ? $encoding : $data_codepage; - if($encoding) - $obj = mb_convert_encoding($obj, 'UTF-8', $encoding); - } - } - return $obj; - } - - /** - * Function convert from UTF8 - * - * @param array|object|string $obj - * @param string $data_codepage - * - * @return mixed (array|object|string) - */ - public static function fromUTF8($obj, $data_codepage = null) - { - // Array || object - if(is_array($obj) || is_object($obj)){ - foreach($obj as $key => &$val){ - $val = self::fromUTF8($val, $data_codepage); - } - unset($key, $val); - - //String - }else{ - if(preg_match('u', $obj) && function_exists('mb_convert_encoding') && $data_codepage !== null) - $obj = mb_convert_encoding($obj, $data_codepage, 'UTF-8'); - } - return $obj; - } - - /** - * Checks if the string is JSON type - * - * @param string - * - * @return bool - */ - static public function is_json($string) - { - return is_string($string) && is_array(json_decode($string, true)) ? true : false; - } - - /** - * Universal method to adding cookies - * - * @param $name - * @param string $value - * @param int $expires - * @param string $path - * @param null $domain - * @param bool $secure - * @param bool $httponly - * @param string $samesite - * - * @return void - */ - public static function apbct_cookie__set ($name, $value = '', $expires = 0, $path = '', $domain = null, $secure = false, $httponly = false, $samesite = 'Lax' ) { - - // For PHP 7.3+ and above - if( version_compare( phpversion(), '7.3.0', '>=' ) ){ - - $params = array( - 'expires' => $expires, - 'path' => $path, - 'domain' => $domain, - 'secure' => $secure, - 'httponly' => $httponly, - ); - - if($samesite) - $params['samesite'] = $samesite; - - setcookie( $name, $value, $params ); - - // For PHP 5.6 - 7.2 - }else { - setcookie( $name, $value, $expires, $path, $domain, $secure, $httponly ); - } - - } - - public static function time__get_interval_start( $interval = 300 ){ - return time() - ( ( time() - strtotime( date( 'd F Y' ) ) ) % $interval ); - } - - /** - * Get mime type from file or data - * - * @param string $data Path to file or data - * @param string $type Default mime type. Returns if we failed to detect type - * - * @return string - */ - static function get_mime_type( $data, $type = '' ) - { - $data = str_replace( chr(0), '', $data ); // Clean input of null bytes - if( ! empty( $data ) && @file_exists( $data )){ - $type = mime_content_type( $data ); - }elseif( function_exists('finfo_open' ) ){ - $finfo = finfo_open(FILEINFO_MIME_TYPE); - $type = finfo_buffer($finfo, $data); - finfo_close($finfo); - } - return $type; - } - - static function buffer__trim_and_clear_from_empty_lines( $buffer ){ - $buffer = (array) $buffer; - foreach( $buffer as $indx => &$line ){ - $line = trim( $line ); - if($line === '') - unset( $buffer[$indx] ); - } - return $buffer; - } - - static function buffer__parse__csv( $buffer ){ - $buffer = explode( "\n", $buffer ); - $buffer = self::buffer__trim_and_clear_from_empty_lines( $buffer ); - foreach($buffer as &$line){ - $line = str_getcsv($line, ',', '\''); - } - return $buffer; - } - - /** - * Pops line from buffer without formatting - * - * @param $csv - * - * @return false|string - */ - static public function buffer__csv__pop_line( &$csv ){ - $pos = strpos( $csv, "\n" ); - $line = substr( $csv, 0, $pos ); - $csv = substr_replace( $csv, '', 0, $pos + 1 ); - return $line; - } - - /** - * Pops line from the csv buffer and fromat it by map to array - * - * @param $csv - * @param array $map - * - * @return array|false - */ - static public function buffer__csv__get_map( &$csv ){ - $line = static::buffer__csv__pop_line( $csv ); - return explode( ',', $line ); - } - - /** - * Pops line from the csv buffer and fromat it by map to array - * - * @param $csv - * @param array $map - * - * @return array|false - */ - static public function buffer__csv__pop_line_to_array( &$csv, $map = array() ){ - $line = trim( static::buffer__csv__pop_line( $csv ) ); - $line = strpos( $line, '\'' ) === 0 - ? str_getcsv($line, ',', '\'') - : explode( ',', $line ); - if( $map ) - $line = array_combine( $map, $line ); - return $line; - } - - /** - * Escapes MySQL params - * - * @param string|int $param - * @param string $quotes - * - * @return int|string - */ - public static function db__prepare_param($param, $quotes = '\'') - { - if(is_array($param)){ - foreach($param as &$par){ - $par = self::db__prepare_param($par); - } - } - switch(true){ - case is_numeric($param): - $param = intval($param); - break; - case is_string($param) && strtolower($param) == 'null': - $param = 'NULL'; - break; - case is_string($param): - global $wpdb; - $param = $quotes . $wpdb->_real_escape($param) . $quotes; - break; - } - return $param; - } - - /** - * Gets every HTTP_ headers from $_SERVER - * - * If Apache web server is missing then making - * Patch for apache_request_headers() - * - * @return array - * @todo Have to replace this method to the new class like HttpHelper - */ - public static function http__get_headers(){ - - $headers = array(); - foreach($_SERVER as $key => $val){ - if( 0 === stripos( $key, 'http_' ) ){ - $server_key = preg_replace('/^http_/i', '', $key); - $key_parts = explode('_', $server_key); - if(count($key_parts) > 0 and strlen($server_key) > 2){ - foreach($key_parts as $part_index => $part){ - $key_parts[$part_index] = function_exists('mb_strtolower') ? mb_strtolower($part) : strtolower($part); - $key_parts[$part_index][0] = strtoupper($key_parts[$part_index][0]); - } - $server_key = implode('-', $key_parts); - } - $headers[$server_key] = $val; - } - } - return $headers; - } - - /** - * Wrapper for http_request - * Requesting HTTP response code for $url - * - * @param string $url - * - * @return array|mixed|string - */ - public static function http__request__get_response_code($url ){ - return static::http__request( $url, array(), 'get_code'); - } - - /** - * Wrapper for http_request - * Requesting data via HTTP request with GET method - * - * @param string $url - * - * @return array|mixed|string - */ - public static function http__request__get_content($url ){ - return static::http__request( $url, array(), 'get dont_split_to_array'); - } - - /** - * Do the remote call to the host. - * - * @param string $rc_action - * @param array $request_params - * @param array $patterns - * @return array|bool - * @todo Have to replace this method to the new class like HttpHelper - */ - public static function http__request__rc_to_host($rc_action, $request_params, $patterns = array() ) - { - $request_params__default = array( - 'spbc_remote_call_action' => $rc_action, - 'plugin_name' => 'apbct', - ); - - $result__rc_check_website = static::http__request( - static::getSiteUrl(), - array_merge( $request_params__default, $request_params, array( 'test' => 'test' ) ), - array( 'get', ) - ); - - if( empty( $result__rc_check_website['error'] ) ){ - - if (is_string($result__rc_check_website) && preg_match('@^.*?OK$@', $result__rc_check_website)) { - - static::http__request( - static::getSiteUrl(), - array_merge( $request_params__default, $request_params ), - array_merge( array( 'get', ), $patterns ) - ); - - }else - return array( - 'error' => 'WRONG_SITE_RESPONSE ACTION: ' . $rc_action . ' RESPONSE: ' . htmlspecialchars( substr( - ! is_string( $result__rc_check_website ) - ? print_r( $result__rc_check_website, true ) - : $result__rc_check_website, - 0, - 400 - ) ) - ); - }else - return array( 'error' => 'WRONG_SITE_RESPONSE TEST ACTION: ' . $rc_action . ' ERROR: ' . $result__rc_check_website['error'] ); - - return true; - } - - /** - * Get site url for remote calls. - * - * @return string@important This method can be overloaded in the CMS-based Helper class. - * - */ - private static function getSiteUrl() - { - return ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ( isset($_SERVER['SCRIPT_URL'] ) ? $_SERVER['SCRIPT_URL'] : '' ); - } - - /** - * Get fw stats from the storage. - * - * @return array - * @example array( 'firewall_updating' => false, 'firewall_updating_id' => md5(), 'firewall_update_percent' => 0, 'firewall_updating_last_start' => 0 ) - * @important This method must be overloaded in the CMS-based Helper class. - */ - public static function getFwStats() - { - die( __METHOD__ . ' method must be overloaded in the CMS-based Helper class' ); - } - - /** - * Save fw stats on the storage. - * - * @param array $fw_stats - * @return bool - * @important This method must be overloaded in the CMS-based Helper class. - */ - public static function setFwStats( $fw_stats ) - { - die( __METHOD__ . ' method must be overloaded in the CMS-based Helper class' ); - } - - /** - * Implement here any actions after SFW updating finished. - * - * @return void - */ - public static function SfwUpdate_DoFinisnAction() - { - // EXAMPLE - // global $apbct; - // $apbct->data['last_firewall_updated'] = current_time('timestamp'); - // $apbct->save('data'); - // $apbct->stats['sfw']['entries'] = $wpdb->get_var('SELECT COUNT(*) FROM ' . APBCT_TBL_FIREWALL_DATA ); - // $apbct->stats['sfw']['last_update_time'] = time(); - // $apbct->save('stats'); - } -} \ No newline at end of file diff --git a/lib/Cleantalk/Common/Helper/Helper.php b/lib/Cleantalk/Common/Helper/Helper.php new file mode 100644 index 0000000..2a5710a --- /dev/null +++ b/lib/Cleantalk/Common/Helper/Helper.php @@ -0,0 +1,1177 @@ + array( + '10.0.0.0/8', + '100.64.0.0/10', + '172.16.0.0/12', + '192.168.0.0/16', + '127.0.0.1/32', + ), + 'v6' => array( + '0:0:0:0:0:0:0:1/128', // localhost + '0:0:0:0:0:0:a:1/128', // ::ffff:127.0.0.1 + ), + ); + + /** + * @var array Set of CleanTalk servers + */ + public static $cleantalks_servers = array( + // MODERATE + 'https://moderate1.cleantalk.org' => '143.198.237.245', + 'https://moderate2.cleantalk.org' => '167.71.167.197', + 'https://moderate3.cleantalk.org' => '88.198.153.60', + 'https://moderate4.cleantalk.org' => '159.69.51.30', + 'https://moderate5.cleantalk.org' => '95.216.200.119', + 'https://moderate6.cleantalk.org' => '143.244.187.11', + 'https://moderate7.cleantalk.org' => '168.119.82.149', + 'https://moderate9.cleantalk.org' => '51.81.55.251', + 'https://moderate10.cleantalk.org' => '5.9.221.162', + + // APIX + 'https://apix1.cleantalk.org' => '35.158.52.161', + 'https://apix2.cleantalk.org' => '18.206.49.217', + 'https://apix3.cleantalk.org' => '3.18.23.246', + 'https://apix4.cleantalk.org' => '44.227.90.42', + 'https://apix5.cleantalk.org' => '15.188.198.212', + 'https://apix6.cleantalk.org' => '54.219.94.72', + //ns + 'http://netserv2.cleantalk.org' => '178.63.60.214', + 'http://netserv3.cleantalk.org' => '188.40.14.173', + ); + + /** + * @var array Stored IPs + * [ + * [ type ] => IP, + * [ type ] => IP, + * ] + */ + private $ips_stored = array(); + + /** + * @var array Stored HTTP headers + */ + private $headers = array(); + + /** + * Getting arrays of IP (REMOTE_ADDR, X-Forwarded-For, X-Real-Ip, Cf_Connecting_Ip) + * + * @param string $ip_type_to_get Type of IP you want to receive + * @param bool $v4_only + * + * @return string|null + * + * @psalm-suppress InvalidReturnStatement + * @psalm-suppress ComplexMethod + * @psalm-suppress FalsableReturnStatement + */ + public static function ipGet($ip_type_to_get = 'real', $v4_only = true, $headers = array()) + { + // If return the IP of the current type if it already has been detected + $ips_stored = self::getInstance()->ips_stored; + if ( ! empty($ips_stored[ $ip_type_to_get ]) ) { + return $ips_stored[ $ip_type_to_get ]; + } + + $out = null; + + switch ($ip_type_to_get) { + // Cloud Flare + case 'cloud_flare': + $headers = $headers ?: self::httpGetHeaders(); + if ( + isset($headers['Cf-Connecting-Ip']) && + (isset($headers['Cf-Ray']) || isset($headers['X-Wpe-Request-Id'])) && + ! isset($headers['X-Gt-Clientip']) + ) { + if (isset($headers['Cf-Pseudo-Ipv4'], $headers['Cf-Pseudo-Ipv6'])) { + $source = $headers['Cf-Pseudo-Ipv6']; + } else { + $source = $headers['Cf-Connecting-Ip']; + } + $tmp = strpos($source, ',') !== false + ? explode(',', $source) + : (array)$source; + $ip_version = self::ipValidate(trim($tmp[0])); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize(trim($tmp[0])) : trim( + $tmp[0] + ); + } + } + break; + + // GTranslate + case 'gtranslate': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Gt-Clientip'], $headers['X-Gt-Viewer-Ip'])) { + $ip_version = self::ipValidate($headers['X-Gt-Viewer-Ip']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['X-Gt-Viewer-Ip'] + ) : $headers['X-Gt-Viewer-Ip']; + } + } + break; + + // ezoic + case 'ezoic': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Middleton'], $headers['X-Middleton-Ip'])) { + $ip_version = self::ipValidate($headers['X-Middleton-Ip']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['X-Middleton-Ip'] + ) : $headers['X-Middleton-Ip']; + } + } + break; + + // Sucury + case 'sucury': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Sucuri-Clientip'])) { + $ip_version = self::ipValidate($headers['X-Sucuri-Clientip']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['X-Sucuri-Clientip'] + ) : $headers['X-Sucuri-Clientip']; + } + } + break; + + // X-Forwarded-By + case 'x_forwarded_by': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Forwarded-By'], $headers['X-Client-Ip'])) { + $ip_version = self::ipValidate($headers['X-Client-Ip']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['X-Client-Ip'] + ) : $headers['X-Client-Ip']; + } + } + break; + + // Stackpath + case 'stackpath': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Sp-Edge-Host'], $headers['X-Sp-Forwarded-Ip'])) { + $ip_version = self::ipValidate($headers['X-Sp-Forwarded-Ip']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['X-Sp-Forwarded-Ip'] + ) : $headers['X-Sp-Forwarded-Ip']; + } + } + break; + + // Ico-X-Forwarded-For + case 'ico_x_forwarded_for': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['Ico-X-Forwarded-For'], $headers['X-Forwarded-Host'])) { + $ip_version = self::ipValidate($headers['Ico-X-Forwarded-For']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['Ico-X-Forwarded-For'] + ) : $headers['Ico-X-Forwarded-For']; + } + } + break; + + // OVH + case 'ovh': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Cdn-Any-Ip'], $headers['Remote-Ip'])) { + $ip_version = self::ipValidate($headers['Remote-Ip']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['Remote-Ip'] + ) : $headers['Remote-Ip']; + } + } + break; + + // Incapsula proxy + case 'incapsula': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['Incap-Client-Ip'], $headers['X-Forwarded-For'])) { + $ip_version = self::ipValidate($headers['Incap-Client-Ip']); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + $headers['Incap-Client-Ip'] + ) : $headers['Incap-Client-Ip']; + } + } + break; + + // Incapsula proxy like "X-Clientside":"10.10.10.10:62967 -> 192.168.1.1:443" + case 'clientside': + $headers = $headers ?: self::httpGetHeaders(); + if ( + isset($headers['X-Clientside']) + && (preg_match('/^([0-9a-f.:]+):\d+ -> ([0-9a-f.:]+):\d+$/', $headers['X-Clientside'], $matches) + && isset($matches[1])) + ) { + $ip_version = self::ipValidate($matches[1]); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize($matches[1]) : $matches[1]; + } + } + break; + + // Remote addr + case 'remote_addr': + $ip_version = self::ipValidate(Server::get('REMOTE_ADDR')); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize( + Server::get('REMOTE_ADDR') + ) : Server::get('REMOTE_ADDR'); + } + break; + + // X-Forwarded-For + case 'x_forwarded_for': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Forwarded-For'])) { + $tmp = explode(',', trim($headers['X-Forwarded-For'])); + $tmp = trim($tmp[0]); + $ip_version = self::ipValidate($tmp); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize($tmp) : $tmp; + } + } + break; + + // X-Real-Ip + case 'x_real_ip': + $headers = $headers ?: self::httpGetHeaders(); + if (isset($headers['X-Real-Ip'])) { + $tmp = explode(",", trim($headers['X-Real-Ip'])); + $tmp = trim($tmp[0]); + $ip_version = self::ipValidate($tmp); + if ($ip_version) { + $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize($tmp) : $tmp; + } + } + break; + + // Real + // Getting real IP from REMOTE_ADDR or Cf_Connecting_Ip if set or from (X-Forwarded-For, X-Real-Ip) if REMOTE_ADDR is local. + case 'real': + // Detect IP type + $out = self::ipGet('cloud_flare', $v4_only, $headers); + $out = $out ?: self::ipGet('sucury', $v4_only, $headers); + $out = $out ?: self::ipGet('gtranslate', $v4_only, $headers); + $out = $out ?: self::ipGet('ezoic', $v4_only, $headers); + $out = $out ?: self::ipGet('stackpath', $v4_only, $headers); + $out = $out ?: self::ipGet('x_forwarded_by', $v4_only, $headers); + $out = $out ?: self::ipGet('ico_x_forwarded_for', $v4_only, $headers); + $out = $out ?: self::ipGet('ovh', $v4_only, $headers); + $out = $out ?: self::ipGet('incapsula', $v4_only, $headers); + $out = $out ?: self::ipGet('clientside', $v4_only, $headers); + + $ip_version = self::ipValidate($out); + + // Is private network + if ( + ! $out || + ( + is_string($ip_version) && ( + self::ipIsPrivateNetwork($out, $ip_version) || + self::ipMaskMatch( + $out, + Server::get('SERVER_ADDR') . '/24', + $ip_version + )) + ) + ) { + //@todo Remove local IP from x-forwarded-for and x-real-ip + $out = $out ?: self::ipGet('x_forwarded_for', $v4_only, $headers); + $out = $out ?: self::ipGet('x_real_ip', $v4_only, $headers); + } + + $out = $out ?: self::ipGet('remote_addr', $v4_only, $headers); + + break; + + default: + $out = self::ipGet('real', $v4_only, $headers); + } + + $ip_version = self::ipValidate($out); + + if ( ! $ip_version ) { + $out = null; + } + + if ( $ip_version === 'v6' && $v4_only ) { + $out = null; + } + + // Store the IP of the current type to skip the work next time + self::getInstance()->ips_stored[ $ip_type_to_get ] = $out; + + return $out; + } + + /** + * Checks if the IP is in private range + * + * @param string $ip + * @param string $ip_type + * + * @return bool + */ + public static function ipIsPrivateNetwork($ip, $ip_type = 'v4') + { + return self::ipMaskMatch($ip, self::$private_networks[$ip_type], $ip_type); + } + + /** + * Check if the IP belong to mask. Recursive. + * Octet by octet for IPv4 + * Hextet by hextet for IPv6 + * + * @param string $ip + * @param string|array $cidr work to compare with + * @param string $ip_type IPv6 or IPv4 + * @param int $xtet_count Recursive counter. Determs current part of address to check. + * + * @return bool + * @psalm-suppress InvalidScalarArgument + */ + public static function ipMaskMatch($ip, $cidr, $ip_type = 'v4', $xtet_count = 0) + { + if (is_array($cidr)) { + foreach ($cidr as $curr_mask) { + if (self::ipMaskMatch($ip, $curr_mask, $ip_type)) { + return true; + } + } + + return false; + } + + if ( ! self::ipValidate($ip) || ! self::cidrValidate($cidr) ) { + return false; + } + + $xtet_base = ($ip_type === 'v4') ? 8 : 16; + + // Calculate mask + $exploded = explode('/', $cidr); + $net_ip = $exploded[0]; + $mask = (int)$exploded[1]; + + // Exit condition + $xtet_end = ceil($mask / $xtet_base); + if ($xtet_count == $xtet_end) { + return true; + } + + // Length of bits for comparison + $mask = $mask - $xtet_base * $xtet_count >= $xtet_base ? $xtet_base : $mask - $xtet_base * $xtet_count; + + // Explode by octets/hextets from IP and Net + $net_ip_xtets = explode($ip_type === 'v4' ? '.' : ':', $net_ip); + $ip_xtets = explode($ip_type === 'v4' ? '.' : ':', $ip); + + // Standartizing. Getting current octets/hextets. Adding leading zeros. + $net_xtet = str_pad( + decbin( + ($ip_type === 'v4' && (int)$net_ip_xtets[$xtet_count]) ? $net_ip_xtets[$xtet_count] : @hexdec( + $net_ip_xtets[$xtet_count] + ) + ), + $xtet_base, + 0, + STR_PAD_LEFT + ); + $ip_xtet = str_pad( + decbin( + ($ip_type === 'v4' && (int)$ip_xtets[$xtet_count]) ? $ip_xtets[$xtet_count] : @hexdec( + $ip_xtets[$xtet_count] + ) + ), + $xtet_base, + 0, + STR_PAD_LEFT + ); + + // Comparing bit by bit + for ($i = 0, $result = true; $mask != 0; $mask--, $i++) { + if ($ip_xtet[$i] != $net_xtet[$i]) { + $result = false; + break; + } + } + + // Recursing. Moving to next octet/hextet. + if ($result) { + $result = self::ipMaskMatch($ip, $cidr, $ip_type, $xtet_count + 1); + } + + return $result; + } + + /** + * Converts long mask like 4294967295 to number like 32 + * + * @param int $long_mask + * + * @return int + * @psalm-suppress PossiblyUnusedMethod + */ + public static function ipMaskLongToNumber($long_mask) + { + $num_mask = strpos(decbin($long_mask), '0'); + + return $num_mask === false ? 32 : $num_mask; + } + + /** + * Validating IPv4, IPv6 + * + * @param string $ip + * + * @return string|bool + */ + public static function ipValidate($ip) + { + if ( ! $ip) { // NULL || FALSE || '' || so on... + return false; + } + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) && $ip != '0.0.0.0') { // IPv4 + return 'v4'; + } + if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && self::ipV6Reduce($ip) != '0::0') { // IPv6 + return 'v6'; + } + + return false; // Unknown + } + + /** + * Validate CIDR + * + * @param string $cidr expects string like 1.1.1.1/32 + * + * @return bool + */ + public static function cidrValidate($cidr) + { + $cidr = explode('/', $cidr); + + return isset($cidr[0], $cidr[1]) && self::ipValidate($cidr[0]) && preg_match('@\d{1,2}@', $cidr[1]); + } + + /** + * Expand IPv6 + * + * @param string $ip + * + * @return string IPv6 + */ + public static function ipV6Normalize($ip) + { + $ip = trim($ip); + // Searching for ::ffff:xx.xx.xx.xx patterns and turn it to IPv6 + if (preg_match('/^::ffff:([0-9]{1,3}\.?){4}$/', $ip)) { + $ip = dechex((int)sprintf("%u", ip2long(substr($ip, 7)))); + $ip = '0:0:0:0:0:0:' . (strlen($ip) > 4 ? substr('abcde', 0, -4) : '0') . ':' . substr($ip, -4, 4); + // Normalizing hextets number + } elseif (strpos($ip, '::') !== false) { + $ip = str_replace('::', str_repeat(':0', 8 - substr_count($ip, ':')) . ':', $ip); + $ip = strpos($ip, ':') === 0 ? '0' . $ip : $ip; + $ip = strpos(strrev($ip), ':') === 0 ? $ip . '0' : $ip; + } + // Simplifyng hextets + if (preg_match('/:0(?=[a-z0-9]+)/', $ip)) { + $ip = preg_replace('/:0(?=[a-z0-9]+)/', ':', strtolower($ip)); + $ip = self::ipV6Normalize($ip); + } + + return $ip; + } + + /** + * Reduce IPv6 + * + * @param string $ip + * + * @return string IPv6 + */ + public static function ipV6Reduce($ip) + { + if (strpos($ip, ':') !== false) { + $ip = preg_replace('/:0{1,4}/', ':', $ip); + $ip = preg_replace('/:{2,}/', '::', $ip); + $ip = strpos($ip, '0') === 0 && substr($ip, 1) !== false ? substr($ip, 1) : $ip; + } + + return $ip; + } + + /** + * Get URL form IP. Check if it's belong to cleantalk. + * + * @param string $ip + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function ipIsCleantalks($ip) + { + if (self::ipValidate($ip)) { + $url = array_search($ip, self::$cleantalks_servers); + + return (bool)$url; + } + + return false; + } + + /** + * Get URL form IP. Check if it's belong to cleantalk. + * + * @param $ip + * + * @return false|int|string|bool + */ + public static function ipResolveCleantalks($ip) + { + if (self::ipValidate($ip)) { + $url = array_search($ip, self::$cleantalks_servers); + + return $url + ? parse_url($url, PHP_URL_HOST) + : self::ipResolve($ip); + } + + return $ip; + } + + /** + * Get URL form IP + * + * @param $ip + * + * @return string + */ + public static function ipResolve($ip) + { + if (self::ipValidate($ip)) { + $url = gethostbyaddr($ip); + if ($url) { + return $url; + } + } + + return $ip; + } + + /** + * Resolve DNS to IP + * + * @param $host + * @param bool $out + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function dnsResolve($host, $out = false) + { + // Get DNS records about URL + if (function_exists('dns_get_record')) { + $records = dns_get_record($host, DNS_A); + if ($records !== false) { + $out = $records[0]['ip']; + } + } + + // Another try if first failed + if ( ! $out && function_exists('gethostbynamel')) { + $records = gethostbynamel($host); + if ($records !== false) { + $out = $records[0]; + } + } + + return $out; + } + + /** + * Function sends raw http request + * + * May use 4 presets(combining possible): + * get_code - getting only HTTP response code + * async - async requests + * get - GET-request + * ssl - use SSL + * + * @param string|array $url URL + * @param array|string|int $data POST|GET indexed array with data to send + * @param string|array $presets String or Array with presets: get_code, async, get, ssl, dont_split_to_array + * @param array $opts Optional option for CURL connection + * + * @return array|bool|string (array || array('error' => true)) + */ + public static function httpRequest($url, $data = array(), $presets = array(), $opts = array()) + { + $http = new Request(); + + return $http->setUrl($url) + ->setData($data) + ->setPresets($presets) + ->setOptions($opts) + ->request(); + } + + /** + * Do multi curl requests. + * + * @param array $urls Array of URLs to requests + * + * @return array|bool|string + */ + public static function httpMultiRequest($urls, $write_to = '') + { + if ( ! is_array($urls) || empty($urls)) { + return array('error' => 'CURL_MULTI: Parameter is not an array.'); + } + + foreach ($urls as $url) { + if ( ! is_string($url)) { + return array('error' => 'CURL_MULTI: Parameter elements must be strings.'); + } + } + + $http = new Request(); + + $http->setUrl($urls) + ->setPresets('get'); + + if ( $write_to ) { + $http->addCallback( + static function ($content, $url) use ($write_to) { + if ( is_dir($write_to) && is_writable($write_to) ) { + return file_put_contents($write_to . self::getFilenameFromUrl($url), $content) + ? 'success' + : 'error'; + } + + return $content; + } + ); + } + + return $http->request(); + } + + /** + * Wrapper for http_request + * Requesting HTTP response code for $url + * + * @param string $url + * + * @return array|mixed|string + */ + public static function httpRequestGetResponseCode($url) + { + return static::httpRequest($url, array(), 'get_code get'); + } + + /** + * Wrapper for http_request + * Requesting data via HTTP request with GET method + * + * @param string $url + * + * @return array|mixed|string + */ + public static function httpRequestGetContent($url) + { + return static::httpRequest($url, array(), 'get dont_split_to_array'); + } + + /** + * Wrapper for http_request + * Get data from remote GZ archive with all following checks + * + * @param string $url + * + * @return array|mixed|string + */ + public static function httpGetDataFromRemoteGz($url) + { + $response_code = static::httpRequestGetResponseCode($url); + + if ($response_code === 200) { // Check if it's there + $data = static::httpRequestGetContent($url); + + if (empty($data['error'])) { + if (static::getMimeType($data, 'application/x-gzip')) { + if (function_exists('gzdecode')) { + $data = gzdecode($data); + + if ($data !== false) { + return $data; + } else { + return array('error' => 'Can not unpack datafile'); + } + } else { + return array('error' => 'Function gzdecode not exists. Please update your PHP at least to version 5.4 ' . $data['error']); + } + } else { + return array('error' => 'Wrong file mime type: ' . $url); + } + } else { + return array('error' => 'Getting datafile ' . $url . '. Error: ' . $data['error']); + } + } else { + return array('error' => 'Bad HTTP response (' . (int)$response_code . ') from file location: ' . $url); + } + } + + /** + * Wrapper for http__get_data_from_remote_gz + * Get data and parse CSV from remote GZ archive with all following checks + * + * @param string $url + * + * @return array|string + */ + public static function httpGetDataFromRemoteGzAndParseCsv($url) + { + $result = static::httpGetDataFromRemoteGz($url); + + return empty($result['error']) + ? static::bufferParseCsv($result) + : $result; + } + + /** + * Merging arrays without resetting numeric keys + * + * @param array $arr1 One-dimensional array + * @param array $arr2 One-dimensional array + * + * @return array Merged array + */ + public static function arrayMergeSaveNumericKeys($arr1, $arr2) + { + foreach ($arr2 as $key => $val) { + $arr1[$key] = $val; + } + + return $arr1; + } + + /** + * Merging arrays without reseting numeric keys recursive + * + * @param array $arr1 One-dimentional array + * @param array $arr2 One-dimentional array + * + * @return array Merged array + * @psalm-suppress PossiblyUnusedMethod + */ + public static function arrayMergeSaveNumericKeysRecursive($arr1, $arr2) + { + foreach ($arr2 as $key => $val) { + // Array | array => array + if (isset($arr1[$key]) && is_array($arr1[$key]) && is_array($val)) { + $arr1[$key] = self::arrayMergeSaveNumericKeysRecursive($arr1[$key], $val); + // Scalar | array => array + } elseif (isset($arr1[$key]) && ! is_array($arr1[$key]) && is_array($val)) { + $tmp = $arr1[$key] = + $arr1[$key] = $val; + $arr1[$key][] = $tmp; + // array | scalar => array + } elseif (isset($arr1[$key]) && is_array($arr1[$key]) && ! is_array($val)) { + $arr1[$key][] = $val; + // scalar | scalar => scalar + } else { + $arr1[$key] = $val; + } + } + + return $arr1; + } + + /** + * Function removing non UTF8 characters from array|string|object + * + * @param array|object|string $data + * + * @return array|object|string + */ + public static function removeNonUTF8($data) + { + // Array || object + if (is_array($data) || is_object($data)) { + foreach ($data as $_key => &$val) { + $val = self::removeNonUTF8($val); + } + unset($val); + //String + } else { + if ( ! preg_match('//u', $data)) { + $data = 'Nulled. Not UTF8 encoded or malformed.'; + } + } + + return $data; + } + + /** + * Function convert anything to UTF8 and removes non UTF8 characters + * + * @param array|object|string $obj + * @param null|string $data_codepage + * + * @return array|false|mixed|string|string[]|null + */ + public static function toUTF8($obj, $data_codepage = null) + { + // Array || object + if (is_array($obj) || is_object($obj)) { + foreach ($obj as $_key => &$val) { + $val = self::toUTF8($val, $data_codepage); + } + unset($val); + //String + } else { + if ( ! preg_match('//u', $obj) + && function_exists('mb_detect_encoding') + && function_exists('mb_convert_encoding') + ) { + $encoding = mb_detect_encoding($obj); + $encoding = $encoding ?: $data_codepage; + if ($encoding) { + $obj = mb_convert_encoding($obj, 'UTF-8', $encoding); + } + } + } + + return $obj; + } + + /** + * Function convert from UTF8 + * + * @param array|object|string $obj + * @param string $data_codepage + * + * @return mixed (array|object|string) + */ + public static function fromUTF8($obj, $data_codepage = null) + { + // Array || object + if (is_array($obj) || is_object($obj)) { + foreach ($obj as $_key => &$val) { + $val = self::fromUTF8($val, $data_codepage); + } + unset($val); + //String + } else { + if (preg_match('u', $obj) && function_exists('mb_convert_encoding') && $data_codepage !== null) { + $obj = mb_convert_encoding($obj, $data_codepage, 'UTF-8'); + } + } + + return $obj; + } + + /** + * Checks if the string is JSON type + * + * @param string $string + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function isJson($string) + { + return is_string($string) && is_array(json_decode($string, true)); + } + + /** + * @param int $interval + * + * @return int + * @psalm-suppress PossiblyUnusedMethod + */ + public static function timeGetIntervalStart($interval = 300) + { + return time() - ((time() - strtotime(date('d F Y'))) % $interval); + } + + /** + * Get mime type from file or data + * + * @param string $data Path to file or data + * @param string $type Default mime type. Returns if we failed to detect type + * + * @return string + * @psalm-suppress PossiblyUnusedMethod + */ + public static function getMimeType($data, $type = '') + { + $data = str_replace(chr(0), '', $data); // Clean input of null bytes + if ( ! empty($data) && @file_exists($data)) { + $type = mime_content_type($data); + } elseif (function_exists('finfo_open')) { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + $type = finfo_buffer($finfo, $data); + finfo_close($finfo); + } + + // @ToDo the method must return comparison result: return $type === mime_content_type($data) + return $type; + } + + public static function bufferTrimAndClearFromEmptyLines($buffer) + { + $buffer = (array)$buffer; + foreach ($buffer as $indx => &$line) { + $line = trim($line); + if ($line === '') { + unset($buffer[$indx]); + } + } + + return $buffer; + } + + /** + * @param $buffer + * + * @return array + * @psalm-suppress PossiblyUnusedMethod + */ + public static function bufferParseCsv($buffer) + { + $buffer = explode("\n", $buffer); + $buffer = self::bufferTrimAndClearFromEmptyLines($buffer); + foreach ($buffer as &$line) { + $line = str_getcsv($line, ',', '\''); + } + + return $buffer; + } + + /** + * Pops line from buffer without formatting + * + * @param $csv + * + * @return false|string + */ + public static function bufferCsvPopLine(&$csv) + { + $pos = strpos($csv, "\n"); + $line = substr($csv, 0, $pos); + $csv = substr_replace($csv, '', 0, $pos + 1); + + return $line; + } + + /** + * Pops line from the csv buffer and format it by map to array + * + * @param $csv + * @param array $map + * + * @return array|false + * @psalm-suppress PossiblyUnusedMethod + */ + public static function bufferCsvGetMap(&$csv) + { + $line = static::bufferCsvPopLine($csv); + + return explode(',', $line); + } + + /** + * Pops line from the csv buffer and format it by map to array + * + * @param $csv + * @param array $map + * + * @return array|false + * @psalm-suppress PossiblyUnusedMethod + */ + public static function bufferCsvPopLineToArray(&$csv, $map = array()) + { + $line = trim(static::bufferCsvPopLine($csv)); + $line = strpos($line, '\'') === 0 + ? str_getcsv($line, ',', '\'') + : explode(',', $line); + if ($map) { + $line = array_combine($map, $line); + } + + return $line; + } + + /** + * Escapes MySQL params + * + * @param string|int|array $param + * @param string $quotes + * + * @return int|string|array + * @psalm-suppress PossiblyUnusedMethod + */ + public static function dbPrepareParam($param, $quotes = '\'') + { + if (is_array($param)) { + foreach ($param as &$par) { + $par = self::dbPrepareParam($par); + } + unset($par); + } + switch (true) { + case is_numeric($param): + $param = (int)$param; + break; + case is_string($param) && strtolower($param) === 'null': + $param = 'NULL'; + break; + case is_string($param): + $param = $quotes . addslashes($param) . $quotes; + break; + } + + return $param; + } + + /** + * Gets every HTTP_ headers from $_SERVER + * + * If Apache web server is missing then making + * Patch for apache_request_headers() + * + * returns array + */ + public static function httpGetHeaders() + { + // If headers already return them + $headers = self::getInstance()->headers; + if ( ! empty($headers) ) { + return $headers; + } + foreach ($_SERVER as $key => $val) { + if (0 === stripos($key, 'http_')) { + $server_key = preg_replace('/^http_/i', '', $key); + $key_parts = explode('_', $server_key); + if (strlen($server_key) > 2) { + foreach ($key_parts as $part_index => $part) { + if ($part === '') { + continue; + } + + $key_parts[$part_index] = function_exists('mb_strtolower') ? mb_strtolower( + $part + ) : strtolower( + $part + ); + $key_parts[$part_index][0] = strtoupper($key_parts[$part_index][0]); + } + $server_key = implode('-', $key_parts); + } + $headers[$server_key] = $val; + } + } + + // Store headers to skip the work next time + self::getInstance()->headers = $headers; + + return $headers; + } + + /** + * Its own implementation of the native method long2ip() + * + * @return string + * @psalm-suppress PossiblyUnusedMethod + */ + public static function ipLong2ip($ipl32) + { + $ip[0] = ($ipl32 >> 24) & 255; + $ip[1] = ($ipl32 >> 16) & 255; + $ip[2] = ($ipl32 >> 8) & 255; + $ip[3] = $ipl32 & 255; + + return implode('.', $ip); + } + + /** + * @param $url string + * + * @return string + */ + protected static function getFilenameFromUrl($url) + { + $array = explode('/', $url); + + return end($array); + } + + /** + * Validate date format Y-m-d + * + * @return boolean + */ + public static function dateValidate($date) + { + $date_arr = explode('-', $date); + + if (count($date_arr) === 3) { + if (checkdate((int)$date_arr[1], (int)$date_arr[2], (int)$date_arr[0])) { + return true; + } + } + + return false; + } + + public static function isApikeyCorrect($api_key) + { + return (bool) preg_match('/^[a-z\d]{3,30}$/', $api_key); + } +} diff --git a/lib/Cleantalk/Common/Http/Request.php b/lib/Cleantalk/Common/Http/Request.php new file mode 100644 index 0000000..b6587b7 --- /dev/null +++ b/lib/Cleantalk/Common/Http/Request.php @@ -0,0 +1,568 @@ + $url, + * CURLOPT_TIMEOUT => 15, + * CURLOPT_LOW_SPEED_TIME => 10, + * CURLOPT_RETURNTRANSFER => true, + * ) + */ + protected $options = []; + + /** + * @var array [callable] Callback function to process after the request is performed without error to process received data + * If passed will be fired for both single and multi requests + */ + protected $callbacks = []; + + /** + * @var Response|array + */ + public $response; + + /** + * @param mixed $url + * + * @return Request + */ + public function setUrl($url) + { + $this->url = $url; + + return $this; + } + + /** + * @param mixed $data + * + * @return Request + */ + public function setData($data) + { + // If $data scalar converting it to array + $this->data = ! empty($data) && ! self::isJson($data) && is_scalar($data) + ? array((string)$data => 1) + : $data; + + return $this; + } + + /** + * Set one or more presets which change the way of the processing Request::request + * + * @param mixed $presets Array with presets + * Example: array('get_code', 'async') + * Or space separated string with presets + * Example: 'get_code async get' + * + * May use the following presets(combining is possible): + * dont_follow_redirects - ignore 300-family response code and don't follow redirects + * get_code - getting only HTTP response code + * async - async requests. Sends request and return 'true' value. Doesn't wait for response. + * get - makes GET-type request instead of default POST-type + * ssl - uses SSL + * cache - allow caching for this request + * retry_with_socket - make another request with socket if cURL failed to retrieve data + * + * @return Request + */ + public function setPresets($presets) + { + // Prepare $presets to process + $this->presets = ! is_array($presets) + ? explode(' ', $presets) + : $presets; + + return $this; + } + + /** + * @param mixed $options + * + * @return Request + */ + public function setOptions($options) + { + $this->options = $options; + + return $this; + } + + /** + * Set callback and additional arguments which will be passed to callback function + * + * @param callable $callback + * @param array $arguments + * @param int $priority + * @param bool $pass_response + * + * @return Request + * @psalm-suppress UnusedVariable + */ + public function addCallback($callback, $arguments = array(), $priority = null, $pass_response = false) + { + $priority = $priority ?: 100; + if ( isset($this->callbacks[$priority]) ) { + return $this->addCallback($callback, $arguments, ++$priority); + } + + $this->callbacks[$priority] = [ + 'function' => $callback, + 'arguments' => $arguments, + 'pass_response' => $pass_response, + ]; + + return $this; + } + + /** + * Function sends raw http request + * + * @return array|bool (array || array('error' => true)) + */ + public function request() + { + // Return the error if cURL is not installed + if ( ! function_exists('curl_init') ) { + return array('error' => 'CURL_NOT_INSTALLED'); + } + + if ( empty($this->url) ) { + return array('error' => 'URL_IS_NOT_SET'); + } + + $this->convertOptionsTocURLFormat(); + $this->appendOptionsObligatory(); + $this->processPresets(); + + // Call cURL multi request if many URLs passed + $this->response = is_array($this->url) + ? $this->requestMulti() + : $this->requestSingle(); + + // Process the error. Unavailable for multiple URLs. + if ( + ! is_array($this->url) && + $this->response->getError() && + in_array('retry_with_socket', $this->presets, true) + ) { + $this->response = $this->requestWithSocket(); + if ( $this->response->getError() ) { + return $this->response->getError(); + } + } + + return $this->runCallbacks(); + } + + /** + * @return Response + */ + protected function requestSingle() + { + // Make a request + $ch = curl_init(); + + curl_setopt_array($ch, $this->options); + + $request_result = curl_exec($ch); // Gather request result + $curl_info = curl_getinfo($ch); // Gather HTTP response information + + // Do not catch timeout error for async requests. + if( in_array('async', $this->presets, true) ){ + $request_result = true; + } + + if ( $request_result === false ) { + $request_result = array('error' => curl_error($ch)); + } + + curl_close($ch); + + + return new Response($request_result, $curl_info); + } + + + /** + * Do multi curl requests without processing it. + * + * @return array + */ + protected function requestMulti() + { + $urls_count = count($this->url); + $curl_arr = array(); + $mh = curl_multi_init(); + $this->response = []; + + for ( $i = 0; $i < $urls_count; $i++ ) { + $this->options[CURLOPT_URL] = $this->url[$i]; + $curl_arr[$i] = curl_init($this->url[$i]); + + curl_setopt_array($curl_arr[$i], $this->options); + curl_multi_add_handle($mh, $curl_arr[$i]); + } + + do { + curl_multi_exec($mh, $running); + usleep(1000); + } while ( $running > 0 ); + + for ( $i = 0; $i < $urls_count; $i++ ) { + $curl_info = curl_getinfo($curl_arr[$i]); // Gather HTTP response information + $received_data = curl_multi_getcontent($curl_arr[$i]); + + // Do not catch timeout error for async requests. + if( in_array('async', $this->presets, true) ){ + $received_data = true; + } + + if ( $received_data === '' ) { + $received_data = array('error' => curl_error($curl_arr[$i])); + } + + $this->response[$this->url[$i]] = new Response($received_data, $curl_info); + } + + return $this->response; + } + + /** + * Make a request with socket, exactly with file_get_contents() + * + * @return Response + */ + private function requestWithSocket() + { + if ( ! ini_get('allow_url_fopen') ) { + return new Response(['error' => 'ALLOW_URL_FOPEN_IS_DISABLED'], []); + } + + $context = stream_context_create( + [ + 'http' => [ + 'method' => 'GET', //in_array('get', $this->presets, true) ? 'GET' : 'POST', + 'timeout' => $this->options[CURLOPT_TIMEOUT], + 'content' => $this->data, + ], + ] + ); + + $response_content = @file_get_contents($this->url, false, $context) + ?: ['error' => 'FAILED_TO_USE_FILE_GET_CONTENTS']; + + return new Response($response_content, []); + } + + // Process with callback if passed. Save the processed result. + protected function runCallbacks() + { + $return_value = []; + + // Cast to array to precess result from $this->requestSingle as $this->requestMulti results + $responses = is_object($this->response) + ? [$this->response] + : $this->response; + + // Sort callback to keep the priority order + ksort($this->callbacks); + + foreach ( $responses as $url => &$response ) { + // Skip the processing if the error occurred in this specific result + if ( $response->getError() ) { + $return_value[] = $response->getError(); + continue; + } + + // Get content to process + $content = $response->getContentProcessed(); + + // Perform all provided callback functions to each request result + if ( ! empty($this->callbacks) ) { + foreach ( $this->callbacks as $callback ) { + if ( is_callable($callback['function']) ) { + // Run callback + $content = call_user_func_array( + $callback['function'], + array_merge( + array( + $callback['pass_response'] ? $response : $content, // Pass Response or content + $url + ), + $callback['arguments'] + ) + ); + + // Foolproof + if ( ! $content instanceof Response ) { + $response->setProcessed($content); + } + } + } + } + + $return_value[$url] = $content instanceof Response ? $content->getContentProcessed() : $content; + } + unset($response); + + // Return a single content if it was a single request + return is_array($this->response) && count($this->response) > 1 + ? $return_value + : reset($return_value); + } + + /** + * Convert given options from simple naming like 'timeout' or 'ssl' + * to sophisticated and standardized cURL defined constants + * + * !! Called only after we make sure that cURL is exists !! + */ + private function convertOptionsTocURLFormat() + { + $temp_options = []; + foreach ( $this->options as $option_name => &$option_value ) { + switch ( $option_name ) { + case 'timeout': + $temp_options[CURLOPT_TIMEOUT] = $option_value; // String + unset($this->options[$option_name]); + break; + case 'sslverify': + if ( $option_value ) { + $temp_options[CURLOPT_SSL_VERIFYPEER] = (bool)$option_value; // Boolean + $temp_options[CURLOPT_SSL_VERIFYHOST] = (int)(bool)$option_value; // Int 0|1 + unset($this->options[$option_name]); + } + break; + case 'sslcertificates': + $temp_options[CURLOPT_CAINFO] = $option_name; // String + unset($this->options[$option_name]); + break; + case 'headers': + $temp_options[CURLOPT_HTTPHEADER] = $option_name; // String[] + unset($this->options[$option_name]); + break; + case 'user-agent': + $temp_options[CURLOPT_USERAGENT] = $option_name; // String + unset($this->options[$option_name]); + break; + + // Unset unsupported string names in options + default: + if ( ! is_int($option_name) ) { + unset($this->options[$option_name]); + } + break; + } + } + unset($option_value); + + $this->options = array_replace($this->options, $temp_options); + } + + /** + * Set default options to make a request + */ + protected function appendOptionsObligatory() + { + // Merging OBLIGATORY options with GIVEN options + $this->options = array_replace( + array( + CURLOPT_URL => ! is_array($this->url) ? $this->url : null, + CURLOPT_TIMEOUT => 10, + CURLOPT_LOW_SPEED_TIME => 7, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_CONNECTTIMEOUT => 5000, + CURLOPT_FORBID_REUSE => true, + CURLOPT_USERAGENT => self::AGENT, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $this->data, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => 0, + CURLOPT_HTTPHEADER => array( + 'Expect:', + // Fix for large data and old servers http://php.net/manual/ru/function.curl-setopt.php#82418 + 'Expires: ' . date(DATE_RFC822, mktime(0, 0, 0, 1, 1, 1971)), + 'Cache-Control: no-store, no-cache, must-revalidate', + 'Cache-Control: post-check=0, pre-check=0', + 'Pragma: no-cache', + ), + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_MAXREDIRS => 5, + ), + $this->options + ); + } + + /** + * Append options considering passed presets + */ + protected function processPresets() + { + foreach ( $this->presets as $preset ) { + switch ( $preset ) { + // Do not follow redirects + case 'dont_follow_redirects': + $this->options[CURLOPT_FOLLOWLOCATION] = false; + $this->options[CURLOPT_MAXREDIRS] = 0; + break; + + // Get headers only + case 'get_code': + $this->options[CURLOPT_HEADER] = true; + $this->options[CURLOPT_NOBODY] = true; + $this->addCallback( + static function (Response $response, $_url) { + return $response->getResponseCode(); + }, + array(), + 60, + true + ); + break; + + // Get headers only + case 'split_to_array': + $this->addCallback( + static function ($response_content, $_url) { + return explode(PHP_EOL, $response_content); + }, + array(), + 50 + ); + break; + + // Make a request, don't wait for an answer + case 'async': + $this->options[CURLOPT_CONNECTTIMEOUT] = 3; + $this->options[CURLOPT_TIMEOUT] = 3; + break; + + case 'get': + $this->options[CURLOPT_CUSTOMREQUEST] = 'GET'; + $this->options[CURLOPT_POST] = false; + $this->options[CURLOPT_POSTFIELDS] = null; + // Append parameter in a different way for single and multiple requests + if ( is_array($this->url) ) { + $this->url = array_map(function ($elem) { + return self::appendParametersToURL($elem, $this->data); + }, $this->url); + } else { + $this->options[CURLOPT_URL] = self::appendParametersToURL( + $this->options[CURLOPT_URL], + $this->data + ); + } + break; + + case 'ssl': + $this->options[CURLOPT_SSL_VERIFYPEER] = true; + $this->options[CURLOPT_SSL_VERIFYHOST] = 2; + if ( defined('CLEANTALK_CASERT_PATH') && CLEANTALK_CASERT_PATH ) { + $this->options[CURLOPT_CAINFO] = CLEANTALK_CASERT_PATH; + } + break; + + case 'no_cache': + // Append parameter in a different way for single and multiple requests + if ( is_array($this->url) ) { + $this->url = array_map(static function ($elem) { + return self::appendParametersToURL($elem, ['no_cache' => mt_rand()]); + }, $this->url); + } else { + $this->options[CURLOPT_URL] = self::appendParametersToURL( + $this->options[CURLOPT_URL], + ['no_cache' => mt_rand()] + ); + } + break; + } + } + } + + /** + * Appends given parameter(s) to URL considering other parameters + * Adds ? or & before the append + * + * @param string $url + * @param string|array $parameters + * + * @return string + */ + public static function appendParametersToURL($url, $parameters) + { + if ( empty($parameters) ) { + return $url; + } + + $parameters = is_array($parameters) + ? http_build_query($parameters) + : $parameters; + + $url .= strpos($url, '?') === false + ? ('?' . $parameters) + : ('&' . $parameters); + + return $url; + } + + /** + * Checks if the string is JSON type + * + * @param string $string + * + * @return bool + */ + public static function isJson($string) + { + return is_string($string) && is_array(json_decode($string, true)); + } +} diff --git a/lib/Cleantalk/Common/Http/Response.php b/lib/Cleantalk/Common/Http/Response.php new file mode 100644 index 0000000..aa6b296 --- /dev/null +++ b/lib/Cleantalk/Common/Http/Response.php @@ -0,0 +1,79 @@ +raw = $raw; + $this->processed = $raw; + $this->info = $info; + $this->error = ! empty($raw['error']) + ? $raw + : null; + if ( isset($this->info['http_code']) ) { + $this->response_code = (int)$this->info['http_code']; + } + } + + /** + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * @return mixed + */ + public function getResponseCode() + { + return $this->response_code; + } + + /** + * @return mixed + */ + public function getContentRaw() + { + return $this->raw; + } + + /** + * @return mixed + */ + public function getContentProcessed() + { + return $this->processed; + } + + /** + * @param mixed $processed + */ + public function setProcessed($processed) + { + $this->processed = $processed; + } + + /** + * @return mixed + */ + public function getInfo() + { + return $this->info; + } +} diff --git a/lib/Cleantalk/Common/Mloader/Mloader.php b/lib/Cleantalk/Common/Mloader/Mloader.php new file mode 100644 index 0000000..cb8026b --- /dev/null +++ b/lib/Cleantalk/Common/Mloader/Mloader.php @@ -0,0 +1,30 @@ +api_key = $api_key; + $this->pid = mt_rand(0, mt_getrandmax()); + + $queue = $this->getQueue(); + if ($queue !== false && isset($queue['stages'])) { + $this->queue = $queue; + } else { + $this->queue = array( + 'started' => time(), + 'finished' => '', + 'stages' => array(), + ); + } + } + + public function getQueue() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + return $storage_handler_class::getSetting(self::QUEUE_NAME); + } + + public static function clearQueue() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + return $storage_handler_class::deleteSetting(self::QUEUE_NAME); + } + + public function saveQueue($queue) + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + return $storage_handler_class::saveSetting(self::QUEUE_NAME, $queue); + } + + /** + * Refreshes the $this->queue from the DB + * + * @return void + */ + public function refreshQueue() + { + $this->queue = $this->getQueue(); + } + + /** + * @param string|array $stage_name + * @param array $args + */ + public function addStage($stage_name, $args = array(), $accepted_tries = 3) + { + $this->queue['stages'][] = array( + 'name' => $stage_name, + 'status' => 'NULL', + 'tries' => '0', + 'accepted_tries' => $accepted_tries, + 'args' => $args, + 'pid' => null, + ); + $this->saveQueue($this->queue); + } + + public function executeStage() + { + $fw_stats = Firewall::getFwStats(); + $stage_to_execute = null; + + if ($this->hasUnstartedStages()) { + $this->queue['stages'][$this->unstarted_stage]['status'] = 'IN_PROGRESS'; + $this->queue['stages'][$this->unstarted_stage]['start'] = time(); + $this->queue['stages'][$this->unstarted_stage]['pid'] = $this->pid; + + $this->saveQueue($this->queue); + + sleep(2); + + $this->refreshQueue(); + + if ($this->queue['stages'][$this->unstarted_stage]['pid'] !== $this->pid) { + return true; + } + + $stage_to_execute = &$this->queue['stages'][$this->unstarted_stage]; + } + + if ($stage_to_execute) { + + if ( is_array($stage_to_execute['name']) ) { + $class_to_execute = $stage_to_execute['name'][0]; + $method_to_execute = $stage_to_execute['name'][1]; + if (is_callable(array($class_to_execute, $method_to_execute))) { + ++$stage_to_execute['tries']; + + if ( !empty($stage_to_execute['args']) ) + { + $result = $class_to_execute::$method_to_execute($this->api_key, $stage_to_execute['args']); + } + else + { + $result = $class_to_execute::$method_to_execute($this->api_key); + } + } else { + return array('error' => $class_to_execute . '::'. $method_to_execute . ' is not a callable function.'); + } + + } else { + if (is_callable($stage_to_execute['name'])) { + ++$stage_to_execute['tries']; + + if ( !empty($stage_to_execute['args']) ) + { + $result = $stage_to_execute['name']($stage_to_execute['args']); + } + else + { + $result = $stage_to_execute['name'](); + } + } else { + return array('error' => $stage_to_execute['name'] . ' is not a callable function.'); + } + } + + if (isset($result['error'])) { + $stage_to_execute['status'] = 'NULL'; + $stage_to_execute['error'][] = $result['error']; + if (isset($result['update_args']['args'])) { + $stage_to_execute['args'] = $result['update_args']['args']; + } + $this->saveQueue($this->queue); + $accepted_tries = isset($stage_to_execute['accepted_tries']) ? $stage_to_execute['accepted_tries'] : 3; + if ($stage_to_execute['tries'] >= $accepted_tries) { + $stage_to_execute['status'] = 'FINISHED'; + $this->saveQueue($this->queue); + return $result; + } + + /** @var \Cleantalk\Common\RemoteCalls\RemoteCalls $remote_calls_class */ + $remote_calls_class = Mloader::get('RemoteCalls'); + return $remote_calls_class::perform( + 'sfw_update__worker', + 'apbct', + $this->api_key, + array( + 'firewall_updating_id' => $fw_stats->updating_id, + 'stage' => 'Repeat ' . $stage_to_execute['name'] + ), + array('async') + ); + } + + if (isset($result['next_stage'])) { + $this->addStage( + $result['next_stage']['name'], + isset($result['next_stage']['args']) ? $result['next_stage']['args'] : array(), + isset($result['next_stage']['accepted_tries']) ? $result['next_stage']['accepted_tries'] : 3 + ); + } + + if (isset($result['next_stages']) && count($result['next_stages'])) { + foreach ($result['next_stages'] as $next_stage) { + $this->addStage( + $next_stage['name'], + isset($next_stage['args']) ? $next_stage['args'] : array(), + isset($result['next_stage']['accepted_tries']) ? $result['next_stage']['accepted_tries'] : 3 + ); + } + } + + $stage_to_execute['status'] = 'FINISHED'; + $this->saveQueue($this->queue); + + return $result; + + } + + return null; + } + + public function isQueueInProgress() + { + return + count($this->queue['stages']) && + ( + in_array('NULL', array_column($this->queue['stages'], 'status'), true) || + in_array('IN_PROGRESS', array_column($this->queue['stages'], 'status'), true) + ); + } + + public function isQueueFinished() + { + return ! $this->isQueueInProgress(); + } + + /** + * Checks if the queue is over + * + * @return bool + */ + public function hasUnstartedStages() + { + if (count($this->queue['stages']) > 0) { + $this->unstarted_stage = array_search('NULL', array_column($this->queue['stages'], 'status'), true); + + return is_int($this->unstarted_stage); + } + + return false; + } +} diff --git a/lib/Cleantalk/Common/RemoteCalls.php b/lib/Cleantalk/Common/RemoteCalls.php deleted file mode 100644 index b090f1c..0000000 --- a/lib/Cleantalk/Common/RemoteCalls.php +++ /dev/null @@ -1,130 +0,0 @@ -api_key = $api_key; - $this->available_rc_actions = $this->getAvailableRcActions(); - } - - /** - * Checking if the current request is the Remote Call - * - * @return bool - */ - public static function check() - { - return - Get::get( 'spbc_remote_call_token' ) && - Get::get( 'spbc_remote_call_action' ) && - Get::get( 'plugin_name' ) && - in_array( Get::get( 'plugin_name' ), array( 'antispam','anti-spam', 'apbct' ) ); - } - - /** - * Execute corresponding method of RemoteCalls if exists - * - * @return void|string - */ - public function perform() - { - $action = strtolower( Get::get( 'spbc_remote_call_action' ) ); - $token = strtolower( Get::get( 'spbc_remote_call_token' ) ); - - $actions = $this->available_rc_actions; - - if( count( $actions ) !== 0 && array_key_exists( $action, $actions ) ){ - - $cooldown = isset( $actions[$action]['cooldown'] ) ? $actions[$action]['cooldown'] : self::COOLDOWN; - - // Return OK for test remote calls - if ( Get::get( 'test' ) ) { - die('OK'); - } - - if( time() - $actions[ $action ]['last_call'] >= $cooldown ){ - - $actions[$action]['last_call'] = time(); - - $this->setLastCall( $action ); - - // Check API key - if( $token === strtolower( md5( $this->api_key ) ) ){ - - // Flag to let plugin know that Remote Call is running. - $this->rc_running = true; - - $action_method = 'action__' . $action; - - if( method_exists( static::class, $action_method ) ){ - - // Delay before perform action; - if ( Get::get( 'delay' ) ) { - sleep(Get::get('delay')); - } - - $action_result = static::$action_method(); - - $response = empty( $action_result['error'] ) - ? 'OK' - : 'FAIL ' . json_encode( array( 'error' => $action_result['error'] ) ); - - if( ! Get::get( 'continue_execution' ) ){ - - die( $response ); - - } - - return $response; - - }else - $out = 'FAIL '.json_encode(array('error' => 'UNKNOWN_ACTION_METHOD')); - }else - $out = 'FAIL '.json_encode(array('error' => 'WRONG_TOKEN')); - }else - $out = 'FAIL '.json_encode(array('error' => 'TOO_MANY_ATTEMPTS')); - }else - $out = 'FAIL '.json_encode(array('error' => 'UNKNOWN_ACTION')); - - die( $out ); - } - - /** - * Get available remote calls from the storage. - * - * @return array - */ - abstract protected function getAvailableRcActions(); - - /** - * Set last call timestamp and save it to the storage. - * - * @param array $action - * @return bool - */ - abstract protected function setLastCall( $action ); - -} \ No newline at end of file diff --git a/lib/Cleantalk/Common/RemoteCalls/RemoteCalls.php b/lib/Cleantalk/Common/RemoteCalls/RemoteCalls.php new file mode 100644 index 0000000..1d58ef3 --- /dev/null +++ b/lib/Cleantalk/Common/RemoteCalls/RemoteCalls.php @@ -0,0 +1,248 @@ +api_key = $api_key; + $this->available_rc_actions = $this->getAvailableRcActions(); + } + + /** + * Checking if the current request is the Remote Call + * + * @return bool + */ + public static function check() + { + return + Request::get( 'spbc_remote_call_token' ) && + Request::get( 'spbc_remote_call_action' ) && + Request::get( 'plugin_name' ) && + in_array( Request::get( 'plugin_name' ), array( 'antispam','anti-spam', 'apbct' ) ); + } + + /** + * Execute corresponding method of RemoteCalls if exists + * + * @return void|string + */ + public function process() + { + $action = strtolower( Request::get( 'spbc_remote_call_action' ) ); + $token = strtolower( Request::get( 'spbc_remote_call_token' ) ); + + $actions = $this->available_rc_actions; + + if( count( $actions ) !== 0 && array_key_exists( $action, $actions ) ){ + + $cooldown = isset( $actions[$action]['cooldown'] ) ? $actions[$action]['cooldown'] : self::COOLDOWN; + + // Return OK for test remote calls + if ( Request::get( 'test' ) ) { + die('OK'); + } + + if( time() - $actions[ $action ]['last_call'] >= $cooldown ){ + + $actions[$action]['last_call'] = time(); + + $this->setLastCall($action); + + // Check API key + if( $token === strtolower( md5( $this->api_key ) ) ){ + + // Flag to let plugin know that Remote Call is running. + $this->rc_running = true; + + $action_method = 'action__' . $action; + + if( method_exists( static::class, $action_method ) ){ + + // Delay before perform action; + if ( Request::get( 'delay' ) ) { + sleep(Request::get('delay')); + } + + try { + $action_result = static::$action_method(); + + $response = empty( $action_result['error'] ) + ? 'OK' + : 'FAIL ' . json_encode( array( 'error' => $action_result['error'] ) ); + + if( ! Request::get( 'continue_execution' ) ){ + + die( $response ); + + } + + return $response; + } catch ( \Exception $exception ) { + error_log('RC error: ' . var_export($exception->getMessage(),1)); + $out = 'FAIL '.json_encode(array('error' => $exception->getMessage())); + } + + }else + $out = 'FAIL '.json_encode(array('error' => 'UNKNOWN_ACTION_METHOD')); + }else + $out = 'FAIL '.json_encode(array('error' => 'WRONG_TOKEN')); + }else + $out = 'FAIL '.json_encode(array('error' => 'TOO_MANY_ATTEMPTS')); + }else + $out = 'FAIL '.json_encode(array('error' => 'UNKNOWN_ACTION')); + + die( $out ); + } + + /** + * Get available remote calls from the storage. + * + * @return array + */ + protected function getAvailableRcActions() + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + $actions = $storage_handler_class::getSetting(static::OPTION_NAME); + return $actions ?: $this->available_rc_actions; + } + + /** + * Set last call timestamp and save it to the storage. + * + * @param string $action + * @return bool + */ + protected function setLastCall($action) + { + /** @var \Cleantalk\Common\StorageHandler\StorageHandler $storage_handler_class */ + $storage_handler_class = Mloader::get('StorageHandler'); + $this->available_rc_actions[$action]['last_call'] = time(); + return $storage_handler_class::saveSetting(static::OPTION_NAME, $this->available_rc_actions); + } + + /************************ Making Request Methods ************************/ + + public static function getSiteUrl() + { + return ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ( isset($_SERVER['SCRIPT_URL'] ) ? $_SERVER['SCRIPT_URL'] : '' ); + } + + public static function buildParameters($rc_action, $plugin_name, $api_key, $additional_params) + { + return array_merge( + array( + 'spbc_remote_call_token' => md5($api_key), + 'spbc_remote_call_action' => $rc_action, + 'plugin_name' => $plugin_name, + ), + $additional_params + ); + } + + /** + * Performs remote call to the current website + * + * @param string $host + * @param string $rc_action + * @param string $plugin_name + * @param string $api_key + * @param array $params + * @param array $patterns + * @param bool $do_check Perform check before main remote call or not + * + * @return bool|string[] + * @psalm-suppress PossiblyUnusedMethod + */ + public static function perform($rc_action, $plugin_name, $api_key, $params, $patterns = array(), $do_check = true) + { + $host = static::getSiteUrl(); + $params = static::buildParameters($rc_action, $plugin_name, $api_key, $params); + + if ($do_check) { + $result__rc_check_website = static::performTest($host, $params, $patterns); + if (! empty($result__rc_check_website['error'])) { + return $result__rc_check_website; + } + } + + $http = new \Cleantalk\Common\Http\Request(); + + return $http + ->setUrl($host) + ->setData($params) + ->setPresets($patterns) + ->request(); + } + + /** + * Performs test remote call to the current website + * Expects 'OK' string as good response + * + * @param string $host + * @param array $params + * @param array $patterns + * + * @return array|bool|string + */ + public static function performTest($host, $params, $patterns = array()) + { + // Delete async pattern to get the result in this process + $key = array_search('async', $patterns, true); + if ($key) { + unset($patterns[$key]); + } + + // Adding test flag + $params = array_merge($params, array('test' => 'test')); + + // Perform test request + $http = new \Cleantalk\Common\Http\Request(); + $result = $http + ->setUrl($host) + ->setData($params) + ->setPresets($patterns) + ->request(); + + // Considering empty response as error + if ($result === '') { + $result = array('error' => 'WRONG_SITE_RESPONSE TEST ACTION : ' . $params['spbc_remote_call_action'] . ' ERROR: EMPTY_RESPONSE'); + // Wrap and pass error + } elseif (! empty($result['error'])) { + $result = array('error' => 'WRONG_SITE_RESPONSE TEST ACTION: ' . $params['spbc_remote_call_action'] . ' ERROR: ' . $result['error']); + // Expects 'OK' string as good response otherwise - error + } elseif (is_string($result) && ! preg_match('@^.*?OK$@', $result)) { + $result = array( + 'error' => 'WRONG_SITE_RESPONSE ACTION: ' + . $params['spbc_remote_call_action'] + . ' RESPONSE: ' + . '"' + . htmlspecialchars(substr($result, 0, 400)) + . '"' + ); + } + + return $result; + } +} diff --git a/lib/Cleantalk/Common/Schema.php b/lib/Cleantalk/Common/Schema.php deleted file mode 100644 index 7cae1b1..0000000 --- a/lib/Cleantalk/Common/Schema.php +++ /dev/null @@ -1,63 +0,0 @@ - 'CREATE TABLE IF NOT EXISTS `%scleantalk_sfw` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `network` int(11) unsigned NOT NULL, - `mask` int(11) unsigned NOT NULL, - `status` TINYINT(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - INDEX ( `network` , `mask` ) - );', - 'ua_bl' => 'CREATE TABLE IF NOT EXISTS `%scleantalk_ua_bl` ( - `id` INT(11) NOT NULL, - `ua_template` VARCHAR(255) NULL DEFAULT NULL, - `ua_status` TINYINT(1) NULL DEFAULT NULL, - PRIMARY KEY ( `id` ), - INDEX ( `ua_template` ) - ) DEFAULT CHARSET=utf8;', // Don't remove the default charset! - 'sfw_logs' => 'CREATE TABLE IF NOT EXISTS `%scleantalk_sfw_logs` ( - `id` VARCHAR(40) NOT NULL, - `ip` VARCHAR(15) NOT NULL, - `status` ENUM(\'PASS_SFW\',\'DENY_SFW\',\'PASS_SFW__BY_WHITELIST\',\'PASS_SFW__BY_COOKIE\',\'DENY_ANTICRAWLER\',\'PASS_ANTICRAWLER\',\'DENY_ANTICRAWLER_UA\',\'PASS_ANTICRAWLER_UA\',\'DENY_ANTIFLOOD\',\'PASS_ANTIFLOOD\') NULL DEFAULT NULL, - `all_entries` INT NOT NULL, - `blocked_entries` INT NOT NULL, - `entries_timestamp` INT NOT NULL, - `ua_id` INT(11) NULL DEFAULT NULL, - `ua_name` VARCHAR(1024) NOT NULL, - PRIMARY KEY (`id`));', - 'ac_logs' => 'CREATE TABLE IF NOT EXISTS `%scleantalk_ac_log` ( - `id` VARCHAR(40) NOT NULL, - `ip` VARCHAR(40) NOT NULL, - `ua` VARCHAR(40) NOT NULL, - `entries` INT DEFAULT 0, - `interval_start` INT NOT NULL, - PRIMARY KEY (`id`));', - ); - - /** - * @param null|string $table Name of called table - * @return array|string Array of schemas or query string for selected scheme. - * @throws \Exception Throws if calling un-existed schema - */ - public static function getSchema( $table = null ) - { - if( is_null( $table ) ) { - return self::$schemas; - } - - if( array_key_exists( $table, self::$schemas ) ) { - return self::$schemas[$table] ; - } - - throw new \Exception( 'Called table scheme not exist.' ); - } - -} \ No newline at end of file diff --git a/lib/Cleantalk/Common/StorageHandler/StorageHandler.php b/lib/Cleantalk/Common/StorageHandler/StorageHandler.php new file mode 100644 index 0000000..5d71d2d --- /dev/null +++ b/lib/Cleantalk/Common/StorageHandler/StorageHandler.php @@ -0,0 +1,16 @@ +init($params); + } + + return static::$instances[$instance]; + } +} diff --git a/lib/Cleantalk/Common/Templates/Singleton.php b/lib/Cleantalk/Common/Templates/Singleton.php new file mode 100644 index 0000000..cf9dd22 --- /dev/null +++ b/lib/Cleantalk/Common/Templates/Singleton.php @@ -0,0 +1,42 @@ +init($params); + } + return static::$instance; + } + + /** + * Alternative constructor + */ + protected function init() + { + } + } +} diff --git a/lib/Cleantalk/Common/Variables/Cookie.php b/lib/Cleantalk/Common/Variables/Cookie.php index 2f97f61..880dd2a 100644 --- a/lib/Cleantalk/Common/Variables/Cookie.php +++ b/lib/Cleantalk/Common/Variables/Cookie.php @@ -7,46 +7,94 @@ * Safety handler for $_COOKIE * * @usage \Cleantalk\Variables\Cookie::get( $name ); - * @since 3.0 + * * @package Cleantalk\Variables */ -class Cookie extends ServerVariables{ - - static $instance; - - /** - * Constructor - * @return $this - */ - public static function getInstance(){ - if (!isset(static::$instance)) { - static::$instance = new static; - static::$instance->init(); - } - return static::$instance; - } - - /** - * Gets given $_COOKIE variable and save it to memory - * @param $name - * - * @return string variable value or '' - */ - protected function get_variable( $name ){ - - // Return from memory. From $this->variables - if(isset(static::$instance->variables[$name])) - return static::$instance->variables[$name]; - - if( function_exists( 'filter_input' ) ) - $value = filter_input( INPUT_COOKIE, $name ); - - if( empty( $value ) ) - $value = isset( $_COOKIE[ $name ] ) ? $_COOKIE[ $name ] : ''; - - // Remember for further calls - static::getInstance()->remebmer_variable( $name, $value ); - - return $value; - } -} \ No newline at end of file +class Cookie extends ServerVariables +{ + protected static $instance; + + /** + * Gets given $_COOKIE variable and save it to memory + * + * @param $name + * + * @return mixed|string + */ + protected function getVariable($name) + { + // Return from memory. From $this->variables + if (! isset(static::$instance->variables[$name])) { + if ( isset($_COOKIE[$name]) ) { + $value = $this->getAndSanitize($_COOKIE[$name]); + } else { + $value = ''; + } + + // Remember for further calls + static::getInstance()->rememberVariable($name, $value); + + return $value; + } + + return static::$instance->variables[$name]; + } + + /** + * Universal method to adding cookies + * Wrapper for setcookie() Conisdering PHP version + * + * @see https://www.php.net/manual/ru/function.setcookie.php + * + * @param string $name Cookie name + * @param string $value Cookie value + * @param int $expires Expiration timestamp. 0 - expiration with session + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $httponly + * @param string $samesite + * + * @return void + * @psalm-suppress PossiblyUnusedMethod + */ + public static function set( + $name, + $value = '', + $expires = 0, + $path = '', + $domain = '', + $secure = null, + $httponly = false, + $samesite = 'Lax' + ) { + if (headers_sent()) { + return; + } + + $secure = ! is_null($secure) ? $secure : ! in_array(Server::get('HTTPS'), ['off', '']) || Server::get('SERVER_PORT') == 443; + + // For PHP 7.3+ and above + if ( version_compare(phpversion(), '7.3.0', '>=') ) { + $params = array( + 'expires' => $expires, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httponly, + ); + + if ($samesite) { + $params['samesite'] = $samesite; + } + + /** + * @psalm-suppress InvalidArgument + */ + setcookie($name, $value, $params); + // For PHP 5.6 - 7.2 + } else { + setcookie($name, $value, $expires, $path, $domain, $secure, $httponly); + } + } +} diff --git a/lib/Cleantalk/Common/Variables/Get.php b/lib/Cleantalk/Common/Variables/Get.php index 7619448..ab29d21 100644 --- a/lib/Cleantalk/Common/Variables/Get.php +++ b/lib/Cleantalk/Common/Variables/Get.php @@ -7,46 +7,36 @@ * Safety handler for $_GET * * @usage \Cleantalk\Variables\Get::get( $name ); - * @since 3.0 + * * @package Cleantalk\Variables */ -class Get extends ServerVariables{ - - static $instance; - - /** - * Constructor - * @return $this - */ - public static function getInstance(){ - if (!isset(static::$instance)) { - static::$instance = new static; - static::$instance->init(); - } - return static::$instance; - } - - /** - * Gets given $_GET variable and save it to memory - * @param $name - * - * @return string variable value or '' - */ - protected function get_variable( $name ){ - - // Return from memory. From $this->variables - if(isset(static::$instance->variables[$name])) - return static::$instance->variables[$name]; - - if( function_exists( 'filter_input' ) ) - $value = filter_input( INPUT_GET, $name ); - - if( empty( $value ) ) - $value = isset( $_GET[ $name ] ) ? $_GET[ $name ] : ''; - - // Remember for further calls - static::getInstance()->remebmer_variable( $name, $value ); - - return $value; - } -} \ No newline at end of file +class Get extends ServerVariables +{ + protected static $instance; + + /** + * Gets given $_GET variable and save it to memory + * + * @param $name + * + * @return mixed|string + */ + protected function getVariable($name) + { + // Return from memory. From $this->variables + if (! isset(static::$instance->variables[$name])) { + if ( isset($_GET[$name]) ) { + $value = $this->getAndSanitize($_GET[$name]); + } else { + $value = ''; + } + + // Remember for further calls + static::getInstance()->rememberVariable($name, $value); + + return $value; + } + + return static::$instance->variables[$name]; + } +} diff --git a/lib/Cleantalk/Common/Variables/Post.php b/lib/Cleantalk/Common/Variables/Post.php index 755c4e2..da98cb9 100644 --- a/lib/Cleantalk/Common/Variables/Post.php +++ b/lib/Cleantalk/Common/Variables/Post.php @@ -8,46 +8,36 @@ * Safety handler for $_POST * * @usage \Cleantalk\Variables\Post::get( $name ); - * @since 3.0 + * * @package Cleantalk\Variables */ -class Post extends ServerVariables{ - - static $instance; - - /** - * Constructor - * @return $this - */ - public static function getInstance(){ - if (!isset(static::$instance)) { - static::$instance = new static; - static::$instance->init(); - } - return static::$instance; - } - - /** - * Gets given $_POST variable and save it to memory - * @param $name - * - * @return string variable value or '' - */ - protected function get_variable( $name ){ - - // Return from memory. From $this->variables - if(isset(static::$instance->variables[$name])) - return static::$instance->variables[$name]; - - if( function_exists( 'filter_input' ) ) - $value = filter_input( INPUT_POST, $name ); - - if( empty( $value ) ) - $value = isset( $_POST[ $name ] ) ? $_POST[ $name ] : ''; - - // Remember for further calls - static::getInstance()->remebmer_variable( $name, $value ); - - return $value; - } -} \ No newline at end of file +class Post extends ServerVariables +{ + protected static $instance; + + /** + * Gets given $_POST variable and save it to memory + * + * @param $name + * + * @return mixed|string + */ + protected function getVariable($name) + { + // Return from memory. From $this->variables + if (! isset(static::$instance->variables[$name])) { + if ( isset($_POST[$name]) ) { + $value = $this->getAndSanitize($_POST[$name]); + } else { + $value = ''; + } + + // Remember for further calls + static::getInstance()->rememberVariable($name, $value); + + return $value; + } + + return static::$instance->variables[$name]; + } +} diff --git a/lib/Cleantalk/Common/Variables/Request.php b/lib/Cleantalk/Common/Variables/Request.php index faa2333..941885d 100644 --- a/lib/Cleantalk/Common/Variables/Request.php +++ b/lib/Cleantalk/Common/Variables/Request.php @@ -7,42 +7,49 @@ * Safety handler for $_REQUEST * * @usage \Cleantalk\Variables\Request::get( $name ); - * @since 3.0 + * * @package Cleantalk\Variables */ -class Request extends ServerVariables{ - - static $instance; - - /** - * Constructor - * @return $this - */ - public static function getInstance(){ - if (!isset(static::$instance)) { - static::$instance = new static; - static::$instance->init(); - } - return static::$instance; - } - - /** - * Gets given $_REQUEST variable and save it to memory - * @param $name - * - * @return string variable value or '' - */ - protected function get_variable( $name ){ - - // Return from memory. From $this->variables - if(isset(static::$instance->variables[$name])) - return static::$instance->variables[$name]; - - $value = isset( $_REQUEST[ $name ] ) ? $_REQUEST[ $name ] : ''; - - // Remember for further calls - static::getInstance()->remebmer_variable( $name, $value ); - - return $value; - } -} \ No newline at end of file +class Request extends ServerVariables +{ + protected static $instance; + + /** + * Gets given $_REQUEST variable and save it to memory + * + * @param $name + * + * @return mixed|string + * @throws \ReflectionException + */ + protected function getVariable($name) + { + // Return from memory. From $this->variables + if (isset(static::$instance->variables[$name])) { + return static::$instance->variables[$name]; + } + + $value = ''; + + $class_name = get_class(self::getInstance()); + $reflection_class = new \ReflectionClass($class_name); + $namespace = $reflection_class->getNamespaceName(); + + $post_class = $namespace . '\\Post'; + $get_class = $namespace . '\\Get'; + $cookie_class = $namespace . '\\Cookie'; + + if ( $post_class::get($name) ) { + $value = $post_class::get($name); + } elseif ( $get_class::get($name) ) { + $value = $get_class::get($name); + } elseif ( $cookie_class::get($name) ) { + $value = $cookie_class::get($name); + } + + // Remember for further calls + static::getInstance()->rememberVariable($name, $value); + + return $value; + } +} diff --git a/lib/Cleantalk/Common/Variables/Server.php b/lib/Cleantalk/Common/Variables/Server.php index 9330a51..b1a7750 100644 --- a/lib/Cleantalk/Common/Variables/Server.php +++ b/lib/Cleantalk/Common/Variables/Server.php @@ -5,79 +5,137 @@ /** * Class Server * Wrapper to safely get $_SERVER variables - * @since 3.0 - * @package Cleantalk\Variables + * + * @usage \CleantalkSP\Variables\Server::get( $name ); + * + * @package \CleantalkSP\Variables */ -class Server extends ServerVariables{ - - static $instance; - - /** - * Constructor - * @return $this - */ - public static function getInstance(){ - if (!isset(static::$instance)) { - static::$instance = new static; - static::$instance->init(); - } - return static::$instance; - } - - /** - * Gets given $_SERVER variable and save it to memory - * - * @param string $name - * - * @return string variable value or '' - */ - protected function get_variable( $name ){ - - // Return from memory. From $this->server - if(isset(static::$instance->variables[$name])) - return static::$instance->variables[$name]; - - $name = strtoupper( $name ); - - if( function_exists( 'filter_input' ) ) - $value = filter_input( INPUT_SERVER, $name ); - - if( empty( $value ) ) - $value = isset( $_SERVER[ $name ] ) ? $_SERVER[ $name ] : ''; - - // Convert to upper case for REQUEST_METHOD - if( in_array( $name, array( 'REQUEST_METHOD' ) ) ) - $value = strtoupper( $value ); - - // Convert HTML chars for HTTP_USER_AGENT, HTTP_REFERER, SERVER_NAME - if( in_array( $name, array( 'HTTP_USER_AGENT', 'HTTP_REFERER', 'SERVER_NAME' ) ) ) - $value = htmlspecialchars( $value ); - - // Remember for further calls - static::getInstance()->remebmer_variable( $name, $value ); - - return $value; - } - - /** - * Checks if $_SERVER['REQUEST_URI'] contains string - * - * @param string $string needle - * - * @return bool - */ - public static function in_uri( $string ){ - return self::has_string( 'REQUEST_URI', $string ); - } - - /** - * Checks if $_SERVER['REQUEST_URI'] contains string - * - * @param string $string needle - * - * @return bool - */ - public static function in_referer( $string ){ - return self::has_string( 'HTTP_REFERER', $string ); - } -} \ No newline at end of file +class Server extends ServerVariables +{ + protected static $instance; + + /** + * Gets given $_SERVER variable and save it to memory + * + * @param string $name + * + * @return mixed|string + */ + protected function getVariable($name) + { + // Return from memory. From $this->server + if (isset(static::$instance->variables[$name])) { + return static::$instance->variables[$name]; + } + + $name = strtoupper($name); + + if ( isset($_SERVER[$name]) ) { + $value = $this->getAndSanitize($_SERVER[$name]); + } else { + $value = ''; + } + + // Convert to upper case for REQUEST_METHOD + if ($name === 'REQUEST_METHOD') { + $value = strtoupper($value); + } + + // Convert HTML chars for HTTP_USER_AGENT, HTTP_USER_AGENT, SERVER_NAME + if (in_array($name, array('HTTP_USER_AGENT', 'HTTP_USER_AGENT', 'SERVER_NAME'))) { + $value = htmlspecialchars($value); + } + + // Remember for further calls + static::getInstance()->rememberVariable($name, $value); + + return $value; + } + + /** + * Checks if $_SERVER['REQUEST_URI'] contains string + * + * @param string $needle + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function inUri($needle) + { + return self::hasString('REQUEST_URI', $needle); + } + + /** + * Is the host contains the string + * + * @param string $needle + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function inHost($needle) + { + return self::hasString('HTTP_HOST', $needle); + } + + /** + * Getting domain name + * + * @return false|string + * @psalm-suppress PossiblyUnusedMethod + */ + public static function getDomain() + { + preg_match('@\S+\.(\S+)\/?$@', self::get('HTTP_HOST'), $matches); + + return isset($matches[1]) ? $matches[1] : false; + } + + /** + * Checks if $_SERVER['REQUEST_URI'] contains string + * + * @param string $needle needle + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function inReferer($needle) + { + return self::hasString('HTTP_REFERER', $needle); + } + + /** + * Checks if the current request method is POST + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function isPost() + { + return self::get('REQUEST_METHOD') === 'POST'; + } + + /** + * Checks if the current request method is GET + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function isGet() + { + return self::get('REQUEST_METHOD') === 'GET'; + } + + /** + * Determines if SSL is used. + * + * @return bool True if SSL, otherwise false. + * @psalm-suppress PossiblyUnusedMethod + */ + public static function isSSL() + { + return self::get('HTTPS') === 'on' || + self::get('HTTPS') === '1' || + self::get('SERVER_PORT') == '443'; + } +} diff --git a/lib/Cleantalk/Common/Variables/ServerVariables.php b/lib/Cleantalk/Common/Variables/ServerVariables.php index 54829a9..80e7c29 100644 --- a/lib/Cleantalk/Common/Variables/ServerVariables.php +++ b/lib/Cleantalk/Common/Variables/ServerVariables.php @@ -2,84 +2,139 @@ namespace Cleantalk\Common\Variables; +use Cleantalk\Common\Cleaner\Sanitize; +use Cleantalk\Common\Cleaner\Validate; +use Cleantalk\Common\Templates\Singleton; + /** * Class ServerVariables * Safety handler for ${_SOMETHING} * * @usage \Cleantalk\Variables\{SOMETHING}::get( $name ); - * @since 3.0 + * * @package Cleantalk\Variables + * @psalm-suppress PossiblyUnusedProperty */ -class ServerVariables{ - - static $instance; +class ServerVariables +{ + use Singleton; + + /** + * @var array Contains saved variables + */ + public $variables = array(); + + /** + * Gets variable from ${_SOMETHING} + * + * @param string $name Variable name + * @param null|string $validation_filter + * @psalm-param (null|"hash"|"int"|"float"|"word"|"isUrl") $validation_filter + * @param null|string $sanitize_filter + * @psalm-param (null|"xss"|"int"|"url"|"word"|"cleanEmail") $sanitize_filter + * + * @return string|array|false + */ + public static function get($name, $validation_filter = null, $sanitize_filter = null) + { + $variable = static::getInstance()->getVariable($name); + + // Validate variable + if ( $validation_filter && ! Validate::validate($variable, $validation_filter) ) { + return false; + } + + if ( $sanitize_filter ) { + $variable = Sanitize::sanitize($variable, $sanitize_filter); + } + + return $variable; + } + + /** + * BLUEPRINT + * Gets given ${_SOMETHING} variable and save it to memory + * + * @param $name + * + * @return mixed|string + */ + protected function getVariable($name){} + + /** + * Save variable to $this->variables[] + * + * @param string $name + * @param string $value + */ + protected function rememberVariable($name, $value) + { + static::$instance->variables[$name] = $value; + } + + /** + * Checks if variable contains given string + * + * @param string $var Haystack to search in + * @param string $string Needle to search + * + * @return bool + */ + public static function hasString($var, $string) + { + return stripos(self::get($var), $string) !== false; + } + + /** + * Checks if variable equal to $param + * + * @param string $var Variable to compare + * @param string $param Param to compare + * + * @return bool + * @psalm-suppress PossiblyUnusedMethod + */ + public static function equal($var, $param) + { + return self::get($var) === $param; + } + + /** + * @param $value + * @param $nesting + * + * @return string|array + */ + public function getAndSanitize($value, $nesting = 0) + { + if ( is_array($value) ) { + foreach ( $value as $_key => & $val ) { + if ( is_array($val) ) { + if ( $nesting > 20 ) { + return $value; + } + $this->getAndSanitize($val, ++$nesting); + } else { + $val = $this->sanitizeDefault($val); + } + } + } else { + $value = $this->sanitizeDefault($value); + } + return $value; + } - public $variables = array(); - - public function __construct(){} - public function __wakeup(){} - public function __clone(){} - - /** - * Constructor - * @return $this - */ - public static function getInstance(){ - if (!isset(static::$instance)) { - static::$instance = new static; - static::$instance->init(); - } - return static::$instance; - } - - /** - * Alternative constructor - */ - protected function init(){ - - } - - /** - * Gets variable from ${_SOMETHING} - * - * @param $name - * - * @return string ${_SOMETHING}[ $name ] - */ - public static function get( $name ){ - return static::getInstance()->get_variable( $name ); - } - - /** - * BLUEPRINT - * Gets given ${_SOMETHING} variable and seva it to memory - * @param $name - * - * @return mixed|string - */ - protected function get_variable( $name ){ - return true; - } - - /** - * Save variable to $this->variables[] - * - * @param string $name - * @param string $value - */ - protected function remebmer_variable( $name, $value ){ - static::$instance->variables[$name] = $value; - } - - /** - * Checks if variable contains given string - * - * @param string $var Haystack to search in - * @param string $string Needle to search - * - * @return bool|int - */ - static function has_string( $var, $string ){ - return stripos( self::get( $var ), $string ) !== false; - } -} \ No newline at end of file + /** + * Sanitize gathering data. + * No sanitizing by default. + * Override this method in the internal class! + * + * @param string $value + * + * @return string + */ + protected function sanitizeDefault($value) + { + return $value; + } +} diff --git a/lib/Cleantalk/Common/error.html b/lib/Cleantalk/Common/error.html deleted file mode 100644 index 7504b61..0000000 --- a/lib/Cleantalk/Common/error.html +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - Blacklisted - - - -

CleanTalk. Spam protection


-%ERROR_TEXT% -

-

« Back

- diff --git a/lib/Cleantalk/ApbctJoomla/DB.php b/lib/Cleantalk/Custom/Db/Db.php similarity index 60% rename from lib/Cleantalk/ApbctJoomla/DB.php rename to lib/Cleantalk/Custom/Db/Db.php index 8ba62f6..23cf5c4 100644 --- a/lib/Cleantalk/ApbctJoomla/DB.php +++ b/lib/Cleantalk/Custom/Db/Db.php @@ -1,8 +1,9 @@ prefix = \JFactory::getDBO()->getPrefix(); } - /** - * Set $this->query string for next uses - * - * @param $query - * @return $this - */ - public function set_query( $query ) { - $this->query = $query; - return $this; - } - - /** - * Safely replace place holders - * - * @param string $query - * @param array $vars - * - * @return $this - */ - public function prepare( $query, $vars = array() ) { - - } - /** * Run any raw request * @@ -43,20 +21,21 @@ public function prepare( $query, $vars = array() ) { * @return bool|int Raw result */ public function execute( $query ) { - $this->db_result = \JFactory::getDBO()->setQuery($query)->execute(); - return $this->db_result; + $this->dbResult = \JFactory::getDBO()->setQuery($query)->execute(); + return $this->dbResult; } /** - * Fetchs first column from query. + * Fetches first column from query. * May receive raw or prepared query. * - * @param bool $query + * @param string $query * @param bool $response_type * * @return array|object|void|null */ - public function fetch( $query = false, $response_type = false ) { + public function fetch( $query = '', $response_type = false ) { + $query = $this->getQuery() ?: $query; $this->result = \JFactory::getDBO()->setQuery($query)->loadAssoc(); return $this->result; @@ -71,7 +50,7 @@ public function fetch( $query = false, $response_type = false ) { * * @return array|object|null */ - public function fetch_all( $query = false, $response_type = false ) { + public function fetchAll( $query = false, $response_type = false ) { $this->result = \JFactory::getDBO()->setQuery($query)->loadAssocList(); return $this->result; @@ -85,8 +64,13 @@ public function fetch_all( $query = false, $response_type = false ) { * @return bool * @psalm-suppress PossiblyUnusedMethod */ - public function is_table_exists($table_name) + public function isTableExists($table_name) { return (bool)$this->execute('SHOW TABLES LIKE "' . $table_name . '"'); } + + public function getAffectedRows() + { + return \JFactory::getDBO()->getAffectedRows(); + } } \ No newline at end of file diff --git a/lib/Cleantalk/Custom/Helper/Helper.php b/lib/Cleantalk/Custom/Helper/Helper.php new file mode 100644 index 0000000..f5baa3f --- /dev/null +++ b/lib/Cleantalk/Custom/Helper/Helper.php @@ -0,0 +1,473 @@ + false, 'firewall_updating_id' => md5(), 'firewall_update_percent' => 0, 'firewall_updating_last_start' => 0 ) + * @important This method must be overloaded in the CMS-based Helper class. + */ + public static function getFwStats() + { + //die( __METHOD__ . ' method must be overloaded in the CMS-based Helper class' ); + $plugin = \JPluginHelper::getPlugin('system', 'cleantalkantispam'); + $params = new \JRegistry($plugin->params); + + return array( + 'firewall_updating_id' => $params->get('firewall_updating_id'), + 'firewall_updating_last_start' => $params->get('firewall_updating_last_start', 0), + 'firewall_update_percent' => $params->get('firewall_update_percent', 0) + ); + } + + /** + * Save fw stats on the storage. + * + * @param array $fw_stats + * @return bool + * @important This method must be overloaded in the CMS-based Helper class. + */ + public static function setFwStats( $fw_stats ) + { + $db = \JFactory::getDBO(); + + $query = $db->getQuery(true); + $query + ->select($db->quoteName('extension_id')) + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' = ' . $db->quote('cleantalkantispam')) + ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); + $db->setQuery($query); + $db->execute(); + + if ($plg = $db->loadObject()) { + $table = \JTable::getInstance('extension'); + $table->load((int) $plg->extension_id); + $params = array(); + $params['firewall_updating_id'] = $fw_stats['firewall_updating_id']; + $params['firewall_updating_last_start'] = $fw_stats['firewall_updating_last_start']; + $params['firewall_update_percent'] = isset($fw_stats['firewall_update_percent']) ? $fw_stats['firewall_update_percent'] : 0; + $jparams = new \JRegistry($table->params); + foreach ($params as $k => $v) + $jparams->set($k, $v); + $table->params = $jparams->toString(); + $table->store(); + } + } + + /** + * Implement here any actions after SFW updating finished. + * + * @return void + */ + public static function SfwUpdate_DoFinisnAction() + { + $db = \JFactory::getDBO(); + + $query = $db->getQuery(true); + $query + ->select($db->quoteName('extension_id')) + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' = ' . $db->quote('cleantalkantispam')) + ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); + $db->setQuery($query); + $db->execute(); + + if ($plg = $db->loadObject()) { + $table = \JTable::getInstance('extension'); + $table->load((int) $plg->extension_id); + $jparams = new \JRegistry($table->params); + $jparams->set('sfw_last_check', time()); + $table->params = $jparams->toString(); + $table->store(); + } + } + + /** + * Wrapper for http_request + * Requesting HTTP response code for $url + * + * @param string $url + * + * @return array|mixed|string + */ + public static function http__request__get_response_code($url ){ + return static::httpRequest( $url, array(), 'get_code'); + } + + /** + * Wrapper for http_request + * Requesting data via HTTP request with GET method + * + * @param string $url + * + * @return array|mixed|string + */ + public static function http__request__get_content($url ){ + return static::httpRequest( $url, array(), 'get dont_split_to_array'); + } + + /** + * Do the remote call to the host. + * + * @param string $rc_action + * @param array $request_params + * @param array $patterns + * @return array|bool + * @todo Have to replace this method to the new class like HttpHelper + */ + public static function http__request__rc_to_host($rc_action, $request_params, $patterns = array() ) + { + $request_params__default = array( + 'spbc_remote_call_action' => $rc_action, + 'plugin_name' => 'apbct', + ); + + $result__rc_check_website = static::httpRequest( + static::getSiteUrl(), + array_merge( $request_params__default, $request_params, array( 'test' => 'test' ) ), + array( 'get', 'dont_split_to_array' ) + ); + + if( empty( $result__rc_check_website['error'] ) ){ + + if (is_string($result__rc_check_website) && preg_match('@^.*?OK$@', $result__rc_check_website)) { + + static::httpRequest( + static::getSiteUrl(), + array_merge( $request_params__default, $request_params ), + array_merge( array( 'get', ), $patterns ) + ); + + }else + return array( + 'error' => 'WRONG_SITE_RESPONSE ACTION: ' . $rc_action . ' RESPONSE: ' . htmlspecialchars( substr( + ! is_string( $result__rc_check_website ) + ? print_r( $result__rc_check_website, true ) + : $result__rc_check_website, + 0, + 400 + ) ) + ); + }else + return array( 'error' => 'WRONG_SITE_RESPONSE TEST ACTION: ' . $rc_action . ' ERROR: ' . $result__rc_check_website['error'] ); + + return true; + } + + /** + * Get site url for remote calls. + * + * @return string@important This method can be overloaded in the CMS-based Helper class. + * + */ + private static function getSiteUrl() + { + return ( isset( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . ( isset($_SERVER['SCRIPT_URL'] ) ? $_SERVER['SCRIPT_URL'] : '' ); + } + + /* + * Get data from submit recursively + */ + static public function get_fields_any($arr, $fields_exclusions = '', $message = array(), $email = null, $nickname = array('nick' => '', 'first' => '', 'last' => ''), $subject = null, $contact = true, $prev_name = '') + { + + //Skip request if fields exists + $skip_params = array( + 'ipn_track_id', // PayPal IPN # + 'txn_type', // PayPal transaction type + 'payment_status', // PayPal payment status + 'ccbill_ipn', // CCBill IPN + 'ct_checkjs', // skip ct_checkjs field + 'api_mode', // DigiStore-API + 'loadLastCommentId', // Plugin: WP Discuz. ticket_id=5571 + ); + + // Fields to replace with **** + $obfuscate_params = array( + 'password', + 'pass', + 'pwd', + 'pswd' + ); + + // Skip fields with these strings and known service fields + $skip_fields_with_strings = array( + // Common + 'ct_checkjs', //Do not send ct_checkjs + 'nonce', //nonce for strings such as 'rsvp_nonce_name' + 'security', + // 'action', + 'http_referer', + 'timestamp', + 'captcha', + // Formidable Form + 'form_key', + 'submit_entry', + // Custom Contact Forms + 'form_id', + 'ccf_form', + 'form_page', + // Qu Forms + 'iphorm_uid', + 'form_url', + 'post_id', + 'iphorm_ajax', + 'iphorm_id', + // Fast SecureContact Froms + 'fs_postonce_1', + 'fscf_submitted', + 'mailto_id', + 'si_contact_action', + // Ninja Forms + 'formData_id', + 'formData_settings', + // E_signature + 'recipient_signature', + 'avatar__file_image_data', + 'task', + 'page_url', + 'page_title', + 'Submit', + 'formId', + 'key', + 'id', + 'hiddenlists', + 'ctrl', + 'task', + 'option', + 'nextstep', + 'acy_source', + 'subid', + 'ct_action', + 'ct_method', + ); + + $skip_fields_with_strings_by_regexp = array( + // Ninja Forms + 'formData_fields_\d+_id', + 'formData_fields_\d+_files.*', + // E_signature + 'output_\d+_\w{0,2}', + // Contact Form by Web-Settler protection + '_formId', + '_returnLink', + // Social login and more + '_save', + '_facebook', + '_social', + 'user_login-', + // Contact Form 7 + '_wpcf7', + ); + + // Reset $message if we have a sign-up data + $skip_message_post = array( + 'edd_action', // Easy Digital Downloads + ); + + foreach ($skip_params as $value) + { + if (@array_key_exists($value, $_GET) || @array_key_exists($value, $_POST)) + $contact = false; + } + unset($value); + + if (count($arr)) + { + foreach ($arr as $key => $value) + { + + if (gettype($value) == 'string') + { + $decoded_json_value = json_decode($value, true); + if ($decoded_json_value !== null) + $value = $decoded_json_value; + } + + if (!is_array($value) && !is_object($value)) + { + //Add custom exclusions + if( is_string($fields_exclusions) && !empty($fields_exclusions) ) { + $fields_exclusions = explode(",",$fields_exclusions); + if (is_array($fields_exclusions) && !empty($fields_exclusions)) { + foreach($fields_exclusions as &$fields_exclusion) { + if( preg_match('/\[*\]/', $fields_exclusion ) ) { + // I have to do this to support exclusions like 'submitted[name]' + $fields_exclusion = str_replace( array( '[', ']' ), array( '_', '' ), $fields_exclusion ); + } + } + $skip_fields_with_strings = array_merge($skip_fields_with_strings, $fields_exclusions); + } + } + if (in_array($key, $skip_params, true) && $key != 0 && $key != '' || preg_match("/^ct_checkjs/", $key)) + $contact = false; + + if ($value === '') + continue; + + // Skipping fields names with strings from (array)skip_fields_with_strings + foreach ($skip_fields_with_strings as $needle) + { + if ($prev_name . $key === $needle) + { + continue(2); + } + } + unset($needle); + + // Skipping fields names with strings from (array)skip_fields_with_strings_by_regexp + foreach ($skip_fields_with_strings_by_regexp as $needle) + { + if (preg_match("/" . $needle . "/", $prev_name . $key) == 1) + { + continue(2); + } + } + unset($needle); + + // Obfuscating params + foreach ($obfuscate_params as $needle) + { + if (strpos($key, $needle) !== false) + { + $value = self::obfuscate_param($value); + continue(2); + } + } + unset($needle); + + + // Removes whitespaces + $value = urldecode( trim( $value ) ); // Fully cleaned message + $value_for_email = trim( $value ); // Removes shortcodes to do better spam filtration on server side. + + // Email + if ( ! $email && preg_match( "/^\S+@\S+\.\S+$/", $value_for_email ) ) { + $email = $value_for_email; + + // Names + } elseif (preg_match("/name/i", $key)) { + + preg_match("/((name.?)?(your|first|for)(.?name)?)$/", $key, $match_forename); + preg_match("/((name.?)?(last|family|second|sur)(.?name)?)$/", $key, $match_surname); + preg_match("/^(name.?)?(nick|user)(.?name)?$/", $key, $match_nickname); + + if (count($match_forename) > 1) + $nickname['first'] = $value; + elseif (count($match_surname) > 1) + $nickname['last'] = $value; + elseif (count($match_nickname) > 1) + $nickname['nick'] = $value; + else + $nickname[$prev_name . $key] = $value; + + // Subject + } + elseif ($subject === null && preg_match("/subject/i", $key)) + { + $subject = $value; + + // Message + } + else + { + $message[$prev_name . $key] = $value; + } + + } + elseif (!is_object($value)) + { + + $prev_name_original = $prev_name; + $prev_name = ($prev_name === '' ? $key . '_' : $prev_name . $key . '_'); + + $temp = self::get_fields_any($value, $fields_exclusions, $message, $email, $nickname, $subject, $contact, $prev_name); + + $message = $temp['message']; + $email = ($temp['email'] ? $temp['email'] : null); + $nickname = ($temp['nickname'] ? $temp['nickname'] : null); + $subject = ($temp['subject'] ? $temp['subject'] : null); + if ($contact === true) + $contact = ($temp['contact'] === false ? false : true); + $prev_name = $prev_name_original; + } + } + unset($key, $value); + } + + foreach ($skip_message_post as $v) + { + if (isset($_POST[$v])) + { + $message = null; + break; + } + } + unset($v); + + //If top iteration, returns compiled name field. Example: "Nickname Firtsname Lastname". + if ($prev_name === '') + { + if (!empty($nickname)) + { + $nickname_str = ''; + foreach ($nickname as $value) + { + $nickname_str .= ($value ? $value . " " : ""); + } + unset($value); + } + $nickname = $nickname_str; + } + + $return_param = array( + 'email' => $email, + 'nickname' => $nickname, + 'subject' => $subject, + 'contact' => $contact, + 'message' => $message + ); + + return $return_param; + } + + /** + * Masks a value with asterisks (*) Needed by the getFieldsAny() + * @return string + */ + static public function obfuscate_param($value = null) + { + if ($value && (!is_object($value) || !is_array($value))) + { + $length = strlen($value); + $value = str_repeat('*', $length); + } + + return $value; + } + + /** + * Print html form for external forms() + * @return string + */ + static public function print_form($arr, $k) + { + foreach ($arr as $key => $value) + { + if (!is_array($value)) + { + + if ($k == '') + print ''; + else + print ''; + } + } + } +} diff --git a/lib/Cleantalk/ApbctJoomla/RemoteCalls.php b/lib/Cleantalk/Custom/RemoteCalls/RemoteCalls.php similarity index 81% rename from lib/Cleantalk/ApbctJoomla/RemoteCalls.php rename to lib/Cleantalk/Custom/RemoteCalls/RemoteCalls.php index 7e6e046..c51d16b 100644 --- a/lib/Cleantalk/ApbctJoomla/RemoteCalls.php +++ b/lib/Cleantalk/Custom/RemoteCalls/RemoteCalls.php @@ -1,8 +1,24 @@ array( + 'last_call' => 0, + 'cooldown' => self::COOLDOWN + ), + 'sfw_update' => array( + 'last_call' => 0, + 'cooldown' => 0 + ), + 'sfw_send_logs' => array( + 'last_call' => 0, + 'cooldown' => self::COOLDOWN + ) + ); -class RemoteCalls extends \Cleantalk\Common\RemoteCalls { /** * SFW update * @@ -23,16 +39,12 @@ public function action__sfw_send_logs() return \plgSystemCleantalkantispam::apbct_sfw_send_logs( $this->api_key ); } - public function action__sfw_update__write_base() - { - return \plgSystemCleantalkantispam::apbct_sfw_update( $this->api_key ); - } /** * Get available remote calls from the storage. * * @return array */ - protected function getAvailableRcActions() + /*protected function getAvailableRcActions() { $plugin = \JPluginHelper::getPlugin('system', 'cleantalkantispam'); $params = new \JRegistry($plugin->params); @@ -46,18 +58,14 @@ protected function getAvailableRcActions() ), 'sfw_update' => array( 'last_call' => 0, - 'cooldown' => self::COOLDOWN + 'cooldown' => 0 ), 'sfw_send_logs' => array( 'last_call' => 0, 'cooldown' => self::COOLDOWN - ), - 'sfw_update__write_base' => array( - 'last_call' => 0, - 'cooldown' => 0 ) ); - } + }*/ /** * Set last call timestamp and save it to the storage. @@ -65,7 +73,7 @@ protected function getAvailableRcActions() * @param array $action * @return void */ - protected function setLastCall( $action ) + /*protected function setLastCall( $action ) { // TODO: Implement setLastCall() method. $remote_calls = $this->getAvailableRcActions(); @@ -89,5 +97,5 @@ protected function setLastCall( $action ) $table->params = $jparams->toString(); $table->store(); } - } + }*/ } \ No newline at end of file diff --git a/lib/Cleantalk/Custom/StorageHandler/StorageHandler.php b/lib/Cleantalk/Custom/StorageHandler/StorageHandler.php new file mode 100644 index 0000000..8079252 --- /dev/null +++ b/lib/Cleantalk/Custom/StorageHandler/StorageHandler.php @@ -0,0 +1,76 @@ +load((int) $plg->extension_id); + $data = new \JRegistry($table->custom_data); + $data_array = $data->toArray(); + if ( isset($data_array[$setting_name]) ) { + return $data_array[$setting_name]; + } + return null; + } + + public static function deleteSetting($setting_name) + { + $plg = self::getPlgEntry(); + + $table = \JTable::getInstance('extension'); + $table->load((int) $plg->extension_id); + $data = new \JRegistry($table->custom_data); + $data_array = $data->toArray(); + if ( isset($data_array[$setting_name]) ) { + $data->remove($setting_name); + } + $table->custom_data = $data->toString(); + $table->store(); + } + + public static function saveSetting($setting_name, $setting_value) + { + $plg = self::getPlgEntry(); + + $table = \JTable::getInstance('extension'); + $table->load((int) $plg->extension_id); + $params = array($setting_name => $setting_value); + $data = new \JRegistry($table->custom_data); + foreach ($params as $k => $v) { + $data->set($k, $v); + } + $table->custom_data = $data->toString(); + $table->store(); + } + + public static function getUpdatingFolder() + { + return APBCT_DIR_PATH . DIRECTORY_SEPARATOR . 'cleantalk_fw_files' . DIRECTORY_SEPARATOR; + } + + private static function getPlgEntry() + { + $db = \JFactory::getDBO(); + + $query = $db->getQuery(true); + $query + ->select($db->quoteName('extension_id')) + ->from($db->quoteName('#__extensions')) + ->where($db->quoteName('element') . ' = ' . $db->quote('cleantalkantispam')) + ->where($db->quoteName('folder') . ' = ' . $db->quote('system')); + $db->setQuery($query); + $db->execute(); + + return $db->loadObject(); + } + + public static function getJsLocation() + { + return \JURI::root(true) . "/plugins/system/cleantalkantispam/js/ct-functions.js"; + } +} diff --git a/sql/mariadb/install.mariadb.utf8.sql b/sql/mariadb/install.mariadb.utf8.sql index cacc3b8..d6a1f01 100644 --- a/sql/mariadb/install.mariadb.utf8.sql +++ b/sql/mariadb/install.mariadb.utf8.sql @@ -1,15 +1,26 @@ CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw` ( - `network` int(10) unsigned NOT NULL, - `mask` int(10) unsigned NOT NULL, - `status` tinyint(1) NOT NULL DEFAULT 0, - KEY `network` (`network`) + `id` INT(11) NOT NULL AUTO_INCREMENT, + `network` int(11) unsigned NOT NULL, + `mask` int(11) unsigned NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT 0, + `source` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + INDEX ( `network` , `mask` ) ); CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( - `ip` varchar(15) NOT NULL, - `all_entries` int(11) NOT NULL, - `blocked_entries` int(11) NOT NULL, - `entries_timestamp` int(11) NOT NULL, - PRIMARY KEY `ip` (`ip`) + `id` VARCHAR(40) NOT NULL, + `ip` VARCHAR(15) NOT NULL, + `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, + `all_entries` INT NOT NULL, + `blocked_entries` INT NOT NULL, + `entries_timestamp` INT NOT NULL, + `ua_id` INT(11) NULL DEFAULT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + `source` TINYINT NULL DEFAULT NULL, + `network` VARCHAR(20) NULL DEFAULT NULL, + `first_url`VARCHAR(100) NULL DEFAULT NULL, + `last_url` VARCHAR(100) NULL DEFAULT NULL, + PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `#__cleantalk_sessions` ( `id` varchar(64) NOT NULL, diff --git a/sql/mariadb/updates/1.8.sql b/sql/mariadb/updates/1.8.sql new file mode 100644 index 0000000..1a94e66 --- /dev/null +++ b/sql/mariadb/updates/1.8.sql @@ -0,0 +1,29 @@ +DROP TABLE IF EXISTS `#__cleantalk_sfw`; +DROP TABLE IF EXISTS `#__cleantalk_sfw_logs`; +DROP TABLE IF EXISTS `#__cleantalk_sessions`; +CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw` ( + `id` INT(11) NOT NULL AUTO_INCREMENT, + `network` int(11) unsigned NOT NULL, + `mask` int(11) unsigned NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + INDEX ( `network` , `mask` ) +); +CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( + `id` VARCHAR(40) NOT NULL, + `ip` VARCHAR(15) NOT NULL, + `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, + `all_entries` INT NOT NULL, + `blocked_entries` INT NOT NULL, + `entries_timestamp` INT NOT NULL, + `ua_id` INT(11) NULL DEFAULT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + PRIMARY KEY (`id`) +); +CREATE TABLE IF NOT EXISTS `#__cleantalk_sessions` ( + `id` varchar(64) NOT NULL, + `name` varchar(40) NOT NULL, + `value` text NULL DEFAULT NULL, + `last_update` datetime NULL DEFAULT NULL, + PRIMARY KEY (`name`(40), `id`(64)) +); \ No newline at end of file diff --git a/sql/mariadb/updates/3.0.sql b/sql/mariadb/updates/3.0.sql new file mode 100644 index 0000000..ee73b56 --- /dev/null +++ b/sql/mariadb/updates/3.0.sql @@ -0,0 +1,17 @@ +ALTER TABLE `#__cleantalk_sfw` ADD source tinyint(1) NOT NULL DEFAULT 0 AFTER `status`; +DROP TABLE IF EXISTS `#__cleantalk_sfw_logs`; +CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( + `id` VARCHAR(40) NOT NULL, + `ip` VARCHAR(15) NOT NULL, + `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, + `all_entries` INT NOT NULL, + `blocked_entries` INT NOT NULL, + `entries_timestamp` INT NOT NULL, + `ua_id` INT(11) NULL DEFAULT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + `source` TINYINT NULL DEFAULT NULL, + `network` VARCHAR(20) NULL DEFAULT NULL, + `first_url`VARCHAR(100) NULL DEFAULT NULL, + `last_url` VARCHAR(100) NULL DEFAULT NULL, + PRIMARY KEY (`id`) +); \ No newline at end of file diff --git a/sql/mysql/install.mysql.utf8.sql b/sql/mysql/install.mysql.utf8.sql index 2e8909b..9668194 100644 --- a/sql/mysql/install.mysql.utf8.sql +++ b/sql/mysql/install.mysql.utf8.sql @@ -3,6 +3,7 @@ CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw` ( `network` int(11) unsigned NOT NULL, `mask` int(11) unsigned NOT NULL, `status` tinyint(1) NOT NULL DEFAULT 0, + `source` tinyint(1) NOT NULL DEFAULT 0, PRIMARY KEY (`id`), INDEX ( `network` , `mask` ) ); @@ -14,7 +15,11 @@ CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( `blocked_entries` INT NOT NULL, `entries_timestamp` INT NOT NULL, `ua_id` INT(11) NULL DEFAULT NULL, - `ua_name` VARCHAR(1024) NOT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + `source` TINYINT NULL DEFAULT NULL, + `network` VARCHAR(20) NULL DEFAULT NULL, + `first_url`VARCHAR(100) NULL DEFAULT NULL, + `last_url` VARCHAR(100) NULL DEFAULT NULL, PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `#__cleantalk_sessions` ( diff --git a/sql/mysql/updates/3.0.sql b/sql/mysql/updates/3.0.sql new file mode 100644 index 0000000..ee73b56 --- /dev/null +++ b/sql/mysql/updates/3.0.sql @@ -0,0 +1,17 @@ +ALTER TABLE `#__cleantalk_sfw` ADD source tinyint(1) NOT NULL DEFAULT 0 AFTER `status`; +DROP TABLE IF EXISTS `#__cleantalk_sfw_logs`; +CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( + `id` VARCHAR(40) NOT NULL, + `ip` VARCHAR(15) NOT NULL, + `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, + `all_entries` INT NOT NULL, + `blocked_entries` INT NOT NULL, + `entries_timestamp` INT NOT NULL, + `ua_id` INT(11) NULL DEFAULT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + `source` TINYINT NULL DEFAULT NULL, + `network` VARCHAR(20) NULL DEFAULT NULL, + `first_url`VARCHAR(100) NULL DEFAULT NULL, + `last_url` VARCHAR(100) NULL DEFAULT NULL, + PRIMARY KEY (`id`) +); \ No newline at end of file diff --git a/sql/sqlsrv/install.mysql.utf8.sql b/sql/sqlsrv/install.mysql.utf8.sql index cacc3b8..d6a1f01 100644 --- a/sql/sqlsrv/install.mysql.utf8.sql +++ b/sql/sqlsrv/install.mysql.utf8.sql @@ -1,15 +1,26 @@ CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw` ( - `network` int(10) unsigned NOT NULL, - `mask` int(10) unsigned NOT NULL, - `status` tinyint(1) NOT NULL DEFAULT 0, - KEY `network` (`network`) + `id` INT(11) NOT NULL AUTO_INCREMENT, + `network` int(11) unsigned NOT NULL, + `mask` int(11) unsigned NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT 0, + `source` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + INDEX ( `network` , `mask` ) ); CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( - `ip` varchar(15) NOT NULL, - `all_entries` int(11) NOT NULL, - `blocked_entries` int(11) NOT NULL, - `entries_timestamp` int(11) NOT NULL, - PRIMARY KEY `ip` (`ip`) + `id` VARCHAR(40) NOT NULL, + `ip` VARCHAR(15) NOT NULL, + `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, + `all_entries` INT NOT NULL, + `blocked_entries` INT NOT NULL, + `entries_timestamp` INT NOT NULL, + `ua_id` INT(11) NULL DEFAULT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + `source` TINYINT NULL DEFAULT NULL, + `network` VARCHAR(20) NULL DEFAULT NULL, + `first_url`VARCHAR(100) NULL DEFAULT NULL, + `last_url` VARCHAR(100) NULL DEFAULT NULL, + PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `#__cleantalk_sessions` ( `id` varchar(64) NOT NULL, diff --git a/sql/sqlsrv/install.sqlsrv.utf8.sql b/sql/sqlsrv/install.sqlsrv.utf8.sql index 2e8909b..d6a1f01 100644 --- a/sql/sqlsrv/install.sqlsrv.utf8.sql +++ b/sql/sqlsrv/install.sqlsrv.utf8.sql @@ -1,21 +1,26 @@ CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw` ( - `id` INT(11) NOT NULL AUTO_INCREMENT, - `network` int(11) unsigned NOT NULL, - `mask` int(11) unsigned NOT NULL, - `status` tinyint(1) NOT NULL DEFAULT 0, - PRIMARY KEY (`id`), - INDEX ( `network` , `mask` ) + `id` INT(11) NOT NULL AUTO_INCREMENT, + `network` int(11) unsigned NOT NULL, + `mask` int(11) unsigned NOT NULL, + `status` tinyint(1) NOT NULL DEFAULT 0, + `source` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (`id`), + INDEX ( `network` , `mask` ) ); CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( - `id` VARCHAR(40) NOT NULL, - `ip` VARCHAR(15) NOT NULL, - `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, - `all_entries` INT NOT NULL, - `blocked_entries` INT NOT NULL, - `entries_timestamp` INT NOT NULL, - `ua_id` INT(11) NULL DEFAULT NULL, - `ua_name` VARCHAR(1024) NOT NULL, - PRIMARY KEY (`id`) + `id` VARCHAR(40) NOT NULL, + `ip` VARCHAR(15) NOT NULL, + `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, + `all_entries` INT NOT NULL, + `blocked_entries` INT NOT NULL, + `entries_timestamp` INT NOT NULL, + `ua_id` INT(11) NULL DEFAULT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + `source` TINYINT NULL DEFAULT NULL, + `network` VARCHAR(20) NULL DEFAULT NULL, + `first_url`VARCHAR(100) NULL DEFAULT NULL, + `last_url` VARCHAR(100) NULL DEFAULT NULL, + PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `#__cleantalk_sessions` ( `id` varchar(64) NOT NULL, diff --git a/sql/sqlsrv/updates/3.0.sql b/sql/sqlsrv/updates/3.0.sql new file mode 100644 index 0000000..ee73b56 --- /dev/null +++ b/sql/sqlsrv/updates/3.0.sql @@ -0,0 +1,17 @@ +ALTER TABLE `#__cleantalk_sfw` ADD source tinyint(1) NOT NULL DEFAULT 0 AFTER `status`; +DROP TABLE IF EXISTS `#__cleantalk_sfw_logs`; +CREATE TABLE IF NOT EXISTS `#__cleantalk_sfw_logs` ( + `id` VARCHAR(40) NOT NULL, + `ip` VARCHAR(15) NOT NULL, + `status` ENUM('PASS_SFW','DENY_SFW','PASS_SFW__BY_WHITELIST','PASS_SFW__BY_COOKIE','DENY_ANTICRAWLER','PASS_ANTICRAWLER','DENY_ANTICRAWLER_UA','PASS_ANTICRAWLER_UA','DENY_ANTIFLOOD','PASS_ANTIFLOOD') NULL DEFAULT NULL, + `all_entries` INT NOT NULL, + `blocked_entries` INT NOT NULL, + `entries_timestamp` INT NOT NULL, + `ua_id` INT(11) NULL DEFAULT NULL, + `ua_name` VARCHAR(1024) NOT NULL, + `source` TINYINT NULL DEFAULT NULL, + `network` VARCHAR(20) NULL DEFAULT NULL, + `first_url`VARCHAR(100) NULL DEFAULT NULL, + `last_url` VARCHAR(100) NULL DEFAULT NULL, + PRIMARY KEY (`id`) +); \ No newline at end of file diff --git a/updater.php b/updater.php index d29b582..b33e80f 100644 --- a/updater.php +++ b/updater.php @@ -30,6 +30,10 @@ public function update($parent) public function postflight($type, $parent) { + if ( $type === 'uninstall' ) { + return; + } + // Updating roles_exclusion $excluded_roles = $this->getParam('roles_exclusions'); From fada7a3fb6a3784034170fc1a78a0a218271651f Mon Sep 17 00:00:00 2001 From: Glomberg Date: Thu, 8 Dec 2022 10:01:52 +0300 Subject: [PATCH 14/18] Upd. Version 3.0. --- README.md | 2 +- cleantalkantispam.php | 2 +- plugin-updates.xml | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 29b3fa3..43e8f59 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ Anti-spam plugin for Joomla 3.X.-4.x ============ -Version 2.3 +Version 3.0 ======= ## Simple antispam test diff --git a/cleantalkantispam.php b/cleantalkantispam.php index 90386f4..036cd48 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -3,7 +3,7 @@ /** * CleanTalk joomla plugin * - * @version 2.3 + * @version 3.0 * @package Cleantalk * @subpackage Joomla * @author CleanTalk (welcome@cleantalk.org) diff --git a/plugin-updates.xml b/plugin-updates.xml index e11428f..46b035d 100644 --- a/plugin-updates.xml +++ b/plugin-updates.xml @@ -6,9 +6,9 @@ cleantalkantispam plugin system - 2.3 + 3.0 - https://github.com/CleanTalk/joomla3.x-4.x-antispam/archive/2.3.zip + https://github.com/CleanTalk/joomla3.x-4.x-antispam/archive/3.0.zip stable @@ -23,9 +23,9 @@ cleantalkantispam plugin system - 2.3 + 3.0 - https://github.com/CleanTalk/joomla3.x-4.x-antispam/archive/2.3.zip + https://github.com/CleanTalk/joomla3.x-4.x-antispam/archive/3.0.zip stable From 08ae8f3b1eccdb52e713df08ed26872d69664788 Mon Sep 17 00:00:00 2001 From: Glomberg Date: Thu, 8 Dec 2022 11:23:43 +0300 Subject: [PATCH 15/18] Fix. Code. Tests updated. --- tests/cleantalk_test.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/cleantalk_test.php b/tests/cleantalk_test.php index ee05356..7b6be39 100644 --- a/tests/cleantalk_test.php +++ b/tests/cleantalk_test.php @@ -1,10 +1,10 @@ Date: Thu, 8 Dec 2022 11:24:21 +0300 Subject: [PATCH 16/18] Fix. Sending feedback updated. --- cleantalkantispam.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index 036cd48..f93a317 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -1676,7 +1676,7 @@ private function ctSendFeedback($auth_key = '', $feedback_request = null) { if ($feedback_request) { - $ct_request = new CleantalkRequest(); + $ct_request = new \Cleantalk\Common\Antispam\CleantalkRequest(); $ct_request->auth_key = $auth_key; $ct_request->feedback = $feedback_request; $ct = new Cleantalk(); From c0b193cd54a169ee72854c734343c657b08598d3 Mon Sep 17 00:00:00 2001 From: Glomberg Date: Thu, 8 Dec 2022 12:06:34 +0300 Subject: [PATCH 17/18] Revert "Fix. Sending feedback updated." This reverts commit 77d618e1f431c873b8596c23ebdace589e4b2a68. --- cleantalkantispam.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cleantalkantispam.php b/cleantalkantispam.php index f93a317..036cd48 100644 --- a/cleantalkantispam.php +++ b/cleantalkantispam.php @@ -1676,7 +1676,7 @@ private function ctSendFeedback($auth_key = '', $feedback_request = null) { if ($feedback_request) { - $ct_request = new \Cleantalk\Common\Antispam\CleantalkRequest(); + $ct_request = new CleantalkRequest(); $ct_request->auth_key = $auth_key; $ct_request->feedback = $feedback_request; $ct = new Cleantalk(); From 36fb02eb81a8e5ab1e256f41a9efbbc5419debeb Mon Sep 17 00:00:00 2001 From: Glomberg Date: Thu, 8 Dec 2022 12:09:55 +0300 Subject: [PATCH 18/18] Fix. Temporary add old Antispam classes. --- lib/Cleantalk/Antispam/Cleantalk.php | 795 +++++++++++++++++++ lib/Cleantalk/Antispam/CleantalkRequest.php | 172 ++++ lib/Cleantalk/Antispam/CleantalkResponse.php | 158 ++++ 3 files changed, 1125 insertions(+) create mode 100644 lib/Cleantalk/Antispam/Cleantalk.php create mode 100644 lib/Cleantalk/Antispam/CleantalkRequest.php create mode 100644 lib/Cleantalk/Antispam/CleantalkResponse.php diff --git a/lib/Cleantalk/Antispam/Cleantalk.php b/lib/Cleantalk/Antispam/Cleantalk.php new file mode 100644 index 0000000..d306ca3 --- /dev/null +++ b/lib/Cleantalk/Antispam/Cleantalk.php @@ -0,0 +1,795 @@ +filterRequest($request); + $msg = $this->createMsg('check_message', $request); + return $this->httpRequest($msg); + } + + /** + * Function checks whether it is possible to publish the message + * @param CleantalkRequest $request + * @return type + */ + public function isAllowUser(CleantalkRequest $request) { + $request = $this->filterRequest($request); + $msg = $this->createMsg('check_newuser', $request); + return $this->httpRequest($msg); + } + + /** + * Function sends the results of manual moderation + * + * @param CleantalkRequest $request + * @return type + */ + public function sendFeedback(CleantalkRequest $request) { + $request = $this->filterRequest($request); + $msg = $this->createMsg('send_feedback', $request); + return $this->httpRequest($msg); + } + + /** + * Filter request params + * @param CleantalkRequest $request + * @return type + */ + private function filterRequest(CleantalkRequest $request) { + // general and optional + foreach ($request as $param => $value) { + if (in_array($param, array('message', 'example', 'agent', + 'sender_info', 'sender_nickname', 'post_info', 'phone')) && !empty($value)) { + if (!is_string($value) && !is_integer($value)) { + $request->$param = NULL; + } + } + + if (in_array($param, array('stoplist_check', 'allow_links')) && !empty($value)) { + if (!in_array($value, array(1, 2))) { + $request->$param = NULL; + } + } + + if (in_array($param, array('js_on')) && !empty($value)) { + if (!is_integer($value)) { + $request->$param = NULL; + } + } + + if ($param == 'sender_ip' && !empty($value)) { + if (!is_string($value)) { + $request->$param = NULL; + } + } + + if ($param == 'sender_email' && !empty($value)) { + if (!is_string($value)) { + $request->$param = NULL; + } + } + + if ($param == 'submit_time' && !empty($value)) { + if (!is_int($value)) { + $request->$param = NULL; + } + } + } + return $request; + } + + /** + * Compress data and encode to base64 + * @param type string + * @return string + */ + private function compressData($data = null){ + + if (strlen($data) > $this->dataMaxSise && function_exists('gzencode') && function_exists('base64_encode')){ + + $localData = gzencode($data, $this->compressRate, FORCE_GZIP); + + if ($localData === false) + return $data; + + $localData = base64_encode($localData); + + if ($localData === false) + return $data; + + return $localData; + } + + return $data; + } + + /** + * Create msg for cleantalk server + * @param type $method + * @param CleantalkRequest $request + * @return \xmlrpcmsg + */ + private function createMsg($method, CleantalkRequest $request) { + switch ($method) { + case 'check_message': + // Convert strings to UTF8 + $request->message = $this->stringToUTF8($request->message, $this->data_codepage); + $request->example = $this->stringToUTF8($request->example, $this->data_codepage); + $request->sender_email = $this->stringToUTF8($request->sender_email, $this->data_codepage); + $request->sender_nickname = $this->stringToUTF8($request->sender_nickname, $this->data_codepage); + + $request->message = $this->compressData($request->message); + $request->example = $this->compressData($request->example); + break; + + case 'check_newuser': + // Convert strings to UTF8 + $request->sender_email = $this->stringToUTF8($request->sender_email, $this->data_codepage); + $request->sender_nickname = $this->stringToUTF8($request->sender_nickname, $this->data_codepage); + break; + + case 'send_feedback': + if (is_array($request->feedback)) { + $request->feedback = implode(';', $request->feedback); + } + 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.'; + } + } + + return $request; + } + + /** + * Send JSON request to servers + * @param $msg + * @return boolean|\CleantalkResponse + */ + private function sendRequest($data, $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){ + if($value !== null){ + $tmp_data[$key] = $value; + } + } + $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')){ + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_TIMEOUT, $server_timeout); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + // receive server response ... + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + // resolve 'Expect: 100-continue' issue + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); + // see http://stackoverflow.com/a/23322368 + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); + + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // Disabling CA cert verivication and + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); // Disabling common name verification + + if ($this->ssl_on && $this->ssl_path != '') { + curl_setopt($ch, CURLOPT_CAINFO, $this->ssl_path); + } + + $result = curl_exec($ch); + if (!$result) { + $curl_error = curl_error($ch); + // Use SSL next time, if error occurs. + if(!$this->ssl_on){ + $this->ssl_on = true; + return $this->sendRequest($original_data, $original_url, $server_timeout); + } + } + + curl_close($ch); + } + + if (!$result) { + $allow_url_fopen = ini_get('allow_url_fopen'); + if (function_exists('file_get_contents') && isset($allow_url_fopen) && $allow_url_fopen == '1') { + $opts = array('http' => + array( + 'method' => 'POST', + 'header' => "Content-Type: text/html\r\n", + 'content' => $data, + 'timeout' => $server_timeout + ) + ); + + $context = stream_context_create($opts); + $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)) { + $response->errno = 0; + $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 + * @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}[;|$]/', + '/\s{0,1}ct_pointer_data=.*5D[;|$]{0,1}/', + '/;{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); + + $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); + } + + if (($result === false || $result->errno != 0) && $this->stay_on_server == false) { + // Split server url to parts + preg_match("@^(https?://)([^/:]+)(.*)@i", $this->server_url, $matches); + $url_prefix = ''; + if (isset($matches[1])) + $url_prefix = $matches[1]; + + $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://'; + + if (empty($pool)) { + return false; + } else { + // Loop until find work server + foreach ($this->get_servers_ip($pool) as $server) { + if ($server['host'] === 'localhost' || $server['ip'] === null) { + $work_url = $server['host']; + } else { + $server_host = $server['ip']; + $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 = $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) { + $this->server_change = true; + break; + } + } + } + } + + $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); + if (!empty($response->errstr)) + $response->errstr = $this->stringFromUTF8($response->errstr, $this->data_codepage); + 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 + * @return array + */ + public function get_servers_ip($host) { + $response = null; + if (!isset($host)) + return $response; + + if (function_exists('dns_get_record')) { + $records = @dns_get_record($host, DNS_A); + + if ($records !== FALSE) { + foreach ($records as $server) { + $response[] = $server; + } + } + } + if (count($response) == 0 && function_exists('gethostbynamel')) { + $records = gethostbynamel($host); + + if ($records !== FALSE) { + foreach ($records as $server) { + $response[] = array("ip" => $server, + "host" => $host, + "ttl" => $this->server_ttl + ); + } + } + } + + if (count($response) == 0) { + $response[] = array("ip" => null, + "host" => $host, + "ttl" => $this->server_ttl + ); + } else { + // $i - to resolve collisions with localhost + $i = 0; + $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; + } 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; + } + } + if (count($r_temp)){ + ksort($r_temp); + $response = $r_temp; + } + } + + return $response; + } + + /** + * Function to get the message hash from Cleantalk.org comment + * @param $message + * @return null + */ + public function getCleantalkCommentHash($message) { + $matches = array(); + if (preg_match('/\n\n\*\*\*.+([a-z0-9]{32}).+\*\*\*$/', $message, $matches)) + return $matches[1]; + else if (preg_match('/\[\n]{0,1}\[\n]{0,1}\*\*\*.+([a-z0-9]{32}).+\*\*\*$/', $message, $matches)) + return $matches[1]; + + return NULL; + } + + /** + * Function adds to the post comment Cleantalk.org + * @param $message + * @param $comment + * @return string + */ + public function addCleantalkComment($message, $comment) { + $comment = preg_match('/\*\*\*(.+)\*\*\*/', $comment, $matches) ? $comment : '*** ' . $comment . ' ***'; + return $message . "\n\n" . $comment; + } + + /** + * Function deletes the comment Cleantalk.org + * @param $message + * @return mixed + */ + public function delCleantalkComment($message) { + $message = preg_replace('/\n\n\*\*\*.+\*\*\*$/', '', $message); + + // DLE sign cut + $message = preg_replace('/\*\*\*.+\*\*\*$/', '', $message); + + $message = preg_replace('/\[\n]{0,1}\[\n]{0,1}\*\*\*.+\*\*\*$/', '', $message); + + return $message; + } + + /** + * Get user IP behind proxy server + */ + public function ct_session_ip( $data_ip ) { + if (!$data_ip || !preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/", $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 + if (!preg_match("/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/", $forwarded_ip[0])) { + return $data_ip; + } + + $private_src_ip = false; + $private_nets = array( + '10.0.0.0/8', + '127.0.0.0/8', + '176.16.0.0/12', + '192.168.0.0/16', + ); + + foreach ($private_nets as $v) { + + // Private IP found + 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]; + } + } + + return $data_ip;*/ + return cleantalk_get_real_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); + } + + /** + * Function to check response time + * param string + * @return int + */ + function httpPing($host){ + + // Skip localhost ping cause it raise error at fsockopen. + // And return minimun value + if ($host == 'localhost') + return 0.001; + + $starttime = microtime(true); + $file = @fsockopen ($host, 80, $errno, $errstr, $this->server_timeout); + $stoptime = microtime(true); + $status = 0; + if (!$file) { + $status = -1; // Site is down + } else { + fclose($file); + $status = ($stoptime - $starttime); + $status = round($status, 4); + } + + return $status; + } + + /** + * 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); + + $encoding = mb_detect_encoding($str); + if ($encoding) + return mb_convert_encoding($str, 'UTF-8', $encoding); + } + + return $str; + } + + /** + * Function convert string from UTF8 + * param string + * param string + * @return string + */ + 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 + * + * @param string api_key + * @return JSON/array + */ + public function get_2s_blacklists_db ($api_key) { + $request=array(); + $request['method_name'] = '2s_blacklists_db'; + $request['auth_key'] = $api_key; + $url='https://api.cleantalk.org'; + $result=CleantalkHelper::sendRawRequest($url,$request); + return $result; + } +} +if( !function_exists('apache_request_headers') ) +{ + function apache_request_headers() + { + $arh = array(); + $rx_http = '/\AHTTP_/'; + foreach($_SERVER as $key => $val) + { + if( preg_match($rx_http, $key) ) + { + $arh_key = preg_replace($rx_http, '', $key); + $rx_matches = array(); + $rx_matches = explode('_', $arh_key); + if( count($rx_matches) > 0 and strlen($arh_key) > 2 ) + { + foreach($rx_matches as $ak_key => $ak_val) $rx_matches[$ak_key] = ucfirst($ak_val); + $arh_key = implode('-', $rx_matches); + } + $arh[$arh_key] = $val; + } + } + return( $arh ); + } +} + +function cleantalk_get_real_ip() +{ + // Getting headers + $headers = function_exists('apache_request_headers') ? apache_request_headers() : $_SERVER; + + // Getting IP for validating + if (array_key_exists( 'X-Forwarded-For', $headers )){ + $ip = explode(",", trim($headers['X-Forwarded-For'])); + $ip = trim($ip[0]); + }elseif(array_key_exists( 'HTTP_X_FORWARDED_FOR', $headers)){ + $ip = explode(",", trim($headers['HTTP_X_FORWARDED_FOR'])); + $ip = trim($ip[0]); + }else{ + $ip = $_SERVER['REMOTE_ADDR']; + } + + // Validating IP + // IPv4 + if(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){ + $the_ip = $ip; + // IPv6 + }elseif(filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)){ + $the_ip = $ip; + // Unknown + }else{ + $the_ip = null; + } + return $the_ip; +} + +function cleantalk_is_JSON($string) +{ + return ((is_string($string) && (is_object(json_decode($string)) || is_array(json_decode($string))))) ? true : false; +} + +// Patch for locale_get_display_region() for old PHP versions +if( !function_exists('locale_get_display_region') ){ + function locale_get_display_region($locale, $curr_lcoale='en'){ + return $locale; + } +} diff --git a/lib/Cleantalk/Antispam/CleantalkRequest.php b/lib/Cleantalk/Antispam/CleantalkRequest.php new file mode 100644 index 0000000..b480dfc --- /dev/null +++ b/lib/Cleantalk/Antispam/CleantalkRequest.php @@ -0,0 +1,172 @@ + 0) { + foreach ($params as $param => $value) { + $this->{$param} = $value; + } + } + } + +} diff --git a/lib/Cleantalk/Antispam/CleantalkResponse.php b/lib/Cleantalk/Antispam/CleantalkResponse.php new file mode 100644 index 0000000..c56ed12 --- /dev/null +++ b/lib/Cleantalk/Antispam/CleantalkResponse.php @@ -0,0 +1,158 @@ + 0) { + foreach ($response as $param => $value) { + $this->{$param} = $value; + } + } else { + $this->errno = $obj->errno; + $this->errstr = $obj->errstr; + + $this->errstr = preg_replace("/.+(\*\*\*.+\*\*\*).+/", "$1", $this->errstr); + + $this->stop_words = isset($obj->stop_words) ? utf8_decode($obj->stop_words) : null; + $this->comment = isset($obj->comment) ? utf8_decode($obj->comment) : null; + $this->blacklisted = (isset($obj->blacklisted)) ? $obj->blacklisted : null; + $this->allow = (isset($obj->allow)) ? $obj->allow : 0; + $this->id = (isset($obj->id)) ? $obj->id : null; + $this->fast_submit = (isset($obj->fast_submit)) ? $obj->fast_submit : 0; + $this->spam = (isset($obj->spam)) ? $obj->spam : 0; + $this->js_disabled = (isset($obj->js_disabled)) ? $obj->js_disabled : 0; + $this->sms_allow = (isset($obj->sms_allow)) ? $obj->sms_allow : null; + $this->sms = (isset($obj->sms)) ? $obj->sms : null; + $this->sms_error_code = (isset($obj->sms_error_code)) ? $obj->sms_error_code : null; + $this->sms_error_text = (isset($obj->sms_error_text)) ? $obj->sms_error_text : null; + $this->stop_queue = (isset($obj->stop_queue)) ? $obj->stop_queue : 0; + $this->inactive = (isset($obj->inactive)) ? $obj->inactive : 0; + $this->account_status = (isset($obj->account_status)) ? $obj->account_status : -1; + $this->received = (isset($obj->received)) ? $obj->received : -1; + + if ($this->errno !== 0 && $this->errstr !== null && $this->comment === null) + $this->comment = '*** ' . $this->errstr . ' Antispam service cleantalk.org ***'; + } + } + +} +?> \ No newline at end of file