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")." as 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