Навигация по автометкам

Хочу поделиться своим способом работы с метками, для дополнительной навигации по ресурсам. Вообще то, для этого уже есть tagLister, но мне нравится контролировать процесс. И вообще, это мой первый пост тут, так что сильно не пинайте :)




Для создания, собственно меток, логично использовать штатный tv параметр с типом autotag (автометка), поэтому вначале, создаю его и как нибудь называю. Не задумываясь особенно, назвал его по названию типа — autotag. Добавляю созданный tv в шаблон для вывода постов и с ним — все.





Для вывода меток, делаю два сниппета, один для создания облака меток — tagCloud, второй для вывода меток в блоге и в самом посте — tagLinks.

tagCloud:
<?php
$base = $modx->config['base_url'];
$tvname = $modx->getOption('tvname', $scriptProperties, "autotag");
$output = "";

$content_type = $modx->getObject('modContentType', array('mime_type' => 'text/html'));
if(substr_count($_SERVER["REQUEST_URI"], $content_type->get('file_extensions'))) {
	$parent = $modx->resource->parent;
	$url = $modx->makeUrl($modx->resource->parent);
}else{
	$parent = $modx->resource->id;
	$url = $modx->resource->uri;
}

$q = $modx->newQuery('modTemplateVarResource');
$q->select('DISTINCT(`modTemplateVarResource`.`value`)');
$q->innerJoin('modTemplateVar', 'tv', "tv.id = modTemplateVarResource.tmplvarid");
$q->innerJoin('modResource', 'res', 'res.id=modTemplateVarResource.contentid');
$q->where(array(
		'tv.name' => $tvname, 
		'res.context_key' => $modx->resource->context_key,
		'res.parent' => $parent
	)
);

if($q->prepare() && $q->stmt->execute()) {
	while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
		$result[] = $row['value'];
	}
}
if($result) {
    $result = implode($result, ',');
    $result = array_unique(explode(',', $result));
    
    foreach($result as $value) {
    	$output .= "<a href='{$base}{$url}?tag={$value}'>{$value}</a> ";
    }
}
return $output;

Этот сниппет выводит список уникальных значений меток, в виде ссылок, с GET параметром, который потом будет использоваться, для фильтрации вывода ресурсов:
[[!tagCloud:default=`Пока нет меток`? &tvname=`autotag`]]

<a href="/path/?tag=метка">метка</a>
<a href="/path/?tag=опятьметка">опятьметка</a>
<a href="/path/?tag=ещеметка">ещеметка</a>

Что в нем происходит:
Из таблицы с классом «modTemplateVarResource», выбираю все tv с типом «autotag» и именем «autotag», связанные с ресурсами находящимися в текущем контексте, родителем для которых, в случае вывода в блоге, является ресурс где вызывается сниппет, а в случае вывода в статье — родитель статьи. Так как в одной автометке, может быть много значений, перечисленных через запятую, то сначала, на уровне запроса, убираю повторяющиеся множественные значения, затем разбиваю результирующий массив по запятым и из него, так же, убираю повторяющиеся значения. В конце формирую ссылки.

tagLinks:
<?php
$tags = $modx->getOption('tags', $scriptProperties);
$get = $modx->getOption('get', $scriptProperties, '0');
$base = $modx->config['base_url'];

$content_type = $modx->getObject('modContentType', array('mime_type' => 'text/html'));
if(substr_count($_SERVER["REQUEST_URI"], $content_type->get('file_extensions'))) {
	$url = $modx->makeUrl($modx->resource->parent);
}else{
	$url = $modx->resource->uri;
}

if(!$get){
	if(!$tags) {return '';}
	$tags = explode(',',$tags);
	foreach($tags as $value) {
		$output[] = "<a href='{$base}{$url}?tag={$value}'>{$value}</a>"; 
	} 

	return implode(' ',$output);
}else{
	return (!empty($_GET['tag']))? "autotag==%{$_GET['tag']}%" : '';
}

Этот сниппет, выводит список ссылок, для блога и ресурса, а так же передает GET-параметр в вызов pdoResources, для фильтрации по меткам. В принципе, это разные задачи, но я решил не плодить сниппеты.

Что в нем происходит:
Сниппет может принимать в качестве параметров два значения:
[[!tagLinks? &tags=`[[*autotag]]`]] 
и
[[!tagLinks? &get=`1`]]

В первом случае, сниппет разбивает полученную автометку на отдельные значения, заворачивает их в ссылки, как в tagCloud и выводит. Для того, чтобы можно было выводить метки и в блоге и в самой статье, в начале сниппета получаю расширение для типа html и проверяю его наличие в адресной строке. Если адрес заканчивается на .html, то в url попадает адрес родителя (для статьи).







Во втором случае, когда передается параметр &get=`1`, сниппет смотрит в адресную строку и если там есть значение метки, то передает его в вывод, оформляя подходящим для фильтрации образом.

А вот вызов pdoResources, для создания блога и фильтрации по автометке.
[[!pdoResources? 
	&parents=`[[*id]]`
	&tvFilters=`[[!tagLinks? &get=`1`]]`
	&tpl=`@INLINE <div class="row">
                    <div class="cols col-10 intro">
                        <h2><a href="{{+link}}">{{+pagetitle}}</a> ({{+publishedon}})</h2>
                        <div>{{!tagLinks? &tags=`{{+tv.autotag}}`}}</div>
                        {{+introtext}} <a href="{{+link}}">читать дальше..</a>
                    </div>
                </div>`	
]]

На уникальность не претендую :)

UPD.
Если захотелось вывести, например в сайдбаре, статьи связанные с текущей, по меткам, то можно использовать вот такой сниппет

tagRelated:
<?php
$tpl = $modx->getOption('tpl', $scriptProperties);
$limit = $modx->getOption('limit', $scriptProperties, 5);
$tvname = $modx->getOption('tvname', $scriptProperties, "autotag");
$tvvalue = $modx->resource->getTVValue($tvname);
$base = $base = $modx->config['base_url'];
$currentid = $modx->resource->id;
$output = '';

$q = $modx->newQuery('modResource', array(
	'context_key' => $modx->resource->context_key,
	'parent' => $modx->resource->parent
));
$q->select('pagetitle,introtext,publishedon,uri,tvres.value as autotag');
$q->innerJoin('modTemplateVarResource', 'tvres', "tvres.contentid = modResource.id");
$q->innerJoin('modTemplateVar', 'tv', "tv.id = tvres.tmplvarid");
$q->limit($limit);
$q->where(array(
		'modResource.id:!=' => $currentid,
		'tv.name' => $tvname,
		'tvres.value:REGEXP' => str_replace( ',', '|', $tvvalue)
	)
);

if($q->prepare() && $q->stmt->execute()) {
	while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
		$output .= $modx->getChunk($tpl, array(
			'url' => $base.$row['uri'], 
			'date' => date("Y-m-d H:i:s", $row['publishedon']),
			'pagetitle' => $row['pagetitle'],
			'text' => $row['introtext']
		));
	}
}

return $output;

Здесь, я выбираю записи из таблицы с классом modResource, с которыми связана автометка имеющая, хотя бы одно значение, такое же как у текущей.

Вызов:
[[!tagRelated:default=`Пока нет связанных постов`?
	 &tpl=`RELATED`
	 &limit=`4`
	 &tvname=`autotag`
]]

Чанк RELATED:
<div>
	<a href="[[+url]]">[[+pagetitle]]</a> <span class="date">[[+date]]</span><br />
	<div class="sidetext">[[+text:notags:ellipsis=`100`]]</div>
</div>
Саша Туманов
16 апреля 2015, 18:58
26
3 420
+10

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

Klike
17 апреля 2015, 08:57
0
Потрясающе! Пару дней искал решение, и тут ИДЕАЛЬНО! Прямо то, что нужно. Спасибо!
Один вопрос, можно ли указать контейнер ресурсов, чтобы на странице со статьёй тоже облако тегов отображалось?
    Саша Туманов
    17 апреля 2015, 11:39
    0
    Поправил tagCloud, для вывода в статье тоже.
      Klike
      17 апреля 2015, 11:49
      0
      Спасибо! И ещё малюсенький вопрос. На сайте есть метки и категории, по сути те же метки. Как добавить в tagLinks, чтобы по двум тв параметрам сравнивал и выводил подходящие. Сделал два сниппета, на каждый тв. В вызове pdoResources указал в tvFilters
      &tvFilters=`[[!tagLinks? &get=`1`]] || [[!caregoryLinks? &get=`1`]]`
      В таком случае все отлично фильтрует, но тогда чистый блог не выводит совсем.
        Klike
        17 апреля 2015, 13:44
        0
        Наверное лучше так:
        return (!empty($_GET['tag']))? "tags1==%{$_GET['tag']}%||tags2==%{$_GET['tag']}%" : '';
      Klike
      17 апреля 2015, 12:16
      0
      И ещё один момент: если расширения нет к html
      Расширение html, как и домен www являются ненужными призраками прошлого — можно смело от них избавляться. ©bezumkin.ru

      Тоже не показывает в статьях, как и tagLinks, если закомментировать несколько строчек, то всё ок.
      $content_type = $modx->getObject('modContentType', array('mime_type' => 'text/html'));
      // if(substr_count($_SERVER["REQUEST_URI"], $content_type->get('file_extensions'))) {
      	$url = $modx->makeUrl($modx->resource->parent);
      /* }else{
      	$url = $modx->resource->uri;
      }
      */
      Klike
      17 апреля 2015, 12:50
      0
      Может лучше делать проверку по наличию .html в строке. А, к примеру, указывать в вызове &parent, а в снимете сравнивать уже:

      кусок tagLinks
      $parent = $modx->getOption('parent', $scriptProperties, $modx->resource->parent);
      if($parent == $modx->resource->parent) {
      	$url = $modx->makeUrl($modx->resource->parent);
      }else{
      	$parent = $modx->resource->id;
      	$url = $modx->resource->uri;
      }
      И аналогично у tagCloud:
      $parent = $modx->getOption('parent', $scriptProperties, $modx->resource->parent);
      if($parent == $modx->resource->parent) {
      	$url = $modx->makeUrl($modx->resource->parent);
      }else{
      	$parent = $modx->resource->id;
      	$url = $modx->resource->uri;
      }
        Саша Туманов
        17 апреля 2015, 21:51
        0
        Тим, это ведь не компонент с инсталлятором, где нужно сразу учитывать все потенциальные нужды. Я думаю, что тут главное хорошо составленный запрос, а танцы вокруг него, выполняются по произвольной программе ;)
          Klike
          18 апреля 2015, 09:10
          0
          Да, верно) В любом случае спасибо огромное за решение! А дорабатывает под свои нужды пусть каждый сам ))
      Михаил
      11 февраля 2016, 10:39
      0
      Саша, спасибо за интересный материал!

      Для мультиязычности Babel используется?
      Настройки как по мануалу?
        Саша Туманов
        17 февраля 2016, 19:50
        +1
        Да, Babel. Делал, в основном, по Васиной инструкции.

        Плагин переключающий контексты:
        <?php
        // Работаем только на фронтенде и только с friendly urls
        if ($modx->event->name != 'OnHandleRequest' || $modx->context->key == 'mgr' || !$modx->getOption('friendly_urls')) {return;}
        
        // Получаем запрашиваемый url
        $alias = $modx->getOption('request_param_alias', null, 'alias', true);
        $request = &$_REQUEST[$alias];
        
        // Выбираем контексты с настройкой base_url
        $q = $modx->newQuery('modContextSetting', array('key' => 'base_url', 'value:!=' => ''));
        $q->select('context_key,value');
        
        $contexts = array();
        $tstart = microtime(true);
        if ($q->prepare() && $q->stmt->execute()) {
        	// Учитываем наш запрос в БД
        	$modx->queryTime += microtime(true) - $tstart;
        	$modx->executedQueries++;
        	// Разбираем результаты
        	while ($row = $q->stmt->fetch(PDO::FETCH_ASSOC)) {
        		$base_url = trim($row['value'], '/');
        		$context = $row['context_key'];
        		// Если запрос начинается с base_url какого-то контекста
        		if (preg_match('/^('.$base_url.')\//i', $request)) {
        			// То переключаемся на этот контекст
        			// Web инициализируется в index.php - на него переключаться не нужно
        			if ($context != 'web') {
        				$modx->switchContext($context);
        			}
        			// Вырезаем base_url из запроса, чтобы MODX нашел ресурс по uri
        			$request = preg_replace('/^'.$base_url.'\//', '', $request);
        			// Дело сделано - выходим из цикла
        			break;
        		}
        	}
        }

        Сниппет переключающий языки:
        <?php
        //error_reporting(E_ALL | E_STRICT);
        //ini_set('display_errors', 1);
        $class = $modx->getOption('class', $scriptProperties, '');
        $divider = PHP_EOL;
        $output = '';
        
        function getLink($context = 'web') {
            global $modx;
        	if ($modx->getOption('site_start') != $modx->resource->id && $modx->getCount('modResource', array('uri' => $modx->resource->uri, 'context_key' => $context))) {
        		return $modx->resource->uri;
        	}
        	return '';
        }
        
        switch ($modx->context->key) {
            case 'en':
            	$output .= "<a class='".$class."' href='/".getLink('web')."'>RUS</a> {$divider}
            	            <a class='".$class."' href='kz/".getLink('kz')."'>KAZ</a>
            	            <span class='".$class."'>ENG</span> {$divider}";
                break;
            case 'kz':
            	$output .= "<a class='".$class."' href='/".getLink('web')."'>RUS</a> {$divider}
            	            <span class='".$class."'>KAZ</span>
            	            <a class='".$class."' href='en/".getLink('en')."'>ENG</a> {$divider}";
                break;
            default:
            	$output .= "<span class='".$class."'>RUS</span> {$divider}
            	            <a class='".$class."' href='kz/".getLink('kz')."'>KAZ</a>
            	            <a class='".$class."' href='en/".getLink('web')."'>ENG</a> {$divider}";
        }
        
        return $output;

        Настройки контекстов:
        en
        base_url				base_url	/en/
        Язык					cultureKey	en
        http_host				http_host	rirme.kz
        Главная страница сайта	site_start	2
        site_url				site_url	http://rirme.kz/
        
        kz
        base_url				base_url	/kz/
        Язык					cultureKey	kz
        http_host				http_host	rirme.kz
        Главная страница сайта	site_start	3
        site_url				site_url	http://rirme.kz/
        
        web
        base_url				base_url	/
        Язык					cultureKey	ru
        http_host				http_host	rirme.kz
        Главная страница сайта	site_start	1
        site_url				site_url	http://rirme.kz/

        Настройки системы -> Babel
        babel.contextKeys		babel.contextKeys	web,en,kz

        Тег base в начале странички:
        <base href="[[++site_url]]" />
          Михаил
          17 февраля 2016, 19:58
          0
          Спасибо огромное за развернутый ответ!
yani
17 апреля 2015, 09:00
0
Полезно, мне как то достался сайт, где было 10-15 ТВшек и все они были с типом АВТОМЕТКА, со временем страничка ресурса в админке перестала грузиться и пришлось убрать там этот тип(
Но повторюсь, кол-во таких ТВ было большое
Владимир
05 мая 2015, 11:31
0
В соц сети очень полезно добавлять хэш тэги, типа #тэг1 и т.п.
Само собой, напрашивается: tagLinks урезаем, делаем HashtagLinks
[[!HashtagLinks? &tags=`[[*tags]]`]]
<?php
$tags = $modx->getOption('tags', $scriptProperties);
if(!$get){
	if(!$tags) {return '';}
	$tags = explode(',',$tags);
	foreach($tags as $value) {
		$output[] = "#{$value}"; 
	} 

	return implode(' ',$output);
}
Одно досадно, предзаполнять поле комментариев по ogp.me/ не предусмотрено, а в <meta property=«og:description» content="[[!HashtagLinks? &tags=`[[*tags]]`]] добавлять бессмысленно, что сводит все на нет, т.е. постинг хэш-тегов в комментарии вручную.
Может у кого есть мысли как реализовать автопостинг и хэштэгов тоже?
Алексей
26 сентября 2015, 21:25
0
Помогите новичку пожалуйста.
Я использовал раньше такой вариант taglist
<?php
$tags = $modx->getOption('tags', $scriptProperties);
$get = $modx->getOption('get', $scriptProperties, '0');
$base = $modx->config['base_url'];
$content_type = $modx->getObject('modContentType', array('mime_type' => 'text/html'));
$url = $modx->makeUrl($modx->resource->parent);
if(!$get){
	if(!$tags) {return '';}
	$tags = explode(',',$tags);
	foreach($tags as $value) {
		$output[] = "<a class='tag' href='/tags?tag={$value}' rel=\"nofollow\">{$value}</a>"; 
	}
	return implode(' ',$output);
}else{
	return (!empty($_GET['tag']))? "tags==%{$_GET['tag']}%" : '';
}
Вызывал pdopage и в чанке прописывал так:
<p>Тэги: [[!tagLinks? &tags=`[[+tv.tags]]`]]</p>
Все прекрасно работало, но вот сейчас перенес все тэги из TV в поле description.
Надеялся что замена
$tags = $modx->getOption('tags', $scriptProperties);
на
$tags = $modx->resource->get('description');
поможет, но в чанк
<p>Тэги: [[!tagLinks? &tags=`[[+description]]`]]</p>
передается только дескрипшен той странице на которой вызывается, а не того ресурса который передается из pdopage.
Подскажите как быть?
Виктор Лобанов
11 февраля 2016, 01:26
0
Подскажите пожалуйста, а как вывести теги не через pdoResources а если статьи выводятся через getPage?
    Воеводский Михаил
    11 февраля 2016, 01:36
    0
    getPage сам по себе ничего не выводит, он только обеспечивает разбиение на страницы. Выводом, скорее всего, занимается getResources. В таком случае самым простым вариантов будет установка pdoTools и замена в вызове [[getPage]] на [[pdoPage]]. А дальше — по многочисленным здесь руководствам и ответам.
Igorevich
20 февраля 2016, 19:43
+1
Спасибо интересное решение, возник вопрос, можно ли как-то страницам с тегами задавать свой title и description, т.е например пользователь кликает на тег и попадает на страницу мойсайт/tag где выводятся посты с текущим тегом и на этой странице был свой заголовок и описание для ПС.
    Борис И
    21 февраля 2016, 09:25
    0
    Тоже думаю на эту тему, решения не нашел. Есть идея, создать отдельный раздел (секцию) — TEG, там будут ресурсы, по одному ресурсу для вывода статей с этим тегом. Так можно прописывать title и description.
    Вывод осуществлять с помощью pdoPage с фильтрацией &tvFilters=`тег`.
    Облако тегов будет — вывод всех ресурсов из созданного раздела — TEG.
    Встанет проблема как вывести теги (ссылки) принадлежащие одной статье. Можно использовать taglister, но он будет формировать свои ссылки, которые нам не нужны. Можно попробовать подменять их с помощью компонента redirector, направлять на наши созданные ресурсы, вместо ссылок taglister.
    Сам пока не реализовывал, но другого варианта пока не нашел. А title и description для страниц вывода материалов по тегам, вещь нужная и полезная, можно получить дополнительных посетителей, поисковики сейчас любят когда у вас подборка материалов по теме, раскрывающая ее (но правильное оформление страницы — обязательно).
    Минус решения — трудоемко и страницы, с тегами придется создавать ручками. Как бы автоматизировать…
Alex DOM
30 марта 2016, 10:06
0
Добрый день. Интересует вопрос. А можно как-то сделать чтобы авто-метки подтягивались для каждого языка только свои. Мультиязычность реализована контекстами через Babel?