/home/edulekha/public_html/wp-content/plugins/wp-slimstat/wp-slimstat.php
<?php
/*
* Plugin Name: SlimStat Analytics
* Plugin URI: https://wp-slimstat.com/
* Description: The leading web analytics plugin for WordPress
* Version: 5.3.5
* Author: Jason Crouse, VeronaLabs
* Text Domain: wp-slimstat
* Domain Path: /languages
* Author URI: https://wp-slimstat.com/
* License: GPL-2.0+
* License URI: https://www.gnu.org/licenses/gpl-2.0.html
* Requires at least: 5.6
* Requires PHP: 7.4
*/
if (!empty(wp_slimstat::$settings)) {
return true;
}
// check if composer autoloader exists
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
return;
}
// Set the plugin version and directory
define('SLIMSTAT_ANALYTICS_VERSION', '5.3.5');
define('SLIMSTAT_FILE', __FILE__);
define('SLIMSTAT_DIR', __DIR__);
define('SLIMSTAT_URL', plugins_url('', __FILE__));
// include the autoloader if it exists
require_once __DIR__ . '/vendor/autoload.php';
class wp_slimstat
{
public static $settings = [];
public static $wpdb;
public static $upload_dir = '';
public static $update_checker = [];
public static $raw_post_array = [];
protected static $data_js = ['id' => 0];
protected static $stat = [];
protected static $date_i18n_filters = [];
/**
* Initializes variables and actions
*/
public static function init()
{
\SlimStat\Providers\RESTService::run();
// Load all the settings
if (is_network_admin() && (empty($_GET['page']) || false === strpos($_GET['page'], 'slimview'))) {
self::$settings = get_site_option('slimstat_options', []);
} else {
self::$settings = get_option('slimstat_options', []);
}
if (empty(self::$settings)) {
// Save the default values in the database
self::update_option('slimstat_options', self::init_options());
}
self::$settings = array_merge(self::init_options(), self::$settings);
// Allow third party tools to edit the options
self::$settings = apply_filters('slimstat_init_options', self::$settings);
// Allow third-party tools to use a custom database for Slimstat
self::$wpdb = apply_filters('slimstat_custom_wpdb', $GLOBALS['wpdb']);
// Define the folder where to store the geolocation database (shared among sites in a network, by default)
if (defined('UPLOADS')) {
self::$upload_dir = ABSPATH . UPLOADS . '/wp-slimstat';
} else {
$upload_dir_info = wp_upload_dir();
self::$upload_dir = $upload_dir_info['basedir'];
// Handle multisite environment
if (is_multisite() && !(is_main_network() && is_main_site() && defined('MULTISITE'))) {
self::$upload_dir = str_replace('/sites/' . get_current_blog_id(), '', self::$upload_dir);
}
self::$upload_dir .= '/wp-slimstat';
}
// Apply filter to allow customization of the upload directory
self::$upload_dir = apply_filters('slimstat_maxmind_path', self::$upload_dir);
// Allow add-ons to turn off the tracker based on other conditions
$is_tracking_filter = apply_filters('slimstat_filter_pre_tracking', false === strpos(self::get_request_uri(), 'wp-admin/admin-ajax.php'));
$is_tracking_filter_js = apply_filters('slimstat_filter_pre_tracking_js', true);
// Enable the tracker (both server- and client-side)
if ((!is_admin() || 'on' == self::$settings['track_admin_pages']) && 'on' == self::$settings['is_tracking'] && $is_tracking_filter) {
// Is server-side tracking active?
if ('on' != self::$settings['javascript_mode']) {
add_action(is_admin() ? 'admin_init' : 'wp', [self::class, 'slimtrack'], 5);
if ('on' != self::$settings['ignore_wp_users']) {
add_action('login_init', [self::class, 'slimtrack'], 10);
}
}
// Slimstat tracks screen resolutions, outbound links and other client-side information using a client-side tracker
add_action(is_admin() ? 'admin_enqueue_scripts' : 'wp_enqueue_scripts', [self::class, 'enqueue_tracker'], 15);
if ('on' != self::$settings['ignore_wp_users']) {
add_action('login_enqueue_scripts', [self::class, 'enqueue_tracker'], 10);
}
add_filter('script_loader_tag', [self::class, 'add_defer_to_script_tag'], 10, 2);
}
// Hook a DB clean-up routine to the daily cronjob
add_action('wp_slimstat_purge', [self::class, 'wp_slimstat_purge']);
// Hook a GeoIP database update routine to the daily cronjob
add_action('wp_slimstat_update_geoip_database', [self::class, 'wp_slimstat_update_geoip_database']);
// Allow external domains on CORS requests
add_filter('allowed_http_origins', [self::class, 'open_cors_admin_ajax']);
// GDPR: Opt-out Ajax Handler
add_action('wp_ajax_slimstat_optout_html', [self::class, 'get_optout_html']);
add_action('wp_ajax_nopriv_slimstat_optout_html', [self::class, 'get_optout_html']);
// If this request was a redirect, we should update the content type accordingly
add_filter('wp_redirect_status', [self::class, 'update_content_type'], 10, 2);
// Shortcodes
add_shortcode('slimstat', [self::class, 'slimstat_shortcode'], 15);
// Init the plugin functionality
add_action('init', [self::class, 'init_plugin']);
// REST API Support
add_action('rest_api_init', [self::class, 'register_rest_route']);
// Rewrite rule for static tracker
add_action('init', [self::class, 'rewrite_rule_tracker']);
add_action('template_redirect', [self::class, 'adblocker_javascript']);
// Load the admin library
if (is_user_logged_in()) {
include_once(plugin_dir_path(__FILE__) . 'src/Constants.php');
include_once(plugin_dir_path(__FILE__) . 'admin/index.php');
add_action('init', ['wp_slimstat_admin', 'init'], 60);
}
}
// end init
/**
* Reads and processes the data received by the XHR tracker
*/
public static function slimtrack_ajax()
{
// If the website is using a caching plugin, the tracking code might still be there, even if the user turned off tracking
if ('on' != self::$settings['is_tracking']) {
exit(self::_log_error(204));
}
$id = 0;
self::$data_js = apply_filters('slimstat_filter_pageview_data_js', self::$raw_post_array);
$site_host = parse_url(get_site_url(), PHP_URL_HOST);
self::$stat['referer'] = '';
if (!empty(self::$data_js['ref'])) {
self::$stat['referer'] = self::_base64_url_decode(self::$data_js['ref']);
$parsed_ref = parse_url(self::$stat['referer'], PHP_URL_HOST);
if (false === $parsed_ref) {
exit(self::_log_error(201));
}
}
// Do we have an id for this request? If we do, we are either updating an existing pageview, or recording an event on the page
if (!empty(self::$data_js['id'])) {
// Make sure that the control code is valid
self::$data_js['id'] = self::_get_value_without_checksum(self::$data_js['id']);
if (false === self::$data_js['id']) {
exit(self::_log_error(101));
}
self::$stat['id'] = intval(self::$data_js['id']);
if (self::$stat['id'] < 0) {
do_action('slimstat_track_exit_' . abs(self::$stat['id']));
exit(self::_get_value_with_checksum(self::$stat['id']));
}
// If self::$data_js[ 'pos' ] is empty, update an existing pageview with client-based information (resolution, server latency, etc)
if (empty(self::$data_js['pos'])) {
self::_set_visit_id(true);
// Retrieves all the client-side info (screen resolution, server latency, etc) and sets the corresponding entries in self::$stat
self::$stat = self::_get_client_info(self::$data_js, self::$stat);
// Visitor is still on this page, record the timestamp in the corresponding field if this WAS NOT a request to update a "server-side" pageview with client-side info
if (empty(self::$stat['resolution'])) {
// Heartbeat / finalize update of dt_out
if (!empty(self::$data_js['hb'])) {
// Use provided ts if valid, else current time
$heartbeat_ts = 0;
if (!empty(self::$data_js['ts'])) {
$heartbeat_ts = intval(self::$data_js['ts']);
}
if ($heartbeat_ts > 0 && $heartbeat_ts <= (time() + 300)) { // sanity: not too far future
self::$stat['dt_out'] = $heartbeat_ts;
} else {
self::$stat['dt_out'] = self::date_i18n('U');
}
} else {
self::$stat['dt_out'] = self::date_i18n('U');
}
}
// Is this a new visitor, based on his fingerprint?
if (!empty(self::$stat['fingerprint']) && self::_is_new_visitor(self::$stat['fingerprint'])) {
self::$stat['notes'] = ['new:yes'];
}
$id = self::_update_row(self::$stat);
} // ... otherwise, is this an event: a click on a link (maybe a 'download'?) or other user action
else {
// Record the event
$event_info = [
'position' => strip_tags(trim(self::$data_js['pos'])),
'id' => self::$stat['id'],
'dt' => self::date_i18n('U'),
];
if (!empty(self::$data_js['no'])) {
$event_info['notes'] = self::_base64_url_decode(self::$data_js['no']);
}
/**
* Allow third-party tools to decide whether to track this event or not
*
* @param bool $shouldEventBeTracked
* @param array $event_info
*
* @return bool
*
* @since 5.2.6
*/
$shouldEventBeTracked = apply_filters('slimstat_track_event_enabled', true, $event_info);
if ($shouldEventBeTracked) {
self::_insert_row($event_info, $GLOBALS['wpdb']->prefix . 'slim_events');
}
if (!empty(self::$data_js['res'])) {
$resource = self::_base64_url_decode(self::$data_js['res']);
$parsed_resource = parse_url($resource);
if (false === $parsed_resource || empty($parsed_resource['host'])) {
exit(self::_log_error(203));
}
// Is this a download? If it is, add a new record to the database
if (!empty($parsed_resource['path']) && in_array(pathinfo($parsed_resource['path'], PATHINFO_EXTENSION), self::string_to_array(self::$settings['extensions_to_track']))) {
self::$stat['resource'] = $parsed_resource['path'] . (empty($parsed_resource['query']) ? '' : '?' . $parsed_resource['query']);
self::$stat['content_type'] = 'download';
if (!empty(self::$data_js['fh'])) {
self::$stat['fingerprint'] = sanitize_text_field(self::$data_js['fh']);
}
$id = self::slimtrack();
} // .. or outbound link? If so, update the pageview with the new info
elseif ($parsed_resource['host'] != $site_host) {
self::$stat['outbound_resource'] = sanitize_url($resource);
// Visitor is still on this page, record the timestamp in the corresponding field
self::$stat['dt_out'] = self::date_i18n('U');
$id = self::_update_row(self::$stat);
}
} else {
// Visitor is still on this page, record the timestamp in the corresponding field
self::$stat['dt_out'] = self::date_i18n('U');
$id = self::_update_row(self::$stat);
}
}
} // If self::$data_js[ 'id' ] is empty, we are tracking a new pageview
else {
self::$stat['resource'] = '';
if (!empty(self::$data_js['res'])) {
self::$stat['resource'] = self::_base64_url_decode(self::$data_js['res']);
if (false === parse_url(self::$stat['resource'])) {
exit(self::_log_error(203));
}
}
// Retrieves all the client-side info (screen resolution, server latency, etc) and sets the corresponding entries in self::$stat
self::$stat = self::_get_client_info(self::$data_js, self::$stat);
if (!empty(self::$data_js['ci'])) {
self::$data_js['ci'] = self::_get_value_without_checksum(self::$data_js['ci']);
if (false === self::$data_js['ci']) {
exit(self::_log_error(102));
}
$content_info = @unserialize(self::_base64_url_decode(self::$data_js['ci']));
if (empty($content_info) || !is_array($content_info)) {
exit(self::_log_error(103));
}
foreach (['content_type', 'category', 'content_id', 'author'] as $a_key) {
if (!empty($content_info[$a_key]) && 'content_id' !== $a_key) {
self::$stat[$a_key] = sanitize_text_field($content_info[$a_key]);
} elseif (!empty($content_info[$a_key])) {
self::$stat[$a_key] = absint($content_info[$a_key]);
}
}
} // ... otherwise we'll track this as an external page
else {
self::$stat['content_type'] = 'external';
}
// Is this a new visitor, based on his fingerprint?
if (!empty(self::$stat['fingerprint']) && self::_is_new_visitor(self::$stat['fingerprint'])) {
self::$stat['notes'] = ['new:yes'];
}
// Track the rest of the information related to this pageview
$id = self::slimtrack();
}
// Was this pageview tracked?
if (empty($id)) {
exit(0);
}
// Send the ID back to Javascript to track future interactions
do_action('slimstat_track_success');
exit(self::_get_value_with_checksum($id));
}
// end slimtrack_ajax
/**
* The main logging function
*
* @param string $message The message to be logged.
* @param string $level The log level (e.g., 'info', 'warning', 'error'). Default is 'info'.
*
* @uses error_log
*/
public static function log($message, $level = 'info')
{
if (is_array($message)) {
$message = wp_json_encode($message);
}
$log_level = strtoupper($level);
// Log when debug is enabled
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log(sprintf('[WP SLIMSTAT] [%s]: %s', $log_level, $message));
}
}
/**
* Rewrite rule for static tracker
*/
public static function rewrite_rule_tracker()
{
if ('adblock_bypass' === (self::$settings['tracking_request_method'] ?? 'rest')) {
add_rewrite_tag('%slimstat_tracker%', '([a-f0-9]{32})');
add_rewrite_rule(
'^([a-f0-9]{32})\\.js$',
'index.php?slimstat_tracker=$matches[1]',
'top'
);
}
}
/**
* Function to detect if Adblock is enabled and serve the JS tracker
*/
public static function adblocker_javascript()
{
// Only handle the tracker JS endpoint if adblock bypass is enabled
if ('adblock_bypass' !== (self::$settings['tracking_request_method'] ?? 'rest')) {
return;
}
$tracker_hash = get_query_var('slimstat_tracker');
if ($tracker_hash && $tracker_hash === md5(site_url() . 'slimstat')) {
// Set the content type to JavaScript
header('Content-Type: application/javascript');
// Set caching headers for one year
header('Cache-Control: public, max-age=31536000');
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + 31536000) . ' GMT');
$js_path = plugin_dir_path(__FILE__) . '/wp-slimstat.min.js';
if (file_exists($js_path)) {
readfile($js_path);
exit;
} else {
status_header(404);
echo '// Tracker not found';
exit;
}
}
}
/**
* THE Slimstat tracker
*/
public static function slimtrack()
{
self::$stat['dt'] = self::date_i18n('U');
if (empty(self::$stat['notes'])) {
self::$stat['notes'] = [];
}
// Allow third-party tools to initialize the stat array
self::$stat = apply_filters('slimstat_filter_pageview_stat_init', self::$stat);
// Third-party tools can decide that this pageview should not be tracked, by setting its datestamp to zero
if (empty(self::$stat) || empty(self::$stat['dt'])) {
return false;
}
// Reset the pageview ID, if it's set for some obscure reason
unset(self::$stat['id']);
// Opt-out of tracking via cookie
if ('on' == self::$settings['display_opt_out']) {
$cookie_names = ['slimstat_optout_tracking' => 'true'];
if (!empty(self::$settings['opt_out_cookie_names'])) {
$cookie_names = [];
foreach (self::string_to_array(self::$settings['opt_out_cookie_names']) as $a_cookie_pair) {
[$name, $value] = explode('=', $a_cookie_pair);
if ('' !== $name && '0' !== $name && ('' !== $value && '0' !== $value)) {
$cookie_names[$name] = $value;
}
}
}
foreach ($cookie_names as $a_name => $a_value) {
if (isset($_COOKIE[$a_name]) && false !== strpos($_COOKIE[$a_name], $a_value)) {
// remove slimstat cookie
unset($_COOKIE['slimstat_tracking_code']);
@setcookie(
'slimstat_tracking_code',
'',
['expires' => time() - (15 * 60), 'path' => COOKIEPATH]
);
return false;
}
}
}
// Opt-in tracking via cookie (only those who have a cookie will be tracked)
if (!empty(self::$settings['opt_in_cookie_names'])) {
$cookie_names = [];
$opt_in_cookie_names = self::string_to_array(self::$settings['opt_in_cookie_names']);
foreach ($opt_in_cookie_names as $a_cookie_pair) {
[$name, $value] = explode('=', $a_cookie_pair);
if ('' !== $name && '0' !== $name && ('' !== $value && '0' !== $value)) {
$cookie_names[$name] = $value;
}
}
$cookie_found = false;
foreach ($cookie_names as $a_name => $a_value) {
if (isset($_COOKIE[$a_name]) && false !== strpos($_COOKIE[$a_name], $a_value)) {
$cookie_found = true;
}
}
if (!$cookie_found) {
// remove slimstat cookie
unset($_COOKIE['slimstat_tracking_code']);
@setcookie(
'slimstat_tracking_code',
'',
['expires' => time() - (15 * 60), 'path' => COOKIEPATH]
);
return false;
}
}
// IP address
[self::$stat['ip'], self::$stat['other_ip']] = self::_get_remote_ip();
if (empty(self::$stat['ip']) || '0.0.0.0' == self::$stat['ip']) {
$error = self::_log_error(202);
return false;
}
// Should we ignore this IP address?
foreach (self::string_to_array(self::$settings['ignore_ip']) as $a_ip_range) {
$ip_to_ignore = $a_ip_range;
if (false !== strpos($ip_to_ignore, '/')) {
[$ip_to_ignore, $cidr_mask] = explode('/', trim($ip_to_ignore));
} else {
$cidr_mask = self::_get_mask_length($ip_to_ignore);
}
$long_masked_ip_to_ignore = substr(self::_dtr_pton($ip_to_ignore), 0, $cidr_mask);
$long_masked_user_ip = substr(self::_dtr_pton(self::$stat['ip']), 0, $cidr_mask);
$long_masked_user_other_ip = substr(self::_dtr_pton(self::$stat['other_ip']), 0, $cidr_mask);
if ($long_masked_user_ip === $long_masked_ip_to_ignore || $long_masked_user_other_ip === $long_masked_ip_to_ignore) {
return false;
}
}
// Do we need to anonymize this IP address?
if ('on' == self::$settings['anonymize_ip']) {
self::$stat['ip'] = wp_privacy_anonymize_ip(self::$stat['ip']);
if (!empty(self::$stat['other_ip'])) {
self::$stat['other_ip'] = wp_privacy_anonymize_ip(self::$stat['other_ip']);
}
}
// Resource URL
if (!isset(self::$stat['resource'])) {
self::$stat['resource'] = self::get_request_uri();
}
// Decode the URL and sanitize it to ensure it's safe and properly formatted
self::$stat['resource'] = sanitize_text_field(urldecode(self::$stat['resource']));
// Re-encode non-ASCII chars, preserving ASCII and slashes for backwards compatibility
self::$stat['resource'] = preg_replace_callback('/[^\x20-\x7E]/', fn ($match) => '%' . bin2hex($match[0]), self::$stat['resource']);
// Is this a 'seriously malformed' URL?
$parsed_url = parse_url(self::$stat['resource']);
if (!$parsed_url) {
$error = self::_log_error(203);
return false;
}
// Don't store the domain name in the database
self::$stat['resource'] = $parsed_url['path'] . (empty($parsed_url['query']) ? '' : '?' . $parsed_url['query']) . (empty($parsed_url['fragment']) ? '' : '#' . $parsed_url['fragment']);
// Is this resource blacklisted?
if (!empty(self::$settings['ignore_resources']) && self::_is_blacklisted(self::$stat['resource'], self::$settings['ignore_resources'])) {
return false;
}
// Referrer URL
if (empty(self::$stat['referer']) && !empty($_SERVER['HTTP_REFERER'])) {
self::$stat['referer'] = sanitize_url(wp_unslash($_SERVER['HTTP_REFERER']));
}
if (!empty(self::$stat['referer'])) {
// Is this a 'seriously malformed' URL?
$parsed_url = parse_url(self::$stat['referer']);
if (!$parsed_url) {
$error = self::_log_error(201);
return false;
}
if (isset($parsed_url['scheme']) && ('' !== $parsed_url['scheme'] && '0' !== $parsed_url['scheme']) && !in_array(strtolower($parsed_url['scheme']), ['http', 'https', 'android-app'])) {
self::$stat['notes'][] = sprintf(__('Attempted XSS Injection: %s', 'wp-slimstat'), self::$stat['referer']);
unset(self::$stat['referer']);
}
// Is this referer blacklisted?
if (!empty(self::$settings['ignore_referers']) && self::_is_blacklisted(self::$stat['referer'], self::$settings['ignore_referers'])) {
return false;
}
// Search terms
self::$stat['searchterms'] = self::_get_search_terms(self::$stat['referer']);
// Are we storing internal referrers in the database?
$parsed_site_url = parse_url(get_site_url(), PHP_URL_HOST);
if (isset($parsed_url['host']) && ('' !== $parsed_url['host'] && '0' !== $parsed_url['host']) && $parsed_url['host'] == $parsed_site_url && 'on' != self::$settings['track_same_domain_referers']) {
unset(self::$stat['referer']);
}
}
// Internal WP search?
if (empty(self::$stat['searchterms']) && !empty($_POST['s'])) {
self::$stat['searchterms'] = sanitize_text_field(str_replace('\\', '', $_REQUEST['s']));
}
// If this function was called by the js tracker (client mode), we've already determined this pageview's content information
if (!isset(self::$stat['content_type'])) {
$content_info = self::_get_content_info();
// Is this content type blacklisted?
if (!empty(self::$settings['ignore_content_types']) && self::_is_blacklisted($content_info['content_type'], self::$settings['ignore_content_types'])) {
return false;
}
if (is_array($content_info)) {
self::$stat += $content_info;
}
}
// Number of results from query_posts
if ((is_archive() || is_search()) && !empty($GLOBALS['wp_query']->found_posts)) {
self::$stat['notes'][] = 'results:' . intval($GLOBALS['wp_query']->found_posts);
}
// Do not track report pages in the admin
if ((!empty(self::$stat['resource']) && false !== strpos(self::$stat['resource'], 'wp-admin/admin-ajax.php')) || (!empty($_GET['page']) && false !== strpos($_GET['page'], 'slimview'))) {
return false;
}
// Should we ignore this user?
if (!empty($GLOBALS['current_user']->ID)) {
// Don't track logged-in users, if the corresponding option is enabled
if ('on' == self::$settings['ignore_wp_users']) {
return false;
}
// Don't track users with given capabilities
foreach ($GLOBALS['current_user']->roles as $a_capability) {
if (self::_is_blacklisted($a_capability, self::$settings['ignore_capabilities'])) {
return false;
}
}
// Is this user blacklisted?
if (!empty(self::$settings['ignore_users']) && self::_is_blacklisted($GLOBALS['current_user']->data->user_login, self::$settings['ignore_users'])) {
return false;
}
self::$stat['username'] = $GLOBALS['current_user']->data->user_login;
self::$stat['email'] = $GLOBALS['current_user']->data->user_email;
self::$stat['notes'][] = 'user:' . $GLOBALS['current_user']->data->ID;
$not_spam = true;
} elseif (isset($_COOKIE['comment_author_' . COOKIEHASH])) {
// Is this a spammer?
$spam_comment = self::$wpdb->get_row(self::$wpdb->prepare('
SELECT comment_author, comment_author_email, COUNT(*) comment_count
FROM `' . DB_NAME . "`.{$GLOBALS['wpdb']->comments}
WHERE comment_author_IP = %s AND comment_approved = 'spam'
GROUP BY comment_author
LIMIT 0,1", self::$stat['ip']), ARRAY_A);
if (!empty($spam_comment['comment_count'])) {
if ('on' == self::$settings['ignore_spammers']) {
return false;
} else {
self::$stat['notes'][] = 'spam:yes';
self::$stat['username'] = $spam_comment['comment_author'];
self::$stat['email'] = $spam_comment['comment_author_email'];
}
} else {
if (!empty($_COOKIE['comment_author_' . COOKIEHASH])) {
self::$stat['username'] = sanitize_user($_COOKIE['comment_author_' . COOKIEHASH]);
}
if (!empty($_COOKIE['comment_author_email_' . COOKIEHASH])) {
self::$stat['email'] = sanitize_email($_COOKIE['comment_author_email_' . COOKIEHASH]);
}
}
}
// Language
self::$stat['language'] = self::_get_language();
// Is this language blacklisted?
if (!empty(self::$stat['language']) && !empty(self::$settings['ignore_languages']) && false !== stripos(self::$settings['ignore_languages'], (string) self::$stat['language'])) {
return false;
}
// Geolocation
$geographicProvider = new \SlimStat\Services\GeoService();
if ($geographicProvider->isGeoIPEnabled()) {
try {
$geolocation_data = \SlimStat\Services\GeoIP::loader(self::$stat['ip']);
} catch (Exception $e) {
self::_log_error(205);
return false;
}
if (!empty($geolocation_data['country']['iso_code']) && 'xx' != $geolocation_data['country']['iso_code']) {
self::$stat['country'] = strtolower($geolocation_data['country']['iso_code']);
if (!empty($geolocation_data['city']['names']['en'])) {
self::$stat['city'] = $geolocation_data['city']['names']['en'];
}
if (!empty($geolocation_data['subdivisions'][0]['iso_code']) && !empty(self::$stat['city'])) {
self::$stat['city'] .= ' (' . $geolocation_data['subdivisions'][0]['iso_code'] . ')';
}
if (!empty($geolocation_data['location']['latitude']) && !empty($geolocation_data['location']['longitude'])) {
self::$stat['location'] = $geolocation_data['location']['latitude'] . ',' . $geolocation_data['location']['longitude'];
}
}
// Is this country blacklisted?
if (!empty(self::$stat['country']) && !empty(self::$settings['ignore_countries']) && false !== stripos(self::$settings['ignore_countries'], (string) self::$stat['country'])) {
return false;
}
}
// Mark or ignore Firefox/Safari prefetching requests (X-Moz: Prefetch and X-purpose: Preview)
if ((isset($_SERVER['HTTP_X_MOZ']) && ('prefetch' === strtolower($_SERVER['HTTP_X_MOZ']))) || (isset($_SERVER['HTTP_X_PURPOSE']) && ('preview' === strtolower($_SERVER['HTTP_X_PURPOSE'])))) {
if ('on' == self::$settings['ignore_prefetch']) {
return false;
} else {
self::$stat['notes'][] = 'pre:yes';
}
}
// User Agent
$browser = \SlimStat\Services\Browscap::get_browser();
// Are we ignoring bots?
if ('on' == self::$settings['ignore_bots'] && 1 == $browser['browser_type']) {
return false;
}
// Is this browser blacklisted?
if (!empty(self::$settings['ignore_browsers']) && self::_is_blacklisted([$browser['browser'], $browser['user_agent']], self::$settings['ignore_browsers'])) {
return false;
}
// Is this operating system blacklisted?
if (!empty(self::$settings['ignore_platforms']) && self::_is_blacklisted($browser['platform'], self::$settings['ignore_platforms'])) {
return false;
}
self::$stat += $browser;
// Do we need to assign a visit_id to this user?
$cookie_has_been_set = self::_set_visit_id(false);
// Allow third-party tools to modify all the data we've gathered so far
self::$stat = apply_filters('slimstat_filter_pageview_stat', self::$stat);
do_action('slimstat_track_pageview', self::$stat);
// Third-party tools can decide that this pageview should not be tracked, by setting its datestamp to zero
if (empty(self::$stat) || empty(self::$stat['dt'])) {
return false;
}
// Implode the notes
if (!empty(self::$stat['notes'])) {
self::$stat['notes'] = '[' . implode('][', self::$stat['notes']) . ']';
}
// Remove empty values
self::$stat = array_filter(self::$stat);
// Save this information in the database
self::$stat['id'] = self::_insert_row(self::$stat, $GLOBALS['wpdb']->prefix . 'slim_stats');
// Did something go wrong during the insert?
if (empty(self::$stat['id'])) {
// Attempt to init the environment (plugin just activated on a blog in a MU network?)
include_once(plugin_dir_path(__FILE__) . 'admin/index.php');
wp_slimstat_admin::init_environment();
// Now let's try again
self::$stat['id'] = self::_insert_row(self::$stat, $GLOBALS['wpdb']->prefix . 'slim_stats');
if (empty(self::$stat['id'])) {
$error = self::_log_error(200);
return false;
}
}
// Does this visitor have a visit_id cookie?
$set_cookie = apply_filters('slimstat_set_visit_cookie', (!empty(self::$settings['set_tracker_cookie']) && 'on' == self::$settings['set_tracker_cookie']));
if ($set_cookie) {
if (empty(self::$stat['visit_id']) && !empty(self::$stat['id'])) {
// Set a cookie to track this visit (Google and other non-human engines will just ignore it)
@setcookie(
'slimstat_tracking_code',
self::_get_value_with_checksum(self::$stat['id'] . 'id'),
['expires' => time() + 2678400, 'path' => COOKIEPATH]
);
} elseif (!$cookie_has_been_set && 'on' == self::$settings['extend_session'] && self::$stat['visit_id'] > 0) {
@setcookie(
'slimstat_tracking_code',
self::_get_value_with_checksum(self::$stat['visit_id']),
['expires' => time() + self::$settings['session_duration'], 'path' => COOKIEPATH]
);
}
}
return self::$stat['id'];
}
// end slimtrack
/**
* Decodes the permalink
*/
public static function get_request_uri()
{
$request_url = '';
if (isset($_SERVER['REQUEST_URI'])) {
return urldecode(sanitize_url(wp_unslash($_SERVER['REQUEST_URI'])));
} elseif (isset($_SERVER['SCRIPT_NAME'])) {
$request_url = sanitize_text_field(wp_unslash($_SERVER['SCRIPT_NAME']));
} elseif (isset($_SERVER['PHP_SELF'])) {
$request_url = sanitize_text_field(wp_unslash($_SERVER['PHP_SELF']));
}
if (isset($_SERVER['QUERY_STRING'])) {
$request_url .= '?' . sanitize_text_field(wp_unslash($_SERVER['QUERY_STRING']));
}
return $request_url;
}
// end get_request_uri
public static function is_local_ip_address($ip_address = '')
{
return !filter_var($ip_address, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE|FILTER_FLAG_NO_RES_RANGE);
}
/**
* Implements the Slimstat Shortcode API
*/
public static function slimstat_shortcode($_attributes = '', $_content = '')
{
shortcode_atts([
'f' => '', // recent, popular, count, widget
'w' => '', // column to use (for recent, popular and count) or widget to use
's' => ' ', // separator
'o' => 0, // offset for counters
], $_attributes);
$f = $_attributes['f'] ?? '';
$w = $_attributes['w'] ?? '';
$s = $_attributes['s'] ?? '';
$o = $_attributes['o'] ?? 0;
$output = '';
$where = '';
$as_column = '';
$s = sprintf("<span class='slimstat-item-separator'>%s</span>", $s);
// Load the localization files (for languages, operating systems, etc)
load_plugin_textdomain('wp-slimstat', false, '/wp-slimstat/languages');
// Look for required fields
if (empty($f) || empty($w)) {
return '<!-- Slimstat Shortcode Error: missing parameter -->';
}
// Validation the parameter w
if (false == in_array($w, ['count', 'display_name', 'hostname', 'post_link', 'post_link_no_qs', 'dt', 'username', 'post_link', 'ip', 'id', 'searchterms', 'username', 'resource', 'slim_p1_01', 'slim_p1_03', 'slim_p1_04', 'slim_p1_06', 'slim_p1_08', 'slim_p1_10', 'slim_p1_11', 'slim_p1_12', 'slim_p1_13', 'slim_p1_15', 'slim_p1_17', 'slim_p1_18', 'slim_p1_19_01', 'slim_p2_01', 'slim_p2_02', 'slim_p2_03', 'slim_p2_04', 'slim_p2_05', 'slim_p2_06', 'slim_p2_07', 'slim_p2_08', 'slim_p2_12', 'slim_p2_13', 'slim_p2_14', 'slim_p2_15', 'slim_p2_16', 'slim_p2_17', 'slim_p2_18', 'slim_p2_19', 'slim_p2_20', 'slim_p2_21', 'slim_p2_22_01', 'slim_p2_24', 'slim_p2_25', 'slim_p3_01', 'slim_p3_02', 'slim_p4_01', 'slim_p4_02', 'slim_p4_04', 'slim_p4_05', 'slim_p4_06', 'slim_p4_07', 'slim_p4_09', 'slim_p4_10', 'slim_p4_11', 'slim_p4_12', 'slim_p4_13', 'slim_p4_15', 'slim_p4_16', 'slim_p4_18', 'slim_p4_19', 'slim_p4_20', 'slim_p4_21', 'slim_p4_22', 'slim_p4_23', 'slim_p4_24', 'slim_p4_25', 'slim_p4_26_01', 'slim_p4_27', 'slim_p6_01', 'slim_p2_23'])) {
return '<!-- Slimstat Shortcode Error: invalid parameter for w -->';
}
// Include the Reports Library, but don't initialize the database, since we will do that separately later
include_once(plugin_dir_path(__FILE__) . 'admin/view/wp-slimstat-reports.php');
wp_slimstat_reports::init();
/**
* @SecurityProfile https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-0630
* Disabled because of the report from WP Scan
*/
// Init the database library with the appropriate filters
/*if ( strpos ( $_content, 'WHERE:' ) !== false ) {
$where = html_entity_decode( str_replace( 'WHERE:', '', $_content ), ENT_QUOTES, 'UTF-8' );
}
else{*/
wp_slimstat_db::init(html_entity_decode($_content, ENT_QUOTES, 'UTF-8'));
//}
switch ($f) {
case 'count':
case 'count-all':
$output = wp_slimstat_db::count_records($w, $where, false === strpos($f, 'all')) + $o;
break;
case 'widget':
if (empty(wp_slimstat_reports::$reports[$w])) {
return __('Invalid Report ID', 'wp-slimstat');
}
wp_register_style('wp-slimstat-frontend', plugins_url('/admin/assets/css/slimstat.css', __FILE__), true, SLIMSTAT_ANALYTICS_VERSION);
wp_enqueue_style('wp-slimstat-frontend');
wp_slimstat_reports::$reports[$w]['callback_args']['is_widget'] = true;
ob_start();
echo wp_slimstat_reports::report_header($w);
call_user_func(wp_slimstat_reports::$reports[$w]['callback'], wp_slimstat_reports::$reports[$w]['callback_args']);
wp_slimstat_reports::report_footer();
$output = ob_get_contents();
ob_end_clean();
break;
case 'recent':
case 'recent-all':
case 'top':
case 'top-all':
$function = 'get_' . str_replace('-all', '', $f);
if ('*' == $w) {
$w = 'id';
}
$w = esc_html($w);
$w = self::string_to_array($w);
// Some columns are 'special' and need be removed from the list
$w_clean = array_diff($w, ['count', 'display_name', 'hostname', 'post_link', 'post_link_no_qs', 'dt']);
// The special value 'display_name' requires the username to be retrieved
if (in_array('display_name', $w)) {
$w_clean[] = 'username';
}
// The special value 'post_list' requires the resource to be retrieved
if (in_array('post_link', $w)) {
$w_clean[] = 'resource';
}
// The special value 'post_list_no_qs' requires a substring to be calculated
if (in_array('post_link_no_qs', $w)) {
$w_clean = ['SUBSTRING_INDEX( resource, "' . (get_option('permalink_structure') ? '?' : '&') . '", 1 )'];
$as_column = 'resource';
}
// Retrieve the data
$results = wp_slimstat_db::$function(implode(', ', $w_clean), $where, '', false === strpos($f, 'all'), $as_column);
// No data? No problem!
if (empty($results)) {
return '<!-- Slimstat Shortcode: No Data -->';
}
// Are nice permalinks enabled?
$permalinks_enabled = get_option('permalink_structure');
// Format results
$output = [];
foreach ($results as $result_idx => $a_result) {
foreach ($w as $a_column) {
$output[$result_idx][$a_column] = sprintf("<span class='col-%s'>", $a_column);
switch ($a_column) {
case 'count':
$output[$result_idx][$a_column] .= $a_result['counthits'];
break;
case 'country':
$output[$result_idx][$a_column] .= wp_slimstat_i18n::get_string('c-' . $a_result[$a_column]);
break;
case 'display_name':
$user_details = get_user_by('login', $a_result['username']);
if (!empty($user_details)) {
$output[$result_idx][$a_column] .= $user_details->display_name;
} else {
$output[$result_idx][$a_column] .= $a_result['username'];
}
break;
case 'dt':
$output[$result_idx][$a_column] .= date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $a_result['dt']);
break;
case 'hostname':
$output[$result_idx][$a_column] .= self::gethostbyaddr($a_result['ip']);
break;
case 'language':
$output[$result_idx][$a_column] .= wp_slimstat_i18n::get_string('l-' . $a_result[$a_column]);
break;
case 'platform':
$output[$result_idx][$a_column] .= wp_slimstat_i18n::get_string($a_result[$a_column]);
break;
case 'post_link':
case 'post_link_no_qs':
$post_id = url_to_postid($a_result['resource']);
if ($post_id > 0) {
$output[$result_idx][$a_column] .= sprintf("<a href='%s'>", esc_url( $a_result[ 'resource' ] )) . esc_html( get_the_title($post_id) ) . '</a>';
} else {
$output[$result_idx][$a_column] .= sprintf("<a href='%s'>%s</a>", esc_url( $a_result[ 'resource' ] ), esc_html( $a_result[ 'resource' ] ));
}
break;
default:
$output[$result_idx][$a_column] .= $a_result[$a_column] ?? '';
break;
}
$output[$result_idx][$a_column] .= '</span>';
}
$output[$result_idx] = '<li>' . implode($s, $output[$result_idx]) . '</li>';
}
$output = '<ul class="slimstat-shortcode ' . $f . implode('-', $w) . '">' . implode('', $output) . '</ul>';
break;
default:
break;
}
return $output;
}
// end slimstat_shortcode
public static function init_plugin()
{
// Include our browser detector library
\SlimStat\Services\Browscap::init();
// Make sure the upload directory is exist and is protected.
self::create_upload_directory();
}
/**
* Opens given domains during CORS requests to admin-ajax.php
*/
public static function open_cors_admin_ajax($_allowed_origins = [])
{
$exploded_domains = self::string_to_array(self::$settings['external_domains']);
if (!empty($exploded_domains) && !empty($exploded_domains[0])) {
$_allowed_origins = array_merge($_allowed_origins, $exploded_domains);
}
return $_allowed_origins;
}
// end open_cors_admin_ajax
/**
* Implements a REST API interface to retrieve Slimstat reports and metrics
*/
public static function rest_api_response($_request = [])
{
$filters = '';
if (!empty($_request['filters'])) {
$filters = $_request['filters'];
}
if (empty($_request['dimension'])) {
return new WP_Error('rest_invalid', esc_html__('[REST API] The <code>dimension</code> parameter is required. Please review your request and try again.', 'wp-slimstat'), ['status' => 400]);
}
if (empty($_request['function'])) {
return new WP_Error('rest_invalid', esc_html__('[REST API] The <code>function</code> parameter is required. Please review your request and try again.', 'wp-slimstat'), ['status' => 400]);
}
include_once(plugin_dir_path(__FILE__) . 'admin/view/wp-slimstat-db.php');
wp_slimstat_db::init($filters);
$response = [
'function' => htmlentities($_request['function'], ENT_QUOTES, 'UTF-8'),
'dimension' => htmlentities($_request['dimension'], ENT_QUOTES, 'UTF-8'),
'data' => 0,
];
switch ($_request['function']) {
case 'count':
case 'count-all':
$response['data'] = wp_slimstat_db::count_records($_request['dimension'], '', false === strpos($_request['function'], '-all'));
break;
case 'recent':
case 'recent-all':
case 'top':
case 'top-all':
$function = 'get_' . str_replace('-all', '', $_request['function']);
// Retrieve the data
$response['data'] = array_values(wp_slimstat_db::$function($_request['dimension'], '', '', false === strpos($_request['function'], '-all')));
break;
default:
// This should never happen, because of the 'enum' condition for this parameter. But never say never...
$response['data'] = new WP_Error('rest_invalid', esc_html__('[REST API] You sent an invalid request. Accepted function values include: <code>count, count-all, recent, recent-all, top and top-all</code>. Please review your request and try again.', 'wp-slimstat'), ['status' => 400]);
break;
}
return rest_ensure_response($response);
}
// end rest_api_response
/**
* Implements a REST API authentication mechanism via token
*/
public static function rest_api_authorization($_request = [])
{
if (empty($_request['token'])) {
return new WP_Error('rest_invalid', esc_html__('[REST API] Please use a valid token in order to access the REST API endpoint at this URL.', 'wp-slimstat'), ['status' => 400]);
}
return in_array($_request['token'], self::string_to_array(self::$settings['rest_api_tokens']));
}
// end rest_api_authorization
/**
* Registers a new REST API route for the Slimstat endpoint
*/
public static function register_rest_route()
{
register_rest_route('slimstat/v1', '/get', [
'methods' => WP_REST_Server::READABLE,
'callback' => [self::class, 'rest_api_response'],
'permission_callback' => [self::class, 'rest_api_authorization'],
'args' => [
'token' => [
'description' => __('You will need to specify a valid token to be able to query the data. Tokens are defined in Slimstat > Settings > Access Control.', 'wp-slimstat'),
'type' => 'string',
],
'function' => [
'description' => __('This parameter specifies the type of QUERY you would like to perform. Accepted funciton values include: count, count-all, recent, recent-all, top and top-all.', 'wp-slimstat'),
'type' => 'string',
'enum' => ['count', 'count-all', 'recent', 'recent-all', 'top', 'top-all'],
],
'dimension' => [
'description' => __('This parameter indicates what dimension to return: * (all data), ip, resource, browser, operating system, etc. You can only specify one dimension at a time.', 'wp-slimstat'),
'type' => 'string',
'enum' => ['*', 'id', 'ip', 'username', 'email', 'country', 'referer', 'resource', 'searchterms', 'browser', 'platform', 'language', 'resolution', 'content_type', 'content_id', 'tz_offset', 'outbound_resource'],
],
'filters' => [
'description' => __('This parameter is used to filter a given dimension (resources, browsers, operating systems, etc) so that it satisfies certain conditions (i.e.: browser contains Chrome). Please make sure to urlencode this value, and to use the usual filter format: browser contains Chrome&&&referer contains slim (encoded: browser%20contains%20Chrome%26%26%26referer%20contains%20slim)', 'wp-slimstat'),
'type' => 'string',
],
],
]);
}
// end register_rest_route
/**
* Converts a series of comma separated values into an array
*/
public static function string_to_array($_option = '')
{
if (empty($_option) || !is_string($_option)) {
return [];
} else {
return array_filter(array_map('trim', explode(',', $_option)));
}
}
// end string_to_array
/**
* Toggles WordPress filters on date_i18n function
*/
public static function toggle_date_i18n_filters($_turn_on = true)
{
if ($_turn_on && !empty(self::$date_i18n_filters) && is_array(self::$date_i18n_filters)) {
foreach (self::$date_i18n_filters as $i18n_priority => $i18n_func_list) {
foreach ($i18n_func_list as $func_args) {
if (!empty($func_args['function']) && is_string($func_args['function'])) {
add_filter('date_i8n', $func_args['function'], $i18n_priority, intval($func_args['accepted_args']));
}
}
}
} elseif (!empty($GLOBALS['wp_filter']['date_i18n']['callbacks']) && is_array($GLOBALS['wp_filter']['date_i18n']['callbacks'])) {
self::$date_i18n_filters = $GLOBALS['wp_filter']['date_i18n']['callbacks'];
remove_all_filters('date_i18n');
}
}
// end toggle_date_i18n_filters
/**
* Calls the date_i18n function without filters
*/
public static function date_i18n($_format)
{
self::toggle_date_i18n_filters(false);
$date = date_i18n($_format);
self::toggle_date_i18n_filters(true);
return $date;
}
// end date_i18n
/**
* Sets the default values for all the options
*/
public static function init_options()
{
return [
'version' => SLIMSTAT_ANALYTICS_VERSION,
'secret' => wp_hash(uniqid(time(), true)),
'browscap_last_modified' => 0,
// General
// -----------------------------------------------------------------------
// General - Tracker
'is_tracking' => 'on',
'track_admin_pages' => 'no',
'javascript_mode' => 'on',
// General - WordPress Integration
'add_dashboard_widgets' => 'on',
'use_separate_menu' => 'no',
'add_posts_column' => 'no',
'posts_column_pageviews' => 'on',
// General - Database
'auto_purge' => 0,
'auto_purge_delete' => 'on',
// Tracker
// -----------------------------------------------------------------------
// Tracker - Data Protection
'anonymize_ip' => 'no',
'set_tracker_cookie' => 'on',
'display_opt_out' => 'no',
'opt_out_cookie_names' => '',
'opt_in_cookie_names' => '',
'opt_out_message' => '<p style="display:block;position:fixed;left:0;bottom:0;margin:0;padding:1em 2em;background-color:#eee;width:100%;z-index:99999;">This website stores cookies on your computer. These cookies are used to provide a more personalized experience and to track your whereabouts around our website in compliance with the European General Data Protection Regulation. If you decide to to opt-out of any future tracking, a cookie will be setup in your browser to remember this choice for one year.<br><br><a href="#" onclick="javascript:SlimStat.optout(event, false);">Accept</a> or <a href="#" onclick="javascript:SlimStat.optout(event, true);">Deny</a></p>',
// Tracker - Link Tracking
'track_same_domain_referers' => 'no',
'do_not_track_outbound_classes_rel_href' => 'noslimstat,ab-item',
'extensions_to_track' => 'pdf,doc,xls,zip',
// Tracker - Advanced Options
'geolocation_country' => 'on',
'session_duration' => 1800,
'extend_session' => 'no',
'enable_cdn' => 'no',
'ajax_relative_path' => 'no',
// Tracker - External Pages
'external_domains' => '',
// Reports
// -----------------------------------------------------------------------
// Reports - Functionality
'use_current_month_timespan' => 'no',
'posts_column_day_interval' => 28,
'rows_to_show' => '20',
'show_hits' => 'no',
'ip_lookup_service' => 'https://ip-api.com/#',
'comparison_chart' => 'on',
'show_display_name' => 'no',
'convert_resource_urls_to_titles' => 'on',
'convert_ip_addresses' => 'no',
// Reports - Access Log and World Map
'refresh_interval' => '60',
'number_results_raw_data' => '50',
'max_dots_on_map' => '50',
// Reports - Miscellaneous
'custom_css' => '',
'chart_colors' => '',
'mozcom_access_id' => '',
'mozcom_secret_key' => '',
'show_complete_user_agent_tooltip' => 'no',
'async_load' => 'no',
'limit_results' => '1000',
'enable_sov' => 'no',
// Exclusions
// -----------------------------------------------------------------------
// Exclusions - User Properties
'ignore_wp_users' => 'no',
'ignore_spammers' => 'on',
'ignore_bots' => 'no',
'ignore_prefetch' => 'on',
'ignore_users' => '',
'ignore_ip' => '',
'ignore_countries' => '',
'ignore_languages' => '',
'ignore_browsers' => '',
'ignore_platforms' => '',
'ignore_capabilities' => '',
// Exclusions - Page Properties
'ignore_resources' => '',
'ignore_referers' => '',
'ignore_content_types' => '',
// Access Control
// -----------------------------------------------------------------------
// Access Control - Reports
'restrict_authors_view' => 'on',
'capability_can_view' => 'manage_options',
'can_view' => '',
// Access Control - Reports
'tracking_request_method' => 'ajax',
// Access Control - Customizer
'capability_can_customize' => 'manage_options',
'can_customize' => '',
// Access Control - Settings
'capability_can_admin' => 'manage_options',
'can_admin' => '',
// Access Control - REST API
'rest_api_tokens' => wp_hash(uniqid(time() - 3600, true)),
// Maintenance
// -----------------------------------------------------------------------
'last_tracker_error' => [0, '', 0],
'show_sql_debug' => 'no',
'db_indexes' => 'on',
'enable_maxmind' => 'disable',
'maxmind_license_key' => '',
'enable_browscap' => 'no',
// Notices
// -----------------------------------------------------------------------
'notice_latest_news' => 'on',
'notice_browscap' => 'on',
'notice_geolite' => 'on',
'notice_caching' => 'on',
'notice_translate' => 'on',
// Network-wide Settings
'locked_options' => '',
];
}
// end init_options
/**
* Saves a given option in the database
*/
public static function update_option($_key = '', $_value = '')
{
if (!is_network_admin()) {
update_option($_key, $_value);
} else {
update_site_option($_key, $_value);
}
}
// end update_option
/**
* Attach a script to every page to track visitors' screen resolution and other browser-based information
*/
public static function enqueue_tracker()
{
// Use the new unified tracking method setting
$method = self::$settings['tracking_request_method'] ?? 'rest';
// Prepare URLs for all methods
$rest_url = rest_url('slimstat/v1/hit');
$ajax_url = admin_url('admin-ajax.php');
$ajax_url_relative = admin_url('admin-ajax.php', 'relative');
$adblock_hash = md5(site_url() . 'slimstat_request' . SLIMSTAT_ANALYTICS_VERSION);
$adblock_url = home_url(sprintf('request/%s/', $adblock_hash));
// Always provide all possible endpoints for fallback logic
$params = [
'transport' => $method,
'ajaxurl_rest' => $rest_url,
'ajaxurl_ajax' => ('on' == self::$settings['ajax_relative_path']) ? $ajax_url_relative : $ajax_url,
'ajaxurl_adblock' => $adblock_url,
];
// Set the primary ajaxurl based on the selected method
if ('rest' === $method) {
$params['ajaxurl'] = $rest_url;
} elseif ('ajax' === $method) {
$params['ajaxurl'] = ('on' == self::$settings['ajax_relative_path']) ? $ajax_url_relative : $ajax_url;
} elseif ('adblock_bypass' === $method) {
$params['ajaxurl'] = $adblock_url;
// Also set transport to 'adblock' for JS clarity
$params['transport'] = 'adblock';
} else {
$params['ajaxurl'] = $rest_url;
}
$baseurl = parse_url(get_home_url());
$params['baseurl'] = empty($baseurl['path']) ? '/' : $baseurl['path'];
if (!empty(self::$settings['do_not_track_outbound_classes_rel_href'])) {
$params['dnt'] = str_replace(' ', '', self::$settings['do_not_track_outbound_classes_rel_href']);
}
if ('on' == self::$settings['display_opt_out']) {
$params['oc'] = ['slimstat_optout_tracking'];
if (!empty(self::$settings['opt_out_cookie_names'])) {
foreach (self::string_to_array(self::$settings['opt_out_cookie_names']) as $a_cookie_pair) {
$params['oc'][] = substr($a_cookie_pair, 0, strpos($a_cookie_pair, '='));
}
}
$params['oc'] = implode(',', $params['oc']);
}
if ('on' != self::$settings['javascript_mode']) {
if (empty(self::$stat['id']) || intval(self::$stat['id']) < 0) {
return false;
}
$params['id'] = self::_get_value_with_checksum(intval(self::$stat['id']));
} else {
$params['ci'] = self::_get_value_with_checksum(self::_base64_url_encode(serialize(self::_get_content_info())));
}
$params['wp_rest_nonce'] = wp_create_nonce('wp_rest');
$params = apply_filters('slimstat_js_params', $params);
// Register the correct script for adblock bypass, CDN, or default
if ('adblock_bypass' === $method) {
$hash = md5(site_url() . 'slimstat');
wp_register_script('wp_slimstat', home_url(sprintf('/%s.js/', $hash)), [], SLIMSTAT_ANALYTICS_VERSION, true);
} elseif ('on' == self::$settings['enable_cdn']) {
wp_register_script('wp_slimstat', 'https://cdn.jsdelivr.net/wp/wp-slimstat/tags/' . SLIMSTAT_ANALYTICS_VERSION . '/wp-slimstat.min.js', [], null, true);
} else {
wp_register_script('wp_slimstat', plugins_url('/wp-slimstat.min.js', __FILE__), [], SLIMSTAT_ANALYTICS_VERSION, true);
}
wp_enqueue_script('wp_slimstat');
/**
* Registers the 'wp_slimstat' script as an interactivity module if the registration function exists.
*
* Ensures compatibility with WordPress Interactivity API by registering the script module and its dependencies.
*/
if (function_exists('wp_interactivity_register_script_module')) {
wp_interactivity_register_script_module('wp_slimstat', [
'name' => 'wp_slimstat',
'dependencies' => [],
]);
}
wp_localize_script('wp_slimstat', 'SlimStatParams', $params);
return null;
}
// end enqueue_tracker
public static function add_defer_to_script_tag($_tag, $_handle)
{
if ('wp_slimstat' === $_handle && false === stripos($_tag, 'defer')) {
$_tag = str_replace('<script ', '<script defer ', $_tag);
}
return $_tag;
}
/**
* Removes old entries from the main table and performs other daily tasks
*/
public static function wp_slimstat_purge()
{
$autopurge_interval = intval(self::$settings['auto_purge']);
if ($autopurge_interval <= 0) {
return;
}
$days_ago = strtotime(self::date_i18n('Y-m-d H:i:s') . sprintf(' -%d days', $autopurge_interval));
// Copy entries to the archive table, if needed
if ('no' != self::$settings['auto_purge_delete']) {
$is_copy_done = self::$wpdb->query("
INSERT INTO {$GLOBALS['wpdb']->prefix}slim_stats_archive (id, ip, other_ip, username, email, country, location, city, referer, resource, searchterms, notes, visit_id, server_latency, page_performance, browser, browser_version, browser_type, platform, language, fingerprint, user_agent, resolution, screen_width, screen_height, content_type, category, author, content_id, tz_offset, outbound_resource, dt_out, dt)
SELECT id, ip, other_ip, username, email, country, location, city, referer, resource, searchterms, notes, visit_id, server_latency, page_performance, browser, browser_version, browser_type, platform, language, fingerprint, user_agent, resolution, screen_width, screen_height, content_type, category, author, content_id, tz_offset, outbound_resource, dt_out, dt
FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats
WHERE dt < {$days_ago}");
if (false !== $is_copy_done) {
self::$wpdb->query(sprintf('DELETE ts FROM %sslim_stats ts WHERE ts.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago));
}
$is_copy_done = self::$wpdb->query(
"
INSERT INTO {$GLOBALS['wpdb']->prefix}slim_events_archive (type, event_description, notes, position, id, dt)
SELECT type, event_description, notes, position, id, dt
FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_events
WHERE dt < {$days_ago}"
);
if (false !== $is_copy_done) {
self::$wpdb->query(sprintf('DELETE te FROM %sslim_events te WHERE te.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago));
}
} else {
// Delete old entries
self::$wpdb->query(sprintf('DELETE ts FROM %sslim_stats ts WHERE ts.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago));
self::$wpdb->query(sprintf('DELETE te FROM %sslim_events te WHERE te.dt < %s', $GLOBALS[ 'wpdb' ]->prefix, $days_ago));
}
// Optimize tables
self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_stats', $GLOBALS[ 'wpdb' ]->prefix));
self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_stats_archive', $GLOBALS[ 'wpdb' ]->prefix));
self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_events', $GLOBALS[ 'wpdb' ]->prefix));
self::$wpdb->query(sprintf('OPTIMIZE TABLE %sslim_events_archive', $GLOBALS[ 'wpdb' ]->prefix));
}
public static function wp_slimstat_update_geoip_database()
{
$this_update = strtotime('first Tuesday of this month') + (86400 * 2);
$last_update = get_option('slimstat_last_geoip_dl', 0);
if ($last_update < $this_update) {
$geographicProvider = new \SlimStat\Services\GeoService();
try {
$geographicProvider
->setEnableMaxmind(wp_slimstat::$settings['enable_maxmind'])
->setUpdate(true)
->setMaxmindLicense(wp_slimstat::$settings['maxmind_license_key'])
->download();
// Set the last update time
$geographicProvider->updateLastUpdateTime(time());
} catch (\Exception $e) {
$geographicProvider->logError($e->getMessage());
}
}
}
/**
* Displays the opt-out box via Ajax request
*/
public static function get_optout_html()
{
die(stripslashes(self::$settings['opt_out_message']));
}
// end get_optout_html
public static function add_plugin_manual_download_link($_links = [], $_plugin_file = '')
{
$a_clean_slug = str_replace(['wp-slimstat-', '/index.php'], ['', ''], $_plugin_file);
if (false !== ($download_url = get_transient('wp-slimstat-download-link-' . $a_clean_slug))) {
$_links[] = '<a href="' . $download_url . '">Download ZIP</a>';
} else {
$url = 'https://www.wp-slimstat.com/update-checker/?slug=' . $a_clean_slug . '&key=' . urlencode(self::$settings['addon_licenses']['wp-slimstat-' . $a_clean_slug]);
$response = wp_safe_remote_get($url, ['timeout' => 300, 'user-agent' => 'Slimstat Analytics/' . SLIMSTAT_ANALYTICS_VERSION . '; ' . home_url()]);
if (!is_wp_error($response) && 200 == wp_remote_retrieve_response_code($response)) {
$data = @json_decode($response['body']);
if (is_object($data)) {
$_links[] = '<a href="' . $data->download_url . '">Download ZIP</a>';
set_transient('wp-slimstat-download-link-' . $a_clean_slug, $data->download_url, 172800); // 48 hours
}
}
}
return $_links;
}
/**
* Resolves a given IP address, by keeping a local cache of hostnames to avoid multiple requests to the DNS server
*/
public static function gethostbyaddr($_ip = '')
{
$hostname = get_transient('slimstat_' . $_ip);
if (empty($hostname)) {
$hostname = gethostbyaddr($_ip);
set_transient('slimstat_' . $_ip, $hostname, HOUR_IN_SECONDS);
}
return $hostname;
}
// end gethostbyaddr
/**
* Registers the Slimstat widget
*/
public static function register_widget()
{
return register_widget('slimstat_widget');
}
// end register_widget
/**
* Generates the key to see if a given host is listed as a search engine in the corresponding Json data file
*/
public static function get_lossy_url($_url = '')
{
return preg_replace(
[
'/^(w+\d*|search)\./',
'/(^|\.)m\./',
'/(\.(com|org|net|co|it|edu))?\.(ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bl|bm|bn|bo|bq|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mf|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)(\/|$)/',
'/(^|\.)(ad|ae|af|ag|ai|al|am|ao|aq|ar|as|at|au|aw|ax|az|ba|bb|bd|be|bf|bg|bh|bi|bj|bl|bm|bn|bo|bq|br|bs|bt|bv|bw|by|bz|ca|cc|cd|cf|cg|ch|ci|ck|cl|cm|cn|co|cr|cu|cv|cw|cx|cy|cz|de|dj|dk|dm|do|dz|ec|ee|eg|eh|er|es|et|fi|fj|fk|fm|fo|fr|ga|gb|gd|ge|gf|gg|gh|gi|gl|gm|gn|gp|gq|gr|gs|gt|gu|gw|gy|hk|hm|hn|hr|ht|hu|id|ie|il|im|in|io|iq|ir|is|it|je|jm|jo|jp|ke|kg|kh|ki|km|kn|kp|kr|kw|ky|kz|la|lb|lc|li|lk|lr|ls|lt|lu|lv|ly|ma|mc|md|me|mf|mg|mh|mk|ml|mm|mn|mo|mp|mq|mr|ms|mt|mu|mv|mw|mx|my|mz|na|nc|ne|nf|ng|ni|nl|no|np|nr|nu|nz|om|pa|pe|pf|pg|ph|pk|pl|pm|pn|pr|ps|pt|pw|py|qa|re|ro|rs|ru|rw|sa|sb|sc|sd|se|sg|sh|si|sj|sk|sl|sm|sn|so|sr|ss|st|sv|sx|sy|sz|tc|td|tf|tg|th|tj|tk|tl|tm|tn|to|tr|tt|tv|tw|tz|ua|ug|um|us|uy|uz|va|vc|ve|vg|vi|vn|vu|wf|ws|ye|yt|za|zm|zw)\./',
],
[
'',
'$1',
'.{}$4',
'$1{}.',
],
$_url
);
}
// end get_lossy_url
/**
* Update content type as needed
*/
public static function update_content_type($_status = 301, $_location = '')
{
if ($_status >= 300 && $_status < 400) {
// SEE WHY THIS DOESN'T WORK?!
self::$stat['content_type'] = 'redirect:' . intval($_status);
self::_update_row(self::$stat);
}
return $_status;
}
// end update_content_type
/**
* Stores the pageview information in the database and returns the ID associated to the new entry
*/
protected static function _insert_row($_data = [], $_table = '')
{
if (empty($_data) || empty($_table)) {
return -1;
}
// Remove unwanted characters from keys (SQL injections, anyone?)
$data_keys = [];
foreach (array_keys($_data) as $a_key) {
$data_keys[] = sanitize_key($a_key);
}
// Remove unwanted characters from data (SQL injections, anyone?)
foreach ($_data as $key => $value) {
$_data[$key] = 'resource' == $key ? sanitize_url($value) : sanitize_text_field($value);
}
self::$wpdb->query(self::$wpdb->prepare("
INSERT IGNORE INTO {$_table} (" . implode(', ', $data_keys) . ')
VALUES (' . substr(str_repeat('%s,', count($_data)), 0, -1) . ')', $_data));
return intval(self::$wpdb->insert_id);
}
// end _insert_row
/**
* Updates an existing row
*/
protected static function _update_row($_data = [])
{
if (empty($_data) || empty($_data['id'])) {
return false;
}
// Extract the ID from the array
$id = abs(intval($_data['id']));
unset($_data['id']);
// Sanitize column names (SQL/XSS injections, anyone?)
$_data = array_filter($_data);
// The 'notes' column stores multiple comma-separated values: we need to append the new value to the existing ones
// Also, values are organized in an array, which we need to implode as a string
$notes = '';
if (!empty($_data['notes']) && is_array($_data['notes'])) {
$notes = (count($_data) > 1 ? ',' : '') . "notes=CONCAT( IFNULL( notes, '' ), '[" . esc_sql(implode('][', $_data['notes'])) . "]' )";
unset($_data['notes']);
}
$prepared_query = self::$wpdb->prepare("
UPDATE IGNORE {$GLOBALS[ 'wpdb' ]->prefix}slim_stats
SET " . implode('=%s,', array_keys($_data)) . "=%s
WHERE id = {$id}
", $_data);
// Add the notes
if ('' !== $notes && '0' !== $notes) {
$prepared_query = str_replace('WHERE id =', $notes . ' WHERE id =', $prepared_query);
}
// Save the data in the database
self::$wpdb->query($prepared_query);
return $id;
}
// end _update_row
/**
* Tries to find the user's REAL IP address
*/
protected static function _get_remote_ip()
{
$ip_array = ['', ''];
if (!empty($_SERVER['REMOTE_ADDR']) && false !== filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP)) {
$ip_array[0] = sanitize_text_field(wp_unslash($_SERVER['REMOTE_ADDR']));
}
$originating_ip_headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR', 'HTTP_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_INCAP_CLIENT_IP'];
foreach ($originating_ip_headers as $a_header) {
if (!empty($_SERVER[$a_header])) {
foreach (explode(',', $_SERVER[$a_header]) as $a_ip) {
if (false !== filter_var($a_ip, FILTER_VALIDATE_IP) && $a_ip != $ip_array[0]) {
$ip_array[1] = $a_ip;
break;
}
}
}
}
return apply_filters('slimstat_filter_ip_address', $ip_array);
}
// end _get_remote_ip
/**
* Extracts the accepted language from browser headers
*/
protected static function _get_language()
{
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// Capture up to the first delimiter (, found in Safari)
preg_match('/([^,;]*)/', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $array_languages);
// Fix some codes, the correct syntax is with minus (-) not underscore (_)
return str_replace('_', '-', strtolower($array_languages[0]));
}
return ''; // Indeterminable language
}
// end _get_language
/**
* Sniffs out referrals from search engines and tries to determine the query string
*/
protected static function _get_search_terms($_url = '')
{
if (empty($_url)) {
return '';
}
$searchterms = '';
// Load the search engines list to mark pageviews accordingly
// Each entry contains the following attributes
// - params: which query string params is associated to the search keyword
// - backlink: format of the URL point to the search engine result page
// - charsets: list of charset used to encode the keywords
//
$search_engines = file_get_contents(plugin_dir_path(__FILE__) . 'admin/assets/data/matomo-searchengine.json');
$search_engines = json_decode($search_engines, true);
$parsed_url = @parse_url($_url);
if (empty($search_engines) || empty($parsed_url) || empty($parsed_url['host'])) {
return '';
}
$sek = self::get_lossy_url($parsed_url['host']);
if (!empty($search_engines[$sek])) {
if (empty($search_engines[$sek]['params'])) {
$search_engines[$sek]['params'] = ['q'];
}
foreach ($search_engines[$sek]['params'] as $a_param) {
if (!empty($parsed_url['query'])) {
$searchterms = self::_get_param_from_query_string($parsed_url['query'], $a_param);
if (!empty($searchterms)) {
break;
}
}
}
// Make sure to use the appropriate charset, if specified
if (!empty($searchterms) && (!empty($search_engines['charsets']) && function_exists('iconv'))) {
$charset = $search_engines['charsets'][0];
if (count($search_engines['charsets']) > 1 && function_exists('mb_detect_encoding')) {
$charset = mb_detect_encoding($searchterms, $search_engines['charsets']);
if (false === $charset) {
$charset = $search_engines['charsets'][0];
}
}
$new_searchterms = @iconv($charset, 'UTF-8//IGNORE', $searchterms);
if (!('' === $new_searchterms || '0' === $new_searchterms || false === $new_searchterms)) {
$searchterms = $new_searchterms;
}
}
} elseif (!empty($parsed_url['query'])) {
// We weren't lucky, but there's still hope
foreach (['ask', 'k', 'q', 'qs', 'qt', 'query', 's', 'string'] as $a_param) {
$searchterms = self::_get_param_from_query_string($parsed_url['query'], $a_param);
if (!empty($searchterms)) {
break;
}
}
}
return sanitize_text_field($searchterms);
}
// end _get_search_terms
/**
* Retrieves a param value from a string treated as a URL query string
*/
protected static function _get_param_from_query_string($_query = '', $_parameter = '')
{
if (empty($_query)) {
return '';
}
@parse_str($_query, $values);
return empty($values[$_parameter]) ? '' : $values[$_parameter];
}
// end _get_param_from_query_string
/**
* Returns details about the resource being accessed
*/
protected static function _get_content_info()
{
$content_info = ['content_type' => ''];
// Mark 404 pages
if (is_404()) {
$content_info['content_type'] = '404';
} // Type
elseif (is_single()) {
if (($post_type = get_post_type()) != 'post') {
$post_type = 'cpt:' . $post_type;
}
$content_info['content_type'] = $post_type;
$category_ids = [];
foreach (get_object_taxonomies($GLOBALS['post']) as $a_taxonomy) {
$terms = get_the_terms($GLOBALS['post']->ID, $a_taxonomy);
if (is_array($terms)) {
foreach ($terms as $a_term) {
$category_ids[] = $a_term->term_id;
}
$content_info['category'] = implode(',', $category_ids);
}
}
$content_info['content_id'] = $GLOBALS['post']->ID;
} elseif (is_page()) {
$content_info['content_type'] = 'page';
$content_info['content_id'] = $GLOBALS['post']->ID;
} elseif (is_attachment()) {
$content_info['content_type'] = 'attachment';
} elseif (is_singular()) {
$content_info['content_type'] = 'singular';
} elseif (is_post_type_archive()) {
$content_info['content_type'] = 'post_type_archive';
} elseif (is_tag()) {
$content_info['content_type'] = 'tag';
$list_tags = get_the_tags();
if (is_array($list_tags)) {
$tag_info = array_pop($list_tags);
if (!empty($tag_info)) {
$content_info['category'] = $tag_info->term_id;
}
}
} elseif (is_tax()) {
$content_info['content_type'] = 'taxonomy';
} elseif (is_category()) {
$content_info['content_type'] = 'category';
$list_categories = get_the_category();
if (is_array($list_categories)) {
$cat_info = array_pop($list_categories);
if (!empty($cat_info)) {
$content_info['category'] = $cat_info->term_id;
}
}
} elseif (is_date()) {
$content_info['content_type'] = 'date';
} elseif (is_author()) {
$content_info['content_type'] = 'author';
} elseif (is_archive()) {
$content_info['content_type'] = 'archive';
} elseif (is_search()) {
$content_info['content_type'] = 'search';
} elseif (is_feed()) {
$content_info['content_type'] = 'feed';
} elseif (is_home() || is_front_page()) {
$content_info['content_type'] = 'home';
} elseif (!empty($GLOBALS['pagenow']) && 'wp-login.php' == $GLOBALS['pagenow']) {
$content_info['content_type'] = 'login';
} elseif (!empty($GLOBALS['pagenow']) && 'wp-register.php' == $GLOBALS['pagenow']) {
$content_info['content_type'] = 'registration';
} // WordPress sets is_admin() to true for all ajax requests ( front-end or admin-side )
elseif (is_admin() && (!defined('DOING_AJAX') || !DOING_AJAX)) {
$content_info['content_type'] = 'admin';
}
if (is_paged()) {
$content_info['content_type'] .= ':paged';
}
// Author
if (is_singular()) {
$author = get_the_author_meta('user_login', $GLOBALS['post']->post_author);
if (!empty($author)) {
$content_info['author'] = $author;
}
}
return $content_info;
}
// end _get_content_info
/**
* Reads the information sent by the Javascript tracker and adds it to the $_stat array
*/
protected static function _get_client_info($_data_js = [], $_stat = [])
{
if (!empty($_data_js['bw'])) {
$_stat['resolution'] = strip_tags(trim($_data_js['bw'] . 'x' . $_data_js['bh']));
}
if (!empty($_data_js['sw'])) {
$_stat['screen_width'] = intval($_data_js['sw']);
}
if (!empty($_data_js['sh'])) {
$_stat['screen_height'] = intval($_data_js['sh']);
}
if (!empty($_data_js['sl']) && $_data_js['sl'] > 0 && $_data_js['sl'] < 60000) {
$_stat['server_latency'] = intval($_data_js['sl']);
}
if (!empty($_data_js['pp']) && $_data_js['pp'] > 0 && $_data_js['pp'] < 60000) {
$_stat['page_performance'] = intval($_data_js['pp']);
}
if (!empty($_data_js['fh']) && 'on' != self::$settings['anonymize_ip']) {
$_stat['fingerprint'] = sanitize_text_field($_data_js['fh']);
}
if (!empty($_data_js['tz'])) {
$_stat['tz_offset'] = intval($_data_js['tz']);
}
return $_stat;
}
// end _get_client_info
/**
* Reads the cookie to get the visit_id and sets the variable accordingly
*/
protected static function _set_visit_id($_force_assign = false)
{
$is_new_session = true;
$identifier = 0;
if (isset($_COOKIE['slimstat_tracking_code'])) {
// Make sure only authorized information is recorded
$identifier = self::_get_value_without_checksum($_COOKIE['slimstat_tracking_code']);
if (false === $identifier) {
return false;
}
$is_new_session = (false !== strpos($identifier, 'id'));
$identifier = intval($identifier);
}
// User doesn't have an active session
if ($is_new_session && ($_force_assign || 'on' == self::$settings['javascript_mode'])) {
if (empty(self::$settings['session_duration'])) {
self::$settings['session_duration'] = 1800;
}
self::$stat['visit_id'] = get_transient('slimstat_visit_id');
if (false === self::$stat['visit_id']) {
self::$stat['visit_id'] = intval(self::$wpdb->get_var(sprintf('SELECT MAX( visit_id ) FROM %sslim_stats', $GLOBALS[ 'wpdb' ]->prefix)));
}
self::$stat['visit_id']++;
set_transient('slimstat_visit_id', self::$stat['visit_id']);
$set_cookie = apply_filters('slimstat_set_visit_cookie', (!empty(self::$settings['set_tracker_cookie']) && 'on' == self::$settings['set_tracker_cookie']));
if ($set_cookie) {
@setcookie(
'slimstat_tracking_code',
self::_get_value_with_checksum(self::$stat['visit_id']),
['expires' => time() + self::$settings['session_duration'], 'path' => COOKIEPATH]
);
}
} elseif ($identifier > 0) {
self::$stat['visit_id'] = $identifier;
}
if ($is_new_session && $identifier > 0) {
self::$wpdb->query(self::$wpdb->prepare(
"
UPDATE {$GLOBALS['wpdb' ]->prefix}slim_stats
SET visit_id = %d
WHERE id = %d AND visit_id = 0",
self::$stat['visit_id'],
$identifier
));
}
return ($is_new_session && ($_force_assign || 'on' == self::$settings['javascript_mode']));
}
// end _set_visit_id
/**
* Saves an error detected by the tracker in the database
*/
protected static function _log_error($_error_code = 0)
{
// Save this error in the database
self::update_option('slimstat_tracker_error', [$_error_code, self::date_i18n('U')]);
// Allow third-party code to trigger actions based on this error
do_action('slimstat_track_exit_' . abs($_error_code), self::$stat);
return -$_error_code;
}
// end _log_error
protected static function _get_value_with_checksum($_value = 0)
{
return $_value . '.' . md5($_value . self::$settings['secret']);
}
protected static function _get_value_without_checksum($_value_with_checksum = '')
{
[$value, $checksum] = explode('.', $_value_with_checksum);
if ($checksum === md5($value . self::$settings['secret'])) {
return $value;
}
return false;
}
/**
* Determines if a given string is listed in the corresponding 'exclusion' field
*/
protected static function _is_blacklisted($_needles = [], $_haystack_string = '')
{
foreach (self::string_to_array($_haystack_string) as $a_item) {
$pattern = str_replace(['\*', '\!'], ['(.*)', '.'], preg_quote($a_item, '@'));
if (!is_array($_needles)) {
$_needles = [$_needles];
}
foreach ($_needles as $a_needle) {
if (preg_match(sprintf('@^%s$@i', $pattern), $a_needle)) {
return true;
}
}
}
return false;
}
// end _is_blacklisted
/**
* Determines if this is a new visitor, meaning that we've never seen this fingerprint before
*/
protected static function _is_new_visitor($_fingerprint = '')
{
// If the privacy option is enabled, all visitors would be considered "new"...
if ('on' == self::$settings['anonymize_ip']) {
return false;
}
$count_fingerprint = self::$wpdb->get_var(self::$wpdb->prepare(
"
SELECT COUNT( id )
FROM {$GLOBALS[ 'wpdb' ]->prefix}slim_stats
WHERE fingerprint = %s",
$_fingerprint
));
return 0 == $count_fingerprint;
}
// end _is_new_visitor
/**
* Validates and unpacks an IP Address
*/
protected static function _dtr_pton($_ip)
{
$unpacked = false;
if (filter_var($_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$unpacked = unpack('A4', inet_pton($_ip));
} elseif (filter_var($_ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) && defined('AF_INET6')) {
$unpacked = unpack('A16', inet_pton($_ip));
}
$binary_ip = '';
if ([] !== $unpacked && false !== $unpacked && isset($unpacked[1])) {
$unpacked = str_split($unpacked[1]);
foreach ($unpacked as $char) {
$binary_ip .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
}
return $binary_ip;
}
// end _dtr_pton
/**
* Helper function to determine if we should ignore visits coming from this IP address
*/
protected static function _get_mask_length($ip)
{
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
return 32;
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
return 128;
}
return false;
}
// end _get_mask_length
/**
* These two functions here implement an URL-safe base64 string
*/
protected static function _base64_url_encode($_input = '')
{
return strtr(base64_encode($_input), '+/=', '._-');
}
protected static function _base64_url_decode($_input = '')
{
return strip_tags(trim(base64_decode(strtr($_input, '._-', '+/='))));
}
// end _base64_url_encode/decode
/**
* Check if slimstat pro plugin is installed
*/
public static function pro_is_installed($pluginSlug = 'wp-slimstat-pro/wp-slimstat-pro.php')
{
include_once(ABSPATH . 'wp-admin/includes/plugin.php');
return (bool) is_plugin_active($pluginSlug);
}
/**
* create upload directory
*/
public static function create_upload_directory()
{
$upload_dir = self::$upload_dir;
wp_mkdir_p($upload_dir);
/**
* Create .htaccess to avoid public access.
*/
if (is_dir($upload_dir) && is_writable($upload_dir)) {
$htaccess_file = path_join($upload_dir, '.htaccess');
if (!file_exists($htaccess_file) && $handle = @fopen($htaccess_file, 'w')) {
fwrite($handle, "Deny from all\n");
fclose($handle);
}
}
}
public static function get_schedule_interval($schedule)
{
$schedulesInterval = wp_get_schedules();
$timeInterval = 86400;
if (isset($schedulesInterval[$schedule]['interval'])) {
$timeInterval = $schedulesInterval[$schedule]['interval'];
}
return $timeInterval;
}
}
// end of class declaration
class slimstat_widget extends WP_Widget
{
/**
* Sets up the widgets name etc
*/
public function __construct()
{
parent::__construct('slimstat_widget', 'Slimstat', [
'classname' => 'slimstat_widget',
'description' => 'Add a Slimstat report to your sidebar',
]);
}
/**
* Outputs the content of the widget
*
* @param array $args
* @param array $instance
*/
public function widget($_args = [], $_instance = [])
{
extract(shortcode_atts([
'slimstat_widget_id' => '',
'slimstat_widget_title' => '',
'slimstat_widget_filters' => '',
], $_instance));
if (!empty($slimstat_widget_title)) {
echo (empty($_args['before_title']) ? '<h2 class="widget-title">' : $_args['before_title']) . $slimstat_widget_title . (empty($_args['after_title']) ? '</h2>' : $_args['after_title']);
}
if (!empty($slimstat_widget_id)) {
echo do_shortcode(sprintf("[slimstat f='widget' w='%s']%s[/slimstat]", $slimstat_widget_id, $slimstat_widget_filters));
} else {
echo '';
}
}
/**
* Outputs the options form on admin
*
* @param array $instance The widget options
*/
public function form($_instance)
{
extract(shortcode_atts([
'slimstat_widget_id' => '',
'slimstat_widget_title' => '',
'slimstat_widget_filters' => '',
], $_instance));
// Let's build the dropdown
include_once(plugin_dir_path(__FILE__) . 'admin/view/wp-slimstat-reports.php');
wp_slimstat_reports::init();
$select_options = '';
foreach (wp_slimstat_reports::$reports as $a_report_id => $a_report_info) {
$select_options .= sprintf("<option value='%s' ", $a_report_id) . (($slimstat_widget_id == $a_report_id) ? 'selected="selected"' : '') . sprintf('>%s</option>', $a_report_info[ 'title' ]);
}
?>
<p>
<label for="<?php echo esc_attr($this->get_field_id('slimstat_widget_id')); ?>"><?php _e('Report', 'wp-slimstat') ?></label>
<select class="widefat" id="<?php echo esc_attr($this->get_field_id('slimstat_widget_id')); ?>" name="<?php echo esc_attr($this->get_field_name('slimstat_widget_id')); ?>">
<option value="">Select a widget</option>
<?php echo $select_options ?>
</select>
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('slimstat_widget_title')); ?>"><?php _e('Title', 'wp-slimstat') ?></label>
<input type="text" class="widefat" id="<?php echo esc_attr($this->get_field_id('slimstat_widget_title')); ?>" name="<?php echo esc_attr($this->get_field_name('slimstat_widget_title')); ?>" value="<?php echo trim(strip_tags($slimstat_widget_title)) ?>">
</p>
<p>
<label for="<?php echo esc_attr($this->get_field_id('slimstat_widget_filters')); ?>"><?php _e('Optional filters', 'wp-slimstat'); ?></label>
<a href="https://wp-slimstat.com/resources/what-is-the-syntax-of-a-slimstat-shortcode-#slimstat-operators" target="_blank">[?]</a>
<textarea class="widefat" id="<?php echo esc_attr($this->get_field_id('slimstat_widget_filters')); ?>" name="<?php echo esc_attr($this->get_field_name('slimstat_widget_filters')); ?>"><?php echo trim(strip_tags($slimstat_widget_filters)) ?></textarea>
</p>
<?php
}
/**
* Processing widget options on save
*
* @param array $new_instance The new options
* @param array $old_instance The previous options
*/
public function update($_new_instance, $_old_instance)
{
$instance = $_old_instance;
$instance['slimstat_widget_id'] = $_new_instance['slimstat_widget_id'];
$instance['slimstat_widget_title'] = $_new_instance['slimstat_widget_title'];
$instance['slimstat_widget_filters'] = $_new_instance['slimstat_widget_filters'];
return $instance;
}
}
// Ok, let's go, Sparky!
if (function_exists('add_action')) {
// Since we use sendBeacon, this function sends raw POST data, which does not populate the $_POST variable automatically
if ((!empty($_SERVER['HTTP_CONTENT_TYPE']) || !empty($_SERVER['CONTENT_TYPE'])) && [] === $_POST) {
$raw_post_string = file_get_contents('php://input');
parse_str($raw_post_string, wp_slimstat::$raw_post_array);
} elseif ([] !== $_POST) {
wp_slimstat::$raw_post_array = $_POST;
}
// Init the Ajax listener
if (!empty(wp_slimstat::$raw_post_array['action']) && 'slimtrack' == wp_slimstat::$raw_post_array['action']) {
// This is needed because admin-ajax.php is reading $_REQUEST to fire the corresponding action
if (empty($_POST['action'])) {
$_POST['action'] = wp_slimstat::$raw_post_array['action'];
}
add_action('wp_ajax_nopriv_slimtrack', ['wp_slimstat', 'slimtrack_ajax']);
add_action('wp_ajax_slimtrack', ['wp_slimstat', 'slimtrack_ajax']);
}
include_once(plugin_dir_path(__FILE__) . 'src/Constants.php');
// From the codex: You can't call register_activation_hook() inside a function hooked to the 'plugins_loaded' or 'init' hooks (or any other hook). These hooks are called before the plugin is loaded or activated.
if (is_admin()) {
include_once(plugin_dir_path(__FILE__) . 'admin/index.php');
register_activation_hook(__FILE__, ['wp_slimstat_admin', 'init_environment']);
register_deactivation_hook(__FILE__, ['wp_slimstat_admin', 'deactivate']);
}
add_action('widgets_init', ['wp_slimstat', 'register_widget']);
// Add the appropriate actions
add_action('plugins_loaded', ['wp_slimstat', 'init'], 20);
// Add the action to fetch chart data
add_action('wp_ajax_slimstat_fetch_chart_data', [\SlimStat\Modules\Chart::class, 'ajaxFetchChartData']);
}