WP Super Cache и куки. Как совместить работу плагина и использование своих cookie?

Итак, в чем проблема? Бывает, что содержимое страниц сайта может меняться в зависимости от установленных cookie. Например на одном из интернет-магазинов меня попросили создать систему отображения разных рекламных баннеров в зависимости от города, с которого пользователь зашел на сайт. Город, если удалось его установить, записывается в cookie и для такого пользователя (и всех других с этого же города) нужно отображать определенный баннер.

Но по-умолчанию для WP Super Cache нет разницы, какие сookie установлены у пользователя. Он будет отдавать ту же, закешированную ранее страницу, поскольку ориентируется только на уникальный УРЛ этой страницы, а он никак не изменяется после установки нами куки. В результате мы получим такую же страницу, какой она была до установки куки. Оказалось, что сам будучи плагином, WP Super Cache поддерживает плагины внутри себя и мы можем написать свой, который бы учитывал нашу ситуацию.

Пример решения для тех, кому не интересен разбор деталей.

По-умолчанию сейчас WP Super Cache распространяется со следующими дополнениями, рассчитаными на разные популярные случаи жизни:

WP Super Cache плагины

Как видим, плагин рассчитан на WordPress Multisite установку, поддерживает работу в паре с плагином Jetpack, ситуацию, когда сайт работает на нескольких доменах т.д. Также в эту папку можно положить свои плагины, написанные под свою уникальную ситуацию. Чем собственно мы и воспользуемся для решения нашей проблемы.

Алгоритм WP Super Cache таков, что для каждого уникального УРЛ геренируется своя копия страницы и помещается в папку, которая называется также, как и путь к этой странице в той части УРЛ, что находится после названия домена. Все это работает при условии наличия модуля mod_rewrite для веб-сервера Apache. Копии кешированных страниц хранятся в папке /wp-content/cache/supercache/%{SERVER_NAME} где последний параметр это название домена сайта. Например копия данной страницы хранится по указанному пути, который совпадает с ее УРЛ

WP Super Cache путь

На всякий случай лежат два файла — сжатый gzip-ом и несжатый. Последний на случай, если браузер пользователя не поддерживает технологию сжатия gzip. Это собственно и есть супер-кеширование данного плагина, так как при наличии mod_rewrite веб-сервер вообще не запускает PHP-скрипты WordPress, не обращается к Базе Данных, а лишь проверяет существование кешированной страницы для данного УРЛ и в случае ее наличия отдает пользователю.

Это самый быстрый способ выдать страницу в ответ на запрос. Для этого плагин и прописывает правила mod_rewrite в файле .htaccess при настройке. С виду они громоздкие и страшные, но на самом деле там четыре раза повторяется почти одно и то же сочетание правил для ситуаций с наличием или отсутствием HTTPS и GZIP.

RewriteCond %{REQUEST_URI} !^.*[^/]$
RewriteCond %{REQUEST_URI} !^.*[^/]$
RewriteCond %{REQUEST_URI} !^.*//.*$
RewriteCond %{REQUEST_METHOD} !POST
RewriteCond %{QUERY_STRING} !.*=.*
RewriteCond %{HTTP:Cookie} !^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$
RewriteCond %{HTTP:X-Wap-Profile} !^[a-z0-9]+ [NC]
RewriteCond %{HTTP:Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP_USER_AGENT} !^.*(2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800).* [NC]
RewriteCond %{HTTP_user_agent} !^(w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-).* [NC]
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{HTTPS} !on
RewriteCond %{DOCUMENT_ROOT}/wp-content/cache/supercache/%{SERVER_NAME}/$1/index.html.gz -f
RewriteRule ^(.*) /wp-content/cache/supercache/%{SERVER_NAME}/$1/index.html.gz [L]

Если разобрать логику этих правил, то упрощенно она такая — «Если это стандартный запрос страницы с обычного браузера, то проверить наличие кешированной копии страницы и отдать ее в случае наличия».

Кроме супер-кеширования с помощью mod_rewrite WP Super Cache поддерживает и РНР-кеширование, когда процесс отдачи страницы требует выполнения минимального объема PHP кода, что происходит чуть медленнее супер-кеширования, но тоже достаточно быстро. Для таких необычных запросов и ситуаций плагин генерирует копии страниц друго формата:

WP Super Cache формат

Примером такой ситуации может служить посещение страницы результатов поиска, УРЛ которой будет содержать строку GET-запроса — например https://stepasyuk.org.ua/?s=кеширование Или посещение страницы администратором сайта, который только что обновил блог и ему надо показать саму свежую копию страницы и возможно еще несколько раз показать ее в будущем. Такие запросы относятся к нестандартным и их сложно или порой невозможно адекватно обработать на уровне модуля mod_rewrite. Поэтому в дело вступет PHP алгоритм.

Если описать сильно упрощенно, то этот PHP-алгоритм генерирует сжатые копии страниц, в названии которых содержится префикс wp-cache и md5_хеш_уникальной_строки, которая формируется для каждой такой нестандартной ситуации. Например при наличии в браузере куки залогиненного в WordPress пользователя, название которой начинается с wordpress_logged_in, будет создан файл, где в названии будет содержаться хеш строки, которая содержит название этой куки. Пример такой строки wordpress_logged_in_2710c71779dfc69ae9a2ef8241b3c297

При повторном обращении к этой странице WP Super Cache получит такие же параметры cookie, что и в предыдущий раз, сформирует такую же строку из названия cookie, хеширует ее, проверит наличие кешированной версии страницы с получивишмся названием и отдаст ее, если с прошлого посещения ничего не поменялось. Ситуация, когда у пользователя установлена кука с названием содержащим wordpress_logged_in предусмотрена в правилах mod_rewrite добавленных плагином:

RewriteCond %{HTTP:Cookie} !^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$
...
RewriteRule ^(.*) /wp-content/cache/supercache/%{SERVER_NAME}/$1/index-https.html.gz [L]

Буквально логика этого правила такова — «если название Cookie НЕ начинается с comment_author или wordpress_logged_in или wp-postpass_, то перейти к следующему правилу». Если же название все-таки начинается с предложенных вариантов, то mod_rewrite не выполнит правило, стоящее после этого условия. То-есть не перенаправит запрос в папку supercache, где уже лежит кешированная копия запрашиваемой страницы, а запустит PHP-код, который уже возьмет «разруливание» запроса в свои руки. Именно механизм разруливания на уровне PHP мы можем использовать для решения нашей проблемы с сообственными cookie.

Решение. Три простых шага

  1. Создадим файл с произвольным названием, например banner.php, нашего плагина для WP Super Cache в папке /wp-content/plugins/wp-super-cache/plugins
    banner.php
  2. Процесс формирования уникальной строки названия файла кешированной копии страницы проходит через хук wp_cache_get_cookies_values в файле /wp-content/plugins/wp-super-cache/wp-cache-phase1.php Только это не классический WordPress-хук, а внутренний для плагина WP Super Cache. И добавлять на него событие следует функцией add_cacheaction( 'wp_cache_get_cookies_values', 'my_function_name' ); а не как принято в WordPress через add_filter(); или add_action();

     

    Чтобы нам учесть ситуацию с наличием установленной нами cookie, допустим, с названием banner, мы должны в процесс формирования названия файла кешированной копии добавить значение этой куки. Для каждого баннера значение cookie будет уникально, поэтому в результате мы получим и уникальное название кешированной копии для каждого значения куки этой страницы:

    function cache_my_page_with_correct_banner( $string ){  
            // с учетом того, что наш баннер отображается только на главной  
            if( isset( $_COOKIE['banner'] ) && is_home() ){  
                    $string = 'banner_' . $_COOKIE['banner'];  
            }  
            return $string;  
    }  
    add_cacheaction( 'wp_cache_get_cookies_values', 'cache_my_page_with_correct_banner' ); 
    
  3. Теперь нам нужно учесть ситуацию с наличием нашей куки в правилах mod_rewrite, иначе запрос будет направлен в папку supercache, где лежит обычная копия нашей страницы. Для этого изменим строку, проверяющую Cookies и добавим к ней проверку наличия нашей куки — в конец строки вариант |banner:
    RewriteCond %{HTTP:Cookie} !^.*(comment_author_|wordpress_logged_in|wp-postpass_|banner).*$
    

    Поскольку данная строка повторяется в .htaccess 4 раза, то и столько же раз нужно внести название нашей куки в правила.

Вот и все — три простых шага и теперь кеширование будет работать корректно для страницы с любым значением нашей куки banner. Важно помнить, что при обновлении плагина из админ-панели WordPress, система полностью удаляет папку со старой версией плагина и в том числе и написанный нами. Поэтому стоит сохранить его копию для восстановления в будущем. Правила mod_rewrite при обновлении WP Super Cache не изменяются, но на всякий случай сохранить их тоже не помешает.

Послесловие

WP Super Cache — отличный плагин. При том, что написан он давно и по-возможностям не самый крутой и даже написан без ООП и даже код его недокументированный и переменные с функциями названы не в лучших традициях программирования. Но зато мне он нравится больше других. Напирмер W3 Total Cache имеет больше возможностей, но с ним админка на рядовом сервере тупит безбожно. Есть еще MaxCache для WordPress, но он платный и процесс установки требует ковыряния в коде, что подходит далеко не для всех. Поэтому WP Super Cache — оптимальное решение для большинства. Хотя в целом эта тема, какой плагин для кеширования лучше — предмет для холиваров. Спорить и доказывать свою точку зрения не буду, у каждого она может быть своя.

<code>...</code>,Wordpress
7 октября 2015

Комментарии(4)

  1. Иван

    Спасибо, очень выручил )

  2. Владимир

    Здравствуйте Андрей. Очень круто и профессионально. Но у меня несколько другая проблема. Яндекс-вебмастерто то видит, то не видит мобильную версию, которая формируется плагином WPtouch. При отключении WP Super Cache — видит всегда. Пытался в robots.txt прописать директиву для мобильного бота на запрет посещения wp-content/cache, не помогло. Службы поддержки хоста и яшки советуют обратиться к разработчику программы, то есть формально отписались. Посоветуйте хоть что -нибудь, пожалуйста.

  3. Даниил

    Благодарю! Только вот плагин лучше класть не в папку плагина (при обновлении он сотрёт её), а в, допустим, `plugins/wp-super-cache-plugins`, и указать её как папку плагинов для WP Super Cache в `wp-config.php` строкой `$wp_cache_plugins_dir = dirname(__FILE__) . ‘/wp-content/plugins/wp-super-cache-plugins/’;`

Оставить комментарий