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

Интеграция Wordpress в Magento - ЧПУ, часть 2

После создания модуля, генерирующего реврайты для вордпресовских адресов, я подумал и решил, что такая идея не будет работать на 100%. Вот почему:
  • Кроме адресов постов, категорий, тэгов, и т.д., т.е. тех адресов, для которых мы делаем реврайты, есть другие адреса, такие, как action формы, отправляющей комментарий, адрес редактирования поста, адрес подписки на комментарии каждого поста. И это только те адреса, которые я заметил, при том все эти адреса ЧПУ, т.е. они просто не будут работать в Magento без реврайтов.
  • Некоторые страницы будут иметь так много постов, что они не будут влезать на одну страницу. Т.е. адреса "следующая страница с постами" не будут работать.
К счастью, я нашёл решение этих проблем, что не позволит выкинуть результат предыдущей работы с автосозданием реврайтов для блога :)

Тепер подробнее.

Проблема с ЧПУ

Сначала рассмотрим проблему со множеством ЧПУ.

Источник проблемы в том, что в настройках Wordpress указана ЧПУ схема адресов. Т.е. абсолютно все адреса, даже для которых мы не писали реврайты, будут ЧПУ. А зная, что такие адреса в Magento работать не будут, получается, что блог будет выдавать "страница не найдена" в неожиданных местах :)

Отсюда следует, что если поставить не-ЧПУ схему, все адреса станут не-ЧПУ, и начнут работать. Это и надо сделать.

Но мы же делали реврайты, т.е. некоторые адреса могут быть ЧПУ. Поэтому мы будем модифицировать шаблоны, выводящие адреса, меняя некоторые из них на ЧПУ.

Для этого я поступил не совсем красиво - вылез в файл index.php, что находится в самом корне Magento. Думаю, есть решение по-красивее, но я его не знаю :)

В этот файл я добавил следующий код (после включения хедеров вордпресса):
...
require_once $mageFilename;

define('WP_USE_THEMES', true);
require('wordpress/wp-blog-header.php');

// bof Blogrewrite module code
$oldPermalink = get_option('permalink_structure');

function ChangePermalink() {
    global $wp_rewrite, $oldPermalink;
    $wp_rewrite->set_permalink_structure("/%year%/%monthnum%/%postname%/");
}

function RestorePermalink() {
    global $wp_rewrite, $oldPermalink;
    $wp_rewrite->set_permalink_structure($oldPermalink);
}
// eof Blogrewrite module code

#Varien_Profiler::enable();
...
Т.е. сначала я запоминаю текущую схему адресов. Вообще-то это достаточно бесполезное занятие т.к. схема должна быть только "по-умолчанию", т.е. $oldPermalink == ''. Т.е. вместо запоминания текущей схемы можно использовать пустую строку. Сделал это, наверное, на будущее...

Затем я добавил две функции. Одна меняет схему адресов на ЧПУ (что автоматически меняет схемы адресов категорий, тэгов и т.д.), другая восставнавливает предыдущую схему (не-ЧПУ, пустая строка).

По вкусу можно добавить вызов RestorePermalink(); в конец файла :) Наверное так будет лучше.

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

После того, как мы поработаем с ЧПУ, вызываем RestorePermalink() - восстанавливаем изменённую схему адресов.

Итак, файлы и код, которые используют эти функции:
  • Mage_Blogrewrite_Adminhtml_BlogrewriteController::makerewritesAction()

    Где-нибудь вначале метода нужно вызвать ChangePermalink, где-нибудь в конце - RestorePermalink.
  • Шаблон блока со списком постов/категорий/и т.д. находится в файле app/design/frontend/default/sunnyD/template/blog/menu.phtml. Т.к. мы создали реврайты для всех ссылок, которые выводятся в этом файле, то вызываем ChangePermalink в начале файла, RestorePermalink - в конце.
  • wordpress/wp-content/themes/Wordpress-theme/magento/magento.php

    Это файл-шаблон, используемый для отображения "контента", содержимого блога - списка постов, отдельного поста. Выводится по-середине страницы :)

    Те адреса, для которых мы писали реврайты, мы выводим с включёнными ЧПУ. Остальные адреса - с выключенными ЧПУ.

    Например, мы знаем, что ссылка на пост может быть ЧПУ. Поэтому код может выглядеть так:
    <?php wp_reset_query(); ?>
    
    <?php if (have_posts()) : ?>
    
      <?php while (have_posts()) : the_post(); ?>
    
       <div class="post" id="post-<?php the_ID(); ?>">
                    <?php ChangePermalink(); ?>
        <h2><a href="<?php the_permalink() ?>" rel="bookmark" title="Permanent Link to <?php the_title_attribute(); ?>"><?php the_title(); ?></a></h2>
                    <?php RestorePermalink(); ?>
    ...

Pagination

Ремонтирование разбиения на страницы оказалось проще. Когда мы получаем список категорий, постов, тэгов, месячных архивов, мы можем узнать количество постов, удовлетворяющих выбранному критерию. Ещё мы можем узнать выводимое количество постов на странице:
$postsPerPage = get_option('posts_per_page');
А, зная это, мы легко можем подсчитать количество страниц, необходимое для отображения всех постов, удовлетворяющих выбранному критерию, и добавить соответствующих реврайтов:
$pagesCount = ceil($totalPostsCount / $postsPerPage);
Вот как был модифицирован код:
  • Для категорий:
    // 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($baseUrl)), '/'),
            'blog/index/index/cat/' . $_c->term_id);
    
        // Also add rewrites for other pages, e.g. page/2, page/3 etc
        for ($i = 2; $i <= ceil($_c->count / $postsPerPage); $i++) {
            $pages++;
            $this->_makeRewrite('blog_cat/' . $_c->term_id . '/page/' . $i,
                trim(substr(get_category_link($_c->term_id), strlen($baseUrl)), '/') . "/page/$i",
                'blog/index/index/cat/' . $_c->term_id . '/page/' . $i);
        }
    }
  • Для тэгов:
    // 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($baseUrl)), '/'),
            'blog/index/index/tag/' . $_t->term_id);
    
        // Also add rewrites for other pages, e.g. page/2, page/3 etc
        for ($i = 2; $i <= ceil($_t->count / $postsPerPage); $i++) {
            $pages++;
            $this->_makeRewrite('blog_tag/' . $_t->term_id . '/page/' . $i,
                trim(substr(get_category_link($_t->term_id), strlen($baseUrl)), '/') . "/page/$i",
                'blog/index/index/tag/' . $_t->term_id . '/page/' . $i);
        }
    }
  • Для месячных архивов:
    $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) {
            $id = sprintf("%4d%02d", $_a->year, $_a->month);
    
            $this->_makeRewrite('blog_month/' . $id,
                trim(substr(get_month_link($_a->year, $_a->month), strlen($baseUrl)), '/'),
                'blog/index/index/m/' . $id);
    
            // Also add rewrites for other pages, e.g. page/2, page/3 etc
            for ($i = 2; $i <= ceil($_a->posts / $postsPerPage); $i++) {
                $pages++;
                $this->_makeRewrite('blog_month/' . $id . '/page/' . $i,
                    trim(substr(get_month_link($_a->year, $_a->month), strlen($baseUrl)), '/') . "/page/$i",
                    'blog/index/index/m/' . $id . '/page/' . $i);
            }
        }
    }

Итог

Итак:
  • Выставляем схему адресов в Wordpress "по-умолчанию". Это гарантирует нам, что любые ссылки, сгенерированне вордпрессом будут работать сами по себе.
  • Во время генерации реврайтов для постов/категорий/тэгов/месячных архивов устанавливаем схему адресов в ЧПУ. Генерируем реврайты, не забываем про "многостраничные страницы". Благодаря реврайтам пользователь сможет зайти на блог по ЧПУ (это относится к постам, категориям, тэгам, месячным архивам).
  • Модифицируем шаблоны, генерирующие вордпресовские адреса, включая ЧПУ схему для тех элементов, для которых мы создали реврайт на предыдущем шаге, выключая ЧПУ для тех элементов, для которых реврайтов нет
В итоге в основном пользователь будет видеть ЧПУ. Остальные адреса, для которых мы не сделали реврайтов, будут не-ЧПУ. Т.е. теперь блог будет 100% рабочий, разве что не 100% ЧПУ.

PS. Ну и недостаток, который есть у данной схемы: смена структуры адресов не самая быстрая операция. При этом она может выполняться несколько десятков раз (при отображении 20 постов на одной странице смен будет 20 * 3 = 60). Ну, Magento сама по себе весьма не скоростная, будем надеяться эти новые смены никто не заметит :)

PPS. Ещё я добавил загрузку реврайта по Request Path, если реврайт не найден по Id Path. Это сделано для того, что бы код без проблем заработал в магазине, где уже есть некоторые реврайты, добавленные вручную.
protected function _makeRewrite($idPath, $requestPath, $targetPath, $permamentRedirect = false) {
    $this->_urlRewrite->loadByIdPath($idPath);
    if (!$this->_urlRewrite->getId())
        $this->_urlRewrite->loadByRequestPath($requestPath);
    ...
}
Код попытается найти реврайт по Id Path, скорее всего не найдёт (Id Path заполнялся вручную и не совпадает с генерируемым нашим модулем). Если затем не попробовать загрузить реврайт по Request Path, Magento упадёт при попытке создать новый реврайт с тем же Request Path, который уже есть в системе (Request Path однозначно идентифицирует реврайт, это уникальное поле). Добавляя дополнительную загрузку по Request Path мы уберегаем себя от подобных падений - вместо падения Magento загрузит уже существующий, добавленный вручную реврайт, и обновит его.

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

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