From 2281f065fcaff10ca78ecee1c53ac41270758548 Mon Sep 17 00:00:00 2001
From: svfcode
Date: Tue, 13 May 2025 09:11:54 +0300
Subject: [PATCH 1/4] Upd. Code. Added suggestion module.
---
README.md | 113 ++---
cleantalk-antispam.php | 12 +-
examples/api_response_description.md | 8 +
lib/Cleantalk.php | 635 ------------------------
lib/CleantalkAPI.php | 299 ------------
lib/CleantalkAntispam.php | 691 +++++++++++++++++++++++++--
lib/CleantalkHelper.php | 415 ----------------
lib/CleantalkRequest.php | 179 -------
lib/CleantalkResponse.php | 209 --------
lib/CleantalkVerdict.php | 21 +
lib/HTTP/CleantalkResponse.php | 170 +++++++
lib/HTTP/Helper.php | 520 ++++++++++++++++++++
lib/HTTP/Request.php | 583 ++++++++++++++++++++++
lib/HTTP/Response.php | 79 +++
lib/TransportException.php | 18 -
lib/cleantalk-php-patch.php | 110 -----
lib/die_page.html | 54 ---
17 files changed, 2073 insertions(+), 2043 deletions(-)
create mode 100644 examples/api_response_description.md
delete mode 100644 lib/Cleantalk.php
delete mode 100644 lib/CleantalkAPI.php
delete mode 100644 lib/CleantalkHelper.php
delete mode 100644 lib/CleantalkRequest.php
delete mode 100644 lib/CleantalkResponse.php
create mode 100644 lib/CleantalkVerdict.php
create mode 100644 lib/HTTP/CleantalkResponse.php
create mode 100644 lib/HTTP/Helper.php
create mode 100644 lib/HTTP/Request.php
create mode 100644 lib/HTTP/Response.php
delete mode 100644 lib/TransportException.php
delete mode 100644 lib/cleantalk-php-patch.php
delete mode 100644 lib/die_page.html
diff --git a/README.md b/README.md
index 0e8010f..95d9659 100644
--- a/README.md
+++ b/README.md
@@ -4,91 +4,68 @@ php-antispam
[](https://packagist.org/packages/cleantalk/php-antispam)
-A PHP API for antispam service cleantalk.org. Invisible protection from spam, no captches, no puzzles, no animals and no math.
+## The Invisible protection from spam, no captches, no puzzles, no animals and no math.
+_API for antispam service cleantalk.org_
-## How API stops spam?
-API uses several simple tests to stop spammers.
- * Spam bots signatures.
- * Blacklists checks by Email, IP, web-sites domain names.
- * JavaScript availability.
- * Relevance test for the comment.
+#### Requirements
+* PHP 5.6 and above
+* CURL support
-## How API works?
-API sends a comment's text and several previous approved comments to the servers. Servers evaluates the relevance of the comment's text on the topic, tests on spam and finaly provides a solution - to publish or put on manual moderation of comments. If a comment is placed on manual moderation, the plugin adds to the text of a comment explaining the reason for the ban server publishing.
+### How we stop spam?
+Cleantalk catch your api request and provides analytical result to you.
-## Requirements
+You are free to do anything with spam, or just allow as to block spam (we will interrupt desirable request).
- * PHP 5.6 and above
- * CURL support
-You can unpack the archive with the plugin to the root of the site or install it using the composer
+## Interesting? Let's make some settings (it will take few minutes)
+
+### Step 1 - install our SDK (2 variants ability)
+
+Through composer install **OR** through download zip arhive and unzip it to root directory (with your index.php)
```php
composer require cleantalk/php-antispam
```
-
-### Sample SPAM test for text comment
-Notice: You can use the example PHP file from ./examples/form_with_handler
-
-```php
-handle();
+```
-//require_once "lib/cleantalk-php-patch.php"; -- PHP-FPM
+### Step 2.1 - add js lib to your html template
+```html
+
+```
+_Need for gathering frontend data._
-$cleantalk_antispam = new CleantalkAntispam($apikey, $email_field, $user_name_field, $message_field, $type_form);
-$api_result = $cleantalk_antispam->handle();
-if ($api_result) { // the check fired
- if ($api_result->account_status !== 1) {
- // something wrong with your key or license, to know why read $api_result->codes
- echo 'Allowed. Spam protection disabled.'; // or do nothing
- } else {
- if ($api_result->allow === 1) {
- echo 'Allowed. Spam protection OK.'; // or do nothing
- } else {
- die('Blocked. Spam protection OK. Reason: ' . $api_result->comment); // or make your own handler
- }
- }
+### Step 3 - do whatever you want with cloud result
+For example add die block for spam.
+```php
+if ($api_result && $api_result->allow === 0) {
+ die('Blocked. Spam protection OK. Reason: ' . $api_result->comment);
+ // or make your own actions/logs/messages ...
}
-// your further code flow here
-?>
-
-
-
-frontendScript(); ?>
```
-## API Response description
-API returns (`$api_result`) PHP object:
- * allow (0|1) - allow to publish or not, in other words spam or ham
- * comment (string) - server comment for requests.
- * id (string MD5 HEX hash) - unique request idenifier.
- * errno (int) - error number. errno == 0 if requests successfull.
- * errstr (string) - comment for error issue, errstr == null if requests successfull.
- * account_status - 0 account disabled, 1 account enabled, -1 unknown status.
+### Step 4 (not required) - we prepare for you special troubleshooting method
+To find possible problems, just add follow snippet after getVerdict method.
+```php
+// TROUBLESHOOTING: logging the suggestions
+error_log($cleantalk_antispam->whatsWrong(true));
+```
+In [example file](https://github.com/CleanTalk/php-antispam/blob/dev/examples/form_with_handler/form_with_handler.php) you can see context.
+
+### Step 5 (not required) - if you have any question, please, feel free to ask it in issue here or in our tiket system
+
+## Examples
+- [api response description](https://github.com/CleanTalk/php-antispam/tree/dev/examples/api_response_description.md)
+- [example with form handler](https://github.com/CleanTalk/php-antispam/blob/dev/examples/form_with_handler/form_with_handler.php)
+
## Don't want to deal with all this?
Universal solution for any CMS or custom website: https://github.com/CleanTalk/php-uni
diff --git a/cleantalk-antispam.php b/cleantalk-antispam.php
index b56e377..d6acb1c 100644
--- a/cleantalk-antispam.php
+++ b/cleantalk-antispam.php
@@ -1,8 +1,8 @@
filterRequest($request);
- $filtered_request = $this->createMsg('check_message', $request);
-
- $this->sender_ip = $filtered_request->sender_ip;
- $this->sender_email = $filtered_request->sender_email;
-
- return $this->httpRequest($filtered_request);
- }
-
- /**
- * Function checks whether it is possible to publish the message
- *
- * @param CleantalkRequest $request
- *
- * @return CleantalkResponse
- * @throws TransportException
- */
- public function isAllowUser(CleantalkRequest $request)
- {
- $request = $this->filterRequest($request);
- $filtered_request = $this->createMsg('check_newuser', $request);
-
- $this->sender_ip = $filtered_request->sender_ip;
- $this->sender_email = $filtered_request->sender_email;
-
- return $this->httpRequest($filtered_request);
- }
-
- /**
- * Function sends the results of manual moderation
- *
- * @param CleantalkRequest $request
- *
- * @return CleantalkResponse
- * @throws TransportException
- */
- public function sendFeedback(CleantalkRequest $request)
- {
- $request = $this->filterRequest($request);
- $filtered_request = $this->createMsg('send_feedback', $request);
-
- $this->sender_ip = $filtered_request->sender_ip;
- $this->sender_email = $filtered_request->sender_email;
-
- return $this->httpRequest($filtered_request);
- }
-
- /**
- * Function checks if visitor is bot or not based on the Bot-detector event token.
- *
- * @param CleantalkRequest $request
- *
- * @return CleantalkResponse
- * @throws TransportException
- */
- public function checkBot(CleantalkRequest $request)
- {
- $request = $this->filterRequest($request);
- $filtered_request = $this->createMsg('check_bot', $request);
-
- return $this->httpRequest($filtered_request);
- }
-
- /**
- * Filter request params
- *
- * @param CleantalkRequest $request
- *
- * @return CleantalkRequest
- */
- private function filterRequest(CleantalkRequest $request)
- {
- // general and optional
- foreach ( $request as $param => $value ) {
- if ( $param == 'js_on' ) {
- if ( ! is_int($value) ) {
- $request->$param = null;
- }
- }
- if ( $param == 'submit_time' ) {
- if ( ! is_int($value) ) {
- $request->$param = null;
- }
- }
- if ( $param == 'message' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- } // Should be array, but servers understand only JSON
- if ( $param == 'example' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- } // Should be array, but servers understand only JSON
- if ( $param == 'sender_info' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- } // Should be array, but servers understand only JSON
- if ( $param == 'post_info' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- } // Should be array, but servers understand only JSON
- if ( $param == 'agent' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- }
- if ( $param == 'sender_nickname' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- }
- if ( $param == 'phone' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- }
- if ( $param == 'sender_email' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- }
- if ( $param == 'sender_ip' ) {
- if ( ! is_string($value) ) {
- $request->$param = null;
- }
- }
- }
-
- return $request;
- }
-
- /**
- * Compress data and encode to base64
- *
- * @param string $data
- * @psalm-suppress UnusedMethod
- * @return string
- */
- private function compressData($data = '')
- {
- 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 string $method
- * @param CleantalkRequest $request
- *
- * @return CleantalkRequest
- */
- private function createMsg($method, CleantalkRequest $request)
- {
- switch ($method) {
- case 'check_message':
- // Convert strings to UTF8
- $request->message = CleantalkHelper::stringToUTF8($request->message, $this->data_codepage);
- $request->example = CleantalkHelper::stringToUTF8($request->example, $this->data_codepage);
- $request->sender_email = CleantalkHelper::stringToUTF8($request->sender_email, $this->data_codepage);
- $request->sender_nickname = CleantalkHelper::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 = CleantalkHelper::stringToUTF8($request->sender_email, $this->data_codepage);
- $request->sender_nickname = CleantalkHelper::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 ( is_array($request->$param) ) {
- $request->$param = CleantalkHelper::removeNonUTF8FromArray($value);
- }
- if ( is_string($request->$param) || is_int($request->$param) ) {
- $request->$param = CleantalkHelper::removeNonUTF8FromString($value);
- }
- }
-
- return $request;
- }
-
- /**
- * Send JSON request to servers
- *
- * @param $data
- * @param $url
- * @param int $server_timeout
- *
- * @return boolean|CleantalkResponse
- */
- private function sendRequest($data, $url, $server_timeout = 3)
- {
- // Convert to array
- $data = (array)json_decode(json_encode($data), true);
-
- //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);
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // receive server response ...
- curl_setopt($ch, CURLOPT_HTTPHEADER, array('Expect:')); // resolve 'Expect: 100-continue' issue
-
- 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($data, $url, $server_timeout);
- }
- }
-
- curl_close($ch);
- }
-
- if ( ! $result ) {
- $allow_url_fopen = ini_get('allow_url_fopen');
- if ( function_exists('file_get_contents') && !empty($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 ( !is_string($result) || ! CleantalkHelper::is_json($result) ) {
- $response = null;
- $response['errno'] = 1;
- if ( $curl_error ) {
- $response['errstr'] = sprintf("CURL error: '%s'", $curl_error);
- } else {
- $response['errstr'] = 'No CURL support compiled in';
- }
- $response['errstr'] .= ' or disabled allow_url_fopen in php.ini.';
- $response = json_decode(json_encode($response));
-
- return $response;
- }
-
- $errstr = null;
- $response = json_decode($result);
- if ( 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 CleantalkResponse
- * @throws TransportException
- */
- private function httpRequest($msg)
- {
- // 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) : '';
-
- // Using current server without changing it
- if ( ! empty($this->work_url) && ($this->server_changed + $this->server_ttl > time()) ) {
- $url = ! empty($this->work_url) ? $this->work_url : $this->server_url;
- $result = $this->sendRequest($msg, $url, $this->server_timeout);
- } else {
- $result = false;
- }
-
- // Changing server
- if ($result === false || $result->errno != 0) {
- // Split server url to parts
- preg_match("@^(https?://)([^/:]+)(.*)@i", $this->server_url, $matches);
-
- $url_prefix = isset($matches[1]) ? $matches[1] : '';
- $url_host = isset($matches[2]) ? $matches[2] : '';
- $url_suffix = isset($matches[3]) ? $matches[3] : '';
-
- if ( empty($url_host) ) {
- throw TransportException::fromUrlHostError($url_host);
- } elseif ( null !== $servers = $this->get_servers_ip($url_host) ) {
- // Loop until find work server
- foreach ( $servers as $server ) {
- $this->work_url = $url_prefix . $server['ip'] . $url_suffix;
- $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;
- }
- }
- } else {
- throw TransportException::fromUrlHostError($url_host);
- }
- }
-
- $response = new CleantalkResponse(null, $result);
-
- $response->sender_ip = $this->sender_ip;
- $response->sender_email = $this->sender_email;
-
- if ( ! empty($this->data_codepage) && $this->data_codepage !== 'UTF-8' ) {
- if ( ! empty($response->comment) ) {
- $response->comment = CleantalkHelper::stringFromUTF8($response->comment, $this->data_codepage);
- }
- if ( ! empty($response->errstr) ) {
- $response->errstr = CleantalkHelper::stringFromUTF8($response->errstr, $this->data_codepage);
- }
- if ( ! empty($response->sms_error_text) ) {
- $response->sms_error_text = CleantalkHelper::stringFromUTF8($response->sms_error_text, $this->data_codepage);
- }
- }
-
- return $response;
- }
-
- /**
- * Function DNS request
- *
- * @param $host
- *
- * @return array|null
- */
- private function get_servers_ip($host) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- 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 recieved
- } 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 = $ping * 1000;
- }
-
- $tmp[(int)$ping] = $server;
-
- $fast_server_found = $ping < $this->min_server_timeout ? true : false;
- }
-
- ksort($tmp);
- $response = $tmp;
- }
-
- return empty($response) ? null : $response;
- }
-
- /**
- * Function to check response time
- * param string
- * @return float
- */
- private 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, 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;
- }
-}
diff --git a/lib/CleantalkAPI.php b/lib/CleantalkAPI.php
deleted file mode 100644
index 70291da..0000000
--- a/lib/CleantalkAPI.php
+++ /dev/null
@@ -1,299 +0,0 @@
- 'get_api_key',
- 'product_name' => 'antispam',
- 'email' => $email,
- 'website' => $host,
- 'platform' => $platform,
- 'timezone' => $timezone,
- 'http_accept_language' => $language,
- 'user_ip' => $ip,
- 'hoster_whitelabel' => $white_label,
- 'hoster_api_key' => $hoster_api_key,
- );
-
- $result = self::send_request($request);
- $result = $do_check ? self::check_response($result, 'get_api_key') : $result;
-
- return $result;
- }
-
- /**
- * Function gets spam report
- *
- * @param string $host website host
- * @param integer $period report days
- * @param bool $do_check do_check
- *
- * @return string
- */
- public static function method__get_antispam_report($host, $period = 1, $do_check = true) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- $request = array(
- 'method_name' => 'get_antispam_report',
- 'hostname' => $host,
- 'period' => $period
- );
-
- $result = self::send_request($request);
- $result = $do_check ? self::check_response($result, 'get_antispam_report') : $result;
-
- return $result;
- }
-
- /**
- * Function gets spam statistics
- *
- * @param string $api_key
- * @param bool $do_check
- *
- * @return string
- */
- public static function method__get_antispam_report_breif($api_key, $do_check = true) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- $request = array(
- 'method_name' => 'get_antispam_report_breif',
- 'auth_key' => $api_key,
- );
-
- $result = self::send_request($request);
- $result = $do_check ? self::check_response($result, 'get_antispam_report_breif') : $result;
-
- return $result;
- }
-
- /**
- * Function gets information about renew notice
- *
- * @param string $api_key
- * @param string $path_to_cms
- * @param bool $do_check
- *
- * @return string
- */
- public static function method__notice_validate_key($api_key = '', $path_to_cms = '', $do_check = true) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- $request = array(
- 'method_name' => 'notice_validate_key',
- 'auth_key' => $api_key,
- 'path_to_cms' => $path_to_cms
- );
-
- $result = self::send_request($request);
- $result = $do_check ? self::check_response($result, 'notice_validate_key') : $result;
-
- return $result;
- }
-
- /**
- * Function gets information about renew notice
- *
- * @param string api_key
- *
- * @return string
- */
- public static function method__notice_paid_till($api_key, $do_check = true) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- $request = array(
- 'method_name' => 'notice_paid_till',
- 'auth_key' => $api_key
- );
-
- $result = self::send_request($request);
- $result = $do_check ? self::check_response($result, 'notice_paid_till') : $result;
-
- return $result;
- }
-
- /**
- * Function sends raw request to API server
- *
- * @param array $data to send
- * @param string $url of API server
- * @param int $timeout is data have to be JSON encoded or not
- * @param bool $ssl should use ssl
- * @psalm-suppress PossiblyUnusedParam
- * @return string JSON encoded string
- */
- public static function send_request($data, $url = self::URL, $timeout = 5, $ssl = false) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- // Possibility to switch API url
- $url = defined('CLEANTALK_API_URL') ? CLEANTALK_API_URL : $url;
-
- // Adding agent version to data
- if ( defined('CLEANTALK_AGENT') ) {
- $data['agent'] = CLEANTALK_AGENT;
- }
-
- // Make URL string
- $data_string = http_build_query($data);
- $data_string = str_replace("&", "&", $data_string);
-
- 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:'));
-
- // Switch on/off SSL
- if ( $ssl === true ) {
- curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
- curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
- } 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);
-
- // Get cURL error if result failed
- if ( $result === false ) {
- // And retry with SSL enabled
- if ( $ssl === false ) {
- return self::send_request($data, $url, $timeout, true);
- }
- }
- } else {
- $errors = 'CURL_NOT_INSTALLED';
- }
-
- // Trying to use file_get_contents() to make a API call
- if ( ! empty($errors) && 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, false, $context);
- } else {
- $errors .= '_AND_ALLOW_URL_FOPEN_IS_DISABLED';
- }
-
- if ( empty($result) && ! empty($errors) ) {
- $json_error = json_encode(array('error' => true, 'error_string' => $errors));
- return false !== $json_error ? $json_error : 'CURL_ERROR';
- }
-
- return $result;
- }
-
- /**
- * Function checks server response
- *
- * @param string $result
- * @param string|null $method_name
- *
- * @return string JSON encoded string
- */
- public static function check_response($result, $method_name = null) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- $out = array('error' => false);
-
- // Errors handling
-
- // Bad connection
- if ( empty($result) ) {
- $out = array(
- 'error' => true,
- 'error_string' => 'CONNECTION_ERROR'
- );
- }
-
- // JSON decode errors
- $result = json_decode($result, true);
- if ( empty($result) ) {
- $out = array(
- 'error' => true,
- 'error_string' => 'JSON_DECODE_ERROR'
- );
- }
-
- // cURL error
- if ( ! empty($result['error']) ) {
- $out = array(
- 'error' => true,
- 'error_string' => 'CONNECTION_ERROR: ' . $result['error_string'],
- );
- }
-
- // Server errors
- if ( $result && (isset($result['error_no']) || isset($result['error_message'])) ) {
- $out = array(
- 'error' => true,
- 'error_string' => "SERVER_ERROR NO: {$result['error_no']} MSG: {$result['error_message']}",
- 'error_no' => $result['error_no'],
- 'error_message' => $result['error_message']
- );
- }
-
- // Patches for different methods
- if ( !$out['error'] ) {
- // mehod_name = notice_validate_key
- if ( $method_name == 'notice_validate_key' && isset($result['valid']) ) {
- $out = $result;
- }
-
- // Other methods
- if ( isset($result['data']) && is_array($result['data']) ) {
- $out = $result['data'];
- }
- }
-
- // method_name = get_antispam_report_breif
- if ( $method_name == 'get_antispam_report_breif' ) {
- if ( !$out['error'] ) {
- $result = $result['data'];
- }
-
- for ( $tmp = array(), $i = 0; $i < 7; $i++ ) {
- $tmp[date('Y-m-d', time() - 86400 * 7 + 86400 * $i)] = 0;
- }
-
- $result['spam_stat'] = array_merge($tmp, isset($result['spam_stat']) ? $result['spam_stat'] : array());
- $result['top5_spam_ip'] = isset($result['top5_spam_ip']) ? $result['top5_spam_ip'] : array();
- $out = array_merge($result, $out);
- }
-
- $out = json_encode($out);
-
- return false !== $out ? $out : 'JSON_ENCODE_ERROR';
- }
-}
diff --git a/lib/CleantalkAntispam.php b/lib/CleantalkAntispam.php
index 5226ad2..88d94e2 100644
--- a/lib/CleantalkAntispam.php
+++ b/lib/CleantalkAntispam.php
@@ -1,73 +1,664 @@
apikey = $apikey;
- $this->email_field = $email_field;
- $this->user_name_field = $user_name_field;
- $this->message_field = $message_field;
- $this->type_form = $type_form;
- }
-
- /**
- * @return CleantalkResponse|null
- * @throws TransportException
+ const MODERATE_URL = 'https://moderate.cleantalk.org/api2.0';
+ const BOT_DETECTOR_LIBRARY_URL = 'https://moderate.cleantalk.org/ct-bot-detector-wrapper.js';
+ const EVENT_TOKEN_FIELD_NAME = 'ct_bot_detector_event_token';
+ const FORM_START_TIME_FIELD_NAME = 'ct_form_start_time';
+ const EMAIL_ADDRESS_REGEXP = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
+
+ /**
+ * @var string Access key for CleanTalk API
+ */
+ private $access_key;
+ /**
+ * @var string Event token for CleanTalk API
+ */
+ private $event_token;
+ /**
+ * @var bool
+ */
+ private $block_no_js_visitor = false;
+ /**
+ * @var string Nickname of the sender
+ */
+ private $nickname;
+ /**
+ * @var string Email of the sender
+ */
+ private $email;
+
+ /**
+ * @var string Message content
+ */
+ private $message;
+
+ /**
+ * @var string IP address of the sender
+ */
+ private $ip;
+
+ /**
+ * @var string Method name for the CleanTalk API request
+ */
+ private $method_name;
+
+ /**
+ * @var CleantalkVerdict Verdict object to store the result of the CleanTalk check
+ */
+ private $verdict;
+
+ /**
+ * @var array Suggestions for improving the data quality
+ */
+ private $improvement_suggestions = array();
+
+ /**
+ * @var array Stack of method calls for fluid interface
+ */
+ private $fluid_call_stack = array();
+
+ /**
+ * @var CleantalkResponse Response object from the CleanTalk API
+ */
+ private $cleantalk_response;
+
+ /**
+ * @var int Form start time
+ */
+ private $form_start_time;
+
+ /**
+ * @var false|string CleanTalk request data in JSON format
+ */
+ private $cleantalk_request_data;
+
+ /**
+ * @var array Data container to get the data from the form, usually is $_POST
+ */
+ private $data_container;
+
+ /**
+ * ------------ Construction kit methods ------------
+ */
+
+ /**
+ * Constructor for the CleanTalkCheck class.
+ *
+ * @param string $accessKey Access key for CleanTalk API
+ */
+ public function __construct($accessKey, $email = '', $user_name_field = '', $message_field = '', $type_form = '')
+ {
+ $this->access_key = $accessKey;
+ $this->verdict = new CleantalkVerdict();
+ //default data preparing
+ $this->data_container = $_POST;
+ if (isset($this->data_container[static::EVENT_TOKEN_FIELD_NAME])) {
+ $this->event_token = $this->data_container[static::EVENT_TOKEN_FIELD_NAME];
+ }
+ if (isset($this->data_container[static::FORM_START_TIME_FIELD_NAME])) {
+ $this->form_start_time = $this->data_container[static::FORM_START_TIME_FIELD_NAME];
+ }
+ $this->ip = Helper::ipGet();
+ if ($email) {
+ $this->email = $email;
+ } else {
+ $this->setEmailAutomatically();
+ }
+ }
+
+ private function setEmailAutomatically()
+ {
+ foreach ($this->data_container as $_key => $value) {
+ if (preg_match(static::EMAIL_ADDRESS_REGEXP, $value)) {
+ $this->email = $value;
+ break;
+ }
+ }
+ }
+
+ /**
+ * ------------ Frontend methods ------------
+ */
+
+ /**
+ * Get the frontend HTML code for the CleanTalk bot detector.
+ *
+ * @param bool $warn_if_js_disabled Flag to include a warning if JavaScript is disabled
+ * @return string HTML code
+ */
+ public static function getFrontendHTMLCode($warn_if_js_disabled = false)
+ {
+ $warn = $warn_if_js_disabled ? '' : '';
+ $submittime_script = '
+
+
+ ';
+ $html = '%s%s';
+ return sprintf($html, static::BOT_DETECTOR_LIBRARY_URL, $warn, $submittime_script);
+ }
+
+ /**
+ * ------------ Internal processing methods ------------
+ */
+
+ /**
+ * Get the response from the CleanTalk API.
+ *
+ * @return CleantalkResponse Response object from the CleanTalk API
+ */
+ private function getCleanTalkResponse()
+ {
+ $http = new Request();
+ $this->cleantalk_request_data = $this->prepareCleanTalkRequestData();
+ $response_raw = $http->setUrl(static::MODERATE_URL)
+ ->setData($this->cleantalk_request_data)
+ ->request();
+
+ return new CleantalkResponse(@json_decode($response_raw), null);
+ }
+
+ /**
+ * Prepare the request data for the CleanTalk API.
+ *
+ * @return string JSON encoded request data
+ */
+ private function prepareCleanTalkRequestData()
+ {
+ $data = array(
+ 'method_name' => $this->method_name,
+ 'auth_key' => $this->access_key,
+ 'message' => $this->message,
+ 'sender_nickname' => $this->nickname,
+ 'sender_email' => $this->email,
+ 'sender_ip' => $this->ip,
+ 'js_on' => !empty($this->event_token) ? 1 : 0,
+ 'submit_time' => !empty($this->form_start_time) ? time() - (int)$this->form_start_time : null,
+ 'event_token' => $this->event_token,
+ 'agent' => 'php-cleantalk-check',
+ 'sender_info' => @json_encode(
+ array(
+ 'REFFERRER' => !empty($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : '',
+ )
+ ),
+ );
+ return @json_encode($data);
+ }
+
+ /**
+ * Get the CleanTalk request data.
+ *
+ * @return string JSON encoded request data
+ */
+ public function getCleanTalkRequestData()
+ {
+ if (empty($this->cleantalk_request_data)) {
+ return '';
+ }
+ $data = @json_decode($this->cleantalk_request_data);
+ return $data ?: '';
+ }
+
+ /**
+ * Verify the data before sending the request to CleanTalk API.
+ */
+ private function verifyData()
+ {
+ try {
+ $this->checkAccessKey();
+ } catch (\Exception $e) {
+ $this->verdict->error = $e->getMessage();
+ $this->verdict->allow = true;
+ }
+
+ try {
+ $this->checkEventToken();
+ } catch (\Exception $e) {
+ if ($this->block_no_js_visitor) {
+ $this->verdict->error = $e->getMessage();
+ $this->verdict->allow = false;
+ $this->verdict->comment = 'Please, enable JavaScript to process the form.';
+ } else {
+ $this->verdict->allow = true;
+ }
+ }
+ }
+
+ /**
+ * Check if the access key is valid.
+ *
+ * @throws \Exception If the access key is invalid
+ */
+ private function checkAccessKey()
+ {
+ if (empty($this->access_key)) {
+ throw new \Exception('Access key is empty');
+ }
+
+ if (!is_string($this->access_key)) {
+ throw new \Exception('Access key is not a string');
+ }
+ }
+
+ /**
+ * Check if the event token is valid.
+ *
+ * @throws \Exception If the event token is invalid
+ */
+ private function checkEventToken()
+ {
+ if (empty($this->event_token)) {
+ throw new \Exception('Event token is empty');
+ }
+
+ if (!is_string($this->event_token)) {
+ throw new \Exception('Event token is not a string');
+ }
+
+ if (strlen($this->event_token) !== 64) {
+ throw new \Exception('Event token is not valid');
+ }
+ }
+
+ /**
+ * Perform actions before returning the verdict.
+ *
+ * @return CleantalkVerdict Verdict object with the result of the CleanTalk check
+ */
+ private function beforeReturnVerdict()
+ {
+ $this->setImprovementSuggestions();
+ return $this->verdict;
+ }
+
+ /**
+ * ------------ Fluid public calls ------------
+ */
+
+ /**
+ * Set the email of the sender.
+ *
+ * @param string $email Email of the sender
+ * @return $this
+ */
+ public function setEmail($email)
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->email = is_string($email) ? $email : null;
+ return $this;
+ }
+
+ /**
+ * Set the nickname of the sender.
+ *
+ * @param string $nickname Nickname of the sender
+ * @return $this
+ */
+ public function setNickName($nickname)
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->nickname = is_string($nickname) ? $nickname : null;
+ return $this;
+ }
+
+ /**
+ * Set the message content.
+ *
+ * @param string $message Message content
+ * @return $this
+ */
+ public function setMessage($message)
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->message = is_string($message) ? $message : null;
+ return $this;
+ }
+
+ /**
+ * Set the IP address of the sender.
+ *
+ * @param string|null $ip IP address of the sender
+ * @return $this
+ */
+ public function setIP($ip = null)
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ if (!Helper::ipValidate($ip)) {
+ $this->setImprovementSuggestion('critical', 'IP address is not valid, the value has been set form the request', 'setIP()');
+ } else {
+ $this->ip = $ip;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the form start time.
+ *
+ * @param int|null $form_start_time Form start time
+ * @return $this
+ */
+ public function setFormStartTime($form_start_time = null)
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->form_start_time = (int)$form_start_time;
+ return $this;
+ }
+
+ /**
+ * Enable blocking of visitors without JavaScript.
+ *
+ * @return $this
+ */
+ public function setDoBlockNoJSVisitor()
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->block_no_js_visitor = true;
+ return $this;
+ }
+
+ /**
+ * Set the event token.
+ *
+ * @param string|null $event_token Event token
+ * @return $this
+ */
+ public function setEventToken($event_token = null)
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->event_token = $event_token;
+ return $this;
+ }
+
+ /**
+ * Use the registration check method.
+ *
+ * @return $this
+ */
+ public function useRegistrationCheck()
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->method_name = 'check_newuser';
+ return $this;
+ }
+
+ /**
+ * Use the contact form check method.
+ *
+ * @return $this
+ */
+ public function useContactFormCheck()
+ {
+ $this->fluidCallStack(__FUNCTION__);
+ $this->method_name = 'check_message';
+ return $this;
+ }
+
+ /**
+ * Set own data container.
+ * @param $data_container
+ *
+ * @return $this
+ */
+ public function setCustomFormDataContainer($data_container)
+ {
+ $this->data_container = $data_container;
+ return $this;
+ }
+
+ /**
+ * Get the verdict from the CleanTalk API.
+ *
+ * @return CleantalkVerdict Verdict object with the result of the CleanTalk check
*/
public function handle()
{
- if ( count($_POST) === 0 ) {
- $_SESSION['ct_submit_time'] = time();
+ $this->verifyData();
+
+ if ($this->verdict->error) {
+ return $this->beforeReturnVerdict();
+ }
+
+ if (empty($this->method_name)) {
+ $this->method_name = empty($this->message) ? 'check_newuser' : 'check_message';
+ }
+
+ $this->cleantalk_response = $this->getCleanTalkResponse();
+
+ if ($this->cleantalk_response->error) {
+ $this->verdict->error = 'CleanTalk moderate server error: ' . $this->cleantalk_response->error;
+ return $this->beforeReturnVerdict();
+ }
+
+ $this->verdict->allow = $this->cleantalk_response->allow;
+ $this->verdict->comment = $this->cleantalk_response->comment;
+ $this->verdict->request_link = !empty($this->cleantalk_response->id)
+ ? 'https://cleantalk.org/my/show_requests?request_id=' . $this->cleantalk_response->id
+ : null
+ ;
+
+ return $this->beforeReturnVerdict();
+ }
+
+ /**
+ * ------------ Improvement suggestions feature ------------
+ */
+
+ /**
+ * Set suggestions for improving the data quality.
+ */
+ private function setImprovementSuggestions()
+ {
+ if ( $this->cleantalk_response instanceof CleantalkResponse ) {
+ if (empty($this->cleantalk_response->comment)) {
+ $this->setImprovementSuggestion(
+ 'critical',
+ 'Something is wrong with connection to the CleanTalk server ' . static::MODERATE_URL,
+ 'getCleanTalkResponse()'
+ );
+ return;
+ }
+ if ($this->cleantalk_response->error) {
+ $this->setImprovementSuggestion(
+ 'critical',
+ 'Please, check the error message from the CleanTalk server',
+ 'getCleanTalkResponse()'
+ );
+ return;
+ }
+ if ($this->cleantalk_response->account_status !== 1) {
+ $this->setImprovementSuggestion(
+ 'critical',
+ 'Something wrong with your CleanTalk license, visit your CleanTalk dashboard to check the license status',
+ 'getCleanTalkResponse()'
+ );
+ return;
+ }
+ }
+
+ if (!empty($this->verdict->error)) {
+ $this->setImprovementSuggestion('critical', 'Error occurred due the CleanTalk check: ' . $this->verdict->error);
+ }
+
+ if (empty($this->ip)) {
+ $fluid_method = 'setIP';
+ $stack = !$this->fluidCallExist($fluid_method)
+ ? "interface method ->$fluid_method() has not been called"
+ : "interface method ->$fluid_method() has been called, but provided var is invalid or automatic IP get has failed";
+ $this->setImprovementSuggestion(
+ 'average',
+ 'Please, provide the visitor IP address to improve check quality.',
+ $stack
+ );
+ }
- return null;
+ if (empty($this->form_start_time)) {
+ $fluid_method = 'setFormStartTime';
+ $stack = !$this->fluidCallExist($fluid_method)
+ ? "interface method ->$fluid_method() has not been called, make sure the form data contains the field " . static::FORM_START_TIME_FIELD_NAME
+ : "interface method ->$fluid_method() has been called, but provided var is invalid";
+ $this->setImprovementSuggestion(
+ 'critical',
+ 'Please, provide the form start time to improve check quality.',
+ $stack
+ );
}
- $sender_email = isset($_POST[$this->email_field]) ? $_POST[$this->email_field] : '';
- $sender_nickname = isset($_POST[$this->user_name_field]) ? $_POST[$this->user_name_field] : '';
- $message = isset($_POST[$this->message_field]) ? $_POST[$this->message_field] : '';
- $sender_ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
+ if (empty($this->event_token)) {
+ $fluid_method = 'setEventToken';
+ $stack = !$this->fluidCallExist($fluid_method)
+ ? "interface method ->$fluid_method() has not been called, make sure the form data contains the field " . static::EVENT_TOKEN_FIELD_NAME
+ : "interface method ->$fluid_method() has been called, but provided var is invalid";
+ $common_token_message = 'Event token is not provided. Most likely the visitor has JavaScript disabled.';
+ $this->setImprovementSuggestion('critical', $common_token_message, $stack);
- $ct_request = new CleantalkRequest();
+ $fluid_method = 'setDoBlockNoJSVisitor';
+ if ($this->fluidCallExist($fluid_method)) {
+ $stack = "seen the call ->$fluid_method()";
+ $common_token_message .= ' All the visitors without token are BLOCKED due the current setting to block users without JS.';
+ } else {
+ $stack = "interface method ->$fluid_method() has not been called";
+ $common_token_message .= ' All the visitors without token are PASSED due the current setting to pass users without JS.';
+ }
+ $this->setImprovementSuggestion('critical', $common_token_message, $stack);
+ }
- $ct_request->auth_key = $this->apikey;
- $ct_request->agent = 'php-api';
- $ct_request->sender_email = $sender_email;
- $ct_request->sender_ip = $sender_ip;
- $ct_request->sender_nickname = $sender_nickname;
- $ct_request->message = $message;
- $ct_request->submit_time = isset($_SESSION['ct_submit_time']) ? $_SESSION['ct_submit_time'] : null;
- $ct_request->event_token = isset($_POST['ct_bot_detector_event_token']) ? $_POST['ct_bot_detector_event_token'] : null;
+ if (empty($this->access_key)) {
+ $this->setImprovementSuggestion(
+ 'critical',
+ 'Please, provide the access key via constructor call ' . __CLASS__ . '()',
+ 'construct()'
+ );
+ }
+
+ if (empty($this->email)) {
+ $fluid_method = 'setEmail';
+ $stack = !$this->fluidCallExist($fluid_method)
+ ? "interface method ->$fluid_method() has not been called"
+ : "interface method ->$fluid_method() has been called, but provided var is invalid";
+ $this->setImprovementSuggestion(
+ 'average',
+ 'Please, provide the email field content to improve check quality.',
+ $stack
+ );
+ }
- $ct = new Cleantalk();
- $ct->server_url = $ct_request::CLEANTALK_API_URL;
+ if (empty($this->nickname)) {
+ $fluid_method = 'setNickName';
+ $stack = !$this->fluidCallExist($fluid_method)
+ ? "interface method ->$fluid_method() has not been called"
+ : "interface method ->$fluid_method() has been called, but provided var is invalid";
+ $this->setImprovementSuggestion(
+ 'average',
+ 'Please, provide the nickname field content to improve check quality.',
+ $stack
+ );
+ }
- // Check
- if ( $this->type_form === 'signup' ) {
- $ct_result = $ct->isAllowUser($ct_request);
+ if (empty($this->message)) {
+ if ($this->fluidCallExist('useContactFormCheck')) {
+ if ($this->fluidCallExist('setMessage')) {
+ $stack = 'seen the call ->setMessage(), but provided value is invalid';
+ } else {
+ $stack = 'seen the call ->useContactFormCheck(), but interface method ->setMessage() has not been called';
+ }
+ $this->setImprovementSuggestion(
+ 'average',
+ 'Please, provide the message field to improve check quality.',
+ $stack
+ );
+ }
}
- if ( $this->type_form === 'contact' ) {
- $ct_result = $ct->isAllowMessage($ct_request);
+ }
+
+ /**
+ * Set an improvement suggestion.
+ *
+ * @param string $level Severity level of the suggestion
+ * @param string $message Suggestion message
+ * @param string|null $stack Call stack information
+ */
+ private function setImprovementSuggestion($level, $message, $stack = null)
+ {
+ $this->improvement_suggestions[$level][] = array('stack' => $stack, 'message' => $message);
+ }
+
+ /**
+ * Get the improvement suggestions.
+ *
+ * @return array Improvement suggestions
+ */
+ private function getImprovementSuggestions()
+ {
+ if (empty($this->improvement_suggestions)) {
+ return array('Everything looks well!');
}
+ ksort($this->improvement_suggestions, SORT_STRING);
+ return $this->improvement_suggestions;
+ }
+
+ /**
+ * Add a method to the fluid call stack.
+ *
+ * @param string $method Method name
+ */
+ private function fluidCallStack($method)
+ {
+ $this->fluid_call_stack[] = $method;
+ }
- return $ct_result;
+ /**
+ * Check if a method exists in the fluid call stack.
+ *
+ * @param string $method Method name
+ * @return bool True if the method exists in the stack, false otherwise
+ */
+ private function fluidCallExist($method)
+ {
+ return in_array($method, $this->fluid_call_stack);
}
- public function frontendScript()
+ /**
+ * Get the suggestions for improving the data quality.
+ * @param $return_as_json
+ *
+ * @return false|string
+ */
+ public function whatsWrong($return_as_json = false)
{
- echo '';
+ $array = array(
+ 'suggestions' => $this->getImprovementSuggestions(),
+ 'request_data' => $this->getCleanTalkRequestData(),
+ 'verdict' => $this->verdict instanceof CleantalkVerdict ? $this->verdict->getArray() : null,
+ );
+
+ if (empty($array['verdict'])) {
+ $array['verdict'] = 'Verdict is not processed. Maybe you forgot to call ->getVerdict() method before';
+ }
+
+ if ($return_as_json) {
+ return @json_encode($array);
+ }
+
+ $suggestions = var_export($array['suggestions'], 1);
+ $data = var_export($array['request_data'], 1);
+ $verdict = var_export($array['verdict'], 1);
+ echo "";
+ echo "";
+ echo "";
+ return '';
}
+
}
diff --git a/lib/CleantalkHelper.php b/lib/CleantalkHelper.php
deleted file mode 100644
index cea3dfb..0000000
--- a/lib/CleantalkHelper.php
+++ /dev/null
@@ -1,415 +0,0 @@
- array(
- 'ipv4' => array(
- '103.21.244.0/22',
- '103.22.200.0/22',
- '103.31.4.0/22',
- '104.16.0.0/12',
- '108.162.192.0/18',
- '131.0.72.0/22',
- '141.101.64.0/18',
- '162.158.0.0/15',
- '172.64.0.0/13',
- '173.245.48.0/20',
- '185.93.231.18/20', // User fix
- '185.220.101.46/20', // User fix
- '188.114.96.0/20',
- '190.93.240.0/20',
- '197.234.240.0/22',
- '198.41.128.0/17',
- ),
- 'ipv6' => array(
- '2400:cb00::/32',
- '2405:8100::/32',
- '2405:b500::/32',
- '2606:4700::/32',
- '2803:f800::/32',
- '2c0f:f248::/32',
- '2a06:98c0::/29',
- ),
- ),
- );
-
- private static $private_networks = 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',
- );
-
- /**
- * Getting arrays of IP (REMOTE_ADDR, X-Forwarded-For, X-Real-Ip, Cf_Connecting_Ip)
- * @returns array ('remote_addr' => 'val', ['x_forwarded_for' => 'val', ['x_real_ip' => 'val', ['cloud_flare' => 'val']]])
- * @phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- */
- public static function ip_get($ips_input = array('real', 'remote_addr', 'x_forwarded_for', 'x_real_ip', 'cloud_flare'), $v4_only = true) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- $ips = array();
- foreach ( $ips_input as $ip_type ) {
- $ips[$ip_type] = '';
- }
- unset($ip_type);
-
- $headers = apache_request_headers();
-
- // REMOTE_ADDR
- if ( isset($ips['remote_addr']) ) {
- $ips['remote_addr'] = $_SERVER['REMOTE_ADDR'];
- }
-
- // X-Forwarded-For
- if ( isset($ips['x_forwarded_for']) ) {
- if ( isset($headers['X-Forwarded-For']) ) {
- $tmp = explode(",", trim($headers['X-Forwarded-For']));
- $ips['x_forwarded_for'] = trim($tmp[0]);
- }
- }
-
- // X-Real-Ip
- if ( isset($ips['x_real_ip']) ) {
- if ( isset($headers['X-Real-Ip']) ) {
- $tmp = explode(",", trim($headers['X-Real-Ip']));
- $ips['x_real_ip'] = trim($tmp[0]);
- }
- }
-
- // Cloud Flare
- if ( isset($ips['cloud_flare']) ) {
- if ( isset($headers['Cf-Connecting-Ip']) ) {
- if ( self::ip_mask_match($ips['remote_addr'], self::$cdn_pool['cloud_flare']['ipv4']) ) {
- $ips['cloud_flare'] = $headers['Cf-Connecting-Ip'];
- }
- }
- }
-
- // 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.
- if ( isset($ips['real']) ) {
- $ips['real'] = $_SERVER['REMOTE_ADDR'];
-
- // Cloud Flare
- if ( isset($headers['Cf-Connecting-Ip']) ) {
- if ( self::ip_mask_match($ips['real'], self::$cdn_pool['cloud_flare']['ipv4']) ) {
- $ips['real'] = $headers['Cf-Connecting-Ip'];
- }
- // Incapsula proxy
- } elseif ( isset($headers['Incap-Client-Ip']) ) {
- $ips['real'] = $headers['Incap-Client-Ip'];
- // Private networks. Looking for X-Forwarded-For and X-Real-Ip
- } elseif ( self::ip_mask_match($ips['real'], self::$private_networks) ) {
- if ( isset($headers['X-Forwarded-For']) ) {
- $tmp = explode(",", trim($headers['X-Forwarded-For']));
- $ips['real'] = trim($tmp[0]);
- } elseif ( isset($headers['X-Real-Ip']) ) {
- $tmp = explode(",", trim($headers['X-Real-Ip']));
- $ips['real'] = trim($tmp[0]);
- }
- }
- }
-
- // Validating IPs
- $result = array();
- foreach ( $ips as $key => $ip ) {
- if ( $v4_only ) {
- if ( self::ip_validate($ip) == 'v4' ) {
- $result[$key] = $ip;
- }
- } else {
- if ( self::ip_validate($ip) ) {
- $result[$key] = $ip;
- }
- }
- }
-
- $result = array_unique($result);
-
- return count($ips_input) > 1
- ? $result
- : (reset($result) !== false
- ? reset($result)
- : null);
- }
-
- /**
- * Check if the IP belong to mask. Recursively if array given
- * @param $ip string
- * @param $cidr mixed (string|array of strings)
- */
- public static function ip_mask_match($ip, $cidr) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- if ( is_array($cidr) ) {
- foreach ( $cidr as $curr_mask ) {
- if ( self::ip_mask_match($ip, $curr_mask) ) {
- return true;
- }
- }
- unset($curr_mask);
-
- return false;
- }
- $exploded = explode('/', $cidr);
- $net = $exploded[0];
- $mask = 4294967295 << (32 - (int)$exploded[1]);
-
- return (ip2long($ip) & $mask) == (ip2long($net) & $mask);
- }
-
- /**
- * Validating IPv4, IPv6
- * @param $ip string
- * @returns string 'v4' || (string) 'v6' || (bool) false
- */
- public static function ip_validate($ip) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- if ( ! $ip ) {
- return false;
- } // NULL || FALSE || '' || so on...
- if ( filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ) {
- return 'v4';
- } // IPv4
- if ( filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ) {
- return 'v6';
- } // IPv6
-
- return false; // Unknown
- }
-
- /**
- * Function sends raw http request
- *
- * May use 4 presets(combining possible):
- * get_code - getting only HTTP response code
- * dont_wait_for_answer - async requests
- * get - GET-request
- * ssl - use SSL
- *
- * @param $url string
- * @param $data array
- * @param $presets string|array
- * @param $opts array
- *
- * @return mixed (array || array('error' => true))
- */
- public static function http__request($url, $data = array(), $presets = null, $opts = array()) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- $out = array();
- if ( function_exists('curl_init') ) {
- $ch = curl_init();
-
- // Obligatory options
- $opts = array(
- CURLOPT_URL => $url,
- CURLOPT_RETURNTRANSFER => 1,
- CURLOPT_CONNECTTIMEOUT_MS => 3000,
- CURLOPT_FORBID_REUSE => true,
- CURLOPT_USERAGENT => 'Cleantalk Antispam ' . defined('CLEANTALK_AGENT')
- ? CLEANTALK_AGENT
- : 'UNKNOWN_AGENT',
- CURLOPT_POST => true,
- CURLOPT_POSTFIELDS => str_replace("&", "&", http_build_query($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
- );
-
- // Use presets
- $presets = is_array($presets) ? $presets : array($presets);
- foreach ( $presets as $preset ) {
- switch ($preset) {
- // 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 'dont_wait_for_answer':
- $opts[CURLOPT_CONNECTTIMEOUT_MS] = 1000;
- $opts[CURLOPT_TIMEOUT_MS] = 500;
- break;
-
- case 'get':
- $opts[CURLOPT_URL] .= '?' . str_replace("&", "&", http_build_query($data));
- $opts[CURLOPT_POST] = false;
- $opts[CURLOPT_POSTFIELDS] = null;
- break;
-
- case 'ssl':
- $opts[CURLOPT_SSL_VERIFYPEER] = true;
- $opts[CURLOPT_SSL_VERIFYHOST] = 2;
- break;
-
- default:
- break;
- }
- }
- unset($preset);
-
- curl_setopt_array($ch, $opts);
- $result = @curl_exec($ch);
-
- if ( in_array('dont_wait_for_answer', $presets) ) {
- return true;
- }
-
- if ( is_string($result) ) {
- $result = explode(PHP_EOL, $result);
- if ( in_array('get_code', $presets) ) {
- $result = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
- }
- curl_close($ch);
-
- return $result;
- } else {
- $error = array('error' => true, 'error_string' => curl_error($ch));
- }
- } else {
- $error = array('error' => true, 'error_string' => 'CURL_NOT_INSTALLED');
- }
-
- /** Fix for get_code preset */
- if (
- $presets && ($presets == 'get_code'
- || (is_array($presets) && in_array('get_code', $presets)))
- && $error['error_string'] == 'CURL_NOT_INSTALLED'
- ) {
- $headers = get_headers($url);
- $out = (int)preg_replace('/.*(\d{3}).*/', '$1', $headers[0]);
- }
-
- return $out;
- }
-
- /**
- * Checks if the string is JSON type
- *
- * @param $string string
- *
- * @return bool
- */
- public static function is_json($string) // phpcs:ignore PSR1.Methods.CamelCapsMethodName.NotCamelCaps
- {
- return is_string($string) && is_array(json_decode($string, true)) ? true : false;
- }
-
- /**
- * Function removing non UTF8 characters from array||string
- *
- * @param $data array|string
- *
- * @return array|string
- */
- public static function removeNonUTF8FromArray($data)
- {
- foreach ( $data as $key => $val ) {
- if ( is_array($val) ) {
- $data[$key] = self::removeNonUTF8FromArray($val);
- } else {
- $data[$key] = self::removeNonUTF8FromString($val);
- }
- }
-
- return $data;
- }
-
- /**
- * Function removing non UTF8 characters from array||string
- * param mixed(array||string)
- * return mixed(array||string)
- */
- public static function removeNonUTF8FromString($data)
- {
- if ( ! preg_match('//u', $data) ) {
- $data = 'Nulled. Not UTF8 encoded or malformed.';
- }
-
- return $data;
- }
-
- /**
- * Function convert array to UTF8 and removes non UTF8 characters
- * param array
- * param string
- * @return array
- */
- public static function arrayToUTF8($array, $data_codepage = null)
- {
- foreach ( $array as $key => $val ) {
- if ( is_array($val) ) {
- $array[$key] = self::arrayToUTF8($val, $data_codepage);
- } else {
- $array[$key] = self::stringToUTF8($val, $data_codepage);
- }
- }
-
- return $array;
- }
-
- /**
- * Function convert string to UTF8 and removes non UTF8 characters
- * @param $str string|null
- * @param $data_codepage string
- * @return string
- */
- public static function stringToUTF8($str, $data_codepage = null)
- {
- //php8 deprecated fix
- if ( is_null($str) ) {
- $str = '';
- }
- 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 from UTF8
- *
- * @param array|object|string $obj
- * @param string $data_codepage
- *
- * @return mixed (array|object|string)
- */
- public static function stringFromUTF8($obj, $data_codepage = null)
- {
- // Array || object
- if ( is_array($obj) || is_object($obj) ) {
- foreach ( $obj as $_key => &$val ) {
- $val = self::stringFromUTF8($val, $data_codepage);
- }
- unset($val);
- //String
- } else {
- if ( $data_codepage !== null && preg_match('//u', $obj) ) {
- if ( function_exists('mb_convert_encoding') ) {
- $obj = mb_convert_encoding($obj, $data_codepage, 'UTF-8');
- } elseif ( version_compare(phpversion(), '8.3', '<') ) {
- $obj = @utf8_decode($obj);
- }
- }
- }
-
- return $obj;
- }
-}
diff --git a/lib/CleantalkRequest.php b/lib/CleantalkRequest.php
deleted file mode 100644
index 76315b9..0000000
--- a/lib/CleantalkRequest.php
+++ /dev/null
@@ -1,179 +0,0 @@
- 0 ) {
- foreach ( $params as $param => $value ) {
- $this->{$param} = $value;
- }
- }
- }
-}
diff --git a/lib/CleantalkResponse.php b/lib/CleantalkResponse.php
deleted file mode 100644
index 4e5f69a..0000000
--- a/lib/CleantalkResponse.php
+++ /dev/null
@@ -1,209 +0,0 @@
- 0 ) {
- foreach ( $response as $param => $value ) {
- $this->{$param} = $value;
- }
- } else {
- $this->errno = $obj->errno;
- $this->errstr = $obj->errstr;
-
- $this->errstr = isset($this->errstr)
- ? preg_replace("/.+(\*\*\*.+\*\*\*).+/", "$1", $this->errstr)
- : '';
-
- $this->stop_words = isset($obj->stop_words) ? CleantalkHelper::stringFromUTF8(
- $obj->stop_words,
- 'ISO-8859-1'
- ) : null;
- $this->comment = isset($obj->comment) ? CleantalkHelper::stringFromUTF8(
- $obj->comment,
- 'ISO-8859-1'
- ) : 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;
- $this->codes = (isset($obj->codes)) ? explode(' ', $obj->codes) : array();
-
- $this->bot_expectation = (isset($obj->bot_expectation)) ? $obj->bot_expectation : 0.0;
- $this->ip_frequency_24hour = (isset($obj->ip_frequency_24hour)) ? $obj->ip_frequency_24hour : 0;
- $this->ip_frequency_1hour = (isset($obj->ip_frequency_1hour)) ? $obj->ip_frequency_1hour : 0;
- $this->ip_frequency_10min = (isset($obj->ip_frequency_10min)) ? $obj->ip_frequency_10min : 0;
-
-
- if ( $this->errno !== 0 && $this->errstr !== null && $this->comment === null ) {
- $this->comment = '*** ' . $this->errstr . ' Antispam service cleantalk.org ***';
- }
- }
- }
-}
diff --git a/lib/CleantalkVerdict.php b/lib/CleantalkVerdict.php
new file mode 100644
index 0000000..53195d0
--- /dev/null
+++ b/lib/CleantalkVerdict.php
@@ -0,0 +1,21 @@
+getJSON(), true);
+ }
+}
diff --git a/lib/HTTP/CleantalkResponse.php b/lib/HTTP/CleantalkResponse.php
new file mode 100644
index 0000000..103dbaf
--- /dev/null
+++ b/lib/HTTP/CleantalkResponse.php
@@ -0,0 +1,170 @@
+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) ? Helper::fromUTF8($obj->stop_words, 'ISO-8859-1') : null;
+ $this->comment = isset($obj->comment) ? $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) : '';
+ $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 ***';
+ }
+
+ $this->failed_connections_urls_string = !empty($failed_urls) ? $failed_urls : '';
+ }
+}
diff --git a/lib/HTTP/Helper.php b/lib/HTTP/Helper.php
new file mode 100644
index 0000000..af78282
--- /dev/null
+++ b/lib/HTTP/Helper.php
@@ -0,0 +1,520 @@
+ 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
+ ),
+ );
+
+ /**
+ * 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())
+ {
+ $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;
+ if ( isset($tmp[0]) ) {
+ $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':
+ $remote_addr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null;
+ if (!empty($remote_addr)) {
+ $ip_version = self::ipValidate($_SERVER['REMOTE_ADDR']);
+ if ($ip_version) {
+ $out = $ip_version === 'v6' && ! $v4_only ? self::ipV6Normalize(
+ $_SERVER['REMOTE_ADDR']
+ ) : $_SERVER['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) ||
+ (
+ $ip_version === self::ipValidate(Server::getString('SERVER_ADDR')) &&
+ self::ipMaskMatch($out, Server::getString('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);
+ }
+
+ if ( is_string($out) ) {
+ $ip_version = self::ipValidate($out);
+
+ if ( ! $ip_version ) {
+ $out = null;
+ }
+
+ if ( $ip_version === 'v6' && $v4_only ) {
+ $out = null;
+ }
+ }
+
+ 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);
+
+ if ( ! isset($exploded[0], $exploded[1]) ) {
+ return false;
+ }
+
+ $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;
+ }
+
+ /**
+ * Validating IPv4, IPv6
+ *
+ * @param string|null|false $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;
+ }
+
+ /**
+ * 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 = array();
+ 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;
+ }
+ }
+
+ return $headers;
+ }
+}
diff --git a/lib/HTTP/Request.php b/lib/HTTP/Request.php
new file mode 100644
index 0000000..e1e4d52
--- /dev/null
+++ b/lib/HTTP/Request.php
@@ -0,0 +1,583 @@
+ $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) &&
+ ! is_array($this->response) && $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
+ *
+ * @psalm-suppress PossiblyInvalidArgument
+ */
+ protected function requestMulti()
+ {
+ $this->response = [];
+
+ if ( ! is_array($this->url) ) {
+ return $this->response;
+ }
+
+ $urls_count = count($this->url);
+ $curl_arr = array();
+ $mh = curl_multi_init();
+
+ 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
+ *
+ * @psalm-suppress PossiblyInvalidArgument
+ * @psalm-suppress PossiblyInvalidCast
+ */
+ 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 process 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 => 50,
+ CURLOPT_LOW_SPEED_TIME => 25,
+ 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('APBCT_CASERT_PATH') && APBCT_CASERT_PATH ) {
+ $this->options[CURLOPT_CAINFO] = APBCT_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, ['apbct_no_cache' => mt_rand()]);
+ }, $this->url);
+ } else {
+ $this->options[CURLOPT_URL] = self::appendParametersToURL(
+ $this->options[CURLOPT_URL],
+ ['apbct_no_cache' => mt_rand()]
+ );
+ }
+ break;
+ case 'api3.0':
+ // api3.0 methods requires 'Content-Type: application/json' http header
+ $this->options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json';
+ }
+ }
+ }
+
+ /**
+ * 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/HTTP/Response.php b/lib/HTTP/Response.php
new file mode 100644
index 0000000..4aaac43
--- /dev/null
+++ b/lib/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/TransportException.php b/lib/TransportException.php
deleted file mode 100644
index 7309bd3..0000000
--- a/lib/TransportException.php
+++ /dev/null
@@ -1,18 +0,0 @@
- $val) {
- if (preg_match('/\AHTTP_/', $key)) {
- $server_key = preg_replace('/\AHTTP_/', '', $key);
- $key_parts = explode('_', $server_key);
- if (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;
- }
-}
-
-/**
- * Patch for locale_get_display_region()
- * For old PHP versions
- */
-if (! function_exists('locale_get_display_region')) {
- function locale_get_display_region($locale, $_in_locale = 'EN')
- {
- return 'Unkonwn' . ($locale ? ': ' . $locale : '');
- }
-}
-
-/**
- * Patch for utf8_decode()
- * If PHP complied without XML support
- * From getID3() by James Heinrich under GNU GPL
- */
-if (! function_exists('utf8_decode')) {
- function utf8_decode($string)
- {
- $newcharstring = '';
- $offset = 0;
- $stringlength = strlen($string);
- while ($offset < $stringlength) {
- if ((ord($string[$offset]) | 0x07) == 0xF7) {
- $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
- ((ord($string[($offset + 1)]) & 0x3F) << 12) &
- ((ord($string[($offset + 2)]) & 0x3F) << 6) &
- (ord($string[($offset + 3)]) & 0x3F);
- $offset += 4;
- } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
- $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
- ((ord($string[($offset + 1)]) & 0x3F) << 6) &
- (ord($string[($offset + 2)]) & 0x3F);
- $offset += 3;
- } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
- $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) &
- (ord($string[($offset + 1)]) & 0x3F);
- $offset += 2;
- } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
- $charval = ord($string[$offset]);
- $offset += 1;
- } else {
- $charval = false;
- $offset += 1;
- }
- if ($charval !== false) {
- $newcharstring .= (($charval < 256) ? chr($charval) : '?');
- }
- }
-
- return $newcharstring;
- }
-}
-
-if (! function_exists("array_column")) {
- function array_column($array, $column_name)
- {
- return array_map(static function ($element) use ($column_name) {
- return $element[$column_name];
- }, $array);
- }
-}
-
-/**
- * array_key_first() polyfill for PHP 7.3-
- */
-if ( ! function_exists('array_key_first') ) {
- function array_key_first(array $arr)
- {
- foreach ( $arr as $key => $_unused ) {
- return $key;
- }
-
- return null;
- }
-}
diff --git a/lib/die_page.html b/lib/die_page.html
deleted file mode 100644
index 7504b61..0000000
--- a/lib/die_page.html
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
- Blacklisted
-
-
-
- CleanTalk. Spam protection
-%ERROR_TEXT%
-
-« Back
-
From c3f783103adab0b75a1e019832701cce128b7409 Mon Sep 17 00:00:00 2001
From: svfcode
Date: Tue, 13 May 2025 09:15:44 +0300
Subject: [PATCH 2/4] Upd. Examples. Update example of use.
---
examples/direct_php/example.php | 61 ------
.../form_with_handler/form_with_handler.php | 176 ++++++++++++++----
2 files changed, 138 insertions(+), 99 deletions(-)
delete mode 100644 examples/direct_php/example.php
diff --git a/examples/direct_php/example.php b/examples/direct_php/example.php
deleted file mode 100644
index e09afb1..0000000
--- a/examples/direct_php/example.php
+++ /dev/null
@@ -1,61 +0,0 @@
-valid;
-
-echo "Access key validation result:";
-echo CleantalkAPI::method__notice_validate_key($auth_key, 'php-api');
-echo "\n";
-
-if (!$is_valid) {
- echo "Access key is not valid. Please check access key in the config.\n";
- exit;
-}
-
-// The facility in which to store the query parameters
-$ct_request = new CleantalkRequest();
-
-$ct_request->auth_key = $auth_key;
-$ct_request->message = 'stop_word';
-$ct_request->sender_email = 'stop_email@example.com';
-$ct_request->sender_nickname = 'John Dow';
-$ct_request->example = str_repeat('Just text ', 10);
-$ct_request->agent = 'php-api';
-$ct_request->sender_ip = '178.32.183.43';
-$ct_request->event_token = isset($_POST['ct_bot_detector_event_token']) ? $_POST['ct_bot_detector_event_token'] : null;
-
-$ct = new Cleantalk();
-$ct->server_url = $config_url;
-
-// Check
-$ct_result = $ct->isAllowMessage($ct_request);
-
-if ( $ct_result->allow == 1 ) {
- echo 'Comment allowed. Reason ' . $ct_result->comment;
-} else {
- echo 'Comment blocked. Reason ' . $ct_result->comment;
-}
diff --git a/examples/form_with_handler/form_with_handler.php b/examples/form_with_handler/form_with_handler.php
index df2ff7b..8dc39ef 100644
--- a/examples/form_with_handler/form_with_handler.php
+++ b/examples/form_with_handler/form_with_handler.php
@@ -1,47 +1,147 @@
+
+
+
+
+
+ Contact Form
+
+
+
+
+
+
+
+ Contact Us
+
+ handle();
+ if ($api_result->allow === 0) {
+ $statusMessage = 'Spam detected - ' . $api_result->comment;
+ $messageType = 'error';
+ }
+
+ // TROUBLESHOOTING: logging the suggestions
+ error_log($cleantalk_antispam->whatsWrong(true));
+ }
+ // END OF HANDLE CLEANTALK ANTISPAM
-$cleantalk_antispam = new CleantalkAntispam($apikey, $email_field, $user_name_field, $message_field, $type_form);
-$api_result = $cleantalk_antispam->handle();
-if ($api_result) { // the check fired
- if ($api_result->account_status !== 1) {
- // something wrong with your key or license, to know why read $api_result->codes
- echo 'Allowed. Spam protection disabled.'; // or do nothing
- } else {
- if ($api_result->allow === 1) {
- echo 'Allowed. Spam protection OK.'; // or do nothing
- } else {
- die('Blocked. Spam protection OK. Reason: ' . $api_result->comment); // or make your own handler
+ if ($messageType !== 'error') {
+ $logEntry = date('Y-m-d H:i:s') . " | Name: $name | Email: $email | Message: $message\n";
+ if (file_put_contents('./contacts.log', $logEntry, FILE_APPEND)) {
+ $statusMessage = 'Thank you for your message! We will get back to you soon.';
+ $messageType = 'success';
+ $name = $email = $message = ''; // Clear form data after successful submission
+ }
}
}
-}
-// your further code flow here
-?>
+ ?>
-
+
+
+
+
+
+
+
+
+
From dc9389b0d546d0e3addc9205f9107fc9892eaba7 Mon Sep 17 00:00:00 2001
From: svfcode
Date: Tue, 13 May 2025 10:09:28 +0300
Subject: [PATCH 3/4] fix psalm
---
cleantalk-antispam.php | 12 +-
composer.json | 2 +-
.../form_with_handler/form_with_handler.php | 6 +-
lib/CleantalkAntispam.php | 60 ++--------
lib/HTTP/Helper.php | 34 +++++-
lib/cleantalk-php-patch.php | 110 ++++++++++++++++++
tests/CleantalkTest.php | 39 -------
7 files changed, 163 insertions(+), 100 deletions(-)
create mode 100644 lib/cleantalk-php-patch.php
diff --git a/cleantalk-antispam.php b/cleantalk-antispam.php
index d6acb1c..26928a4 100644
--- a/cleantalk-antispam.php
+++ b/cleantalk-antispam.php
@@ -1,8 +1,8 @@
@@ -119,7 +119,7 @@
}
?>
-
+
diff --git a/lib/CleantalkAntispam.php b/lib/CleantalkAntispam.php
index 88d94e2..88c2abe 100644
--- a/lib/CleantalkAntispam.php
+++ b/lib/CleantalkAntispam.php
@@ -12,7 +12,6 @@ class CleantalkAntispam
const MODERATE_URL = 'https://moderate.cleantalk.org/api2.0';
const BOT_DETECTOR_LIBRARY_URL = 'https://moderate.cleantalk.org/ct-bot-detector-wrapper.js';
const EVENT_TOKEN_FIELD_NAME = 'ct_bot_detector_event_token';
- const FORM_START_TIME_FIELD_NAME = 'ct_form_start_time';
const EMAIL_ADDRESS_REGEXP = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
/**
@@ -71,11 +70,6 @@ class CleantalkAntispam
*/
private $cleantalk_response;
- /**
- * @var int Form start time
- */
- private $form_start_time;
-
/**
* @var false|string CleanTalk request data in JSON format
*/
@@ -94,6 +88,7 @@ class CleantalkAntispam
* Constructor for the CleanTalkCheck class.
*
* @param string $accessKey Access key for CleanTalk API
+ * @psalm-suppress PossiblyUnusedParam
*/
public function __construct($accessKey, $email = '', $user_name_field = '', $message_field = '', $type_form = '')
{
@@ -104,9 +99,6 @@ public function __construct($accessKey, $email = '', $user_name_field = '', $mes
if (isset($this->data_container[static::EVENT_TOKEN_FIELD_NAME])) {
$this->event_token = $this->data_container[static::EVENT_TOKEN_FIELD_NAME];
}
- if (isset($this->data_container[static::FORM_START_TIME_FIELD_NAME])) {
- $this->form_start_time = $this->data_container[static::FORM_START_TIME_FIELD_NAME];
- }
$this->ip = Helper::ipGet();
if ($email) {
$this->email = $email;
@@ -138,16 +130,8 @@ private function setEmailAutomatically()
public static function getFrontendHTMLCode($warn_if_js_disabled = false)
{
$warn = $warn_if_js_disabled ? '' : '';
- $submittime_script = '
-
-
- ';
- $html = '%s%s';
- return sprintf($html, static::BOT_DETECTOR_LIBRARY_URL, $warn, $submittime_script);
+ $html = '%s';
+ return sprintf($html, static::BOT_DETECTOR_LIBRARY_URL, $warn);
}
/**
@@ -167,6 +151,7 @@ private function getCleanTalkResponse()
->setData($this->cleantalk_request_data)
->request();
+ /** @psalm-suppress InvalidArgument */
return new CleantalkResponse(@json_decode($response_raw), null);
}
@@ -185,7 +170,6 @@ private function prepareCleanTalkRequestData()
'sender_email' => $this->email,
'sender_ip' => $this->ip,
'js_on' => !empty($this->event_token) ? 1 : 0,
- 'submit_time' => !empty($this->form_start_time) ? time() - (int)$this->form_start_time : null,
'event_token' => $this->event_token,
'agent' => 'php-cleantalk-check',
'sender_info' => @json_encode(
@@ -344,19 +328,6 @@ public function setIP($ip = null)
return $this;
}
- /**
- * Set the form start time.
- *
- * @param int|null $form_start_time Form start time
- * @return $this
- */
- public function setFormStartTime($form_start_time = null)
- {
- $this->fluidCallStack(__FUNCTION__);
- $this->form_start_time = (int)$form_start_time;
- return $this;
- }
-
/**
* Enable blocking of visitors without JavaScript.
*
@@ -438,6 +409,7 @@ public function handle()
$this->cleantalk_response = $this->getCleanTalkResponse();
if ($this->cleantalk_response->error) {
+ /** @psalm-suppress InvalidOperand */
$this->verdict->error = 'CleanTalk moderate server error: ' . $this->cleantalk_response->error;
return $this->beforeReturnVerdict();
}
@@ -504,18 +476,6 @@ private function setImprovementSuggestions()
);
}
- if (empty($this->form_start_time)) {
- $fluid_method = 'setFormStartTime';
- $stack = !$this->fluidCallExist($fluid_method)
- ? "interface method ->$fluid_method() has not been called, make sure the form data contains the field " . static::FORM_START_TIME_FIELD_NAME
- : "interface method ->$fluid_method() has been called, but provided var is invalid";
- $this->setImprovementSuggestion(
- 'critical',
- 'Please, provide the form start time to improve check quality.',
- $stack
- );
- }
-
if (empty($this->event_token)) {
$fluid_method = 'setEventToken';
$stack = !$this->fluidCallExist($fluid_method)
@@ -652,13 +612,15 @@ public function whatsWrong($return_as_json = false)
return @json_encode($array);
}
- $suggestions = var_export($array['suggestions'], 1);
- $data = var_export($array['request_data'], 1);
- $verdict = var_export($array['verdict'], 1);
+ /** @psalm-suppress InvalidScalarArgument */
+ $suggestions = var_export($array['suggestions'], 1);
+ /** @psalm-suppress InvalidScalarArgument */
+ $data = var_export($array['request_data'], 1);
+ /** @psalm-suppress InvalidScalarArgument */
+ $verdict = var_export($array['verdict'], 1);
echo "";
echo "";
echo "";
return '';
}
-
}
diff --git a/lib/HTTP/Helper.php b/lib/HTTP/Helper.php
index af78282..b9a9e0b 100644
--- a/lib/HTTP/Helper.php
+++ b/lib/HTTP/Helper.php
@@ -258,8 +258,8 @@ public static function ipGet($ip_type_to_get = 'real', $v4_only = true, $headers
is_string($ip_version) && (
self::ipIsPrivateNetwork($out, $ip_version) ||
(
- $ip_version === self::ipValidate(Server::getString('SERVER_ADDR')) &&
- self::ipMaskMatch($out, Server::getString('SERVER_ADDR') . '/24', $ip_version)
+ $ip_version === self::ipValidate($_SERVER['SERVER_ADDR']) &&
+ self::ipMaskMatch($out, $_SERVER['SERVER_ADDR'] . '/24', $ip_version)
)
)
)
@@ -517,4 +517,34 @@ public static function httpGetHeaders()
return $headers;
}
+
+ /**
+ * 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 ($data_codepage !== null && preg_match('//u', $obj)) {
+ if ( function_exists('mb_convert_encoding') ) {
+ $obj = mb_convert_encoding($obj, $data_codepage, 'UTF-8');
+ } elseif (version_compare(phpversion(), '8.3', '<')) {
+ $obj = @utf8_decode($obj);
+ }
+ }
+ }
+
+ return $obj;
+ }
}
diff --git a/lib/cleantalk-php-patch.php b/lib/cleantalk-php-patch.php
new file mode 100644
index 0000000..62f932d
--- /dev/null
+++ b/lib/cleantalk-php-patch.php
@@ -0,0 +1,110 @@
+ $val) {
+ if (preg_match('/\AHTTP_/', $key)) {
+ $server_key = preg_replace('/\AHTTP_/', '', $key);
+ $key_parts = explode('_', $server_key);
+ if (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;
+ }
+}
+
+/**
+ * Patch for locale_get_display_region()
+ * For old PHP versions
+ */
+if (! function_exists('locale_get_display_region')) {
+ function locale_get_display_region($locale, $_in_locale = 'EN')
+ {
+ return 'Unkonwn' . ($locale ? ': ' . $locale : '');
+ }
+}
+
+/**
+ * Patch for utf8_decode()
+ * If PHP complied without XML support
+ * From getID3() by James Heinrich under GNU GPL
+ */
+if (! function_exists('utf8_decode')) {
+ function utf8_decode($string)
+ {
+ $newcharstring = '';
+ $offset = 0;
+ $stringlength = strlen($string);
+ while ($offset < $stringlength) {
+ if ((ord($string[$offset]) | 0x07) == 0xF7) {
+ $charval = ((ord($string[($offset + 0)]) & 0x07) << 18) &
+ ((ord($string[($offset + 1)]) & 0x3F) << 12) &
+ ((ord($string[($offset + 2)]) & 0x3F) << 6) &
+ (ord($string[($offset + 3)]) & 0x3F);
+ $offset += 4;
+ } elseif ((ord($string[$offset]) | 0x0F) == 0xEF) {
+ $charval = ((ord($string[($offset + 0)]) & 0x0F) << 12) &
+ ((ord($string[($offset + 1)]) & 0x3F) << 6) &
+ (ord($string[($offset + 2)]) & 0x3F);
+ $offset += 3;
+ } elseif ((ord($string[$offset]) | 0x1F) == 0xDF) {
+ $charval = ((ord($string[($offset + 0)]) & 0x1F) << 6) &
+ (ord($string[($offset + 1)]) & 0x3F);
+ $offset += 2;
+ } elseif ((ord($string[$offset]) | 0x7F) == 0x7F) {
+ $charval = ord($string[$offset]);
+ $offset += 1;
+ } else {
+ $charval = false;
+ $offset += 1;
+ }
+ if ($charval !== false) {
+ $newcharstring .= (($charval < 256) ? chr($charval) : '?');
+ }
+ }
+
+ return $newcharstring;
+ }
+}
+
+if (! function_exists("array_column")) {
+ function array_column($array, $column_name)
+ {
+ return array_map(static function ($element) use ($column_name) {
+ return $element[$column_name];
+ }, $array);
+ }
+}
+
+/**
+ * array_key_first() polyfill for PHP 7.3-
+ */
+if ( ! function_exists('array_key_first') ) {
+ function array_key_first(array $arr)
+ {
+ foreach ( $arr as $key => $_unused ) {
+ return $key;
+ }
+
+ return null;
+ }
+}
diff --git a/tests/CleantalkTest.php b/tests/CleantalkTest.php
index f25599e..3d75530 100644
--- a/tests/CleantalkTest.php
+++ b/tests/CleantalkTest.php
@@ -3,45 +3,6 @@
use Cleantalk\Cleantalk;
use Cleantalk\CleantalkRequest;
-require_once "lib/Cleantalk.php";
-require_once "lib/CleantalkRequest.php";
-require_once "lib/CleantalkResponse.php";
-require_once "lib/CleantalkHelper.php";
-require_once "lib/CleantalkAPI.php";
-require_once "lib/cleantalk-php-patch.php";
-
-
class CleantalkTest extends \PHPUnit\Framework\TestCase
{
- protected $ct;
-
- protected $ct_request;
-
- public function setUp()
- {
- $this->ct = new Cleantalk();
- $this->ct->server_url = 'https://moderate.cleantalk.org';
- $this->ct_request = new CleantalkRequest();
- $this->ct_request->auth_key = getenv("CLEANTALK_TEST_API_KEY");
- }
-
- public function testIsAllowMessage()
- {
- $this->ct_request->sender_email = 's@cleantalk.org';
- $this->ct_request->message = 'stop_word bad message';
- $result = $this->ct->isAllowMessage($this->ct_request);
- $this->assertEquals(0, $result->allow);
-
- $this->ct_request->message = '';
- $this->ct_request->sender_email = '';
- }
-
- public function testIsAllowUser()
- {
- $this->ct_request->sender_email = 's@cleantalk.org';
- $result = $this->ct->isAllowUser($this->ct_request);
- $this->assertEquals(0, $result->allow);
-
- $this->ct_request->sender_email = '';
- }
}
From f14c830ea0b8ea57f4ebde7857ee000ae166ebef Mon Sep 17 00:00:00 2001
From: svfcode
Date: Mon, 26 May 2025 09:12:53 +0300
Subject: [PATCH 4/4] Update readme.
---
README.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 95d9659..40a977c 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@ php-antispam
[](https://packagist.org/packages/cleantalk/php-antispam)
-## The Invisible protection from spam, no captches, no puzzles, no animals and no math.
+## The Invisible protection from spam, no captcha, no recaptcha, no puzzles, no math captcha.
_API for antispam service cleantalk.org_
#### Requirements
@@ -12,7 +12,9 @@ _API for antispam service cleantalk.org_
* CURL support
### How we stop spam?
-Cleantalk catch your api request and provides analytical result to you.
+PHP Anti-Spam library providing invisible spam protection for your websites, registration forms, and comment sections. CleanTalk API offers an effective CAPTCHA alternative that silently blocks spam without interrupting your users' experience.
+
+When users submit forms on your website form, the form data is securely sent to CleanTalk’s cloud servers. CleanTalk analyzes submissions using advanced heuristics. CleanTalk then returns a real-time verdict— legitimate requests or spam.
You are free to do anything with spam, or just allow as to block spam (we will interrupt desirable request).