TagSaver

TagSaver: Плагин для работы с тегами

Плагин TagSaver для работы с тегами.

Вообще, фильтрация по тегам в MDOX реализуется довольно кисло. Представьте, у вас есть какой-то TV параметр, в котором у вас к каждой статье прописано по 10 тегов.

А в базе все эти теги для документа хранятся в одной строке. Т.е. Выглядит это примерно так

modx, теги, работа с тегами, фильтрация по тегам

Как обычно происходит фильтрация? Создается SQL запрос в таблицу site_tmplvar_contentvalues с применением WHERE value LIKE %тег%

Таким образом, мы можем искать не только по тем тегам, которые задумывались автором сайта, но и по тегам

  • и, р
  • ми, фи
  • льтра
  • бот
  • фил
  • mod

Более того, если документов очень много, то поиск происходит нереально долго. Как вообще идеально должна выглядеть работа с тегами?

Таблица с документами вида: id, content, pagetitle

Таблица с тегами: id, tag

Таблица связей с тегами: doc_id, tag_id

Можно конечно долго спорить о необходимости таблицы связи, т.к. придется делать лишний JOIN. Но за счет правильно расставленых индексов этот джоин вообще незаметен. Более того, немного доработав таблицу мы можем забивать теги в нескольких TV-шках, а хранить в одной таблице по вышеописаному стандарту.

На основании всего вышесказанного у нас получаются вот такие 2 таблицы

DROP TABLE IF EXISTS `modx_site_content_tags`;
CREATE TABLE `modx_site_content_tags` (
  `doc_id` int(11) NOT NULL,
  `tag_id` int(11) NOT NULL,
  `tv_id` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`tag_id`,`doc_id`,`tv_id`),
  UNIQUE KEY `dtt` (`doc_id`,`tag_id`,`tv_id`) USING BTREE,
  KEY `doc_id` (`doc_id`),
  KEY `tag_id` (`tag_id`),
  KEY `tv_id` (`tv_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

DROP TABLE IF EXISTS modx_tags; CREATE TABLE modx_tags ( id int(11) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, PRIMARY KEY (id), UNIQUE KEY name (name) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Как можно заметить, формат у таблиц MyISAM. Но у такого выбора есть своя причина. Одна из них это auto_increment. А вторая – уникальный ключ по нескольким полям. Не буду долго задерживаться на этом пункте. Скажу одно – если у вас MODX установлен с префиксом таблиц отличным от modx, то измените его на свой;-)

Создали таблицы? И что? Как теперь инфу то в них добавлять?

Все очень просто. Создаете как обычно TV параметр. Можете даже добавить виджет mm_widget_tags от ManagerManager. После этого создаете плагин TagSaver с параметрами

&tv=ID TV-параметра;input; &sep=Разделитель тегов;input;

Если разделителя нет, то можно оставить пустым.

Что делать если мне нужно 2 поля с тегами?

Создавайте копию плагина под другим именем с аналогичными параметрами. Только не забудьте у новой версии плагина указать разделитель и ID другого TV параметра.

Почему плагин делает SELECT за значениями TV, а не принимает $_POST['tv'.$tv]?

  1. Мы работаем в админке и ± 1 SQL запрос особого вреда не принесет
  2. Могут стоять другие плагины которые вызываются раньше и изменят значение сохраняемого TV параметра
  3. Значение TV параметра из $_POST может отличаться от сохраненного в базу

Как вывести на странице теги привязаные к текущей стрнице?

Выводить их можете как и раньше – через сниппеты DocInfo, просто подставляя tv параметр на странице .

Как теперь отфильтровать данные по нужному тегу?

Очень просто. Создаете сниппет примерно такого содержания

$tag = ((isset($tag) && is_scalar($tag))? $tag : (isset($_GET['tag']) && !is_array($_GET['tag']) ? $_GET['tag'] : ''));
$id = isset($id) ? (int)$id : 0;
$out = array();
if($id>0 && $tag!=''){
    $sql=$modx->db->query("SELECT doc_id FROM ".$modx->getFullTableName("tags")." as t
    LEFT JOIN ".$modx->getFullTableName("site_content_tags")." s ct ON ct.tag_id = id
    WHERE t.`name`='".$modx->db->escape($tag)."' AND ct.tv_id={$id}");
    $sql=$modx->db->makeArray($sql);
    foreach($sql as $item){
        $out[]=$item['doc_id'];
    }
}
return implode(",",$out);

И результат работы этого сниппета передаете в параметр documents от Ditto. У сниппета указаного выше есть 2 параметра:

  • tag – если этот параметр передан, то сниппет использует его значение. В противном случа пытается взять значение $_GET переменной tag
  • id – Идентификатор TV переменной в которой хранятся теги

Получается примерно так

[[Ditto? &documents=`[!GetTag? &id=`2`!]`]]

Как теперь вывести популярные теги?

$count = isset($count) ? (int)$count : 10;
$tv = isset($tv) ? (int)$tv : '';
$out = array();
$sql = $modx->db->query(
    "SELECT ct.tv_id,t.name,count(ct.tag_id) as count
    FROM ".$modx->getFullTableName("tags")." as t
    LEFT JOIN ".$modx->getFullTableName("site_content_tags")." as ct ON ct.tag_id=t.id
    LEFT JOIN ".$modx->getFullTableName("site_content")." as c on c.id=ct.doc_id
    WHERE deleted=0 AND published=1 ".(($tv!='') ? ("AND ct.tv_id=".$tv) : "")."
    GROUP BY tag_id ORDER BY count(ct.tag_id) DESC LIMIT 0,".$count
);
$sql = $modx->db->makeArray($sql);
foreach($sql as $item){
    $out[]=$modx->parseChunk($tpl,$item,"[+","+]");
}
return implode((isset($outSep) ? $outSep : ""),$out);

У сниппета 3 параметра

  • count – сколько тегов выводить (по умолчанию 10)
  • tv – Идентификатор TV переменной в которой хранятся теги
  • tpl – чанк в который будут подставляться теги. У меня он выглядит так
    <a href="/11.html?tag=[[urlencode? &input=`[+name+]`]]" title="Статьи с тегом [+name+]" class="label">[+name+] ([+count+])</a>
        

Как видно, этот сниппет не только теги подставляет, но еще и считает сколько раз они используются. Таким образом вызов сниппета для облака тегов выглядит так:

[[TagCloud? &tpl=`TagCloudItem` &tv=`7` &count=`20`]]

А что за сниппет urlencode?

return isset($input) ? urlencode($input) : '';

Для чего он нужен найдете в гугле

В силу того, что мне довольно часто приходится работать с сайтами сделаными другими программистами, то могу сказать вам одно – таких решений еще нигде небыло. Все фильтруют по старинке средствамми Ditto и изобретают свои велосипеды в виде экстендеров для него. Мое решение повзоляет вам работать с видимой частью так же, как и раньше. Зато существенно снимает нагрузку на сервер при фильтрациях. И облегчает выборку даже если вы используете знаменитый сниппет DropDownDocs для привязки статей к нескольким разделам (ведь по сути это тоже теги, только вид с боку). Соответственно, если вам нужны теги и фильтрации по TV параметрам в значении которых может быть несколько данных – используйте TagSaver.

Автор: Agel_Nash

Mem: 3.75 mb, MySQL: 0.0021 s, 2 req., PHP: 0.0395 s, all: 0.0416 s, cache.