В процессе создания и эксплуатации плагина WatchMan-Site7 мне пришла в голову идея создать такой механизм, который мог бы сам, без ручного вмешательства со стороны админа – обновлял основной экран посещений всех типов посетителей (людей, роботов) по мере их появления на сайте. Потому что – надоело: сидишь в админке и как дятел стучишь по клавише “обновить” экран браузера, что бы увидеть новых посетителей сайта. Пусть бы сам сайт посылал мне в браузер информацию о новых посещениях по мере их поступления!
Пришлось углубиться в теорию. И вот, что я накопал… Существуют три технологии динамической связи “клиент-сервер”:
1. AJAX (набор ранее существующих технологий – JS, PHP, HTML)
2. WebSocket (мощная вещь, но достаточно сложная в реализации)
3. SSE (Server-Sent Events)
Пришлось познакомиться с перечисленными технологиями, причем зная, что базовый класс WP_LIST_TABLE в WordPress адаптирован к технологии AJAX. И поэтому, подспудно, думая на какой технологии остановиться, я склонялся к AJAX. Однако, спустя некоторое время, я все-таки остановился на технологии SSE ( спецификация SSE ). И вот, как это удалось реализовать.
На стороне сервера:
** * Description: Used to send a count of records of visitor, number of unseen emails. * * PHP version 5 * * @category Wms7-sse-backend.php * @package WatchMan-Site7 * @author Oleg Klenitskiy <klenitskiy.oleg@mail.ru> * @version 3.0.3 * @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 mail inbox connection. * Function with the same name is in the file wms7-mail.php plugin. * * @return object. */ function wms7_mail_inbox_connection() { $val = get_option( 'wms7_main_settings' ); $select_box = $val['mail_select']; $box = $val[ $select_box ]; $server = '{' . $box['imap_server'] . ':' . $box['mail_box_port'] . '/imap/' . $box['mail_box_encryption'] . '/novalidate-cert}INBOX'; $username = $box['mail_box_name']; $password = $box['mail_box_pwd']; if ( $box && '' !== $username && '' !== $password ) { try { $imap = imap_open( $server, $username, $password ); } catch ( Exception $e ) { $imap = $e->getMessage() . '<br>server: ' . $server . '<br>username: ' . $username . '<br>password: ' . $password; } return $imap; } } /** * Used for mail inbox unseen. Function with the same name is in the file wms7-mail.php plugin. * * @return number. */ function wms7_mail_unseen() { $imap = wms7_mail_inbox_connection(); $i = 0; if ( $imap ) { $mc = imap_check( $imap ); // Get an overview of all the letters in the box. $result = imap_fetch_overview( $imap, "1:{$mc->Nmsgs}", 0 ); foreach ( $result as $overview ) { if ( 0 === $overview->seen ) { $i++; } } imap_close( $imap ); } return $i; } /** * Used for to obtain the number of visits records. * * @return number. */ function wms7_count_rows() { global $wpdb; $cache_key = 'all_total'; $results = wp_cache_get( $cache_key ); if ( ! $results ) { $results = $wpdb->get_var( $wpdb->prepare( " SELECT count(%s) FROM {$wpdb->prefix}watchman_site ", '*' ) );// db call ok; cache ok. wp_cache_set( $cache_key, $results, 'wms7' ); } return $results; } /** * Used for Serves to send information to the client browser (backend of site). * * @param string $data1 Number of visits records. * @param string $data2 Number of mails inbox unseen. */ function wms7_send_backend( $data1, $data2 ) { if ( ! headers_sent() ) { header( 'Content-Type: text/event-stream' ); header( 'Cache-Control: no-cache' ); header( 'Connection: keep-alive' ); } echo 'data: ' . intval( $data1 ); echo '|' . intval( $data2 ); echo "\n\n"; // check for output_buffering activation. if ( 0 !== count( ob_get_status() ) ) { ob_flush(); } flush(); } while ( true ) { $new_count_rows = wms7_count_rows(); $mail_unseen = wms7_mail_unseen(); wms7_send_backend( $new_count_rows, $mail_unseen ); sleep( 10 ); }
Прокомментирую:
В выше показанном коде – основное действие в строках 116-121. Остальное – необходимая прелюдия. Хотя и тут есть интересный момент. В строках 80 и 90 можно заметить, что используется кеширование запросов из БД (функции wp_cache_set и wp_cache_get). Молодцы ребята из WordPress! И действительно, зачем теребить частыми и тяжелыми запросами БД, когда можно брать данные из кеша!? А вот когда данные в БД изменились, вот тогда и делать запрос в БД для обновления кеша. Короче – очень изящное решение придумали в WordPress-е. Кстати, надо не забывать, там где в коде добавляются данные в БД (происходит обновление) прописать – удаление кеша (wp_cache_delete( ‘all_total’, ‘wms7’ ); – а то в строке 81 будете все время считывать старые данные) Так… немного отвлекся, возвращаюсь к изложению статьи.
Создается цикл с периодом ожидания 10 сек. Здесь, конечно можно было бы придумать что-то по-изящнее. Как-то, через хуки ловить появления новых записей в БД, или как-то иначе… Но пока – так, просто в цикле 🙂
p.s.: Это очень важно. Вот так, как я делал в строках 017-019 – НЕ ДЕЛАЙТЕ. Сейчас WordPress за такое сильно наказывает, вплоть до блокировки плагина автора и изгнанием его из сообщества WordPress. Это я привел пример из моей “грешной молодости” и в качестве эксперимента. А вот как сейчас это надо делать – спрашивайте – отвечу. (Если коротко, это делается через хук add_action(‘wp_ajax_my_action’, ‘my_function’);)
На стороне клиента:
/** * Description: Used to manage the plug-in on the client side. * * @category Wms7_backend.js * @package WatchMan-Site7 * @author Oleg Klenitskiy <klenitskiy.oleg@mail.ru> * @version 3.0.3 * @license GPLv2 or later */ /** * Process Control Server Sent Events on the client side (backend). */ function wms7_sse_backend() { var myElement = document.getElementById( 'sse' ); if (myElement.checked) { document.cookie = 'wms7_sse_backend=on'; if ( ! ! window.EventSource ) { var source = new EventSource( wms7_url + 'includes/wms7-sse-backend.php' ); source.addEventListener( 'message', function(e) { console.log( e.data ); var arr = e.data.split( '|' ); if (get_cookie( 'wms7_records_count' ) !== arr[0] || get_cookie( 'wms7_unseen_count' ) !== arr[1]) { document.cookie = 'wms7_records_count=' + arr[0]; document.cookie = 'wms7_unseen_count=' + arr[1]; wms7_beep(); location.replace( window.location.href ); } }, 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 backend. document.cookie = 'wms7_sse_backend=off'; location.replace( window.location.href ); } wms7_ctrl_btn_href(); }
Прокомментирую:
В последнем коде, в строке 27 происходит сравнение ранее сохраненных данных в cookies с вновь пришедшими данными и сохраняются в cookies для последующего сравнения (когда придут с сервера новые данные). Затем, в строке 30 – звуковой сигнал, и в строке 31 – обновление экрана. Все действие можно наблюдать в консоли, нажав F12.
😉Совет:
Такой подход мог бы быть полезен при проведении каких-либо коммерческих акций, опросов, политических компаний, голосования…