Работа с разделами инфоблока

Раздел инфоблока группирует элементы. Разделы образуют дерево: у раздела может быть родитель и подразделы, а элемент может находиться в нескольких разделах. Разделы читают и записывают разными слоями 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
Название раздела
CODE
string
Символьный код. Метод не создает код из названия. Код по настройкам поля Символьный код возвращает метод createMnemonicCode() объекта CIBlockSection, он принимает массив с ключами NAME и IBLOCK_ID
IBLOCK_SECTION_ID
int
Родительский раздел. Если ключ не передан или равен 0, раздел создается на верхнем уровне
ACTIVE
string
Активность, Y или N
SORT
int
Сортировка
XML_ID
string
Внешний код для обмена с другой системой
DESCRIPTION
string
Описание раздела
DESCRIPTION_TYPE
string
Тип описания, text или html
PICTURE, DETAIL_PICTURE
array
Картинка и детальная картинка — массив файла, который возвращает CFile::MakeFileArray(). Удаление картинки описано в разделе Заменить или удалить картинку
UF_<код> Значение пользовательского поля раздела
IPROPERTY_TEMPLATES
array
SEO-шаблоны раздела. Шаблоны наследуются по цепочке «инфоблок → раздел → элемент», формат описан в статье SEO-шаблоны инфоблока
SECTION_PROPERTY
array
Привязка свойств к разделу для умного фильтра. Ключ работает, если в настройках инфоблока включена привязка свойств к разделам. Метод 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. Пример и условие по активности приведены в разделе Посчитать элементы раздела

Проверить результат

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

  1. Создайте раздел и подраздел через Add() и проверьте, что у обоих GLOBAL_ACTIVE равно Y.

  2. Получите дерево разделов и путь к подразделу и сравните с деревом в административном разделе.

  3. Снимите родителя с публикации и проверьте, что у подраздела GLOBAL_ACTIVE стало N.

  4. Добавьте элемент в подраздел и удалите родительский раздел. Убедитесь, что подраздел и элемент удалены.

  5. Если код работает без пользователя, проверьте, что Delete() вызывается с false вторым аргументом и раздел действительно удаляется.

  6. Запустите импорт дерева дважды и убедитесь, что число разделов не изменилось.

  7. Попробуйте перенести раздел в его подраздел и проверьте обработку текста ошибки getLastError().

Типовые ошибки и способы ускорить выборку собраны в статье Производительность и частые ошибки.