File: /var/www/test/wp-content/plugins/wp-statistics/includes/class-wp-statistics-exclusion.php
<?php
namespace WP_STATISTICS;
use WP_Statistics\Service\Analytics\VisitorProfile;
use WP_Statistics\Utils\Request;
class Exclusion
{
/**
* Array for storing options.
*
* @access private
* @var array
* @static
*/
private static $options = [];
/**
* Get Exclusion List
*
* @return array
*/
public static function exclusion_list()
{
return array(
'ajax' => __('Ajax', 'wp-statistics'),
'cronjob' => __('Cron job', 'wp-statistics'),
'robot' => __('Robot', 'wp-statistics'),
'headless' => __('Headless Browser', 'wp-statistics'),
'prefetch' => __('Prefetch', 'wp-statistics'),
'BrokenFile' => __('Broken Link', 'wp-statistics'),
'ip match' => __('IP Match', 'wp-statistics'),
'self referral' => __('Self Referral', 'wp-statistics'),
'login page' => __('Login Page', 'wp-statistics'),
'admin page' => __('Admin Page', 'wp-statistics'),
'feed' => __('Feed', 'wp-statistics'),
'404' => __('404', 'wp-statistics'),
'excluded url' => __('Excluded URL', 'wp-statistics'),
'user role' => __('User Role', 'wp-statistics'),
'geoip' => __('Geolocation', 'wp-statistics'),
'robot_threshold' => __('Robot threshold', 'wp-statistics'),
'xmlrpc' => __('XML-RPC', 'wp-statistics'),
'cross site' => __('Cross site Request', 'wp-statistics'),
'pre flight' => __('Pre-flight Request', 'wp-statistics'),
);
}
/**
* Check to see if the user wants us to record why we're excluding hits.
*
* @return mixed
*/
public static function record_active()
{
return !empty(self::$options['record_exclusions']);
}
/**
* Checks exclusion tracking visits and visitors.
* @param $visitorProfile VisitorProfile
*/
public static function check($visitorProfile)
{
// Create Default Object
$exclude = array('exclusion_match' => false, 'exclusion_reason' => '');
// Get List Of Exclusion WP Statistics
$exclusion_list = apply_filters('wp_statistics_exclusion_list', array_keys(Exclusion::exclusion_list()));
if (empty(self::$options)) {
self::$options = Option::getOptions();
}
// Check Exclusion
foreach ($exclusion_list as $list) {
$method = 'exclusion_' . strtolower(str_replace(array("-", " "), "_", $list));
// Check if method exists
if (method_exists(self::class, $method)) {
$check = call_user_func([self::class, $method], $visitorProfile);
if ($check) {
$exclude = array('exclusion_match' => true, 'exclusion_reason' => $list);
break;
}
}
}
return apply_filters('wp_statistics_exclusion', $exclude, $visitorProfile);
}
/**
* Record Exclusion in WP Statistics DB.
*
* @param array $exclusion
*/
public static function record($exclusion = array())
{
global $wpdb;
// If we're not storing exclusions, just return.
if (self::record_active() != true) {
return;
}
// Check Exist this Exclusion in this day
$result = $wpdb->query(
$wpdb->prepare("UPDATE `" . DB::table('exclusions') . "` SET `count` = `count` + 1 WHERE `date` = %s AND `reason` = %s", TimeZone::getCurrentDate('Y-m-d'), $exclusion['exclusion_reason'])
);
if (!$result) {
$insert = $wpdb->insert(
DB::table('exclusions'),
array(
'date' => TimeZone::getCurrentDate('Y-m-d'),
'reason' => $exclusion['exclusion_reason'],
'count' => 1,
)
);
if (!$insert) {
if (!empty($wpdb->last_error)) {
\WP_Statistics::log($wpdb->last_error);
}
}
do_action('wp_statistics_save_exclusion', $exclusion, $wpdb->insert_id);
}
}
/**
* Detect if we're running an ajax request.
*/
public static function exclusion_ajax()
{
// White list actions
if (Helper::isBypassAdBlockersRequest() || Request::compare('action', 'wp_statistics_event') || Request::compare('action', 'wp_statistics_custom_event')) {
return false;
}
return (defined('DOING_AJAX') and DOING_AJAX);
}
/**
* Detect if we're running an WordPress CronJob.
*/
public static function exclusion_cronjob()
{
return (defined('DOING_CRON') && DOING_CRON === true) || (function_exists('wp_doing_cron') && wp_doing_cron() === true);
}
/**
* Detect if WordPress Feed.
*/
public static function exclusion_feed()
{
return !empty(self::$options['exclude_feeds']) && is_feed();
}
/**
* Detect if WordPress 404 Page.
*/
public static function exclusion_404()
{
if (!empty(self::$options['exclude_404s'])) {
if (Helper::is_rest_request() && isset($_REQUEST['source_type']) && $_REQUEST['source_type'] == '404') {
return true;
}
return is_404();
}
}
/**
* Detect if robot threshold.
* @param $visitorProfile VisitorProfile
*/
public static function exclusion_robot_threshold($visitorProfile)
{
$robotThreshold = ! empty(self::$options['robot_threshold']) ? intval(self::$options['robot_threshold']) : 0;
if ($robotThreshold <= 0) {
return false;
}
$visitor = $visitorProfile->isIpActiveToday();
if (!$visitor) {
return false;
}
return ($visitor->hits + 1 > $robotThreshold);
}
/**
* Detect if Exclude WordPress User role.
*/
public static function exclusion_user_role()
{
if (empty(self::$options)) {
self::$options = Option::getOptions();
}
$current_user = false;
if (Helper::is_rest_request() && isset($GLOBALS['wp_statistics_user_id'])) {
$user_id = $GLOBALS['wp_statistics_user_id'];
if ($user_id) {
$current_user = get_user_by('id', $user_id);
}
} elseif (is_user_logged_in()) {
$current_user = wp_get_current_user();
}
if ($current_user) {
foreach ($current_user->roles as $role) {
$option_name = 'exclude_' . str_replace(' ', '_', strtolower($role));
if (!empty(self::$options[$option_name])) {
return true;
}
}
} else {
// Guest visitor
if (!empty(self::$options['exclude_anonymous_users'])) {
return true;
}
}
return false;
}
/**
* Detects if current URL opened by the visitor should be excluded.
*
* @param VisitorProfile $visitorProfile VisitorProfile
*
* @return bool
*/
public static function exclusion_excluded_url($visitorProfile)
{
$excludedUrls = self::$options['excluded_urls'] ?? '';
if (!empty($excludedUrls)) {
$requestUri = $visitorProfile->getRequestUri();
$delimiter = strpos($requestUri, '?');
// Remove query parameters from the request URI
if ($delimiter > 0) {
$requestUri = substr($requestUri, 0, $delimiter);
}
// Strip slashes from the beginning and the end of the request URI
$requestUri = trim($requestUri, '/\\');
// Decode request URI since input URLs will be decoded too
$requestUri = urldecode($requestUri);
foreach (explode("\n", $excludedUrls) as $url) {
// Sanitize input URL
$url = wp_make_link_relative($url);
$url = trim($url);
$url = trim($url, '/\\');
$url = urldecode($url);
if (strlen($url) > 2) {
// Check if the URL contains a wildcard (*)
if (strpos($url, '*') !== false) {
// Escape special characters for regex, then replace '*' with '.*' for wildcards
$pattern = str_replace('\*', '.*', preg_quote($url, '/'));
// Adjust the pattern to allow wildcards at both ends or in the middle
if (preg_match('/^' . $pattern . '$/i', $requestUri)) {
return true;
}
} else {
// Exact match check
if (strtolower($url) == strtolower($requestUri)) {
return true;
}
}
}
}
}
return false;
}
/**
* Detect if Self Referral WordPress.
* @param $visitorProfile VisitorProfile
*/
public static function exclusion_self_referral($visitorProfile)
{
$userAgent = $visitorProfile->getHttpUserAgent();
$version = Helper::get_wordpress_version();
return in_array($userAgent, [
'WordPress/' . $version . '; ' . get_home_url(null, '/'),
'WordPress/' . $version . '; ' . get_home_url()
]);
}
/**
* Detect if WordPress Login Page.
*/
public static function exclusion_login_page()
{
return !empty(self::$options['exclude_loginpage']) && Helper::is_login_page();
}
/**
* Detect if WordPress Admin Page.
* @param $visitorProfile VisitorProfile
*/
public static function exclusion_admin_page($visitorProfile)
{
$requestUri = $visitorProfile->getRequestUri();
if (isset($_SERVER['SERVER_NAME']) and isset($requestUri)) {
// Remove Query From Url
$url = Helper::RemoveQueryStringUrl($_SERVER['SERVER_NAME'] . $requestUri);
if (!Helper::isBypassAdBlockersRequest() && !Request::compare('action', 'wp_statistics_event') && !Request::compare('action', 'wp_statistics_custom_event') && stripos($url, 'wp-admin') !== false) {
return true;
}
}
return false;
}
/**
* Detect if IP Match.
*
* @throws \Exception
*/
public static function exclusion_iP_match()
{
if (empty(self::$options['exclude_ip'])) {
return false;
}
// Pull the sub nets from the database.
$SubNets = explode("\n", self::$options['exclude_ip']);
// Check in Loop
foreach ($SubNets as $subnet) {
// Sanitize SubNet
$subnet = trim($subnet);
// The shortest ip address is 1.1.1.1, anything less must be a malformed entry.
if (strlen($subnet) > 6) {
// Check in Range
if (IP::checkIPRange(array($subnet))) {
return true;
}
}
}
return false;
}
/**
* Detect if Broken Link.
* @param $visitorProfile VisitorProfile
*/
public static function exclusion_brokenfile($visitorProfile)
{
// Check is 404
if (is_404()) {
$requestUri = $visitorProfile->getRequestUri();
//Check Current Page
if (isset($_SERVER["HTTP_HOST"]) and isset($requestUri)) {
//Get Full Url Page
$page_url = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http") . "://{$_SERVER["HTTP_HOST"]}{$requestUri}";
//Check Link file
$page_url = wp_parse_url($page_url, PHP_URL_PATH);
$ext = pathinfo($page_url, PATHINFO_EXTENSION);
if (!empty($ext) and $ext != 'php') {
return true;
}
}
}
return false;
}
/**
* Detect if Robots.
* @param $visitorProfile VisitorProfile
*/
public static function exclusion_robot($visitorProfile)
{
$rawUserAgent = $visitorProfile->getHttpUserAgent();
// Check user ip is empty or not user agent
if (empty($rawUserAgent) || empty($visitorProfile->getIp())) {
return true;
}
$userAgent = $visitorProfile->getUserAgent();
if ($userAgent->isBot()) {
return true;
}
if (!$userAgent->isBrowserDetected() && !$userAgent->isPlatformDetected()) {
return true;
}
// Pull the robots from the database.
$robots = explode("\n", self::$options['robotlist'] ?? '');
$robots = apply_filters('wp_statistics_exclusion_robots', $robots);
// Check to see if we match any of the robots.
if(is_array($robots) && !empty($robots)) {
foreach ($robots as $robot) {
$robot = trim($robot);
// If the match case is less than 4 characters long, it might match too much so don't execute it.
if (strlen($robot) > 3) {
if (stripos($rawUserAgent, $robot) !== false) {
return true;
}
}
}
}
return false;
}
/**
* Detect browser prefetch / prerender requests (Chrome Speculation Rules,
* Firefox link prefetching, <link rel="prefetch">). These use the real
* browser UA, so bot detection can't catch them.
*/
public static function exclusion_prefetch()
{
foreach (['HTTP_PURPOSE', 'HTTP_SEC_PURPOSE', 'HTTP_X_MOZ'] as $header) {
if (!empty($_SERVER[$header]) && stripos($_SERVER[$header], 'prefetch') !== false) {
return true;
}
}
return false;
}
/**
* Detect headless browsers and automation tools. Device Detector
* intentionally classifies Headless Chrome / PhantomJS as browsers rather
* than bots (matomo-org/device-detector#5691, #7441), so we filter them
* here. Puppeteer/Playwright/Selenium aren't in DD, hence the UA fallback.
*
* @param $visitorProfile VisitorProfile
*/
public static function exclusion_headless($visitorProfile)
{
$browser = $visitorProfile->getUserAgent()->getBrowser();
if (in_array($browser, ['Headless Chrome', 'PhantomJS'], true)) {
return true;
}
return (bool) preg_match('/Puppeteer|Playwright|Selenium/i', $visitorProfile->getHttpUserAgent());
}
/**
* Detect if GeoIP include or exclude country.
*
* @param VisitorProfile VisitorProfile
* @throws \Exception
*/
public static function exclusion_geoip($visitorProfile)
{
static $excludedCountries = null;
static $includedCountries = null;
if ($excludedCountries === null) {
$excluded_option = self::$options['excluded_countries'] ?? '';
$excludedCountries = empty($excluded_option) ? [] :
array_flip(array_filter(explode("\n", strtoupper(str_replace("\r\n", "\n", $excluded_option)))));
}
if ($includedCountries === null) {
$included_option = self::$options['included_countries'] ?? '';
if (empty($included_option)) {
$includedCountries = [];
} else {
$included_countries_string = trim(strtoupper(str_replace("\r\n", "\n", $included_option)));
$includedCountries = $included_countries_string === '' ? [] :
array_flip(array_filter(explode("\n", $included_countries_string)));
}
}
if ( empty($excludedCountries) && empty($includedCountries) ) {
return false;
}
$location = $visitorProfile->getCountry();
if (empty($location)) {
return false;
}
$location = strtoupper($location);
if (isset($excludedCountries[$location])) {
return true;
}
return !empty($includedCountries) && !isset($includedCountries[$location]);
}
/**
* Detect if XML-RPC
*/
public static function exclusion_xmlrpc()
{
return (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST === true);
}
/**
* Detect if Cross Site
*/
public static function exclusion_cross_site()
{
return isset($_SERVER['HTTP_SEC_FETCH_SITE']) && 'cross-site' === $_SERVER['HTTP_SEC_FETCH_SITE'];
}
/**
* Detect if Pre Flight
*/
public static function exclusion_pre_flight()
{
return isset($_SERVER['REQUEST_METHOD'], $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'], $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'], $_SERVER['HTTP_ORIGIN']) && 'OPTIONS' === $_SERVER['REQUEST_METHOD'];
}
}