jQuery: решение проблемы с куками на WordPress при использовании MaxCache
Этот пост адресован пользователям WordPress, у которых стоит кэширование с помощь MaxCache.
WordPress имеет в своем функционале две очень хорошие особенности, которые повышают юзабилити сайта для посетителей, оставляющих комментарии:
- Однажды оставив одобренный администратором сайта комментарий, посетитель избавляется от необходимости при каждом следующем комментарии вновь вводить свои данные, такие, как имя, e-mail, и адрес сайта.
- Если добавляемый комментарий уходит на модерацию, то комментирующий все равно видит его на сайте, обычно с пометкой “коммент ожидает модерации”. Это дает посетителю понять, что комментарий успешно добавлен и не попал в спам-фильтр.
Проблема
Не так давно известный программист Максим создал очень полезный и нужный скрипт - кэш для WordPress (MaxCache), который существенно снижает нагрузку на сервер хостинга. Для моего тормознутого хостинга, на котором в данный момент мой блог dimox.name вынужден, так сказать, волочить свое существование, этот скрипт оказался спасением.
Но речь сейчас не о хостинге и не о скрипте MaxCache. Суть поста заключается в нижеследующем.
При использовании кэша MaxCache WordPress-сайт теряет те самые 2 замечательные функции, о которых я написал выше. Это связано с особенностью работы MaxCache, и, к сожалению, на уровне скрипта ничего сделать нельзя.
После установки кэша я неоднократно видел комментарии, в которых мои постоянные читатели с сожалением сообщали о необходимости каждый раз снова вводить свои данные.
Я всегда стараюсь идти на встречу посетителям своих сайтов, поэтому я нашел выход из данной ситуации - написал на jQuery скрипт, который выполняет 2 утерянные после установки кэша функции.
Далее я подробненько опишу код скрипта. Если у вас нет желания все это читать, то можете сразу “перескочить” на конечный код скрипта.
Функция №1 - “вспоминаем” комментирующего
Чтобы избежать возможных конфликтов jQuery с другими фреймворками, создадим вот такую переменную, которую в дальнейшем и будем использовать:
1 | var $j = jQuery.noConflict(); |
Ну а все содержимое скрипта будем размещать вот в такую функцию, которая выполнит находящийся в ней код после загрузки веб-страницы:
1 | $j(function() { ... }) |
Для того, чтобы “вспомнить” нашего посетителя, который уже оставлял комментарий, мы создадим куки, в которых запишем его имя, e-mail и адрес сайта. Работать мы будешь, соответственно, с формой комментирования WordPress. Ее HTML-код выглядит примерно следующим образом (показываю только теги, необходимы нам):
1 2 3 4 5 6 | <form action="/wp-comments-post.php" method="post" id="commentform"> <input type="text" name="author" /> <input type="text" name="email" /> <input type="text" name="url" /> ... </form> |
Чтобы скрипт, который мы получим в результате, у вас работал, убедитесь, что в вашем шаблоне WordPress у тега form используется точно такой же идентификатор (id=”commentform”).
У каждого input-тега в этой форме есть неизменный параметр name=”", за которые мы и будем цепляться при написании скрипта.
Куки мы будет создавать в тот момент, когда комментирующий нажимает кнопку “Добавить комментарий”, для этого воспользуемся вот такой функцией:
1 | $j('#commentform').submit(function() { ... }); |
В ней мы считаем значения полей input[name=”author”], input[name=”email”], input[name=”url”] и с помощью функции createCookie() (ее код я приведу в конце, дабы не раздувать размер статьи) поместим их в куки браузера:
1 2 3 4 5 6 7 | $j('#commentform').submit(function() { createCookie('wp_commenter_author', $j('input[name="author"]').val(), 365); createCookie('wp_commenter_email', $j('input[name="email"]').val(), 365); createCookie('wp_commenter_url', $j('input[name="url"]').val(), 365); }); |
В результате в браузере пользователя появляются куки со следующими названиями: wp_commenter_author, wp_commenter_email и wp_commenter_url. Цифра 365 указывает на количество дней хранения этих куков.
Можно было так и оставить вышеуказанный код создания куков, но в ходе тестирования обнаружился следующий момент. Как вы знаете, если вы залогинены на своем блоге, то при добавлении комментария поля имя/е-mail/сайт за ненадобностью не отображаются. Поэтому, поскольку этих полей нет, то в 3-х создаваемых куках записывается значение “undifined” (т.е. значение не определено). А, поскольку, далее мы будем эти куки считывать, то нам таких неопределенностей необходимо избежать. Поэтому мы делаем это следующим образом:
1 2 3 4 5 6 7 | $j('#commentform').submit(function() { if ($j('input[name="author"]').length) createCookie('wp_commenter_author', $j('input[name="author"]').val(), 365); if ($j('input[name="email"]').length) createCookie('wp_commenter_email', $j('input[name="email"]').val(), 365); if ($j('input[name="url"]').length) createCookie('wp_commenter_url', $j('input[name="url"]').val(), 365); }); |
Т.е. условием вида if ($j(’input[name=”author”]’).length) мы проверяем существования 3-х полей, и, если они есть на странице (т.е. это не залогиненный пользователь), то создаем куки со значениями данных полей.
Хорошо, куки создали. Теперь нужно их считать для последующий подстановки в эти поля. Создадим переменные, в которые сразу же заносим значения наших кук (код функции readCookie() будет приведен также в конце):
1 2 3 | var author = readCookie('wp_commenter_author'); var email = readCookie('wp_commenter_email'); var url = readCookie('wp_commenter_url'); |
Куки прочитали, теперь нужно заполнить ими 3 наших поля (имя/е-mail/сайт):
1 2 3 | if (author) $j('input[name="author"]').val(author); if (email) $j('input[name="email"]').val(email); if (url) $j('input[name="url"]').val(url); |
Условие вида if (author) говорит нам, что, если кука существует в браузере пользователя, то мы вставляем ее значение в соответствующее поле. Эта проверка необходима, чтобы избежать ошибки, если кук нет.
В результате мы получаем вот такой конечный скрипт, который реализует 1-ю функцию - запоминает данные, введенные комментирующим, и в последствии автоматически подставляет их в поля.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | var $j = jQuery.noConflict(); $j(function() { var author = readCookie('wp_commenter_author'); var email = readCookie('wp_commenter_email'); var url = readCookie('wp_commenter_url'); if (author) $j('input[name="author"]').val(author); if (email) $j('input[name="email"]').val(email); if (url) $j('input[name="url"]').val(url); $j('#commentform').submit(function() { if ($j('input[name="author"]').length) createCookie('wp_commenter_author', $j('input[name="author"]').val(), 365); if ($j('input[name="email"]').length) createCookie('wp_commenter_email', $j('input[name="email"]').val(), 365); if ($j('input[name="url"]').length) createCookie('wp_commenter_url', $j('input[name="url"]').val(), 365); }); }) |
Можно было бы и остановится на таком скрипте. Но, ведь у нас еще есть и вторая задача. Дело в том, что при использовании MaxCache, если комментарий уходит на модерацию, то комментирующий не видит своего комментария на странице, (как это бывает, если кэш не используется). В результате некоторые товарищи думают, что комментарий ушел в спам (или просто что-то пошло не так) и отправляют комментарий заново по нескольку раз.
Чтобы подобного избежать, мы доработаем наш скрипт.
Функция №2 - даем знать, что комментарий добавлен
Сразу отмечу, что, для того, чтобы работала нижеследующая часть кода, необходимо, чтобы в шаблоне комментариев стояла якорь-ссылка для каждого комментария, т.е. в PHP-коде это выглядит примерно следующим образом:
1 | <a href="<?php echo htmlspecialchars( get_comment_link( $comment->comment_ID ) ) ?>"><?php printf(__('%1$s at %2$s'), get_comment_date(), get_comment_time()) ?></a> |
А если смотреть исходный HTML-код страницы WordPress-сайта, то должен быть примерно такой код:
1 | <a href="адрес_страницы/#comment-123" title="02 февраля, 2008 в 10:06 пп">текст ссылки</a> |
Сначала мы добавим новую куку, которая будет создаваться также при нажатии на кнопку “Добавить комментарий” и создадим переменную, в которую ее будем считывать:
1 2 3 4 5 6 | var submitCheck = readCookie('wp_submit_check'); $j('#commentform').submit(function() { ... createCookie('wp_submit_check', '1', 1); }); |
Ее значение ставим равным “1″ и срок ее хранения - 1 день.
Все, что мы будем делать дальше, будет работать только при условии, что значение куки “wp_submit_check” равно “1″.
1 | if (submitCheck == '1') { ... } |
А кука эта когда у нас появляется? Правильно - при отправке формы. Т.е. все манипуляции с кукой wp_submit_check сделаны для того, чтобы выполнять нижеследующие действия только тогда, когда отправлен комментарий, и посетителю нужно об этом сообщить.
Если кратко на словах, то мы будем делать следующее - после добавления комментария посетитель перенаправляется на страницу с адресом вида http://site.ru/postname/#comment-123, где цифра - это идентификатор добавленного комментария. Далее проверяем, есть ли уже на странице ссылка с точно таким же якорем (#comment-123), если нет, то показываем посетителю сообщение о том, что его комментарий добавлен.
Дальнейшие комментарии напишу в самом скрипте:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 | if (submitCheck == '1') { // считываем URL из адресной строки браузера и заносим в переменную var href = window.location.href; // проверяем, находимся ли мы на странице вида http://site.ru/postname/#comment-123 // (страница с таким адресом открывается всегда при добавлении комментария в WordPress) if (/.+#comment-(\d+)/ig.test(href)) { // получаем цифру из URL в адресной строке, // которая стоит после "#comment-", // и помещаем ее в переменную var cid = href.replace(/.+#comment-(\d+)/ig, '$1'); // создаем регулярное выражение, которое будем использовать дальше, // и заносим его в переменную reg var reg = new RegExp('#comment-' + cid,'ig'); // переменная, значение которой будем менять, // если ссылка с якорем найдется var commentIdFind = 0; // пробегаемся по каждой ссылке в комментариях $j('#commentlist a').each(function() { // тестируем URL ссылки (атрибут href) с помощью // регулярного выражения reg (см. выше) if (reg.test($j(this).attr('href'))) { // если нашли такую ссылку с якорем из адресной строки // (а это значит, что коммент выведен на страницу и ничего // дополнительно показывать посетителю не нужно), // то переменной commentIdFind ставим значение = 1 // и удаляем куку wp_submit_check commentIdFind = 1; eraseCookie('wp_submit_check'); } }) // если commentIdFind = 0, получается, что ссылка не была найдена, // значит комментарий не выведен на страницу, и нужно показать сообщение if (commentIdFind == 0) { // выводим сообщение путем добавления тега <li></li> в конец списка с комментариями // и назначаем ему идентификатор вида comment-123, // где вместо цифры подставляется ID добавленного комментария, // для того, чтобы проскроллить страницу к этому сообщению // HTML-код сообщения измените под себя $j('#commentlist').append('<li id="comment-' + cid + '"><em><strong>' + author + '</strong>, спасибо за Ваш комментарий! Он будет опубликован, как только его проверит администратор сайта.</em></li>'); // считываем юзер-агента для определения браузера Opera, // чтобы применить к нему особые стили var userAgent = navigator.userAgent.toLowerCase(); if (/opera/.test(userAgent)) { var top = 'html'; } else { var top = 'html, body'; } // определяем расстояние от верхнего края браузера // до элемента с нашим сообщением, заносим в переменную var offset = $j('#comment-' + cid).offset().top; // плавно прокручиваем окно браузера до сообщения, // которое показываем посетителю $j(top).animate({scrollTop: offset}, 500); } // если мы ушли со странице вида http://site.ru/postname/#comment-123, // то удаляем куку wp_submit_check } else { eraseCookie('wp_submit_check'); } } |
В результате выполнения этой части кода комментатор увидит вот такое сообщение (естественно, если выполнены все вышеописанные условия):
%username%, спасибо за Ваш комментарий! Он будет опубликован, как только его проверит администратор сайта.
Конечный код jQuery-скрипта
В итоге у нас получился вот такой скрипт (для тех, кто не читал мои подробности по коду - обратите внимание на условия работы скрипта: условие раз, условие два):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | function createCookie(name,value,days) { if (days) { var date = new Date(); date.setTime(date.getTime()+(days*24*60*60*1000)); var expires = "; expires="+date.toGMTString(); } else var expires = ""; document.cookie = name+"="+value+expires+"; path=/"; } function readCookie(name) { var nameEQ = name + "="; var ca = document.cookie.split(';'); for(var i=0;i < ca.length;i++) { var c = ca[i]; while (c.charAt(0)==' ') c = c.substring(1,c.length); if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length); } return null; } function eraseCookie(name) { createCookie(name,"",-1); } var $j = jQuery.noConflict(); $j(function() { var author = readCookie('wp_commenter_author'); var email = readCookie('wp_commenter_email'); var url = readCookie('wp_commenter_url'); var submitCheck = readCookie('wp_submit_check'); if (author) $j('input[name="author"]').val(author); if (email) $j('input[name="email"]').val(email); if (url) $j('input[name="url"]').val(url); $j('#commentform').submit(function() { if ($j('input[name="author"]').length) createCookie('wp_commenter_author', $j('input[name="author"]').val(), 365); if ($j('input[name="email"]').length) createCookie('wp_commenter_email', $j('input[name="email"]').val(), 365); if ($j('input[name="url"]').length) createCookie('wp_commenter_url', $j('input[name="url"]').val(), 365); createCookie('wp_submit_check', '1', 1); }); if (submitCheck == '1') { var href = window.location.href; if (/.+#comment-(\d+)/ig.test(href)) { var cid = href.replace(/.+#comment-(\d+)/ig, '$1'); var reg = new RegExp('#comment-' + cid,'ig'); var commentIdFind = 0; $j('#commentlist a').each(function() { if (reg.test($j(this).attr('href'))) { commentIdFind = 1; eraseCookie('wp_submit_check'); } }) if (commentIdFind == 0) { $j('#commentlist').append('<li id="comment-' + cid + '"><em><strong>' + author + '</strong>, спасибо за Ваш комментарий! Он будет опубликован, как только его проверит администратор сайта.</em></li>'); var userAgent = navigator.userAgent.toLowerCase(); if (/opera/.test(userAgent)) { var top = 'html'; } else { var top = 'html, body'; } var offset = $j('#comment-' + cid).offset().top; $j(top).animate({scrollTop: offset}, 500); } } else { eraseCookie('wp_submit_check'); } } }) |
Ну а как подключить этот скрипт к сайту, я, надеюсь, вы уже знаете, об этом я не раз писал в своих предыдущих статьях по jQuery.
Огромное спасибо, Димокс! В закладки и блог-шоу однозначно!
Пожалуйста, Миша ;)
Большое спасибо!
И проблема решена важная и статья познавательная.
Дима, я так подумал, наверное всё-таки правильней в форме прицепиться не к id, а к name. В WordPress’е эти name стандартные, а id, как ты и написал, могут меняться. Так что переход к name гарантирует работу (заполнение полей) с любым шаблоном.
Точно! Как же я об этом не подумал? Привык всегда цепляться за id =) Спасибо, Макс, за подсказку! Внесу поправки.
Ура, данные сохраняются :)
Хм, только что-то после добавки предыдущего коммента моё имя само изменилось сами видите на что.
Странненько… я сколько тестировал, все нормально было.
Я вот глянул, а оно будет куки в днях хранить? Может было бы лучше в Unix?
Что значит “в Unix”?
Чтобы писать $, а не $j
Можно весь свой скрипт обернуть в такую конструкцию, это самый правильный вариан:
(function($){
здесь ваш скрипт…
})(jQuery);
Это гарантирует отстутствие конфликтов с другими фреймворками?
Спасибо Dimox огромное, давно искал +один подписчик
Тоже кириллические имена сохраняются как смайлики.
Да, есть такая проблема. Пока не знаю, как решить.
Я бы назвал все вышеизложенное “ПерАнусДерАстро” :-D
Все можно сделать гораздо проще.
1. Удалить из кеша страницу с комментарием, и для этого пользователя (однократно) вывести лайф версию.
2. Не писать новые свои куки, а взять родные которые создал Вердпресс, и джаваскриптом распихать в инпуты.
Таким образом не придется править код при обновлении движка.
Пример скрипта который надо впихнуть в свой шаблон комментариев:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
var cookie = " " + document.cookie;
var search = " " + name;
var setStr = null;
var offset = 0;
var end = 0;
if (cookie.length > 0) {
offset = cookie.indexOf(search);
if (offset != -1) {
offset += search.length;
end = cookie.indexOf(";", offset)
if (end == -1) {
end = cookie.length;
}
setStr = cookie.substring(offset, end);
begin=setStr.indexOf('=')+1;
setStr = unescape(setStr.substring(begin,setStr.length));
}
}
return(setStr);
}
function getToInput()
{
var arg = getToInput.arguments;
if (!arg[0]) arg=new Array('author','email','url');
if (document.cookie)
{
var commData=new Array(searchCookie('comment_author'), searchCookie('comment_author_email'), searchCookie('comment_author_url'));
if (!commData.join('')) return false;
for (i=0;i<3;i++)
{
obj=document.getElementById(arg[i]);
if ((obj) && (obj.value=='')) obj.value=commData[i];
}
}
}
// инициализация обязательно вставить после вывода инпутов.
getToInput('author','email','url');
Как удалить из кеша страницу с комментарием? И что такое “лайф версия”?
Для этого в файле wp-comments-post.php (в корне сайта) добавить
перед wp_redirect($location); ?> (строка находится в самом конце файла) setcookie(’comment’,'clearCache’);
а в начале функции max_cashe() после
2
$filepath = $dir . md5($url);
вставить:
2
3
4
5
6
7
{
if (is_file($filepath)) unlink($filepath);
$my_cache_no = true;
setcookie('comment','');
return false;
}
Версия не из кеша.
Ой там не $my_cache_no = true;, а наверное $max_cache_no = true;
Не знаю точно, пока оригинального кода Max’a не видел. Но идея я думаю понятна?
Тут вот в чем дело. В платной версии это уже все работает автоматом. Поэтому ничего менять и доделывать не нужно. :)
А вот что касается использования родных куков вместо своих - нужно думать. То что не нужно менять шаблон заманчиво, но отлавливать и устанавливать куку нужно до отработки WordPress и тут может быть сложность: в WordPress редиректы при комментировании. Кроме этого кука сама по себе отлавливается WordPress’ом в своих недрах и явно подставляется в инпуты в value. То есть на уровне html-кода. Так что вариант Димы более надежен.
Мы сейчас говорим, не о подстановке куков в вордпресовскую версию а о подстановке куков в кешированную версию, где поля (по идее) всегда должны быть чистыми.
Кроме того, джаваскриптовые функции можно внедрять в момент кеширования только в кешированные версии. В таком случае даже рекомендаций по изменению шаблона не нужно будет делать.
Кешированная страница отдается как есть голой статикой. В ней если и использовать куки, то только на уровне js. Но внедрять js в кэшированную версию не есть хорошо. Код должен оставаться чистым, так как его отдает WordPress.
У меня выскочила аналогичная проблема, Джаваскрипт вычитывает из куков не совсем так как хотелось бы. К примеру:
русс = %D1%80%D1%83%D1%81%D1%81;
unescape(’%D1%80%D1%83%D1%81%D1%81′) = ÑÑÑÑ;
Решается проблема следующим методом:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// public method for Cookie decoding
decode : function (string) {
return this._utf8_decode(unescape(string));
},
// private method for UTF-8 decoding
_utf8_decode : function (utftext) {
var string = "";
var i = 0;
var c = c1 = c2 = 0;
while ( i < utftext.length ) {
c = utftext.charCodeAt(i);
if (c < 128) { string += String.fromCharCode(c); i++; }
else if((c > 191) && (c < 224)) {
c2 = utftext.charCodeAt(i+1);
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = utftext.charCodeAt(i+1);
c3 = utftext.charCodeAt(i+2);
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return string;
}
}
В результате:
cookie.decode(’%D1%80%D1%83%D1%81%D1%81′) = русс
Спасибо! В свободное время проверю и дополню пост.
Мм, мучился мучился. Вроде все условия выполнены, но во-первых, так и не заработало, а во-вторых, “редактор” вместо простановки тегов лепит мне .
Скрин вот
Не знаю, чем тебе помочь.
Прочитай про обновление плагина.
Вот, редактор ожил, спасибо.
Эх, че ж делать то с куками..
Наконец-то я добрался. Попробовал прицепить ваш код к своему скрипту - ничего не изменилось. Непонятно, что я неправильно делаю…
cookie.decode() нужно применять при записи в куки или при чтении из кук?