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