Как вывести дерево категорий в Joomla и не только?

В CMS Joomla есть возможность создавать категории и размещать в них материалы. Плюс ко всему, категории можно делать вложенными друг в друга, в результате чего получается дерево категорий. Это очень удобно и практично.

Если Вы пробовали использовать мой Модуль Effect Content для Joomla 3, то наверняка обращали внимание на поля выбора категорий для материалов Joomla, записей K2, товаров JoomShopping и объявлений AdsManager. Заметили, как удобно структурирован список? Все вложенные категории идут строго под своим родителем, что в свою очередь упрощает поиск необходимой.

Я давно уже хотел описать, как можно этого добиться.

Вообще, в самой Joomla есть встроенные механизмы генерации дерева категорий. Но они работают не совсем так, как мне было нужно. Поиски в интернете и на различных форумах результата особо не принесли.

В этой статье я постараюсь описать наиболее распространенные варианты деревьев категорий, которые Вам могут понадобиться. А также опишу те, которые использую я.

Как это работает?

Код будет адаптирован исключительно под Joomla. Имея немного опыта в PHP, Вы самостоятельно сможете переделать его под себя.

Вариант 1. Обычный список.

Самый простой вариант, это если Вам понадобится просто составить список всех категорий.

<?php
$db = JFactory::getDBO();
$query = $db->getQuery(true);

$query->select('`id`, `title`, `parent_id`');
$query->from($db->quoteName('#__categories'));
$query->where($db->quoteName('extension') . ' LIKE "com_content"');
$query->order($db->quoteName('lft') . ' ASC');

$db->setQuery($query);
$results = $db->loadObjectList();

$categories = array();
if (count($results)) {
	$categories[] = JHTML::_('select.option', '0', 'Выберите категорию', 'id', 'title'); // Добавляем пустую первую строку, не обязательно.
	$categories = array_merge($categories, $results); // Добавляем результаты выборки в общий массив
	echo JHtml::_('select.genericlist', $categories, 'list-categories', 'class="select-list"', 'id', 'title'); // Формируем список select с помощью JHtml
}
?>

Как видим, здесь всё просто. С помощью запроса делаем выборку категорий, если вернулся результат, выводим сформированный список.

Обратите внимание на строки ‘id’ и ‘title’ в JHTML, они должны быть равны значениям выборки. Т.е. из базы данных мы получаем id и title, соответственно в JHTML мы сообщаем что value = id, а text = title.

К сожалению такой вариант мне совершенно не подошел, да и статья у нас именно про дерево категорий, а не просто список.

Вариант 2. Структурированное дерево категорий.

<?php
$db = JFactory::getDBO();
$query = $db->getQuery(true);

$query->select('`id`, `title`, `parent_id`');
$query->from($db->quoteName('#__categories'));
$query->where($db->quoteName('extension') . ' LIKE "com_content"');
$query->order($db->quoteName('lft') . ' ASC');

$db->setQuery($query);
$results = $db->loadObjectList();

$categories = array();
if (count($results)) {
	$temp_options = array();
	$categories[] = JHTML::_('select.option', '0', 'Выберите категорию', 'id', 'title'); // Добавляем пустую первую строку, не обязательно.
	foreach($results as $item) { // Формируем нужный вид массива
		array_push($temp_options, array($item->id, $item->title, $item->parent_id));	
	}
	foreach($temp_options as $option) {
		if ($option[2] == 1) { // Обратите внимание на условие сравнения, подробнее об этом ниже
			$categories[] = JHTML::_('select.option', $option[0], $option[1], 'id', 'title');
			recursive_options($temp_options, 1, $option[0], $categories);
		}
	}
	echo JHTML::_('select.genericlist', $categories, 'list-categories', 'class="select-list"', 'id', 'title');
}

function recursive_options($temp_options, $level, $parent, &$categories) {
	foreach($temp_options as $option) {
		if ($option[2] == $parent) {
			$level_string = '';
			for ($i = 0; $i < $level; $i++) {
				$level_string .= '- ';
			}
			$categories[] = JHTML::_('select.option',  $option[0], $level_string . $option[1], 'id', 'title');
			recursive_options($temp_options, $level+1, $option[0], $categories);
		}
	}
}
?>

Как видите, здесь уже немного сложнее, но принцип тот же.

Сперва мы делаем точно такой же запрос, как и в первом варианте. Далее, если у нас есть результаты выборки, мы опять добавляем пустую строку.

После этого переформировываем массив нужным образом, это пригодится для правильного создания списка.

Как только массив готов, мы проходим по нему циклом. В цикле используется вызов функции проверки на родителя и генерации элемента списка.

Обратите внимание на первое условие до вызова функции:

if ($option[2] == 1) {

Здесь мы делаем проверку, что у нас именно первый уровень. $option[2] — это у нас родитель категории, он должен быть равен единице. Т.к. в категориях Joomla самым корнем является ROOT с id = 0, но при этом он имеет тип system, а в выборке мы получаем только `extension` LIKE ‘com_content’, то нам остается делать проверку именно на единицу, будьте внимательнее. Если Вы делаете дерево категорий JoomShopping, то проверку уже делаем на ноль:

if ($option[2] == 0) {

Дело в том, что там нет корневой категории и родителем является ноль (0). Вы должны понимать это, иначе либо получите пустой список, либо бесконечное зацикливание.

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

В самой же функции есть примерно такое же условие:

if ($option[2] == $parent) {

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

Именно этот вариант я предпочел использовать в модуле Effect Content для Joomla 3.

Вариант 3. Дерево категорий по заданной категории.

А теперь предлагаю разобрать более сложный вариант.

В одном из заказов мне необходимо было сделать фильтр материалов Joomla, в котором категории играли роль городов. Но, помимо городов там были еще и обычные категории.

В этом случае ни первый, ни второй вариант мне не подошли, т.к. в обоих выводится весь список категорий, а нужно строить дерево только из заданных.

Ответ на этот вопрос я так и не смог найти на просторах интернет. Либо никто не задавался подобным, либо я плохой искатель.

Бросив поиски, я начал писать новую функцию. Да, она у меня получилась, но, к сожалению, она оказалась настолько тяжелой, что при большом количестве категорий и уровнях вложенности сайт может просто падать.

Основная ирония в том, что перед написанием функции, я совсем забыл про второй вариант, описанный выше, и лишь когда функция была готова и сайт пару раз упал, я вспомнил про универсальность функции, описанной во втором варианте.

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

Так вот, помните во втором примере есть самое первое условие?

if ($option[2] == 1) {

Здесь мы указываем, что будем формировать список от родителя id которого равен 1 (или 0, если корень категорий равен 0).
Просто укажите id категории, от которой необходимо строить дерево, и всё. К примеру, нам необходимо построить дерево категорий от категории с id = 11, условие будет таким:

if ($option[2] == 11) {

Если же Вам нужно включить в дерево всех потомков от нескольких категорий, то ничего сложного. Условие примет вид примерно такой:

if ($option[2] == 11 || $option[2] == 15) {

Вариант 4. Для тех кто не использует Joomla.

И напоследок функция из второго варианта для тех, кто не использует Joomla.

<?php
// Здесь Ваша выборка
if (count($results)) {
	$temp_options = array();
	foreach($results as $item) {
		array_push($temp_options, array($item->id, $item->title, $item->parent_id));	
	}
	echo '<select name="list-categories" class="select-list">';
		echo '<option value="0">Выберите категорию</option>';
		foreach($temp_options as $option) {
			if ($option[2] == 1) {
				echo '<option value="' . $option[0] . '">' . $option[1] . '</option>';
				recursive_options($temp_options, 1, $option[0]);
			}
		}
	echo '</select>';
}

function recursive_options($temp_options, $level, $parent) {
	foreach($temp_options as $option) {
		if ($option[2] == $parent) {
			$level_string = '';
			for ($i = 0; $i < $level; $i++) {
				$level_string .= '- ';
			}
			echo '<option value="' . $option[0] . '">' . $level_string . $option[1] . '</option>';
			recursive_options($temp_options, $level+1, $option[0]);
		}
	}
}
?>

Здесь предполагается, что Вы самостоятельно сделаете выборку категорий и просто передадите полученный массив в функцию.

Всё доступно и просто.

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

Комментарии

Здесь еще никто не оставлял комментарии.

Добавить комментарий