Счетчик посещений сайта

Как-то, пару-тройку лет назад решил поставить себе на сайт счетчик посещений. Стал выбирать: чтобы был и красивый и информативный… Подобрал штук пять счетчиков. Повесил их все к себе на сайт. Красота! как орденские планки. Однако заметил, что эти счетчики говорят о посещениях по-разному!? И потом – что за тормоза на сайте!? Пришлось углубиться в мат.часть.

В результате убрал все счетчики с сайта и он заработал в штатном режиме. Вывод: не надо кормить хитрозадых своим трафиком. Причем они (хозяева счетчиков) используют таких наивных админов – как я, бесплатно собирают статистику для целей, известных только им… Да и реальная картина посещений искажена.
Пришлось написать свой счетчик. Сделал в виде виджета. Поставил себе и не зависим от кого-либо. И картина посещений – такая, как есть на самом деле.
Виджет “счетчик посещений” устанавливается стандартным образом. В моем случае, я установил его в нижней части сайдбара.

Этот счетчик отличает от других то, что он динамический. Это означает, что данные о посещениях сайта будут поступать в него постоянно, пока страница на которой он находится – есть на экране. Иначе говоря, не требуется обновлять страницу в браузере.
Счетчик является частью плагина WatchMan-Site7. Информацию о посещениях счетчик берет из таблицы, созданной плагином {$wpdb->prefix}watchman_site базы данных сайта.
“Счетчик посещений” строится следующим образом:

<table class="counter" id="counter" style="font-size: 8pt; background: <?php echo( esc_html( $instance['grnd'] ) ); ?>;">
<tr><th><?php esc_html_e( 'Interval', 'wms7' ); ?></th><th><?php esc_html_e( 'Visits', 'wms7' ); ?></th><th><?php esc_html_e( 'Visitors', 'wms7' ); ?></th><th><?php esc_html_e( 'Robots', 'wms7' ); ?></th></tr>
<tr style="color: blue;"><th><?php esc_html_e( 'month', 'wms7' ); ?></th><th id=counter_month_visits></th><th id=counter_month_visitors></th><th id=counter_month_robots></th></tr>
<tr style="color: green;"><th><?php esc_html_e( 'week', 'wms7' ); ?></th><th id=counter_week_visits></th><th id=counter_week_visitors></th><th id=counter_week_robots></th></tr>
<tr style="color: brown;"><th><?php esc_html_e( 'today', 'wms7' ); ?></th><th id=counter_today_visits></th><th id=counter_today_visitors></th><th id=counter_today_robots></th></tr>
</table>

Фрагмент кода html взят из файла class-wms7-widget.php плагина WatchMan-Site7. Из текста кода видно, что поля таблицы виджета – пусты. Однако, каждое пустое поле имеет свой id. Значения этих полей заносятся кодом JS – в файле wms7-frontend.js. Ниже приведен код JS:

/**
* Description: Used to manage the plug-in on the client side.
*
* @category    Wms7_frontend.js
* @package     WatchMan-Site7
* @author      Oleg Klenitskiy <klenitskiy.oleg@mail.ru>
* @version     3.0.2
* @license     GPLv2 or later
*/

/**
* Process Control Server Sent Events on the client side (frontend).
*/
function wms7_sse_frontend() {
var myElement = document.getElementById( 'counter' );

if (myElement) {
document.cookie = 'wms7_sse_frontend=on';
if ( ! ! window.EventSource ) {
var source = new EventSource( wms7_url + 'includes/wms7-sse-frontend.php' );

source.addEventListener(
'message',
function(e) {
console.log( e.data );
if (get_cookie( 'wms7_widget_counter' ) !== e.data) {
document.cookie = 'wms7_widget_counter=' + e.data;
var arr = e.data.split( '|' );
} else {
var arr = get_cookie( 'wms7_widget_counter' ).split( '|' );
}
//Redraw the widget - counter of visits
var counter_month_visits = document.getElementById( 'counter_month_visits' );
counter_month_visits.innerHTML = arr[0];
var counter_month_visitors = document.getElementById( 'counter_month_visitors' );
counter_month_visitors.innerHTML = arr[1];
var counter_month_robots = document.getElementById( 'counter_month_robots' );
counter_month_robots.innerHTML = arr[2];
var counter_week_visits = document.getElementById( 'counter_week_visits' );
counter_week_visits.innerHTML = arr[3];
var counter_week_visitors = document.getElementById( 'counter_week_visitors' );
counter_week_visitors.innerHTML = arr[4];
var counter_week_robots = document.getElementById( 'counter_week_robots' );
counter_week_robots.innerHTML = arr[5];
var counter_today_visits = document.getElementById( 'counter_today_visits' );
counter_today_visits.innerHTML = arr[6];
var counter_today_visitors = document.getElementById( 'counter_today_visitors' );
counter_today_visitors.innerHTML = arr[7];
var counter_today_robots = document.getElementById( 'counter_today_robots' );
counter_today_robots.innerHTML = arr[8];
},
false
);

source.addEventListener(
'open',
function(e) {
console.log( 'Connection was opened.' );
},
false
);

source.addEventListener(
'error',
function(e) {
console.log( 'Error - connection was lost.' );
},
false
);

} else {
alert( 'Your browser does not support Server-Sent Events. Please upgrade it.' );
return;
}
} else {
// stop SSE frontend.
document.cookie = 'wms7_sse_frontend=off';
}
}

/**
* Get content cookie.
*
* @param string cookie_name Cookie name.
* @return string $result.
*/
function get_cookie(cookie_name) {
var results = document.cookie.match( '(^|;) ?' + cookie_name + '=([^;]*)(;|$)' );

if ( results ) {
result = decodeURI( results[2] );
return ( result );
} else {
result = null;
return result;
}
}

/**
* Main function onload.
*/
window.onload = function() {
var myElement = document.getElementById( 'counter' );

if (myElement) {
wms7_sse_frontend();
}
}

В коде видно, что функция wms7_sse_frontend() ищет элемент с id=’counter’ на текущей загруженной странице сайта. И если находит – инициирует новый EventSource(). Затем слушает – source.addEventListener().
На стороне сервера работает следующий код PHP.

<?php
/**
* Description: Used to transfer data about site visits to a widget - counter of visits.
*
* PHP version 5
*
* @category   Wms7-sse-frontend.php
* @package    WatchMan-Site7
* @author     Oleg Klenitskiy <klenitskiy.oleg@mail.ru>
* @version    3.0.2
* @license    GPLv2 or later
*/

/**
* We specify that we need at least WP.
*/
define( 'SHORTINIT', true );
// loadable environment WordPress.
$_document_root = filter_input( INPUT_SERVER, 'DOCUMENT_ROOT', FILTER_SANITIZE_STRING );
require_once $_document_root . '/wp-load.php';

/**
* Used for create the number of visits to different categories of visitors and different time.
*
* @return number.
*/
function wms7_widget_counter() {
global $wpdb;

$cache_key         = 'wms7_data_month_visits';
$data_month_visits = wp_cache_get( $cache_key );
if ( ! $data_month_visits ){
$data_month_visits = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(%s) FROM {$wpdb->prefix}watchman_site
WHERE login_result <> %d AND MONTH(time_visit) = MONTH(now()) AND YEAR(time_visit) = YEAR(now())
",
'*',
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_month_visits, 'wms7' );
}
$cache_key           = 'wms7_data_month_visitors';
$data_month_visitors = wp_cache_get( $cache_key );
if ( ! $data_month_visitors ) {
$data_month_visitors = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(DISTINCT user_ip) FROM {$wpdb->prefix}watchman_site
WHERE login_result <> %d AND MONTH(time_visit) = MONTH(now()) AND YEAR(time_visit) = YEAR(now())
",
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_month_visitors, 'wms7' );
}
$cache_key         = 'wms7_data_month_robots';
$data_month_robots = wp_cache_get( $cache_key );
if ( ! $data_month_robots ) {
$data_month_robots = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(DISTINCT robot) FROM {$wpdb->prefix}watchman_site
WHERE login_result = %d AND MONTH(time_visit) = MONTH(now()) AND YEAR(time_visit) = YEAR(now())
",
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_month_robots, 'wms7' );
}
$cache_key        = 'wms7_data_week_visits';
$data_week_visits = wp_cache_get( $cache_key );
if ( ! $data_week_visits ) {
$data_week_visits = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(%s) FROM {$wpdb->prefix}watchman_site
WHERE login_result <> %d AND WEEK(time_visit) = WEEK(now()) AND YEAR(time_visit) = YEAR(now())
",
'*',
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_week_visits, 'wms7' );
}
$cache_key          = 'wms7_data_week_visitors';
$data_week_visitors = wp_cache_get( $cache_key );
if ( ! $data_week_visitors ) {
$data_week_visitors = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(DISTINCT user_ip) FROM {$wpdb->prefix}watchman_site
WHERE login_result <> %d AND WEEK(time_visit) = WEEK(now()) AND YEAR(time_visit) = YEAR(now())
",
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_week_visitors, 'wms7' );
}
$cache_key        = 'wms7_data_week_robots';
$data_week_robots = wp_cache_get( $cache_key );
if ( ! $data_week_robots ) {
$data_week_robots = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(DISTINCT robot) FROM {$wpdb->prefix}watchman_site
WHERE login_result = %d AND WEEK(time_visit) = WEEK(now()) AND YEAR(time_visit) = YEAR(now())
",
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_week_robots, 'wms7' );
}
$cache_key         = 'wms7_data_today_visits';
$data_today_visits = wp_cache_get( $cache_key );
if ( ! $data_today_visits ) {
$data_today_visits = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(%s) FROM {$wpdb->prefix}watchman_site
WHERE login_result <> %d AND time_visit >= CURDATE()
",
'*',
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_today_visits, 'wms7' );
}
$cache_key           = 'wms7_data_today_visitors';
$data_today_visitors = wp_cache_get( $cache_key );
if ( ! $data_today_visitors ) {
$data_today_visitors = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(DISTINCT user_ip) FROM {$wpdb->prefix}watchman_site
WHERE login_result <> %d AND time_visit >= CURDATE()
",
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_today_visitors, 'wms7' );
}
$cache_key         = 'wms7_data_today_robots';
$data_today_robots = wp_cache_get( $cache_key );
if ( ! $data_today_robots ) {
$data_today_robots = $wpdb->get_var(
$wpdb->prepare(
"
SELECT count(DISTINCT robot) FROM {$wpdb->prefix}watchman_site
WHERE login_result = %d AND time_visit >= CURDATE()
",
3
)
);// db call ok; cache ok.
wp_cache_set( $cache_key, $data_today_robots, 'wms7' );
}

$result = intval( $data_month_visits ) . '|' . intval( $data_month_visitors ) . '|' . intval( $data_month_robots ) . '|' .
intval( $data_week_visits ) . '|' . intval( $data_week_visitors ) . '|' . intval( $data_week_robots ) . '|' .
intval( $data_today_visits ) . '|' . intval( $data_today_visitors ) . '|' . intval( $data_today_robots );

return $result;
}
/**
* Used for Serves to send information to the client browser (frontend of site).
*
* @param string $data1 Number of visits records.
*/
function wms7_send_frontend( $data1 ) {
if ( ! headers_sent() ) {
header( 'Content-Type: text/event-stream' );
header( 'Cache-Control: no-cache' );
header( 'Connection: keep-alive' );
}
echo 'data: ' . $data1;
echo "\n\n";
// check for output_buffering activation.
if ( 0 !== count( ob_get_status() ) ) {
ob_flush();
}
flush();
}

while ( true ) {
$new_count_rows = wms7_widget_counter();
wms7_send_frontend( $new_count_rows );
sleep( 10 );
}

В выше приведенном коде функция wms7_widget_counter() формирует 9 значений для 9 полей виджета и передает через функцию wms7_send_frontend() на сторону клиента JS.
На стороне клиента функция wms7_sse_frontend() смотрит: если вновь поступившие данные отличаются от старых (уже отрисованных на странице), то перерисовывает их заново, сохраняя новые данные в cookie. Вот и все.
🙂 p.s.: Здесь тоже используется кешированные данные на стороне сервера: wp_cache_get() и wp_cache_set(). Очень рекомендую их использовать. Надо щадить СУБД сайта от излишних нагрузок. Такой подход более профессиональный.