Как последовательно применять правило перезаписи (Rewrite Rule) при разработке плагинов
Допустим, я создаю плагин поддержки клиентов, который перехватывает часть URL /support и перенаправляет его в MVC-фреймворк в папке app моего плагина, например:
RewriteRule ^support(.*)$ wp-content/plugins/csupport/app/$1 [L]
Я знаю, что это можно сделать в .htaccess блога прямо перед строкой RewriteBase, но не у всех включен .htaccess, если только они не добавили пользовательскую постоянную ссылку. Я нашел способ принудительно включить пользовательские постоянные ссылки, но это грубый метод и он не рекомендуется.
Какой рекомендуемый способ для разработчиков плагинов добавить правило перезаписи, чтобы они могли перехватывать вызов поддиректории типа /support и перенаправлять его в другое место?

Вот решение. Смотрите комментарии в конце...
// Я - файл functions.php плагина под названием "sample" - реализуйте меня немного иначе для темы.
// Обратите внимание, этот код требует WP 3.0 или выше.
class SAMPLE {
public static function activatePlugin() {
self::rewriteURL();
flush_rewrite_rules();
if (get_option('permalink_structure') == '') {
self::updatePermalinks();
}
// добавьте сюда другой код активации плагина
}
public static function deactivatePlugin(){
flush_rewrite_rules();
// добавьте сюда другой код деактивации плагина
}
public static function drawAdminMenu(){
$s = <<<EOD
<div class="wrap">
<div id="icon-options-general" class="icon32"><br></div>
<h2>Панель Sample</h2>
<!-- костыль для исправления проблемы с постоянными ссылками - это связано с кодом активации плагина -->
<iframe style="position:absolute;top:-5000px" src="<?= admin_url() ?>options-permalink.php"></iframe>
<p>Ваши настройки здесь.</p>
</div><!-- .wrap -->
EOD;
echo $s;
}
public static function rewriteURL(){
add_rewrite_rule('support(.*)$','wp-content/plugins/sample/app/$1','top');
// это редактирует ваш .htaccess файл и добавляет:
// RewriteRule ^support(.*)$ /wp-content/plugins/sample/app/$1 [QSA,L]
}
public static function updatePermalinks(){
global $wp_rewrite;
$wp_rewrite->set_permalink_structure('/%postname%/');
$wp_rewrite->flush_rules();
// Обратите внимание, что остальное выполняется через скрытый IFRAME в форме регистрации
// чтобы создать файл .htaccess. Это костыль - но он хорошо работает!
}
} // конец класса SAMPLE
register_activation_hook(__FILE__,'SAMPLE::activatePlugin');
register_deactivation_hook(__FILE__,'SAMPLE::deactivatePlugin');
add_action('admin_menu', 'SAMPLE::drawAdminMenu');
add_action('init','SAMPLE::rewriteURL');
Вы никогда не должны запускать flush_rewrite_rules() постоянно. Эндрю Нацин, один из основных разработчиков WordPress, неоднократно указывал, что это должно выполняться только в колбэках активации и деактивации плагина или темы. (У тем нет таких колбэков, но в интернете есть примеры, которые имитируют их для тем.) Эндрю говорит, что это снижает производительность, если делать иначе. Это очевидно, потому что потенциально может перезаписывать файл .htaccess при каждой загрузке страницы.
Я использую статические методы класса вместо глобальных функций в пространстве имен. Это менее рискованно и более аккуратно.
Обратите внимание на последовательность в activatePlugin() - это важно. Сначала нужно переписать URL, затем сбросить правила, и если пользовательские постоянные ссылки не включены - включить их.
Пользовательские постоянные ссылки критически важны. Без них не создается файл .htaccess и, следовательно, не работают правила перезаписи.
Обратите внимание, что мы не включаем пользовательские постоянные ссылки принудительно. Мы проверяем, включены ли они. Если нет - включаем и используем распространенный тип постоянных ссылок, часто применяемый для SEO.
Обратите внимание на проблему в моей функции updatePermalinks(). Это то, что я обнаружил во всех версиях WordPress. Я выяснил, что файл .htaccess не создается, пока пользователь не откроет панель настроек постоянных ссылок. Не знаю, почему в WordPress есть эта ошибка, но она есть. Как видно из IFRAME ниже, я нашел хорошее решение. Этот IFRAME гарантирует создание файла .htaccess, если он не был создан - при условии, что вызов updatePermalinks() был выполнен ранее.
В WordPress Codex говорилось, что у меня будут проблемы с параметрами запроса в переписанном URL, если я не реализую add_query_vars(). Однако я обнаружил, что это не так. Я смог перехватить /support и перенаправить его в MVC-фреймворк внутри моего плагина, затем загрузить этот фреймворк с красивыми URL типа /support/tickets/1, а также использовать параметры запроса в конце, например /support/tickets/1?q=open&s=sample+keywords.
Обратите внимание, что:
add_rewrite_rule('support(.*)$','wp-content/plugins/sample/app/$1','top');
...эквивалентно:
RewriteRule ^support(.*)$ /wp-content/plugins/sample/app/$1 [QSA,L]
...в файле .htaccess, что именно мне и нужно.
Почему документация так запутана и вводит в заблуждение по теме RewriteRules - понятия не имею.
EDIT1: Изменил site_url() на admin_url() в вызове IFRAME.

это работает в определенных случаях, но не очень переносимо, так как вы полагаетесь на доступность mod_rewrite и возможность записи в .htaccess. также ваше правило перезаписи предполагает, что домашний URL и URL сайта одинаковы. возможно, стоит рассмотреть запасной вариант создания страницы, через которую можно маршрутизировать запросы, и дать пользователям возможность выбрать, какой метод использовать.

Вы правы. Это не предназначено для переносимости и требует .htaccess и mod_rewrite. То есть, если только вы не предложите решение, которое работает без этого.

@Milo можете объяснить, почему домашний URL и URL сайта могут отличаться?

WordPress можно установить в директорию, отличную от той, из которой он публично обслуживается, поэтому возможно, что wp-content находится на один уровень ниже этого файла htaccess. Что касается альтернативного решения — вообще не используйте перезаписи, создайте страницу, через которую будут маршрутизироваться запросы, подключитесь рано в процессе запроса, проверьте, является ли эта страница запрашиваемой, а затем загрузите код вашего плагина. Для примера посмотрите, как большинство плагинов для корзин создают свои страницы корзины и оформления заказа.

@Milo Хорошо, что я пообщался с вами! Я только что узнал разницу между site_url() и home_url(). У меня были другие части кода, где использовался site_url() вместо home_url(). В коде выше я также изменил и использовал admin_url().
