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

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

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

Этот счетчик отличает от других то, что он динамический. Это означает, что данные о посещениях сайта будут поступать в него постоянно, пока страница на которой он находится – есть на экране. Иначе говоря, не требуется обновлять страницу в браузере.
Счетчик является частью плагина 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(). Очень рекомендую их использовать. Надо щадить СУБД сайта от излишних нагрузок. Такой подход более профессиональный.

Leave a Reply

Your email address will not be published. Required fields are marked *