Брутальное решение: Views - вывод родительских терминов со счётчиками материалов во всех вложенных терминах

Главные вкладки

Аватар пользователя OldWarrior OldWarrior 28 марта 2014 в 1:16

Родилось жёсткое решение "на скорую руку": как вывести средствами views и несложного хука список терминов верхнего уровня из одного словаря со счётчиками материалов во вложенных (дочерних) терминах (рекурсия без привязки к глубине).

Я специально заострю ваше внимание: только РОДИТЕЛЬСКИЕ ТЕРМИНЫ и вывод именно СРЕДСТВАМИ VIEWS (поскольку требуется кроме этого вывод ещё ряда связанных с терминами полей - изображение, tid и т.д. плюс условие не перестраивать имеющуюся CSS разметку выборки, связанную с views).

Как известно, вывести родительские (верхние) термины из словаря с количеством материалов можно используя Relationship + Aggregation + COUNT (если views 3). Ну и разумеется, настройка фильтра на "Таксономия: Родительский термин = empty (NULL)". Таких решений много и я не буду на них останавливаться. Однако, это решение выводит только количество материалов из верхних терминов словаря и не подсчитывает во вложенных. Например, для реальной иерархии словаря "Сотрудники":

Сотрудники (словарь)
- Штатные сотрудники (термин, 1 уровень)
-- 7-й отдел (термин, 2 уровень)
--- Иванов (материал)
--- Петров (материал)
--- Сидоров (материал)
- Внештатные сотрудники (термин, 1 уровень)
-- Кузнецов (материал)
-- Смирнов (материал)
- Стажёры (термин, 1 уровень) - допустим, не имеет материалов и вложенных терминов.

в случае с вышеописанным решением будет отображено:

- Штатные сотрудники (0)
- Внештатные сотрудники (2)
- Стажёры (0)

Что как бы не отвечает реальной картине внутри словаря и печалит разработчика. Smile Получить же рекурсию средствами views невозможно (ну или очень сложно и лимитировано - используя, например, что-то вроде Views Field View или попытавшись поколдовать с Views PHP).

Вместе с тем в сети имеется множество сниппетов для вывода полной иерархии словаря вместе со счётчиками материалов на всех без исключения уровнях. Рассмотрим, например, решение, найденное на сайте т-ща xandeadx. Здесь в первом шаге и одним SQL-запросом получаем всю иерархию в массив объектов-терминов (объекты при этом уже содержат актуальные счётчики связанных с ними нод). Вторым шагом рекурсивно "парсим" полученный массив, создавая новый массив с подсчётом/сложением всех "вложенных" счётчиков. Наконец, на третьем шаге рендерим полученный массив как список UL:

<?php
/**
 * Return rendered taxonomy tree
 */
function mymodule_taxonomy_tree($vid) {
  
$terms db_query("
    SELECT td.tid, td.name, th.parent, (
      SELECT COUNT(*) FROM {taxonomy_index} ti
      LEFT JOIN {node} n ON ti.nid = n.nid
      WHERE ti.tid = td.tid AND n.status = 1
    ) node_count FROM {taxonomy_term_data} td
    INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = td.tid
    WHERE vid = :vid
    ORDER BY weight
  "
, array(':vid' => $vid))->fetchAll();
 
  return 
theme('item_list', array('items' => _mymodule_taxonomy_tree($terms)));
}
 
/**
 * Helper for mymodule_taxonomy_tree()
 */
function _mymodule_taxonomy_tree($terms$parent 0, &$node_count 0) {
  
$items = array();
  
$node_count 0;
 
  foreach (
$terms as $term) {
    if (
$term->parent == $parent) {
      
$children _mymodule_taxonomy_tree($terms$term->tid$childs_node_count);
      
$node_count += $term->node_count $childs_node_count;
      
$items[] = array(
        
'data' => l($term->name'catalog/' $term->tid) . ' (' . ($term->node_count $childs_node_count) . ')',
        
'children' => $children,
      );
    }
  }
 
  return 
$items;
}
?>

Взяв за основу этот код, избавляемся от отображения вложенных терминов (но оставляем подсчёт), а также упрощаем конечный массив (нам не нужен список UL) и привязываемся к хуку hook_views_pre_render(). Итак, по шагам:

1. Создаём view, отображающий термины таксономии. В детальных настройках Aggregation нам НЕ НУЖНО. Relationship нам также НЕ НУЖНО. В общем, самый-самый обычный term view. Добавляете все необходимые вам поля. Настраиваете необходимые вам фильтры. Кроме этого, добавляете фильтр "Термин таксономии: Родительский термин (= empty (NULL))". И - ОБЯЗАТЕЛЬНО - сразу за тем полем, за которым нам нужен вывод количества материалов (обычно это имя термина) - добавляем поле "Термин таксономии: Вес" (зачем - описано ниже). Если нужно в настройках формата ряда включите "Inline fields" для этого поля и любых других - чтобы отображать в одну строку.

2. Пишем простейший модуль, содержащий только один хук и одну вспомогательную функцию. Как писать модули под D7 - отдельная широко освещённая тема и здесь не рассматривается. (Можно не писать новый, если вы уже используете на своём сайте какой-то самописный модуль, а просто добавить этот код). Итак, код модуля:

<?php
function MYMODULE_views_pre_render(&$view) { // MYMODULE заменяем на имя вашего модуля
  
switch ($view->name) {
    case 
'my_view_name':  // это заменяем на машинное имя нужного view 
                          // (определить можно по адресной строке браузера)

      

$all_terms db_query("
        SELECT td.tid, td.name, th.parent, (
          SELECT COUNT(*) FROM {taxonomy_index} ti
          LEFT JOIN {node} n ON ti.nid = n.nid
          WHERE ti.tid = td.tid AND n.status = 1
        ) node_count FROM {taxonomy_term_data} td
        INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = td.tid
        WHERE vid = :vid
        ORDER BY name
      "
, array(':vid' => 3))->fetchAll(); // здесь 3 меняем на vid вашего словаря

      

$parent_terms _parse_nested_terms($all_terms);

      

// На этом этапе мы уже имеем массив "правильных" значений счётчиков материалов
      // во всех вложенных терминах. Ключами массива являются tid'ы терминов.

      

foreach($view->result as $row) {
        
// Вот здесь происходит самое прикольное.
        // Мы заменяем ненужное нам значение веса термина на... количество материала во вложенных терминах.
        
$row->taxonomy_term_data_weight $parent_terms[$row->tid];
      }

      break;
  }
}

function 

_parse_nested_terms($terms$parent 0, &$node_count 0) {
  
$items = array();
  
$node_count 0;

  foreach (

$terms as $term) {
    if (
$term->parent == $parent) {
      
$children _parse_nested_terms($terms$term->tid$childs_node_count);
      
$node_count += $term->node_count $childs_node_count;
      if (
$parent == 0) {
        
$items[$term->tid] = $term->node_count $childs_node_count;
      }
    }
  }

  return 

$items;
}
?>

Вуаля! В случае с примером иерархии, описанным в начале этого поста, получим:

- Штатные сотрудники (3)
- Внештатные сотрудники (2)
- Стажёры (0)

Что полностью соответствует истине и радует разработчика.

Почему именно так? Более корректный способ - копание хэндлеров views, но это песня длинная и не всем под силу. Тут же мы просто жертвуем выводом обычно никому не нужного поля веса термина, заменив его вывод на количество материалов. Казалось бы, можно тогда добавить просто Custom markup и заменять его, но - нет, в выборке $view->result присутствуют только поля, реально выбранные из базы и, получается, работать можно только с ними. Либо - как уже писалось выше - обрабатывать не на уровне ряда, а на уровне handlers.

Ещё как вариант - попробовать как-то замороченно сделать аналогичную процедуру используя опять же Views PHP.

Ну и ещё один недостаток способа - добавляется один лишний запрос (помимо запроса views). Если это, конечно, кого-то тревожит. В общем, несколько грязно и брутально, но вполне себе юзабельно. Smile