Человеко Понятные Урлы

Моя доработка LS пригодится тем, кто хочет видеть ссылки на топики в блогах в виде http://www.newmusic.ru/blog/events/2174/zavtra-shpongle-live-v-moskve.html Все ищется и индексируется, как и прежде, по ID топика, поэтому эта переделка минимально затрагивает движок и абсолютно не сказывается на производительности системы...

Итак, для этого сначала подергаем базу, а именно таблицу prefix_topic,
добавив туда поле topic_url
ALTER TABLE `prefix_topic` ADD `topic_url` VARCHAR( 250 ) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT 'topic';


в корневую папку сайта (там где ваш index.php) заливаем файл i18n-ascii.txt

идем в function.php и в конце дописываем функцию

function TransURL($output) {

		$separator = '-';
		$pattern = '/[^a-zA-Z0-9\/]+/ ';

		$path = $_SERVER['DOCUMENT_ROOT'];
		if (is_file($path.'/i18n-ascii.txt')) {
			$translations = parse_ini_file($path.'/i18n-ascii.txt');
			$output = strtr($output, $translations);
		} 

		$output = str_replace('/', '', $output);		
		$output = strtolower(preg_replace($pattern, $separator,$output));

		$output = preg_replace("/^$separator+|$separator+$/", "", $output);
		$output = preg_replace("/$separator+/", "$separator", $output);	
		return $output;
}


идем в Topic.entity.class.php

добавляем где Get'ы
public function getTopicURL() {
        return $this->_aData['topic_url']; //ЧПУ
}


внизу, где Set'ы
public function setURL($data) { //ЧПУ
        $this->_aData['topic_url']=$data;
} 


правим function getUrl() на

public function getUrl() {
    	if ($this->getBlogType()=='personal') {
    		return DIR_WEB_ROOT.'/blog/'.$this->getId().'/'.$this->getTopicURL().'.html'; // ЧПУ /a
    	} else {
    		return DIR_WEB_ROOT.'/blog/'.$this->getBlogUrl().'/'.$this->getId().'/'.$this->getTopicURL().'.html'; // ЧПУ /a
    	}
    }

идем в Topic.maper.class.php, там должно быть так

public function AddTopic(TopicEntity_Topic $oTopic) {
		$sql = "INSERT INTO ".DB_TABLE_TOPIC." 
			(blog_id,
			user_id,
			topic_type,
			topic_title,
		    topic_url,
			topic_tags,
			topic_date_add,
			topic_user_ip,
			topic_publish,
			topic_publish_index,
			topic_cut_text,
			topic_forbid_comment
			)
			VALUES(?d,  ?d,	?,	?,	?,	?,  ?, ?, ?d, ?d, ?, ?)
		";			
		if ($iId=$this->oDb->query($sql,$oTopic->getBlogId(),$oTopic->getUserId(),$oTopic->getType(),$oTopic->getTitle(),$oTopic->getTopicUrl(),
			$oTopic->getTags(),$oTopic->getDateAdd(),$oTopic->getUserIp(),$oTopic->getPublish(),$oTopic->getPublishIndex(),$oTopic->getCutText(),$oTopic->getForbidComment())) 
		{ 
			$oTopic->setId($iId);
			$this->AddTopicContent($oTopic);
			return $iId;
		}		
		return false;
	}	

public function UpdateTopic(TopicEntity_Topic $oTopic) {		
		$sql = "UPDATE ".DB_TABLE_TOPIC." 
			SET 
				blog_id= ?d,
				topic_title= ?,	
				topic_url= ?,
				topic_tags= ?,
				topic_date_edit = ?,
				topic_user_ip= ?,
				topic_publish= ? ,
				topic_publish_index= ?,
				topic_rating= ?f,
				topic_count_vote= ?d,
				topic_count_read= ?d,
				topic_count_comment= ?d, 
				topic_cut_text = ? ,
				topic_forbid_comment = ?	
			WHERE
				topic_id = ?d
		";			
		if ($this->oDb->query($sql,$oTopic->getBlogId(),$oTopic->getTitle(),$oTopic->getTopicURL(),$oTopic->getTags(),$oTopic->getDateEdit(),$oTopic->getUserIp(),$oTopic->getPublish(),$oTopic->getPublishIndex(),$oTopic->getRating(),$oTopic->getCountVote(),$oTopic->getCountRead(),$oTopic->getCountComment(),$oTopic->getCutText(),$oTopic->getForbidComment(),$oTopic->getId())) {
			$this->UpdateTopicContent($oTopic);
			return true;
		}		
		return false;
	}

Идем в TopicComment.entity.class.php и добавлям
public function getTopUrl() { 
	return $this->_aData['topic_url'];
	}

и редактируем
public function getTopicUrl() {
    	if ($this->getBlogType()=='personal') {
    		return DIR_WEB_ROOT.'/blog/'.$this->getTopicId().'/'.$this->getTopURL().'.html'; // ЧПУ /a
    	} else {
    		return DIR_WEB_ROOT.'/blog/'.$this->getBlogUrl().'/'.$this->getTopicId().'/'.$this->getTopURL().'.html'; // ЧПУ /a
    	}
    }


Идем в TopicComment.mapper.class.php и
редактируем запросы, добавляя t.topic_url as topic_url:
1. в public function GetCommentsRatingByDate($sDate,$iLimit) {

$sql = "SELECT 
					c_full.*,
					t.topic_title as topic_title,
					t.topic_count_comment as topic_count_comment,
					t.topic_url as topic_url,
	

2. в public function GetCommentsAll(&$iCount,$iCurrPage,$iPerPage) {
/**
		 * оптимизирован
		 */
		$sql = "SELECT
					c_fast.*,
					c_full.*,
					u.user_profile_avatar as user_profile_avatar,
					u.user_profile_avatar_type as user_profile_avatar_type,
					u.user_login as user_login,
					u.user_login_confirm as user_login_confirm,
					b.blog_title as blog_title,
					b.blog_type as blog_type,
					b.blog_url as blog_url,
					u_owner.user_login	as blog_owner_login
				FROM (
					SELECT 					
						c.comment_id,
						t.topic_title as topic_title,
						t.topic_count_comment as topic_count_comment,
						t.topic_url as topic_url,

3. в public function GetCommentsAllGroup($iLimit) {
/**
		 * оптимизирован // здесь копать прямой эфир
		 */
		$sql = "SELECT 					
					c.*,
					t.topic_title as topic_title,
					t.topic_count_comment as topic_count_comment,
					t.topic_url as topic_url,

4. в public function GetCommentsByUserId($sId,&$iCount,$iCurrPage,$iPerPage) {
$sql = "SELECT 
					c.*,
					t.topic_title as topic_title,
					t.topic_count_comment as topic_count_comment,
					t.topic_url as topic_url,
					

Идем в ActionTopic.class.php

Где protected function SubmitAdd() {

ищем
/**
		 * Теперь можно смело добавлять топик к блогу
		 */
		$oTopic=new TopicEntity_Topic();
		$oTopic->setBlogId($oBlog->getId());
		$oTopic->setUserId($this->oUserCurrent->getId());
		$oTopic->setType('topic');
		$oTopic->setTitle(getRequest('topic_title'));
		

дописываем
// ЧПУ
		$output = TransURL(getRequest('topic_title'));
		$oTopic->setUrl($output);				


в конце этой функции находим и редактируем
func_header_location(DIR_WEB_ROOT.'/blog/'.$oTopic->getId().'/'.$oTopic->getTopicURL().'.html'); //ЧПУ 

в protected function SubmitEdit($oTopic)

находим
/**
		 * Теперь можно смело редактировать топик
		 */		
		$oTopic->setBlogId($oBlog->getId());		
		$oTopic->setTitle(getRequest('topic_title'));

и добавляем
// ЧПУ
		$output = TransURL(getRequest('topic_title'));
		$oTopic->setUrl($output);

в конце этой функции находим и редактируем
func_header_location(DIR_WEB_ROOT.'/blog/'.$oTopic->getId().'/'.$oTopic->getTopicURL().'.html'); //ЧПУ

Точно также как ActionTopic редактируем ActionLink, ActionQuestion (SubmitEdit в ActionQuestion не правим)!!!

Идем в ActionBlog.class.php
находим и комментируем старое и дописываем новое
//$this->AddEventPreg('/^(\d+)\.html$/i','EventShowTopicPersonal');	// было
		$this->AddEventPreg('/^(\d+)+$/i','/^([\w\-\_)+]+)\.html$/i','EventShowTopicPersonal'); //стало
		//$this->AddEventPreg('/^[\w\-\_]+$/i','/^(\d+)\.html$/i','EventShowTopic');  //было
		$this->AddEventPreg('/^[\w\-\_]+$/i','/^(\d+)+$/i','/^([\w\-\_)+]+)\.html$/i','EventShowTopic');  //стало


теперь осталось только везде отредактировать func_header_location
/**
		 * Если запросили не персональный топик то перенаправляем на страницу для вывода коллективного топика
		 */
		if ($oTopic->getBlogType()!='personal') {
			func_header_location(DIR_WEB_ROOT.'/blog/'.$oTopic->getBlogUrl().'/'.$oTopic->getId().'/'.$oTopic->getTopicURL().'.html'); //ЧПУ 
		}


		/**
		 * Если запросили топик из персонального блога то перенаправляем на страницу вывода коллективного топика
		 */
		if ($oTopic->getBlogType()=='personal') {
			func_header_location(DIR_WEB_ROOT.'/blog/'.$oTopic->getId().'/'.$oTopic->getTopicURL().'.html'); //ЧПУ
		}

/**
		 * Если номер топика правильный но УРЛ блога косяный то корректируем его и перенаправляем на нужный адрес
		 */
		if ($oTopic->getBlogUrl()!=$sBlogUrl) {
			func_header_location(DIR_WEB_ROOT.'/blog/'.$oTopic->getBlogUrl().'/'.$oTopic->getId().'/'.$oTopic->getTopicURL().'.html'); //ЧПУ
		}	


/**
				 * Отправляем уведомление тому на чей коммент ответили
				 */
				if ($oCommentParent and $oCommentParent->getUserId()!=$oTopic->getUserId() and $oCommentNew->getUserId()!=$oCommentParent->getUserId()) {					
					$oUserAuthorComment=$this->User_GetUserById($oCommentParent->getUserId());					
					$this->Notify_SendCommentReplyToAuthorParentComment($oUserAuthorComment,$oTopic,$oCommentNew,$this->oUserCurrent);					
				}
				func_header_location(DIR_WEB_ROOT.'/blog/'.$oTopic->getId().'/'.$oTopic->getTopicUrl().'.html#comment'.$oCommentNew->getId());


Привим шаблоны:
block.comments.tpl и comment_list.tpl
находим
{$oComment->getTopicId()}.html

меняем на
{$oComment->getTopicId()}/{$oComment->getTopURL()}.html#comment


topic_list.tpl
находим
{$oTopic->getId()}.html#comments

меняем на
{$oTopic->getId()}/{$oTopic->getTopicURL()}.html#comments


Вроде все, все должно работать! ;) Enjoy!

59 комментариев

avatar
так же незабудьте подправить шаблоны topic_list, topic и block.comment
avatar
да, конечно же, дописал :)
avatar
Если человек решит потом отредактировать материал заголовок сохранится без изменений ведь?
avatar
ну естественно или вы думаете, что уважаемый Covax этого не предусмотрел :)
avatar
ну если честно, то если вы смените заголовок, то урл изменится, но и старый будет работать ;)) вообще если вы руками напишите www.newmusic.ru/blog/events/2174/segodnya-prazdnik-u-rebyat.html то все равно откроется топик с ID 2174, поисковики конечно же по головке не погладят, если вы будете все время менять титули-урлы, но, а кто будет менять титули-урлы?
avatar
Мне просто хотелось порассуждать на тему того насколько вообще это так актуально на взгляд других людей ведь при достаточно обширной практике редактирования своих материалов чпу может быть не столь соответствующ отредактированному материалу.
Насколько вообще это может быть удобно? Не на ваш взгляд, Lora_GT. а на взгляд общественности?
avatar
Я не спорю с вами, тогда объясните пожалуйста почему вы комментируете даже не протестировав материал? получается, что вы заведомо создаёте в ветке флуд
avatar
Было бы хорошо включить ЧПУ в релиз.
Естественно с возможностью его отключения))
  • _et
  • 0
avatar
ну так что, кто нибудь повторил подвиг? :)) или только мне и Лоре интересна данная фича?
avatar
Фича, несомненно, интересная и нужная. Сейчас ломаю голову как перейти на Ливстирит с вордпресса с сохранением старой структуры типа сайт/раздел/топик.html так понял ваш способ мне без напильника не подойдет :)
avatar
C сохранением старой структуры видимо не получится, сам переходил с помощью Redirect 301. Быть может в новом релизе будет что-то типа встроенной возможности ЧПУ…
avatar
А где редиректы прописывал, в .htaccess?

Понимаю, что давний разговор, но может помнишь — и может быть какие-то новшества появились в этом вопросе? (Nice URL — рулит, но надо что-то для 301 по старым ссылкам при импорте материалов из другой CMS)
avatar
Covax а в уведомлениях на емаил незабыл урлы подредактировать? ;)
avatar
да, там темплейты уведомлений notify.***.tpl тоже надо отредактировать, спасибо Лора :)
avatar
Это слишком избыточные урлы получаются: www.newmusic.ru/blog/events/2174/zavtra-shpongle-live-v-moskve.html
Если убрать 2174, то ещё приемлемо будет. Хотя с простой транслитерацией тоже поиграть можно. Много раз убеждался уже, что в неумелых руках такие ссылки превращаются в бардак. Необходима возможность руками изменять slug: и поисковикам хорошо, и посетителям.
avatar
про неумелые руки я лихо промолчу, умелый ты наш :))) предложи код, идеями тут любой из нас закидает по самые нехочу. давай, на раз два три, пару строк дабавь в TransUrl(), которые выкинут из урла предлоги и другой лишний мусор. ждем.
avatar
Могу предложить идею: =)
у сфинкса в апи есть функционал генерирования кейвордов из текста. отдаешь ему кусок текста, а он возвращает массив ключевых слов, по которым будет искать. обычно слова приведены к именительному падежу. иногда отрезаны окончания.
можно заджойнить этот список и его в качестве слага использовать :-)

Плз, пинать только за идею не надо)
avatar
а для генерации отдавать либо только заголовок, либо весь текст, но это ессно будет более длительно (генерация кейвордов).
avatar
Идея хорошая, но легче тогда из тэгов делать делать url, которые юзер задает :)

p.s. а что, часто стали пинать? :)))
avatar
Крафт исправился ;)
avatar
а я не портился ;)
я по прежнему уверен, что допускать к ЖУ типичных юзеров ДЛЕ вредно для движка, а точнее его репутации
avatar
нуу, карма моя сильно пострадала во всякого рода перепалках с новичками… :/
avatar
Во-первых, про умеле ручки я говорил, когда имел ввиду клиентов, а не вас.
Во-вторых, код не предложу, т.к. за движком пока только слежу, но в его внутренностях не разьбирался.
А в том, что предложил идею, ничего плохого не вижу.
avatar
ну, извините, не так понял тогда. я тогда сам к вечерку код подработаю, а глядишь и руками можно будет слаги прописывать…
avatar
а для чего их руками прописывать то? Автоматически же прекрасно прописывает транслитом
avatar
ну типа сырбор весь в том, что заголовок не отражает ключевых слов статьи, для поисковика желательны ключевые слова. Не www.newmusic.ru/blog/events/2174/zavtra-shpongle-live-v-moskve.html, а www.newmusic.ru/blog/events/2174/shpongle-live-moskvа.html
avatar
Пример для статьи:
livestreet.ru/blog/tips_and_tricks/606.html
Это хороший урл получается?
livestreet.ru/blog/tips_and_tricks/sozdanie_adminki_ch2_sozdanie_svoego_menyu_poluchenie_i_sohranenie_nastroek.html
А бывает слов ещё больше. Я, например, делал «обрезание» по некоторому количеству слов, но тогда получается ещё больший бред. Например:
livestreet.ru/blog/tips_and_tricks/sozdanie_adminki_ch2_sozdanie_svoego.html
avatar
у меня это проблема решена ограничением 50 симоволов на титуль. соотвественно и урл больше 50 символов не получится.
avatar
Ну и это не решение :(
Получается:
livestreet.ru/blog/tips_and_tricks/sozdanie_adminki_ch2_sozdanie_svoego_menyu_poluch.html
avatar
а может не называть так сильно длинно а?
avatar
нет, вы не поняли, титуль не обрезается а дает пользователю понять, что больше 50 символов он не может использовать, тогда пользователь напишет «Админка 2: создание своего меню», не будет же он нелогичный тайтл на свой топик вешать.
avatar
И это не очень хорошо: а если всё же никак меньше 50 нельзя?
Ну а как правильно — я не знаю. Но мне кажется, лучше сочетать возможность использования slug (при чём активно призывать юзера его использовать) и обрезания, если slug не задан (со всеми теми проблемами, обсуждаемыми выше).
avatar
их (пользователей_ теги то не заставишь прописывать, а требовать прописывать слаг это вообще утопия…
а вот по моему опыту, ограничение символов в титуле работает практичсеки идеально ;)
avatar
как это нельзя, можно все, краткость — сестра таланта
avatar

в форме <input name="title" maxlength="50"> я имею ввиду
avatar
еще со сотороны сервера поставить условие обязательно
avatar
В comment_list.tpl нет такого:

{$oComment->getTopicId()}.html
avatar
А так — всё ок! Спасибо большое!
avatar

Спасибо.
По поводу выше сказанного, в плане сео, не туда смотрите.
Проблема:
http://newmusic.ru/forum/house/111628/koktejmpo.html
http://newmusic.ru/forum/house/111628/ja-chelovek-molekula.html
avatar

Если идти до конца по скользкой сеошной дорожке, тогда стоит делать так:
http://newmusic.ru/forum/house/111628/я-человек-молекула.html
avatar

Глюк словил. Из-за того, что в опросе нельзя редактировать вопрос (который является названием топика), при правке топика-опроса URL сбрасывается и зайти на топик уже нельзя. Приходится залезать в базу и указывать URL вручную.

Лечение:
1. Идём в classes/actions/ActionQuestion.class.php
2. Находим функцию protected function SubmitEdit($oTopic)
3. Удаляем или комментируем эти две строчки:

$output = TransURL(getRequest('topic_title'));
$oTopic->setUrl($output);
avatar
отлично, спасибо, сейчас отредактирую топик!
avatar
Всё сделал по инструкции, всё заработало.

Но после публикации ТОПИКА выходит 404, так же выходит ошибка 404 при редактировании. Что делать? где я ошибся?
avatar
Подскажите как сделать, чтобы по старому урл вида blog/name/1234.html топик тоже был доступен, так же как по /blog/name/(1234)/tekst.html, а то поисковики явно не обрадуются если у уже давно работающего сайта включить данный хак…
попробовала RewriteRule покрутить, но что то не вышло :(
avatar
ещё как обижаются :) попробовал на одном сайте поставить — почти все старые страницы вылетели из индекса :)
avatar
сделай дополнительные event'ы и пропиши для них стандартное формирование URL. ХЗ коряво это или нет, но у меня работает.
avatar
При правке кода увидел такую картину:

1) block.comments.tpl — такого файла в стандартной сборке нет. Господа, где вы его нашли? (Стандартный шаблон New)

2) В файле comment_list.tpl нет такого {$oComment->getTopicId()}.html. Что же там заменять?

В общем, главный вопрос на последнем этапе, как правильно отредактировать файлы шаблона? (Я так понял, что это разные версии шаблонов)
avatar
1. ничего что хак писался для более ранней версии ЛС? в частности для шаблона habra
2. если приложить чуточку сообразительности то все прекрасно работает и в ЛС 0.3.1 с шаблоном new (докозательство сайт в профайле)
avatar
Спасибо за доказательства, но если не сложно, напиши пожалуйста пост с изменениями, либо расшарь нужные файлы, в которых производились изменения, чтоб их сразу можно было залить. Моих знаний PHP и элементарной логики явно не хватает на то, чтоб сделать подобные изменения(знания PHP у меня ограничиваются на фразе «это что-то похожее на С++ ?»). Если сделаешь пост по теме ЧПУ в ЛС 0.3.1, то тебе будут благодарны многие!
avatar
знания PHP у меня ограничиваются на фразе «это что-то похожее на С++ ?»
Тогда зачем лезть в сайтостроительство? вот с++ и изучайте.
avatar
Правил по наитию так что уважаемые гуру поправте если не все поправил, но вроде работает.
Если шаблон стандартный new
В файле comment.tpl
заменить с
<li><a href="#comment{$oComment->getId()}" class="imglink link"></a></li>

на
<li><a href="#comment{$oComment->getId()}/{$oComment->getTopURL()}" class="imglink link"></a></li>


В файле comment_list.tpl
заменить с
<li><a href="{$oComment->getTopicUrl()}#comment{$oComment->getId()}" class="imglink link"></a></li>

на
<li><a href="{$oComment->getTopicUrl()}/{$oComment->getTopURL()}#comment{$oComment->getId()}" class="imglink link"></a></li>


В файле topic_list.tpl
заменить с
/{$oTopic->getTopicURL()}

на
/{$oTopic->getId()}/{$oTopic->getTopicURL()}


avatar
И еще. Я у себя чуток накосяцил :)
Но думаю для первого раза простительно.
Совет для отладки: Постим тестовый топик, с катор, и с коментариями. И этот топик проверяем со всех сторон. По всем ссылкам которые есть у топика. Если находим что что то отображается не совсем верно
Например .../2/ а хотелось бы .../2/topic/ то соответственно подставляем
{$oTopic->getTopicURL()} — в шаблоны по топику.
{$oComment->getTopURL()} — в шаблоны по коментариям
avatar
Поправочка

для файла topic_list.tpl
менять
/{$oTopic->getId()}

И еще нужно корректировать файл topic.tpl
с
/{$oTopic->getId()}/

на
/{$oTopic->getId()}/{$oTopic->getTopicURL()}/


Проверте что бы где нужно слеши стояли.
Простите что много постов.
avatar
Посмотрел заметки в интернете о трпнслитерации
Заметка А
Заметка 2
и мне кажется что файл i18n-ansii.txt нужно изменить вот так:
; global transliteration
[default]
А = "A"
Б = "B"
В = "V"
Г = "G"
Д = "D"
Е = "E"
Ё = "JO"
Ж = "ZH"
З = "Z"
И = "I"
Й = "J"
К = "K"
Л = "L"
М = "M"
Н = "N"
О = "O"
П = "P"
Р = "R"
С = "S"
Т = "T"
У = "U"
Ф = "F"
Х = "H"
Ц = "C"
Ч = "CH"
Ш = "SH"
Щ = "SHCH"
Ы = "Y"
Э = "E"
Ю = "YU"
Я = "YA"
а = "a"
б = "b"
в = "v"
г = "g"
д = "d"
е = "e"
ё = "jo"
ж = "zh"
з = "z"
и = "i"
й = "j"
к = "k"
л = "l"
м = "m"
н = "n"
о = "o"
п = "p"
р = "r"
с = "s"
т = "t"
у = "u"
ф = "f"
х = "h"
ц = "c"
ч = "ch"
ш = "sh"
щ = "shch"
ы = "y"
э = "e"
ю = "yu"
я = "ya"
Ъ = ""
ъ = ""
Ь = ""
ь = ""


Проверял сочетания типа jozhik в поиске гугла и яши и они точно определяют что это Ёжик.
avatar
Возник вопрос в каком файле шаблона прописан RSS?
Не могу найти в каком месте добавить функцию что бы был вид чпу /2/topic/
Спасибо
avatar
а неужели нельзя было написать полный путь до нужных файлов?
avatar
Автору просьба скорректировать инструкцию под 0.4 версию
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.