Работа с разделами инфоблока
Раздел инфоблока группирует элементы. Разделы образуют дерево: у раздела может быть родитель и подразделы, а элемент может находиться в нескольких разделах. Разделы читают и записывают разными слоями API:
-
ORM-класс разделов инфоблока
Section{ID}Table— получает разделы, дерево, путь к разделу и пользовательские поляUF_*, -
классический API
CIBlockSection— добавляет, изменяет и удаляет разделы.
ORM-класс тоже умеет сохранять и удалять разделы, но оставляет у нового раздела неверный признак видимости в дереве и не удаляет содержимое раздела. Различия собраны в разделе Выбрать API для записи. Если код проекта использует CIBlockSection::GetList(), GetNavChain() или другие методы чтения классического API, их замены приведены в разделе Заменить методы классического API для чтения.
Перед работой подключите модуль iblock. Переменная $iblockId содержит идентификатор инфоблока, $sectionId — идентификатор раздела. Примеры используют разделы инфоблока новостей Мероприятия с символьным кодом events и Выставки с кодом exhibitions, а также пользовательское поле раздела UF_MANAGER — строку с ответственным. Создание пользовательских полей описано в статье Пользовательские поля.
Элементы раздела выбирают способами из раздела Отфильтровать по разделу, привязку элемента к разделам задают способами из статьи Работа с элементами инфоблока.
Выбрать API для записи
Добавляйте, изменяйте и удаляйте разделы методами CIBlockSection, а ORM-класс Section{ID}Table используйте для чтения. Выбор API для остальных задач модуля описан в статье Как выбрать API инфоблоков, события записи — в статье События инфоблоков.
Оба слоя API пересчитывают поля дерева LEFT_MARGIN, RIGHT_MARGIN и DEPTH_LEVEL, обновляют дату изменения и сбрасывают тегированный кеш инфоблока — кеш компонентов с меткой инфоблока, подробнее — в статье Кеширование. Кеш ORM-запроса разделов, заданный методом setCacheTtl(), методы CIBlockSection не сбрасывают, после записи вызовите $sectionClass::cleanCache(). Остальные действия выполняет только классический API. Поведение одинаково для обеих версий хранения свойств.
| Что происходит при записи | CIBlockSection::Add(), Update(), Delete() |
ORM save() и delete() |
|---|---|---|
Признак активности в дереве GLOBAL_ACTIVE |
Метод устанавливает Y, если раздел и все его родители активны, и пересчитывает признак у подразделов |
Метод save() вычисляет признак у нового раздела наоборот — N у активного раздела и Y у неактивного. Компоненты и выборки с условием GLOBAL_ACTIVE Y не показывают активный раздел и показывают неактивный |
Авторы CREATED_BY и MODIFIED_BY |
Метод Add() записывает ID текущего пользователя, если поля не переданы. Метод Update() не меняет CREATED_BY, даже если поле передано, а MODIFIED_BY без передачи перезаписывает ID текущего пользователя. Без авторизованного пользователя, например в агенте или консольном скрипте, методы записывают 0, поэтому передавайте CREATED_BY в Add() и MODIFIED_BY в Add() и Update() сами |
Поля остаются пустыми |
| Настройки поля Символьный код | Метод проверяет уникальность кода, если она включена в настройках | Настройки не применяются, раздел сохраняется с повторяющимся кодом |
| Обязательные пользовательские поля | Метод возвращает ошибку, если значение не передано | Метод проверяет обязательные поля, только если в save() передано хотя бы одно пользовательское поле. Иначе раздел сохраняется без значения |
Дата создания DATE_CREATE |
Метод записывает текущее время | Поле остается пустым |
Поисковый индекс модуля search |
Метод индексирует раздел, если в настройках инфоблока включена индексация разделов, инфоблок активен и у раздела GLOBAL_ACTIVE равно Y. Иначе метод удаляет раздел из индекса |
Индекс не меняется |
События OnAfterIBlockSectionAdd, OnAfterIBlockSectionUpdate и другие |
Метод вызывает | ORM не вызывает |
| Подразделы и элементы при удалении | Метод Delete() удаляет подразделы и элементы раздела |
Метод delete() удаляет только раздел и значения его пользовательских полей. Подразделы остаются со ссылкой на удаленного родителя, элементы — с привязкой к нему |
Признак GLOBAL_ACTIVE раздела, созданного через ORM, не исправляют ни setGlobalActive(true) перед save(), ни последующий вызов CIBlockSection::Update(). Пересчитать признак можно только для всего инфоблока методом CIBlockSection::ReSort(), он описан в разделе Добавить много разделов. Если код проекта уже создает разделы через ORM, вызовите ReSort() после записи и задайте setCreatedBy(), setModifiedBy() и setDateCreate() сами. Уникальность кода, обязательные пользовательские поля, события и поиск ORM при этом не обработает.
Проверить права пользователя
Методы CIBlockSection::Add(), Update() и ORM-класс не проверяют права текущего пользователя. Если код выполняет действие пользователя, проверьте право перед записью методом CIBlockSectionRights::UserHasRightTo() с операцией:
-
section_edit— изменить раздел, -
section_delete— удалить раздел, -
section_section_bind— создать подраздел внутри раздела, -
section_element_bind— добавить элемент в раздел.
Чтобы проверить право создать раздел верхнего уровня, вызовите CIBlockRights::UserHasRightTo() с ID инфоблока вместо ID раздела и операцией section_section_bind. Методы возвращают true для администратора и false, если в коде нет объекта текущего пользователя, например в агенте. Подробнее о методах проверки прав — в разделе Проверить права пользователя, назначение прав инфоблоку — в разделе Настроить права доступа.
Пример. Проверьте, может ли текущий пользователь изменить раздел.
if (!\CIBlockSectionRights::UserHasRightTo($iblockId, $sectionId, 'section_edit'))
{
throw new \RuntimeException('Недостаточно прав для изменения раздела');
}
Метод CIBlockSection::Delete() по умолчанию проверяет права сам и без прав не удаляет раздел, но возвращает true. Как удалить раздел в коде без пользователя, описано в разделе Удалить раздел.
Классический метод CIBlockSection::GetList() по умолчанию возвращает только разделы, доступные текущему пользователю. ORM-класс права не проверяет, поэтому для списка с учетом прав используйте CIBlockSection::GetList().
Получить ORM-класс разделов
Метод Bitrix\Iblock\Model\Section::compileEntityByIblock() создает ORM-класс разделов конкретного инфоблока. Метод принимает ID инфоблока или его символьный код API. Символьный код API задают в настройках инфоблока, это не символьный код CODE, подробнее — в разделе Представление инфоблоков в ORM. В отличие от класса элементов Element{ApiCode}Table, имя класса разделов строится по ID инфоблока. Метод возвращает полное имя класса, например \Bitrix\Iblock\Section10Table, где 10 — ID инфоблока. Если инфоблок с переданным символьным кодом API не найден, метод возвращает null. Для числового ID метод не проверяет, существует ли инфоблок, и всегда возвращает имя класса.
Пример. Получите ORM-класс разделов инфоблока.
use Bitrix\Iblock\Model\Section;
use Bitrix\Main\Loader;
if (!Loader::includeModule('iblock'))
{
throw new \RuntimeException('Не удалось подключить модуль iblock');
}
// News — символьный код API инфоблока новостей
$sectionClass = Section::compileEntityByIblock('News');
if ($sectionClass === null)
{
throw new \RuntimeException('Инфоблок с символьным кодом API News не найден');
}
Класс содержит поля раздела, пользовательские поля UF_* и связь PARENT_SECTION с родительским разделом. Запросы класса автоматически ограничены своим инфоблоком. Общий класс Bitrix\Iblock\SectionTable работает с разделами всех инфоблоков и не содержит пользовательских полей.
Примеры ниже используют переменную $sectionClass из этого раздела.
Получить разделы
ORM-класс разделов строит запросы так же, как ORM-класс элементов, — методами query(), getByPrimary() и getList(). Правила фильтра, сортировки и постраничной навигации описаны в статье Выборка элементов инфоблока. Как и для элементов, запрос не проверяет права пользователя и активность раздела. Список с учетом прав описан в разделе Проверить права пользователя.
Получить раздел по ID или коду
Метод getByPrimary() создает запрос по ID раздела, метод query() с условием where() — по символьному коду. Метод fetchObject() возвращает объект раздела или null.
Пример. Найдите раздел по символьному коду.
$section = $sectionClass::query()
->setSelect(['ID', 'NAME', 'CODE', 'ACTIVE', 'IBLOCK_SECTION_ID'])
->where('CODE', 'events')
->setLimit(1)
->fetchObject()
;
if ($section === null)
{
throw new \RuntimeException('Раздел не найден');
}
$sectionName = $section->getName();
Символьный код раздела уникален в инфоблоке, только если в настройках поля включена проверка уникальности.
Получить подразделы
Прямые подразделы хранят ID родителя в поле IBLOCK_SECTION_ID. У разделов верхнего уровня это поле пустое.
Пример. Получите активные подразделы раздела $sectionId по порядку сортировки.
$children = $sectionClass::query()
->setSelect(['ID', 'NAME', 'CODE'])
->where('IBLOCK_SECTION_ID', $sectionId)
->where('ACTIVE', 'Y')
->setOrder(['SORT' => 'ASC', 'NAME' => 'ASC'])
->fetchCollection()
;
Чтобы получить разделы верхнего уровня, используйте условие whereNull('IBLOCK_SECTION_ID').
Получить дерево разделов
Дерево хранится по модели вложенных множеств в полях LEFT_MARGIN, RIGHT_MARGIN и DEPTH_LEVEL. Сортировка по LEFT_MARGIN возвращает разделы в порядке обхода дерева, где за каждым разделом идут его подразделы. Поле DEPTH_LEVEL содержит уровень вложенности, у разделов верхнего уровня он равен 1.
Пример. Получите все видимые разделы инфоблока в порядке дерева.
$tree = $sectionClass::query()
->setSelect(['ID', 'NAME', 'DEPTH_LEVEL'])
->where('GLOBAL_ACTIVE', 'Y')
->setOrder(['LEFT_MARGIN' => 'ASC'])
->fetchAll()
;
$menuItems = [];
foreach ($tree as $row)
{
// Отступ по уровню вложенности
$menuItems[] = str_repeat('—', (int)$row['DEPTH_LEVEL'] - 1) . $row['NAME'];
}
Условие GLOBAL_ACTIVE Y отбирает разделы, которые активны вместе со всеми родителями. Поле ACTIVE хранит только собственную активность раздела.
Порядок соседних разделов в дереве задает поле SORT, а при равной сортировке — название. Методы записи CIBlockSection пересчитывают границы так, что раздел с меньшей сортировкой идет раньше. Чтобы изменить порядок, измените SORT методом Update().
Дерево разделов меняется редко, поэтому запрос для меню и навигации можно закешировать способом из раздела Кешировать запрос. Методы CIBlockSection не сбрасывают кеш ORM-запроса разделов. После их вызова закешированный запрос возвращает прежние данные до окончания времени жизни кеша. Чтобы сбросить кеш сразу, вызовите $sectionClass::cleanCache() после записи.
Получить путь к разделу
Родители раздела занимают в дереве диапазон, который включает границы раздела. Чтобы получить путь от корня до раздела, например для навигационной цепочки, отберите разделы с LEFT_MARGIN не больше и RIGHT_MARGIN не меньше границ раздела. Запрос заменяет классический метод CIBlockSection::GetNavChain().
Пример. Получите путь к разделу $sectionId.
use Bitrix\Iblock\SectionTable;
$current = SectionTable::getByPrimary($sectionId, [
'select' => ['LEFT_MARGIN', 'RIGHT_MARGIN'],
])
->fetch()
;
if (!$current)
{
throw new \RuntimeException('Раздел не найден');
}
$path = $sectionClass::query()
->setSelect(['ID', 'NAME', 'CODE'])
->where('LEFT_MARGIN', '<=', $current['LEFT_MARGIN'])
->where('RIGHT_MARGIN', '>=', $current['RIGHT_MARGIN'])
->setOrder(['LEFT_MARGIN' => 'ASC'])
->fetchAll()
;
Первая строка результата — раздел верхнего уровня, последняя — сам раздел $sectionId. Общий класс SectionTable в примере читает границы раздела без привязки к ORM-классу конкретного инфоблока. Добавить путь в навигационную цепочку страницы можно способами из статьи Хлебные крошки. Метод страницы $APPLICATION->GetNavChain() из этой статьи не связан с методом разделов CIBlockSection::GetNavChain().
Получить родительский раздел
Связь PARENT_SECTION присоединяет родительский раздел в том же запросе. Метод getParentSection() возвращает объект родителя или null, если раздел находится на верхнем уровне или связь не выбрана в setSelect().
Пример. Получите раздел Выставки и его родителя.
$section = $sectionClass::query()
->setSelect(['ID', 'NAME', 'PARENT_SECTION'])
->where('CODE', 'exhibitions')
->setLimit(1)
->fetchObject()
;
$parentName = $section?->getParentSection()?->getName();
Прочитать пользовательские поля
Пользовательские поля раздела попадают в результат, если их коды указаны в setSelect(). Маска 'UF_*' выбирает все пользовательские поля раздела. Значение возвращает метод get() с кодом поля.
Пример. Прочитайте ответственного за раздел из пользовательского поля UF_MANAGER.
$section = $sectionClass::getByPrimary($sectionId, [
'select' => ['ID', 'NAME', 'UF_MANAGER'],
])
->fetchObject()
;
$manager = $section?->get('UF_MANAGER');
В массиве строки от fetch() пользовательское поле приходит под своим кодом, например UF_MANAGER.
Посчитать элементы раздела
Классический метод CIBlockSection::GetSectionElementsCount() считает элементы раздела вместе с подразделами. В ORM посчитайте элементы через ORM-класс элементов. Отберите элементы поддерева раздела способом из раздела Отфильтровать по разделу и посчитайте их способом из раздела Посчитать элементы. Условие по связи SECTIONS для поддерева может повторить элемент, поэтому считайте через подзапрос по ID.
Пример. Посчитайте элементы раздела $sectionId и его подразделов. Переменная $elementNewsClass содержит ORM-класс элементов из раздела Получить элемент.
use Bitrix\Iblock\SectionTable;
$section = SectionTable::getByPrimary($sectionId, [
'select' => ['LEFT_MARGIN', 'RIGHT_MARGIN'],
])
->fetch()
;
if (!$section)
{
throw new \RuntimeException('Раздел не найден');
}
$subtreeElementIds = $elementNewsClass::query()
->setSelect(['ID'])
->where('SECTIONS.LEFT_MARGIN', '>=', $section['LEFT_MARGIN'])
->where('SECTIONS.RIGHT_MARGIN', '<=', $section['RIGHT_MARGIN'])
;
$elementCount = (int)$elementNewsClass::query()
->whereIn('ID', $subtreeElementIds)
->queryCountTotal()
;
Запрос возвращает то же число, что и CIBlockSection::GetSectionElementsCount(). Чтобы считать только активные элементы, добавьте во внешний запрос условие where('ACTIVE', 'Y'). Для меню с числом элементов у каждого раздела выполняйте подсчет для каждого раздела или используйте классический CIBlockSection::GetList() с третьим аргументом true.
Добавить раздел
Метод CIBlockSection::Add() создает раздел по массиву полей. Метод вызывают у объекта класса CIBlockSection. Метод возвращает ID нового раздела или false, а текст ошибки возвращает метод getLastError(). Если ошибок несколько, они разделены тегом <br>.
Подготовить поля
Таблица перечисляет поля, которые нужны для типовой структуры разделов. Полный список полей раздела приведен в разделе «Разделы» статьи Схема работы инфоблоков и основные объекты.
Обязательные поля обозначены *.
| Ключ | Описание |
|---|---|
IBLOCK_ID*int |
Идентификатор инфоблока |
NAME*string |
Название раздела |
CODEstring |
Символьный код. Метод не создает код из названия. Код по настройкам поля Символьный код возвращает метод createMnemonicCode() объекта CIBlockSection, он принимает массив с ключами NAME и IBLOCK_ID |
IBLOCK_SECTION_IDint |
Родительский раздел. Если ключ не передан или равен 0, раздел создается на верхнем уровне |
ACTIVEstring |
Активность, Y или N |
SORTint |
Сортировка |
XML_IDstring |
Внешний код для обмена с другой системой |
DESCRIPTIONstring |
Описание раздела |
DESCRIPTION_TYPEstring |
Тип описания, text или html |
PICTURE, DETAIL_PICTUREarray |
Картинка и детальная картинка — массив файла, который возвращает CFile::MakeFileArray(). Удаление картинки описано в разделе Заменить или удалить картинку |
UF_<код> |
Значение пользовательского поля раздела |
IPROPERTY_TEMPLATESarray |
SEO-шаблоны раздела. Шаблоны наследуются по цепочке «инфоблок → раздел → элемент», формат описан в статье SEO-шаблоны инфоблока |
SECTION_PROPERTYarray |
Привязка свойств к разделу для умного фильтра. Ключ работает, если в настройках инфоблока включена привязка свойств к разделам. Метод Update() заменяет все привязки раздела, формат и пример приведены в разделе Показать свойство только в разделе |
Создать раздел
Пример. Создайте раздел Выставки внутри раздела $sectionId и заполните пользовательское поле.
$sectionApi = new \CIBlockSection();
$newSectionId = $sectionApi->Add([
'IBLOCK_ID' => $iblockId,
'IBLOCK_SECTION_ID' => $sectionId,
'NAME' => 'Выставки',
'CODE' => 'exhibitions',
'ACTIVE' => 'Y',
'UF_MANAGER' => 'Пресс-служба',
]);
if (!$newSectionId)
{
throw new \RuntimeException($sectionApi->getLastError());
}
Метод проверяет поля и обязательные пользовательские поля, сохраняет раздел, пересчитывает дерево и GLOBAL_ACTIVE, индексирует раздел в поиске при условиях из раздела Выбрать API для записи и вызывает события OnBeforeIBlockSectionAdd и OnAfterIBlockSectionAdd.
У метода Add() есть необязательные параметры после массива полей:
-
$bResort— пересчитать поля дерева после добавления, по умолчаниюtrue, -
$bUpdateSearch— добавить раздел в поисковый индекс, по умолчаниюtrue, -
$bResizePictures— применить к картинкам настройки полей инфоблока, по умолчаниюfalse.
Добавить много разделов
При импорте дерева каждый вызов Add() сдвигает границы соседних и родительских разделов. Чтобы пересчитать дерево один раз, передайте вторым аргументом false и после импорта вызовите CIBlockSection::ReSort() с ID инфоблока.
Пример. Добавьте разделы из массива $items и пересчитайте дерево в конце. Каждый элемент массива содержит поля раздела в формате из раздела Подготовить поля.
$sectionApi = new \CIBlockSection();
foreach ($items as $fields)
{
$fields['IBLOCK_ID'] = $iblockId;
if (!$sectionApi->Add($fields, false))
{
throw new \RuntimeException($sectionApi->getLastError());
}
}
\CIBlockSection::ReSort($iblockId);
До вызова ReSort() у разделов, добавленных с false, пустые поля LEFT_MARGIN, RIGHT_MARGIN и DEPTH_LEVEL. Выборки по дереву и навигация по разделам до пересчета работают неверно. Метод ReSort() также пересчитывает GLOBAL_ACTIVE всех разделов инфоблока.
Не создавать дубли при повторном импорте
Повторный запуск импорта не должен создавать разделы заново. Перед записью найдите раздел по внешнему коду XML_ID и измените его методом Update(), если он есть, или создайте методом Add(). Передайте обоим методам аргумент $bResort со значением false и вызовите CIBlockSection::ReSort() после импорта, как в разделе Добавить много разделов. Родителя указывайте по ID, который получил раздел-родитель в этом же запуске, поэтому импортируйте родителей раньше подразделов.
Пример. Импортируйте дерево из массива $items. Каждый элемент массива содержит внешний код раздела XML_ID, внешний код родителя PARENT_XML_ID или null для раздела верхнего уровня и название NAME. Родители в массиве идут раньше подразделов.
$sectionApi = new \CIBlockSection();
$idsByXmlId = [];
foreach ($items as $item)
{
$fields = [
'IBLOCK_ID' => $iblockId,
'IBLOCK_SECTION_ID' => $item['PARENT_XML_ID'] !== null ? $idsByXmlId[$item['PARENT_XML_ID']] : 0,
'NAME' => $item['NAME'],
'XML_ID' => $item['XML_ID'],
];
$existing = $sectionClass::query()
->setSelect(['ID'])
->where('XML_ID', $item['XML_ID'])
->setLimit(1)
->fetchObject()
;
if ($existing === null)
{
$savedId = $sectionApi->Add($fields, false);
}
else
{
$savedId = $sectionApi->Update($existing->getId(), $fields, false) ? $existing->getId() : false;
}
if (!$savedId)
{
throw new \RuntimeException($sectionApi->getLastError() ?: 'Раздел не сохранен');
}
$idsByXmlId[$item['XML_ID']] = (int)$savedId;
}
\CIBlockSection::ReSort($iblockId);
Проверка перед записью защищает от дублей при последовательном запуске. Два параллельных процесса могут одновременно не найти раздел и создать два раздела с одним внешним кодом, поэтому не запускайте импорт одного инфоблока параллельно.
Изменить раздел
Метод CIBlockSection::Update() меняет поля существующего раздела. Метод принимает ID раздела и массив изменяемых полей в формате из раздела Подготовить поля. Поля, которые не переданы, остаются без изменений. Метод возвращает true или false, текст ошибки возвращает getLastError(). Если раздела с переданным ID нет, метод вернет false с пустым текстом ошибки. Вычисленные SEO-свойства раздела и его элементов метод не пересчитывает. Если название раздела используется в SEO-шаблонах, после Update() вызовите clearValues() способом из раздела Сбросить вычисленные значения.
Пример. Переименуйте раздел и снимите его с публикации.
$sectionApi = new \CIBlockSection();
$isUpdated = $sectionApi->Update($sectionId, [
'NAME' => 'Выставки и форумы',
'ACTIVE' => 'N',
]);
if (!$isUpdated)
{
throw new \RuntimeException($sectionApi->getLastError() ?: 'Раздел не изменен');
}
Когда раздел становится неактивным, метод устанавливает GLOBAL_ACTIVE N у всех его подразделов. Собственная активность ACTIVE подразделов не меняется. После повторной активации раздела метод возвращает подразделам GLOBAL_ACTIVE Y.
Чтобы перенести раздел в другого родителя, передайте новый IBLOCK_SECTION_ID. Подразделы и элементы раздела переходят вместе с ним, метод пересчитывает границы дерева. Чтобы перенести раздел на верхний уровень, передайте 0. Родитель должен принадлежать тому же инфоблоку. Перенести раздел в него самого или в его подраздел нельзя, метод вернет false с ошибкой «Нельзя перенести раздел внутрь себя».
Чтобы изменить пользовательские поля раздела, передайте UF_<код> в массиве полей Update().
Необязательные параметры $bResort, $bUpdateSearch и $bResizePictures метода Update() идут после ID и массива полей и работают так же, как у метода Add().
Заменить или удалить картинку
Картинку раздела передайте в PICTURE или DETAIL_PICTURE массивом файла, который возвращает CFile::MakeFileArray(). Метод сохранит новый файл и удалит прежний. Массив с ключом del и значением Y удаляет картинку и ее файл.
Пример. Замените картинку раздела и удалите детальную картинку. Переменная $picturePath содержит путь к новому файлу на сервере.
$sectionApi = new \CIBlockSection();
$isUpdated = $sectionApi->Update($sectionId, [
'PICTURE' => \CFile::MakeFileArray($picturePath),
'DETAIL_PICTURE' => ['del' => 'Y'],
]);
if (!$isUpdated)
{
throw new \RuntimeException($sectionApi->getLastError() ?: 'Раздел не изменен');
}
При чтении через ORM метод getPicture() возвращает ID файла. Путь к файлу возвращает метод CFile::GetPath().
Удалить раздел
Статический метод CIBlockSection::Delete() удаляет раздел вместе с содержимым:
-
подразделы на всех уровнях,
-
элементы, которые привязаны только к удаляемым разделам,
-
файлы картинки и детальной картинки раздела,
-
права раздела и SEO-шаблоны,
-
значения пользовательских полей и запись в поисковом индексе,
-
привязки свойств к разделу для умного фильтра,
-
ссылки на раздел в свойствах типа Привязка к разделам у элементов.
Элемент, который привязан еще к одному разделу, метод не удаляет. Если основным разделом элемента был удаляемый раздел, метод назначает основным другой раздел элемента.
Метод вызывает события OnBeforeIBlockSectionDelete и OnAfterIBlockSectionDelete для каждого удаляемого раздела. Обработчик OnBeforeIBlockSectionDelete может отменить удаление, тогда метод вернет false. Текст ошибки обработчика вернет $APPLICATION->GetException(), метод добавляет к нему имя обработчика. Метод удаляет элементы раздела раньше подразделов, поэтому отмена удаления на уровне подраздела не восстанавливает уже удаленные элементы. Для каждого элемента вызываются и события удаления элемента, подробнее — в статье События инфоблоков.
Второй параметр метода $bCheckPermissions по умолчанию равен true. Метод ищет раздел с проверкой прав текущего пользователя. Если у пользователя нет прав, например в агенте или в скрипте импорта, метод не удалит раздел и при этом вернет true. В коде без пользователя передайте вторым аргументом false.
Пример. Удалите раздел со всем содержимым из фонового кода.
global $APPLICATION;
if (!\CIBlockSection::Delete($sectionId, false))
{
$exception = $APPLICATION->GetException();
throw new \RuntimeException($exception ? $exception->GetString() : 'Раздел не удален');
}
При успешном удалении метод возвращает объект результата запроса, который приводится к true, поэтому проверяйте результат как логическое значение. Для ID, которого нет в базе, метод возвращает true. Удаление необратимо.
Если раздел нужно скрыть с сайта, а элементы сохранить, снимите раздел с публикации способом из раздела Изменить раздел.
Заменить методы классического API для чтения
Таблица помогает перенести код чтения разделов на ORM-класс.
| Метод классического API | Замена в ORM | Что учесть |
|---|---|---|
CIBlockSection::GetList() |
$sectionClass::query() или getList() |
Классический метод по умолчанию проверяет права текущего пользователя, если в фильтре не передан CHECK_PERMISSIONS N. ORM-запрос права не проверяет |
CIBlockSection::GetList() с 'GLOBAL_ACTIVE' => 'Y' |
where('GLOBAL_ACTIVE', 'Y') |
— |
CIBlockSection::GetList() с UF_* в $arSelect |
setSelect(['ID', 'UF_*']) у ORM-класса Section{ID}Table |
Общий класс SectionTable пользовательских полей не содержит |
CIBlockSection::GetList() с bIncCnt true |
Подсчет через ORM-класс элементов | Способ приведен в разделе Посчитать элементы раздела |
CIBlockSection::GetByID() |
getByPrimary() и fetchObject() |
— |
Фильтр 'SECTION_ID' => $parentId в GetList() |
where('IBLOCK_SECTION_ID', $parentId) |
В классическом фильтре разделов ключ SECTION_ID означает родителя, а не сам раздел |
Фильтр 'DEPTH_LEVEL' => 1 в GetList() |
where('DEPTH_LEVEL', 1) |
— |
GetNext() результата GetList() и поле SECTION_PAGE_URL |
Перебор коллекции или fetch(); адрес по шаблону возвращает CIBlock::ReplaceSectionUrl() |
Метод GetNext() экранирует значения для HTML и формирует адрес раздела. ORM возвращает исходные значения без адреса, экранируйте их при выводе |
CIBlockSection::GetTreeList() |
Запрос с сортировкой по LEFT_MARGIN |
Пример приведен в разделе Получить дерево разделов |
CIBlockSection::GetCount() |
$sectionClass::getCount(['=ACTIVE' => 'Y']) или queryCountTotal() |
Метод getCount() принимает фильтр в формате getList() |
CIBlockSection::GetNavChain() |
Запрос по LEFT_MARGIN и RIGHT_MARGIN |
Пример приведен в разделе Получить путь к разделу |
CIBlockSection::GetSectionElementsCount() |
Подсчет через ORM-класс элементов | Классический метод считает элементы подразделов и неактивные элементы, если не передан фильтр CNT_ACTIVE. Пример и условие по активности приведены в разделе Посчитать элементы раздела |
Проверить результат
Перед публикацией кода выполните на тестовом инфоблоке:
-
Создайте раздел и подраздел через
Add()и проверьте, что у обоихGLOBAL_ACTIVEравноY. -
Получите дерево разделов и путь к подразделу и сравните с деревом в административном разделе.
-
Снимите родителя с публикации и проверьте, что у подраздела
GLOBAL_ACTIVEсталоN. -
Добавьте элемент в подраздел и удалите родительский раздел. Убедитесь, что подраздел и элемент удалены.
-
Если код работает без пользователя, проверьте, что
Delete()вызывается сfalseвторым аргументом и раздел действительно удаляется. -
Запустите импорт дерева дважды и убедитесь, что число разделов не изменилось.
-
Попробуйте перенести раздел в его подраздел и проверьте обработку текста ошибки
getLastError().
Типовые ошибки и способы ускорить выборку собраны в статье Производительность и частые ошибки.