Создавая на днях два сайта (один новостного профиля, другой — онлайн тендеры) на MODx Evo столкнулся со следующей проблемой: товары (или новости) создаются не в виде отдельных страниц (что естественно, т.к. их может быть многие тысячи), а в виде одной страницы с параметром. Например, /news?newsid=876 или /tender?tender=873.
Стал вопрос об отдельной реализации ЧПУ для таких страниц (с подачи назойливого SEO-шника), но штатные средства MODx такой возможности не предоставляют. Пришлось написать своё решение, которое и описано ниже — авось пригодится братьям по MODx-у.
Итак, идея проста: заменить при генерации вывода страницы все ссылки вида ?newsid=xxx на транслитерацию заголовка статьи + ID (если будут одинаковые заголовки), обработать запросы к таким страницам (обработка плагином + .htaccess). Htaccess при таком подходе будет генерироваться плагином. Сразу отмечу, что страхи того, что при большом размере данного файла замедлится работа сайта не обоснованны. По крайней мере при достигнутом размере в 500Кб скорость не изменилась. Единственный возможный небольшой «тормоз» — при первом просмотре страницы с вновь созданными новостями — поскольку идет генерация ссылок и htaccess.
Для хранения данных о том, какие линки мы уже сгенерировали (чтобы не создавать htaccess при каждом просмотре даже для старых ссылок) будет использована таблица в БД. Приступим.
Создаем таблицу uri по схеме:
Далее, необходимо подготовить сам стандартный MODx-овый .htaccess (если у вас свой, модифицированный htaccess — принцип аналогичный, главное не забыть включить все параметры преобразования). Перемещаем блок
в самый конец файла с тем, чтобы вновь добавляемые строки всегда были в конце. Строка "RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]" будет дописываться после добавленных строк автоматически.
Если вы используете особые файлы (у меня — это скрипт генерации превьюшек thumb.php), то их необходимо добавить в исключения: меняем строку
на
и после нее пишем:
RewriteRule thumb.php$ — [L]
Стоит ли говорить, что должны быть установлены права на запись в .htaccess, если оные у него отсутствуют.
Создаем плагин SEOAddr. Вешаем на него системные события OnWebPagePrerender и OnPageNotFound.
В коде плагина присутствует функция транслитерации кириллического заголовка в латиницу — советую оставить ее нетронутой, поскольку в иных реализациях есть проблема при транслите UTF в латиницу.
Параметры плагина:
$postpar = 'newsid'; — собственно динамический параметр, т.е. /viewpage?newsid=232
$table = 'oco_news_original'; — таблица, где хранится динамический контент (в моем случае — новости)
$table_title = 'title'; — поле вышеуказанной таблицы, в котором находится заголовок статьи
$initpage = 'news'; — страница, которая выводит новость, для /viewpage?newsid=232 это viewpage
$view_pageId = 184; — ID этой же страницы
$error404_pageId = 130; — ID страницы 404.
Таким образом, мы ищем ссылки указанного формата в генерируемой странице и заменяем их на сгенерированный алиас. К примеру, заголовок «В Испании обнаружена потерянная могила Сервантеса» транслируется в v_ispanii_obnaruzhena_poteryannaya_mogila_servantesa_191, а соответствующие ссылки /news?newsid=191 превратятся в /v_ispanii_obnaruzhena_poteryannaya_mogila_servantesa_191.
Осталось записать результат (если он еще там не присутствует) в таблицу БД «uri», дабы не делать транслит и модификацию .htaccess еще раз и подправить сам .htaccess.
Благодаря параметрам плагина он достаточно гибок. Для подтверждения этого сделал ровно все действия, указанные в этой статье, на одном старом (рабочем) сайте — всё прошло гладко, напильник не пригодился. Тем не менее, держите напильник и руки на готове — возможно, что-то нужно будет подправить, исходя из реалий вашего сайта. Качество кода прошу не критиковать, а вот принцип — пожалуйста, предлагайте свои идеи.
Стал вопрос об отдельной реализации ЧПУ для таких страниц (с подачи назойливого SEO-шника), но штатные средства MODx такой возможности не предоставляют. Пришлось написать своё решение, которое и описано ниже — авось пригодится братьям по MODx-у.
Итак, идея проста: заменить при генерации вывода страницы все ссылки вида ?newsid=xxx на транслитерацию заголовка статьи + ID (если будут одинаковые заголовки), обработать запросы к таким страницам (обработка плагином + .htaccess). Htaccess при таком подходе будет генерироваться плагином. Сразу отмечу, что страхи того, что при большом размере данного файла замедлится работа сайта не обоснованны. По крайней мере при достигнутом размере в 500Кб скорость не изменилась. Единственный возможный небольшой «тормоз» — при первом просмотре страницы с вновь созданными новостями — поскольку идет генерация ссылок и htaccess.
Для хранения данных о том, какие линки мы уже сгенерировали (чтобы не создавать htaccess при каждом просмотре даже для старых ссылок) будет использована таблица в БД. Приступим.
Готовим почву
Создаем таблицу uri по схеме:
CREATE TABLE IF NOT EXISTS `uri` (
`id` int(11) NOT NULL,
`docid` int(11) NOT NULL,
`link` varchar(255) NOT NULL
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=cp1251;
Далее, необходимо подготовить сам стандартный MODx-овый .htaccess (если у вас свой, модифицированный htaccess — принцип аналогичный, главное не забыть включить все параметры преобразования). Перемещаем блок
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
в самый конец файла с тем, чтобы вновь добавляемые строки всегда были в конце. Строка "RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]" будет дописываться после добавленных строк автоматически.
Если вы используете особые файлы (у меня — это скрипт генерации превьюшек thumb.php), то их необходимо добавить в исключения: меняем строку
RewriteRule ^(manager|assets|js|css|images|img)/.*$ - [L]
на
RewriteRule ^(manager|assets|js|css|images|img|php|js|css)/.*$ - [L]
и после нее пишем:
RewriteRule thumb.php$ — [L]
Стоит ли говорить, что должны быть установлены права на запись в .htaccess, если оные у него отсутствуют.
Плагин
Создаем плагин SEOAddr. Вешаем на него системные события OnWebPagePrerender и OnPageNotFound.
В коде плагина присутствует функция транслитерации кириллического заголовка в латиницу — советую оставить ее нетронутой, поскольку в иных реализациях есть проблема при транслите UTF в латиницу.
Параметры плагина:
$postpar = 'newsid'; — собственно динамический параметр, т.е. /viewpage?newsid=232
$table = 'oco_news_original'; — таблица, где хранится динамический контент (в моем случае — новости)
$table_title = 'title'; — поле вышеуказанной таблицы, в котором находится заголовок статьи
$initpage = 'news'; — страница, которая выводит новость, для /viewpage?newsid=232 это viewpage
$view_pageId = 184; — ID этой же страницы
$error404_pageId = 130; — ID страницы 404.
Таким образом, мы ищем ссылки указанного формата в генерируемой странице и заменяем их на сгенерированный алиас. К примеру, заголовок «В Испании обнаружена потерянная могила Сервантеса» транслируется в v_ispanii_obnaruzhena_poteryannaya_mogila_servantesa_191, а соответствующие ссылки /news?newsid=191 превратятся в /v_ispanii_obnaruzhena_poteryannaya_mogila_servantesa_191.
Осталось записать результат (если он еще там не присутствует) в таблицу БД «uri», дабы не делать транслит и модификацию .htaccess еще раз и подправить сам .htaccess.
Результаты
Благодаря параметрам плагина он достаточно гибок. Для подтверждения этого сделал ровно все действия, указанные в этой статье, на одном старом (рабочем) сайте — всё прошло гладко, напильник не пригодился. Тем не менее, держите напильник и руки на готове — возможно, что-то нужно будет подправить, исходя из реалий вашего сайта. Качество кода прошу не критиковать, а вот принцип — пожалуйста, предлагайте свои идеи.
Код плагина
GitHub — зло :-)
$postpar = 'newsid';
$table = 'oco_news_original';
$table_title = 'title';
$initpage = 'news';
$view_pageId = 184;
$error404_pageId = 130;
function GetInTranslit($string) {
$replace=array(
"'"=>"",
"`"=>"",
"а"=>"a","А"=>"a",
"б"=>"b","Б"=>"b",
"в"=>"v","В"=>"v",
"г"=>"g","Г"=>"g",
"д"=>"d","Д"=>"d",
"е"=>"e","Е"=>"e",
"ж"=>"zh","Ж"=>"zh",
"з"=>"z","З"=>"z",
"и"=>"i","И"=>"i",
"й"=>"y","Й"=>"y",
"к"=>"k","К"=>"k",
"л"=>"l","Л"=>"l",
"м"=>"m","М"=>"m",
"н"=>"n","Н"=>"n",
"о"=>"o","О"=>"o",
"п"=>"p","П"=>"p",
"р"=>"r","Р"=>"r",
"с"=>"s","С"=>"s",
"т"=>"t","Т"=>"t",
"у"=>"u","У"=>"u",
"ф"=>"f","Ф"=>"f",
"х"=>"h","Х"=>"h",
"ц"=>"c","Ц"=>"c",
"ч"=>"ch","Ч"=>"ch",
"ш"=>"sh","Ш"=>"sh",
"щ"=>"sch","Щ"=>"sch",
"ъ"=>"","Ъ"=>"",
"ы"=>"y","Ы"=>"y",
"ь"=>"","Ь"=>"",
"э"=>"e","Э"=>"e",
"ю"=>"yu","Ю"=>"yu",
"я"=>"ya","Я"=>"ya",
"і"=>"i","І"=>"i",
"ї"=>"yi","Ї"=>"yi",
"є"=>"e","Є"=>"e"
);
return $str=iconv("UTF-8","UTF-8//IGNORE",strtr($string,$replace));
}
$e = &$modx->Event;
switch($e->name) {
case 'OnPageNotFound':
$link = str_replace('/', '', $_SERVER['REQUEST_URI']);
$res = $modx->db->query("SELECT * FROM `uri` WHERE `link`='".$link."' LIMIT 1");
if ( $row = $modx->db->getRow($res) ) {
$lnk = '/'.$initpage.'?'.$postpar.'='.$row['docid'];
$modx->sendForward( $view_pageId );
} else {
$lnk = $modx->makeUrl( $error404_pageId );
$modx->sendRedirect($lnk,0,'REDIRECT_HEADER','HTTP/1.1 301 Moved Permanently');
}
break;
case 'OnWebPagePrerender':
$doc = $modx->documentOutput;
preg_match_all("/\?".$postpar."=(.*?)(\"|')/", $doc, $res);
$add = '';
foreach ( $res[1] as $val ) {
$val = str_replace('"', '', $val);
$res = $modx->db->query("select * from `".$table."` WHERE `id`='".(int)$val."' LIMIT 1");
if ( $row = $modx->db->getRow($res) )
{
$link = GetInTranslit($row[ $table_title ]);
$link = str_replace(' ', '_', $link);
$link = str_replace('=', '_', $link);
$link = str_replace('?', '_', $link);
$link = str_replace('"', '_', $link);
$link = str_replace("'", '_', $link);
$link = str_replace("*", '_', $link);
$link = str_replace("#", '_', $link);
$link = $link.'_'.(int)$val;
$res = $modx->db->query("SELECT * FROM `uri` WHERE `link`='".$link."' LIMIT 1");
if ( $row = $modx->db->getRow($res) ) {} else {
$modx->db->query("INSERT INTO `uri` (`docid`,`link`) VALUES ('".(int)$val."','".$link."')");
$current = file_get_contents('.htaccess');
$current = str_replace('RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]', '', $current)."\n";
$current .= 'RewriteRule '.$link.' /'.$initpage.'?'.$postpar.'='.(int)$val.' [L,QSA]'."\n";
$current .= 'RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]'."\n";
file_put_contents('.htaccess', $current);
}
$doc = str_replace('href="/'.$initpage.'?'.$postpar.'='.(int)$val.'"', 'href="/'.$link.'"', $doc);
}
}
$modx->documentOutput = $doc;
break;
}
GitHub — зло :-)