15 сентября 2009 г.

Интеграция Wordpress в Magento - ЧПУ

Уже давно у нас в в Magento был внедрён Wordpress, специальным модулем-плагином для Magento. Но, используя такой метод внедрения, есть проблема с адресами в Wordpress - можно использовать только простые адреса, вида ?p=<номер поста>, ?cat=<категория> и т.д. Это связано с тем, что обработку всех адресов берёт на себя Magento, т.е. все адреса должны быть в специальном формате - <module name>/<controller name>/<controller action>[/<param1>/<value1>/<param2>/<value2>...]. Если же в Wordpress включить ЧПУ адреса, например, вида <year>/<month>/<post title>, такой адрес придёт в Magento, которая попытается найти модуль <year>, в нём найти контроллер <month>, и выполнить метод <post title>. Естественно ничего такого в Magento нет, поэтому будет ошибка "страница не найдена".

Тем не менее, в Magento можно использовать ЧПУ. Для этого надо писать реврайты. Magento сама создаёт по реврайту на каждый продукт и категорию. Rewrite говорит, какой произвольный адрес перенаправить на какой корректный "Magento-адрес". Например, адрес /benq-2220hd нужно заменить адресом /catalog/product/view/id/496.

Реврайты бывают "внтуренние" и "с редиректом". Внутренние работают внутри :) Т.е. пользователь ввёл один адрес, он видит его в адресной строке, а Magento внутри себя вызывает другой адрес для обработки (более точно - другое действие другого контроллера). Реврайт с редиректом наоборот сразу бросается в глаза - браузер пользователя перенаправляется с одного адреса на другой.

Но даже с реврайтами есть проблема. Модуль, использующийся для интеграции Wordpress в Magento, не позволял использовать ЧПУ. Я его немного переделал, и теперь стало возможным написать адрес вида blog/index/index/param1/value1/param2/value2, и эти параметры будут переданы Wordpress, который на их основе выполнит запрос. Т.е. раньше приходилось писать реврайты с редиректом на не-ЧПУ адрес (например, на /blog/?p=123), сейчас же можно писать "внутренние" реврайты. А подсмотреть, какие нужно передавать параметры, можно в документации на Wordpress. Т.е., если перейти по адресу /blog/index/index/p/123, Wordpress покажет пост с id=123. Кроме p есть другие параметры, cat - для категорий, tag - для тэгов, m - для архивов, и т.д.

Сначала мы писали реврайты для постов вручную. Но это очень неудобно, медленно, не расширяемо и т.д., и я подумал что это дело можно автоматизировать. Сейчас будем этим заниматься :)

Мы напишем модуль, который сам будет создавать реврайты на все посты/категории/тэги/архивы блога/rss. Делать это будет специальная волшебная кнопка "пыщь" в админке :)

Создание модуля

Создадим модуль Blogrewrite в пространстве имён Mage. Что бы сделать свою страницу в админке нужно иметь такой config.xml:
<?xml version="1.0"?>
<config>
    <global>
        <helpers>
            <Blogrewrite>
                <class>Mage_Blogrewrite_Helper</class>
            </Blogrewrite>
        </helpers>
    </global>

    <admin>
        <routers>
            <Blogrewrite>
                <use>admin</use>
                <args>
                    <module>Mage_Blogrewrite</module>
                    <frontName>Blogrewrite</frontName>
                </args>
            </Blogrewrite>
        </routers>
    </admin>

    <adminhtml>
        <menu>
            <catalog module="catalog">

            <children>
                <Blogrewrite translate="title" module="Blogrewrite">
                    <title>Blog Rewrite</title>
                    <action>Blogrewrite/adminhtml_Blogrewrite</action>
                </Blogrewrite>
            </children>

            </catalog>
        </menu>
    </adminhtml>
</config>
В секции admin мы говорим, что наш модуль будет доступен по адресу Blogrewrite (www.example.com/Blogrewrite). Т.о. контроллер нашего модуля - Mage_Blogrewrite_Adminhtml_BlogrewriteController.

В секции adminhtml мы добавляем новый пункт меню, в меню Catalog, и говорим, какое действие нужно выполнить, когда пользователь нажмёт на этом пункте меню - модуль Blogrewrite, контроллер Mage_Blogrewrite_Adminhtml_BlogrewriteController, действие по-умолчанию - index.

Действие index всего лишь отображает блок Mage_Blogrewrite_Block_Adminhtml_Blogrewrite:
public function indexAction() {
    $this->_initAction();
    $this->getLayout()->getBlock('head')
         ->setCanLoadRulesJs(true);
    $this->_addContent($this->getLayout()->createBlock('Blogrewrite/adminhtml_Blogrewrite')
         ->setCanLoadRulesJs(true));
    $this->renderLayout();
}
Этот блок находится в файле Block/Adminhtml/Blogrewrite.php:
<?php
class Mage_Blogrewrite_Block_Adminhtml_Blogrewrite extends Mage_Adminhtml_Block_Template {
    public function __construct() {
        parent::__construct();
        $this->setTemplate('Blogrewrite/index.phtml');
    }
}
Блок всего лишь выводит шаблонный файл index.phtml, который должен быть в app/design/adminhtml/default/default/template/Blogrewrite/index.phtml. В шаблоне - одна кнопка, которая вызывает действие makerewrites нашего контроллера:
<div class="content-header">
    <table cellspacing="0">
        <tr>
            <td>
                <h3 class="head-dashboard"><?php echo $this->__('Blog Rewrite') ?></h3>
            </td>
        </tr>
    </table>
</div>

<form action="<?php print $this->getUrl('*/*/makerewrites'); ?>">
    <button type="submit">Make Blog Rewrites</button>
</form>
Итак, модуль готов, он виден в админке, форма с кнопкой отображается. Надо делать собственно добавление реврайтов

Создаём rewrites

Как создать реврайт? Для этого есть модель core/url_rewrite, мы можем получить её так:
Mage::getModel('core/url_rewrite');
Можно загрузить данные из базы по некоторым параметрам, нам вполне хватит загрузки по Id Path:
Mage::getModel('core/url_rewrite')->loadByIdPath($idPath);
Если запись с таким Id Path есть в базе, она загрузится в модель, иначе нет :)

После того как мы загрузили данные из базы (или если данных нет), нужно задать параметры реврайта:
Mage::getModel('core/url_rewrite')
    ->loadByIdPath($idPath)
    ->setIdPath($idPath)
    ->setRequestPath($requestPath)
    ->setTargetPath($targetPath)
    ->setDescription('Automagically generated, Blogrewrite module')
    ->setIsSystem(0);
И, наконец, сохраняем реврайт:
->save();
Для удобства вынесем создание/обновление реврайта в отдельную функцию:
protected function _makeRewrite($idPath, $requestPath, $targetPath, $permamentRedirect = false) {
    $this->_urlRewrite->loadByIdPath($idPath);
    $this->_urlRewrite->setIdPath($idPath)
          ->setRequestPath($requestPath)
          ->setTargetPath($targetPath)
          ->setOptions($permamentRedirect ? 'RP' : '')
          ->setDescription('Automagically generated, Blogrewrite module')
          ->setIsSystem(0);
    $this->_urlRewrite->save();
}
Ещё я добавил новый параметр $permanentRedirect, который, соответственно, делает реврайт редиректом (по-умолчанию реврайты создаются "внутренними").

Если сохранить реврайт так, как выше, без указания магазина (Store Id), он добавится для всех магазинов. Это хорошо :)

makerewritesAction

Создание реврайтов будет происходить в действии makerewrites контроллера Mage_Blogrewrite_Adminhtml_BlogrewriteController:
<?php
class Mage_Blogrewrite_Adminhtml_BlogrewriteController extends Mage_Adminhtml_Controller_Action {
...
    public function makerewritesAction() {
...
    }
...
}
?>
Что бы создать реврайт, нужно знать, для чего его создавать. Т.е. нужно перебрать все посты, категории, архивы, и т.д., узнать их адреса, и создать по реврайту, перенаправляя эти адреса на правильные.

Сейчас будет в основном Wordpress часть.

Wordpress

Мы будем пользоваться функциями Wordpress для получения ЧПУ постов/категорий и т.д. Отсюда следует два замечения:
  • Пермалинки в настройках Wordpress должны быть настроены на ЧПУ. Иначе мы будем делать ревайты на адреса вида ?p=123. Нам этого не надо, эти адреса и так работают.

    С другой стороны, схема ЧПУ может быть любой. Всё что нужно сделать после смены вида пермалинков - нажать кнопку "пыщь" на странице нашего модуля :)
  • Все функции, возвращающие адреса, возвращают полные, абсолютные адреса. Реврайты же нужно создавать на относительные адреса (иначе они просто не будут работать). Поэтому нужно удалять доменную часть. Для этого получаем базовый адрес Magento - вызываем метод Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_WEB), и получаем, например, http://www.example.com/. Далее при получении адреса для реврайта, будем отсекать эту часть:
    trim(substr(get_permalink(), strlen($baseUrl)), '/')
    Кроме того, нужно убрать последний слэш, без него реврайты тоже не сработают. trim это делает.

Перебор постов

Для перебора постов можно воспользоваться "циклом", loop. Но по-умолчанию Wordpress загружает лишь несколько постов в цикл. А нам надо все. Это делается вызовом метода query_posts. После этого запускаем цикл, перебирающий все посты. На каждой итерации добавляем/обновляем реврайт:
// Query all published posts
query_posts(array('post_status' => 'publish', 'showposts' => -1));
while (have_posts()) {
    the_post();

    // get_permalink returns full absolute url. We need to remove domain info.
    // Also remove trailing slash, it's important.
    // E.g. was http://www.example.com/blog/2009/02/title
    //   become                                             blog/2009/02/title
    $this->_makeRewrite('blog/' . get_the_ID(),
        trim(substr(get_permalink(), strlen($blogUrl) - 4), '/'),
        'blog/index/index/p/' . get_the_ID());

    $posts++;
}

Перебор категорий

Для получения всех категорий достаточно вызвать функцию get_categories. Для получения адреса категории есть функция get_category_link.
// Update rewrites for categories
$cats = get_categories();
foreach ($cats as $_c) {
    $this->_makeRewrite('blog_cat/' . $_c->term_id,
        trim(substr(get_category_link($_c->term_id), strlen($blogUrl) - 4), '/'),
        'blog/index/index/cat/' . $_c->term_id);
}

Перебор тэгов

Тэги так же доступны вызовом всего одной функции get_tags, а для получения адреса есть функция get_tag_url:
// Update rewrites for tags
$tags = get_tags();
foreach ($tags as $_t) {
    $this->_makeRewrite('blog_tag/' . $_t->term_id,
        trim(substr(get_tag_link($_t->term_id), strlen($blogUrl) - 4), '/'),
        'blog/index/index/tag/' . $_t->term_id);
}

Перебор архивов

Я посмотрел, как архивы выводятся у нас на сайте. Это задаётся в шаблонном файле app/design/frontend/default/sunnyD/template/blog/menu.phtml, а именно - вызов функции wp_get_archives('type=monthly'). Эта функция возвращает уже отформатированный html. К сожалению, нет нормального способа выбрать нужные мне ссылки. Пришлось скопировать код этой функции к себе и немного его переделать:
// Update rewrites for archive (from Wordpress core file wp-includes\general-template.php,
// function wp_get_archives, from line 753)
global $wpdb;
$defaults = array(
    'type' => 'monthly', 'limit' => '',
    'format' => 'html', 'before' => '',
    'after' => '', 'show_post_count' => false,
    'echo' => 1
);
$r = wp_parse_args('', $defaults);
$where = apply_filters('getarchives_where', "WHERE post_type = 'post' AND post_status = 'publish'", $r);
$join = apply_filters('getarchives_join', "", $r);
$query = "SELECT DISTINCT YEAR(post_date) AS `year`, MONTH(post_date) AS `month`, count(ID) as posts FROM $wpdb->posts $join $where GROUP BY YEAR(post_date), MONTH(post_date) ORDER BY post_date DESC $limit";
$archive = $wpdb->get_results($query);
if ($archive) {
    $afterafter = $after;
    foreach ((array) $archive as $_a) {
        $this->_makeRewrite('blog_month/' . $_a->year . $_a->month,
            trim(substr(get_month_link($_a->year, $_a->month), strlen($blogUrl) - 4), '/'),
            'blog/index/index/m/' . $_a->year . $_a->month);
    }
}

RSS

Последние ссылки без реврайтов - подписка на новости. Их две - подписка на новые посты, и новые комментарии.
$feeds = trim(substr(get_bloginfo('rss2_url'), strlen($baseUrl)), '/');
$feedsComments = trim(substr(get_bloginfo('comments_rss2_url'), strlen($baseUrl)), '/');

$this->_makeRewrite('blog_feeds', $feeds, 'blog/index/index/feed/rss2');
$this->_makeRewrite('blog_comments_feeds', $feedsComments, 'blog/index/index/feed/comments-rss2');

Заключение

Итак, мы сделали по реврайту на посты, категории, тэги, месячные архивы и rss. Теперь пользователи будут видеть ЧПУ, относящиеся к блогу, в адресной строке браузера. Так же адреса, связанные с Wordpress, выводимые на странице магазина, будут ЧПУ (т.к. это указано в настройках Wordpress).

Но всё же есть одна неприятность. Страницы просмотра категории, или тэга, могут иметь несколько страниц. Например, blog/my-category/page/2. Такой адрес выдаст страницу "не найдено" :(

Комментариев нет:

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