Родилось жёсткое решение "на скорую руку": как вывести средствами views и несложного хука список терминов верхнего уровня из одного словаря со счётчиками материалов во вложенных (дочерних) терминах (рекурсия без привязки к глубине).
Я специально заострю ваше внимание: только РОДИТЕЛЬСКИЕ ТЕРМИНЫ и вывод именно СРЕДСТВАМИ VIEWS (поскольку требуется кроме этого вывод ещё ряда связанных с терминами полей - изображение, tid и т.д. плюс условие не перестраивать имеющуюся CSS разметку выборки, связанную с views).
Как известно, вывести родительские (верхние) термины из словаря с количеством материалов можно используя Relationship + Aggregation + COUNT (если views 3). Ну и разумеется, настройка фильтра на "Таксономия: Родительский термин = empty (NULL)". Таких решений много и я не буду на них останавливаться. Однако, это решение выводит только количество материалов из верхних терминов словаря и не подсчитывает во вложенных. Например, для реальной иерархии словаря "Сотрудники":
Сотрудники (словарь)
- Штатные сотрудники (термин, 1 уровень)
-- 7-й отдел (термин, 2 уровень)
--- Иванов (материал)
--- Петров (материал)
--- Сидоров (материал)
- Внештатные сотрудники (термин, 1 уровень)
-- Кузнецов (материал)
-- Смирнов (материал)
- Стажёры (термин, 1 уровень) - допустим, не имеет материалов и вложенных терминов.
в случае с вышеописанным решением будет отображено:
- Штатные сотрудники (0)
- Внештатные сотрудники (2)
- Стажёры (0)
Что как бы не отвечает реальной картине внутри словаря и печалит разработчика. Получить же рекурсию средствами 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). Если это, конечно, кого-то тревожит. В общем, несколько грязно и брутально, но вполне себе юзабельно.