Search engine friendly (SEF), человеко-понимаемыми или чистыми URL-адресами являются адреса, которые имеют смысл для человека и поисковых систем, поскольку они объясняют путь к конкретной странице на которую указывают. Начиная с версии 1.5, Joomla способна создавать и анализировать URL-адреса в любом формате, включая SEF URL. Это не зависит от перезаписи URL-адресов, которые выполняются на веб-сервере, поэтому это работает, даже если Joomla работает на сервере, отличном от Apache с модулем mod_rewrite. SEF URL-адреса придерживаются определенной фиксированной схеме, но пользователь может определить короткий описательный текст (псевдоним, алиас) для каждого сегмента URL.

Внутренняя локальная часть URL SEF (часть после доменного имени), называется route.

В Joomla, каждый компонент отвечает за обработку своих собственных SEF URL-адресов. Поэтому, как разработчик компонента вам придется создать свой собственный маршрутизатор, чтобы позволить компоненту использовать SEF URL-адреса.

Концепция

Скорее всего, при разработке компонента вы использовали системные URL, которые очень похожи на:
http://www.example.com/index.php?option=com_yourcomponent&view=article&id=1&catid=20&Itemid=50, и ваша цель, превратить их в:
http://www.example.com/example-menu-item/20/1. Как у разработчика у вас есть две задачи: некоторые куски текста и URL-адреса должны быть преобразованы, и объяснить системе, как преобразовать эти URL-адреса.

Применение JRoute::_

Для поддержки SEF URL-адресов, вам нужно будет изменить URL генерацию кода так, чтобы он применял JRoute::_ перед выводом URL:

echo JRoute::_('index.php?view=article&id=1&catid=20');



Обратите внимание, что можно оставить параметр option и Itemid. option – по умолчанию имя компонента, выполняемого в данный момент, а Itemid – по умолчанию это ID текущего пункта меню.

В общем, это нужно применять только к URL-адресам, которые пользователи и/или поисковые системы способны видеть. Например, нет необходимости преобразовывать URL-адреса, используемые при редиректе, которые сразу же приведут к другим пере направлениям.

Если пользователь выключит SEF URL в настройках сайта, JRoute::_ будет производить не SEF URL, без каких-либо изменений в коде.

Написание маршрутизатора (router)

Вам также понадобится написать маршрутизатор, который представляет собой один файл с двумя функциями, которые преобразуют URL системы в SEF URL-адреса. Этот файл должен находиться в /components/com_yourcomponent/router.php.

Первая функция, [componentname]BuildRoute(&$query), необходима для преобразования массива параметров URL-адреса в массив сегментов образующих SEF URL. Схематично преобразование работает следующим образом:

http://www.example.com/index.php?option=com_yourcomponent&view=article&id=1&catid=20&Itemid=50
JRoute::_, вызывается из компонента или любого другого расширения
$query = array('view' => 'article', 'id' => 1, 'catid' => 20)
Ваш маршрутизатор: com_yourcomponentBuildRoute
$segments = array(20, 1);
Внутренний маршрут Joomla (для отображения)
http://www.example.com/example-menu-item/20/1

Вторая функция, [componentname]ParseRoute($segments), необходима для преобразования массива сегментов обратно в массив параметров URL-адреса. Схема:

http://www.example.com/example-menu-item/20/1
Разбор внутренних маршрутов Joomla
$segments = array(20, 1);
Ваш маршрутизатор: com_yourcomponentParseRoute
$query = array('view' => 'article', 'id' => 1, 'catid' => 20)

Эти две функции должны сотрудничать таким образом, чтобы исходные URL-адреса могли быть реконструированы. Вы можете думать о BuildRoute как о форме кодирования и о ParseRoute как о декодировании. Когда исходный URL-адрес не воспроизводится правильно, ваш компонент перестанет работать.

Подготовка данных для маршрутизации

Псевдоним

Первым шагом является создание так называемого псевдонима (алиас). Псевдоним используется в URL-адресе вместо названия (псевдоним — это текст, который вы хотите использовать в URL). Псевдоним должен быть URI безопасным, что означает, что UTF8 символы заменяются на их эквиваленты ASCII7, пробелы дефисы и т.д.

Псевдоним может быть определен пользователем, но вы должны убедиться, что вышеуказанные требования для безопасных URL псевдонимов будут выполнены. Хороший способ сделать это заключается в использовании метода JTable::check() во время процесса сохранения. Посмотрите на этот пример кода:

function check() 
{
    jimport('joomla.filter.output');
    if(empty($this->alias)) { $this->alias = $this->title; }
    $this->alias = JFilterOutput::stringURLSafe($this->alias);
    /* Все другие проверки */
    return true;
}



Если поле псевдонима будет пустое, то название будет использоваться как псевдоним. Затем псевдоним будет приводиться к безопасному URL, при помощью метода JFilterOutput::stringURLSafe().

Slug

Продолжая тот же пример, 'slug''1­:welcome­-to­-joomla' состоит из двух частей. Первая часть статьи это идентификатор (id), а вторая – это псевдоним. Они разделяются двоеточием. Эти два элемента были объединены в ходе запроса к базе данных в модели:

$query =     'SELECT a.*, ' 
            .'CASE WHEN CHAR_LENGTH(a.alias) THEN CONCAT_WS(":", a.id, a.alias) ELSE a.id END as slug,'
            /*...*/;



После этого шага slug используется вместо id.

Маршрутизация URL-адреса

Метод JRoute::_ преобразует внутренний URL-адрес Joomla в пользовательский URL. JRoute имеет три параметра и его прототип:

JRoute::_($url, $xhtml = true, $ssl=null);



Где:

  • $url представляет собой строку, содержащею абсолютный или относительный внутренний URL Joomla.
  • $xhtml – это логическое значение, которое указывает, следует ли вывод в XHTML. Этот параметр является необязательным и, если опущен, то по умолчанию равно true.
  • $ssl является целочисленным значением, которое указывает, должен ли быть URI безопасным. Он должен быть установлен в 1, чтобы заставить URI быть безопасным в использовании глобальной безопасности URI сайта, 0 оставит его в том же состоянии, в котором он был принят, и -1, чтобы заставить URI быть незащищенным с использованием глобальной незащищённости URI сайта.

Наиболее важным параметром является $url. Вызов этого метода может выглядеть так:

JRoute::_('index.php?view=article&id='.$row->slug);



Еще одним преимуществом использования JRoute является то, что маршрутизатор теперь обрабатывает $option (имя компонента) и $Itemid (ID пункта меню). Компонент сам по себе не должен знать своё имя ($option) или пункта активного меню ($Itemid) так как это было в предыдущей версии Joomla.

Важно, что вы думаете о последовательности URL параметров на данном этапе. Это будет более понятным, когда мы рассмотрим router.php в следующем разделе.

Процесс построения JRouter делится на два этапа:

  • Создание приложения маршрута. Приложение маршрута полностью обрабатываются JRouter и разработчику компонента не нужно ничего делать, чтобы заставить его работать.
  • Создайте маршрута компонента. Чтобы создать маршрут компонента, JRouter ищет router.php в каталоге компонента, который отвечает за создание маршрута для компонента.

Маршрутизатор компонента

В router.php у нас будет две функции. Одна отвечает за создание URL-адреса и другая отвечает за его синтаксический анализ. В следующем примере мы предполагаем, что у нас есть три представления и ссылки, которые могут указывать на них. Первое – это обзор категорий (view=categories), второе – это одна категория (view=category) и третье – одна статья (view=article).

Файл router.php должны быть в области сайта вашего компонента. Он не используется на страницах администратора/backend. Не забудьте добавить его в XML-файл манифеста.

Простой пример

Этот простой пример проиллюстрирует основные принципы реализации маршрутизатора для компонента.

function [componentname]BuildRoute(&$query) 
{
    $segments = array();
    if(isset($query['view']))
    {
        $segments[] = $query['view'];
        unset( $query['view'] );
    }
    if(isset($query['id']))
    {
        $segments[] = $query['id'];
        unset( $query['id'] );
    };
    return $segments;
}



JRouter передает массив $query функции [componentname]BuildRoute. Эта функция будет добавлять соответствующие части массива в массив $segments в правильном порядке, и будет возвращать надлежащим образом упорядоченный массив. Содержимое массива $query должно быть отключено, в противном случае JRouter будет добавлять его в URL-адрес в виде строки запроса (т.е. любые переменные, которые не обрабатываются маршрутизатором, будут передаваться в строке запроса).

Префикс componentname — это имя для компонента, которое находятся в каталоге файлов компонента. Например, компонент "Magic" в директории /components/com_magic/... будет использовать префикс magic (в нижнем регистре).

Следующая функция в router.php разбирает URL:

function [componentname]ParseRoute($segments) 
{
    $vars = array();
    switch($segments[0])
    {
        case 'categories':
            $vars['view'] = 'categories';
            break;
        case 'category':
            $vars['view'] = 'category';
            $id = explode( ':', $segments[1] );
            $vars['id'] = (int) $id[0];
            break;
        case 'article':
            $vars['view'] = 'article';
            $id = explode( ':', $segments[1] );
            $vars['id'] = (int) $id[0];
            break;
    }
    return $vars;
}



Что здесь происходит? В функции [componentname]BuildRoute мы расположили элементы в массиве $query в определенной последовательности. Это означает, что в этом примере элемент массива [view] является первым, а [id] является вторым.

Читая $segments[0], мы получим доступ к имени представления. Мы устанавливаем правильный view и/или идентификатор в зависимости от его значения, и мы возвращаемся массив $vars к JRouter. $vars должен быть ассоциативным массивом, похожим на массив, который был передан в метод BuildRoute.

В приведенном выше примере router.php является очень простым способом для создания SEF URL, но это должно показать, что это работает довольно просто.

Созданный URL-адрес в этом примере содержит имя представления и не отражает иерархию контента:

http://www.example.com/[menualias]/[view]/[slug]

Более сложный пример

В следующем примере мы постараемся избавиться от необходимости view, и мы будем стараться отражать текущий уровень иерархии в URL.
Наша цель это URL, которые выглядят так:

Предположим, что мы сделали шаг 1 и 2 также для категории.

Ссылка на статью будет выглядеть следующим образом:

JRoute::_('index.php?view=article&catid='.$row-­>catslug .'&id='.$row-­>slug);



И ссылка на категорию будет выглядеть следующим образом:

JRoute::_('index.php?view=category&id='.$row->catslug);



Соответствующий router.php:

function [Componentname]BuildRoute(&$query) 
{
    $segments = array();
    if(isset( $query['catid'] ))
    {
        $segments[] = $query['catid'];
        unset( $query['catid'] );
    };
    if( isset($query['id']) )
    {
        $segments[] = $query['id'];
        unset( $query['id'] );
    };
    unset( $query['view'] );
    return $segments;
}



Разница теперь заключается в том, что мы не добавляем имя представления в массив $segments. Мы по-прежнему не указываем ключ view, так как в противном случае, JRouter бы добавил его в URL-адрес как часть строки запроса. Еще одна новая вещь здесь – это идентификатор catid, дополнительный параметр, который мы помещаем в массив $segments.

function [Componentname]ParseRoute($segments) 
{
    $vars = array();
    $app =& JFactory::getApplication();
    $menu =& $app->getMenu();
    $item =& $menu->getActive();
    
    // Количество элементов
    $count = count( $segments );
    
    // Обрабатывать вид и идентификатор
    switch( $item->query['view'] )
    {
        case 'categories':
            if($count == 1)
            {
                $vars['view'] = 'category';
            }
            if($count == 2)
            {
                $vars['view'] = 'article';
            }
            $id = explode( ':', $segments[$count-1] );
            $vars['id'] = (int) $id[0];
            break;
        case 'category':
            $id = explode( ':', $segments[$count-1] );
            $vars['id'] = (int) $id[0];
            $vars['view'] = 'article';
            break;
    }
    return $vars;
}



Вы можете увидеть, что функция ParseRoute имеет много различных частей кода по сравнению с предыдущей функцией. Причина этого проста. Мы не имеем имени представления в массиве $segments и нам нужно найти другой способ, чтобы определить его.

Мы должны выяснить, на каком уровне иерархии мы находимся, путем получения корневого элемента. Мы делаем это, смотря на имя представления активного пункта меню:

$item->query['view'];



Также нам необходимо знать количество элементов в массиве $segments:

$count = count($segments);



С помощью этой информации мы можем правильно задать представление для всех возможных трёх случаев:

  • Пункт меню является ссылкой на view категории, и массив $segments имеет два элемента ($catid и $id). В этом случае мы знаем, что нам нужно разобрать ссылку на статью.
  • Пункт меню является ссылкой на view категории, и массив $segments имеет один элемент ($id). В этом случае мы знаем, что нам нужно разобрать ссылку на категорию.
  • Пункт меню — это ссылка на категорию. В этом случае мы знаем, что любой элемент в массиве $segments является идентификатором для статьи.

Результатом этого кода являются чистые и удобочитаемые URL-адреса компонента.

Маршрутизаторы и пункты меню

Последней важной частью создания маршрутизатора является вопрос, что делать с элементами меню. Как поясняется в Search Engine Friendly URLs, выход из компонентов маршрутизатора используется после первого отрезка маршрута, первый сегмент является псевдонимом пункта меню. Появляется вопрос: как маршрутизатор и/или другой код, знает, через какой пункт меню проходит маршрут?

Например, предположим, что компонент в настоящее время производит вывод для страницы /dogs, где перечислены все собаки в системе. Конечно, элементы в списке должны быть ссылки на страницы, отображающие больше деталей об одной собаке. Какой URL-адрес должен быть для собаки с ID 21 и именем Бобик? С помощью маршрутизатора, который работает в соответствии с принципами, которые мы видели до сих пор, маршрут – это dogs/21-bobik, или /dogs/bobik. Но возможно пользователь создал меню с псевдонимом mydoggy, который отображает именно Подробнее о собаке. Тогда это, вероятно, намерения пользователя проложить маршрут через этот URL путем выбора пункта меню, и в пункте следует ссылка на страницу /mydoggy.

Вообще всякий раз, когда вы создаете маршрут, вам нужно будет найти пункт меню, который наиболее подходит в качестве отправной точки для создания вашего маршрута. Термин, отправной точки является выделенным потому, что остальной маршрут зависит от конфигурации элемента меню. В нашем примере выше, /dogs/21-bobik является приемлемым маршрутом, /mydoggy возможно, даже лучше, но /mydoggy/21-bobik это неправильно, так как /mydoggy сам по себе пункт меню, который настроен для отображения сведений о Бобике.

Несколько подходов для решения этой проблемы. Основные компоненты Joomla принимают смешанный подход, разделяя обязанности в двух блоках кода: маршрутизатора, так называемый [componentname]RouteHelper. [componentname]RouteHelper предоставляет методы, чтобы найти наиболее подходящее меню для этого фрагмента данных, который будет отображаться, в то время как маршрутизатор анализирует меню и ставит любую информацию, которая не определяется конфигурацией пункта меню в маршруте. Это означает, что вызывающий код необходимо явно вызвать как вспомогательный метод до маршрутизации (echo JRoute::_(DogsRouteHelper::getDogRoute(21));).

Оригинал материала – http://docs.joomla.org/Supporting_SEF_URLs_in_your_component