home chevron_right
Модули: simai.framework, simai.property, simai.property4field, simai.property4iblock, вспомогательные

Основной модуль: simai.framework — ядро фреймворка (компоненты, actions, утилиты, классы Configuration/Setting/Iblock/Page/Utility/IO/Block/Search)link

simai.framework — это “несущая конструкция” SIMAI Framework 4: он задаёт системный runtime SF4, поставляет библиотеку классов и набор готовых компонентов, а также включает инфраструктуру мастеров (wizard) и связанных действий (actions). На уровне проекта этот модуль важно воспринимать как поставку, которую обновляют, а не как место для кастомизации.

Практическая архитектура модуля делится на несколько крупных частей:

1) Библиотека классов (/bitrix/modules/simai.framework/lib/...) В библиотеке ядра SF4 выделяются три смысловых пространства имён:

  • SIMAI\Main\* — основной API для проекта и внутренних подсистем:

    • SIMAI\Main\Configuration\* — работа с конфигурациями и уровнями настроек (site/section/page/user) + итоговое хранилище свойств на время сессии.
    • SIMAI\Main\IO\* — подключение шаблонных областей и чтение конфигов.
    • SIMAI\Main\Page\* — подключение ассетов/шрифтов/мета-информации.
    • SIMAI\Main\Block\* — логика блоков/разделов и режимы редактирования в публичной части.
    • SIMAI\Main\Iblock\*, SIMAI\Main\Search\*, SIMAI\Main\Utility\* — работа с инфоблоками/поиском/утилитами.
  • SIMAI\Install\* — сервисные операции импорта/экспорта (то, что обычно используется в мастерах и обслуживании).

  • SIMAI\Wizard\* — утилиты и инфраструктура, связанные с выполнением шагов мастера.

2) Компоненты (/bitrix/components/simai/...) simai.framework поставляет набор компонентов SF4. Среди них есть как “инфраструктурные” (например, sf.wizard и sf.wizard.stage для мастеров), так и “прикладные”/UI-компоненты. Важно, что компоненты — это часть поставки, но их шаблоны и поведение часто расширяются через слой данных проекта (в зависимости от того, как у вас организованы шаблоны и simai.data).

3) Runtime SF4 и системные директории Вместе с модулем существует системный слой SF4, который используется в работе мастеров и системных механизмов. Этот слой обновляется вместе с модулем и не предназначен для прямых правок.

Главное правило расширения без форка для simai.framework: всё проектное (шаблон, области, блоки/grid/view, конфиги сайта) держать в {site_dir}/simai.data, а не в /bitrix/modules/... и не в системных директориях SF4.

Модули свойств: simai.property, simai.property4field, simai.property4iblock — универсальные свойства и пользовательские свойстваlink

Семейство simai.property* отвечает за “типизацию” полей и настроек: вместо того чтобы в каждом месте вручную рисовать HTML и обрабатывать ввод, SF4 опирается на единый механизм “тип свойства + шаблон отображения”.

simai.property — базовый модуль универсальных свойств Он содержит ядро рендера/валидации шаблонов свойства и общий API, через который вызываются шаблоны отображения:

  • проверка существования типа/шаблона и получение файлов реализации;
  • экранирование/безопасная подготовка значений и параметров;
  • три базовых режима вывода: view, edit, filter;
  • вспомогательный API для перечней сущностей (когда свойство должно выбирать “что-то из системы”: инфоблок/раздел/группа/хранилище и т.п.).

Минимальный “паттерн вызова” выглядит так (пример именно про контракт метода, без привязки к конкретному типу):

<?php

use SIMAI\Property;

// Отрисовать значение свойства в режиме просмотра
echo Property::view(
    'type_code',
    '.default',
    ['VALUE' => '...'],
    ['TITLE' => '...']
);

simai.property4field — дополнительные типы для пользовательских полей (UF) Этот модуль расширяет базовую систему свойств набором конкретных типов/рендереров, ориентированных на пользовательские поля (в том числе с JS-логикой и готовыми UI-шаблонами). В поставке встречаются типы, связанные, например, с выбором элементов/разделов/пользователей/задач, а также специализированные типы (вплоть до интеграций вроде карты).

simai.property4iblock — дополнительные типы для свойств инфоблоков Это аналогичное расширение, но ориентированное на свойства инфоблоков: добавляет прикладные типы, которые удобнее держать отдельным модулем (чтобы не раздувать базовый simai.property). В поставке встречаются типы для “тегов”, “тикетов/обращений”, “пользователей”, “задач” и т.п., а также готовые компоненты/шаблоны селекторов.

Практический смысл разделения на три модуля такой:

  • simai.property задаёт контракт “тип + шаблон + режим”.
  • simai.property4field и simai.property4iblock добавляют конкретные типы под разные домены (UF и инфоблоки), не ломая и не усложняя базовый модуль.

Вспомогательные (если есть в поставке SF4) — фиксировать наличие и назначениеlink

В экосистеме SF4 предусмотрены и встречаются вспомогательные модули, которые не являются обязательными для базовой работы “ядра”, но закрывают инфраструктурные задачи:

  • simai.update — модуль центра обновления (инфраструктура обновлений экосистемы SF4).
  • simai.backup — модуль резервирования данных.
  • simai.iblockcopy — модуль операций копирования/переноса структур и данных, связанных с инфоблоками.
  • simai.bxeditor — модуль расширения функционала HTML-редактора.

Такие модули обычно подключают “сервисные” сценарии: обслуживание, миграции, сопровождение, удобные инструменты администрирования. При этом отключение/удаление сервисного модуля корректнее планировать от задач проекта: ядро SF4 продолжит работать, но соответствующие сервисные возможности станут недоступны.

Картина мира ядра SF4: как читать и использовать APIlink

Пространства имён и “стили” кодаlink

В ядре SIMAI Framework 4 (SF4) классы сгруппированы по функциональным областям и в основном живут в неймспейсе SIMAI\Main\.... Это “прикладной” слой SF4, которым пользуются и шаблоны/компоненты, и админ-инструменты.

На практике в API SF4 встречаются три характерных “стиля” использования Bitrix:

  1. D7-подход (Bitrix\Main...) — там, где удобно опираться на современные классы ядра Bitrix. Типичные примеры: загрузка модулей (\Bitrix\Main\Loader), управление ассетами страницы (\Bitrix\Main\Page\Asset), работа с путями (Loader::getDocumentRoot()).

  2. Legacy-подход (глобальные классы/функции Bitrix) — там, где SF4 взаимодействует с “классическим” API Bitrix (например, CSite::GetList(), CIBlock::...) или где это проще/исторически сложилось.

  3. Собственный слой SF4 (SIMAI\Main...) — это фасады/хелперы, которые берут на себя “склейку” инфраструктуры: конфигурации, файловые слои, блоки, include-области, обработчики поиска. Именно этот слой чаще всего стоит рассматривать как API SF4 для проекта.

Кроме SIMAI\Main\... в составе ядра SF4 присутствуют отдельные пространства для инфраструктурных задач:

  • SIMAI\Install\... — импорт/экспорт (обычно вокруг установочных сценариев и миграций).
  • SIMAI\Wizard\... — утилиты мастера (поддержка пошаговых сценариев).

Хорошее практическое правило для чтения API такое: если вы пишете проектный код (шаблон, область, блок, view), то почти всегда начинаете с SIMAI\Main\... и только при необходимости уходите в Bitrix API.

Общий паттерн: singleton + статические методыlink

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

Обычно это выглядит так:

  • есть getInstance(), который создаёт/возвращает единственный экземпляр;
  • публичные статические методы (например, getValue()/setValue()) внутри берут getInstance() и вызывают “настоящую” логику экземпляра.

Это удобно в шаблонах и компонентах: можно вызвать один метод и не держать сервисы вручную.

Пример на базе конфигурационного хранилища свойств (самый частый кейс в проектах SF4):

<?php

use SIMAI\Main\Configuration\Property;

// Прочитать итоговое свойство страницы/сайта (значение уже собрано на текущую сессию)
$layoutType = Property::getValue(SF_SITE_DIR, 'layout_type');

// Записать значение в runtime-хранилище (в сессию) на текущий сайт
Property::setValue(SF_SITE_DIR, 'layout_type', 'wide');

Важно понимать вторую часть паттерна: где именно живут данные. В SF4 можно выделить три основных “среды хранения”, и от этого зависит, как вы должны воспринимать метод.

  1. Только память (на время текущего запроса) Типичный пример — “временные переносчики” и лёгкие runtime-объекты, которым не нужно сохранять состояние между запросами. Здесь изменения действуют только до конца исполнения PHP.

  2. Сессия пользователя В SF4 есть как минимум два выраженных сессионных хранилища:

  • итоговые свойства сайта/страницы живут в $_SESSION['site_property'][<storageId>] (это использует SIMAI\Main\Configuration\Property);
  • кеш соответствий для инфоблоков по коду живёт в $_SESSION['iblock_property'] (это использует SIMAI\Main\Configuration\Iblock).

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

  1. Файлы (персистентное хранение на диске) Это настройки, которые должны переживать сессию и быть частью проекта/поставки. В SF4 ключевые места такие:
  • системный конфиг фреймворка (общие настройки SF4) читается из системного слоя /simai/config/.framework.config.php;
  • настройки сайта (как проекта) читаются из {site_dir}/simai.data/.site.property.php;
  • настройки разделов/страниц читаются из /.property.php в директориях сайта, причём настройки страницы хранятся в этом же файле внутри ветки page[<имя_файла>].

Отдельно важно зафиксировать для вашей практики: проектный {site_dir}/simai.data/config/.asset.config.php не используется, поэтому “расширение ассетов через simai.data/config” не является публичной точкой расширения в ваших проектах. Реестр ассетов SF4 живёт в системном слое, а проектные CSS/JS подключаются через {site_dir}/simai.data/template/... (обычно через style.php/js.php или прямые подключения в проектном template.php).

Константы окружения SF4link

Окружение SF4 задаётся набором констант, которые превращают “абстрактный сайт” в конкретные пути и каталоги. Эти константы используются повсеместно: в конфигурациях, в шаблонах, в поиске файлов и в механике simai.data.

Главные константы и их смысл:

SF_DIR и SF_PATH SF_DIR — относительный корень системного слоя SF4, фиксирован как /simai. SF_PATH — абсолютный путь до этого слоя на диске: $_SERVER["DOCUMENT_ROOT"] . SF_DIR.

SF_SITE_DIR, SF_SITE_PATH SF_SITE_DIR — корень текущего сайта в URL-структуре Bitrix (например, /ru или /). В SF4 есть важная нормализация: если SITE_DIR оканчивается на / и это не корень, завершающий слеш убирается. То есть ожидаемое поведение: SF_SITE_DIR хранится без “хвостового слеша”, кроме корня /.

Особый случай, который встречается в админских сценариях: если SITE_DIR пустой, SF4 пытается определить сайт через HTTP_REFERER, сопоставляя путь реферера с DIR сайтов в настройках Bitrix. Если определить не удалось — используется /.

SF_SITE_PATH — абсолютный путь до корня сайта на диске: $_SERVER["DOCUMENT_ROOT"] . SF_SITE_DIR.

SF_DATA_DIR, SF_DATA_PATH SF_DATA_DIR — путь к слою данных SF4 относительно document root: SF_SITE_DIR . "/simai.data", после чего SF4 нормализует слеши (схлопывает // и смешанные \//).

SF_DATA_PATH — абсолютный путь до {site_dir}/simai.data на диске: $_SERVER["DOCUMENT_ROOT"] . SF_DATA_DIR.

Практически это означает: когда вы работаете с данными проекта (шаблон, области, grid, include), вы почти всегда “переходите” через SF_DATA_PATH/SF_DATA_DIR, а не конструируете пути вручную.

SF_LANG SF_LANG вычисляется из SF_SITE_DIR простым удалением слешей. Для /ru это даст ru. Для корня / получится пустая строка — и это нормально: корневой сайт не имеет “кода папки”.

Константы решения (SF_SOLUTION_*) В окружении определяются мета-константы решения:

  • SF_SOLUTION_NAME
  • SF_SOLUTION_LINK
  • SF_SOLUTION_PATH
  • SF_SOLUTION_DIR

Их смысл — описать “подключенное решение” (название/ссылка) и его расположение (абсолютный/относительный путь). Константа SF_SOLUTION как отдельное значение не найдена в локальных данных (если вы предполагаете, что она должна существовать в других сборках, это стоит отдельно уточнить как вариативность поставки).

Изображения-заглушки и lazyload

  • SF_IMAGE_BLANK — это не путь к файлу, а data-URI (base64) для 1×1 пикселя (заглушка).
  • SF_IMAGE_LAZYLOAD_BG — берётся из конфигурационных свойств (ключ image_lazyload_bg) и используется как фон/заглушка для ленивой загрузки. Важный нюанс: это значение зависит от итоговых свойств, поэтому корректность SF_IMAGE_LAZYLOAD_BG предполагает, что свойства на текущий сайт уже доступны.

Практическое правило нормализации путей В коде SF4 для путей используется единая нормализация: “схлопнуть повторяющиеся слеши” и привести к /. Для проектного кода полезно держаться того же принципа и по возможности использовать готовую утилиту:

<?php

use SIMAI\Main\Utility\Path;

// Было: ручная склейка и риск получить двойные слеши
$path = SF_DATA_PATH . '/template//area/sidebar/right/template.php';

// Стало: нормализация через утилиту ядра SF4
$path = Path::getCorrect(SF_DATA_PATH . '/template/area/sidebar/right/template.php');

Так вы избегаете “случайных” ошибок путей (особенно когда часть сегментов приходит из настроек/кодов) и придерживаетесь того же соглашения, что использует само ядро SF4.

Подсистема Configuration: настройки, уровни и хранениеlink

Идея “property-файлов” и формата данныхlink

В SF4 “property-файлы” — это обычные PHP-файлы, которые возвращают массив через return ...;. Ядро SF4 читает такие файлы через include/require, а при записи формирует содержимое через var_export() и сохраняет обратно на диск.

Важно различать два близких, но разных по смыслу слоя:

  • Персистентные настройки на диске (то, что переживает сессии):

    • общий конфиг ядра: /simai/config/.framework.config.php
    • настройки сайта (как проекта): {site_dir}/simai.data/.site.property.php
    • настройки разделов/страниц: /.property.php в каталогах сайта
  • Runtime-настройки в сессии (то, что собирается и живёт “для пользователя”):

    • итоговые свойства (уже объединённые из site/section/page/user/overrides) складываются в сессионное хранилище.

По формату данных есть важный нюанс: в разных конфигурационных файлах SF4 используется разная структура значений.

  • Для .framework.config.php значения хранятся как массивы вида:
<?php
return [
    'some_option' => [
        'value' => '...',
        'readonly' => false,
    ],
];
  • Для .site.property.php значения обычно хранятся “плоско” (код → значение):
<?php
return [
    'layout_type' => 'wide-fixed',
    'development_mode' => 'Y',
];
  • Для /.property.php структура обычно “ветвистая” (по назначению): отдельные ветки под раздел, страницы и (опционально) блоки. Пример структуры, которую ожидают классы Section и Page:
<?php
return [
    'section' => [
        'layout_type' => 'wide-fixed',
    ],
    'page' => [
        'index.php' => [
            'title' => 'Заголовок страницы',
        ],
    ],
    'block' => [
        'index.php' => [
            'hero' => [
                'theme' => 'dark',
            ],
        ],
    ],
];

Отдельный файл вида .<page>.property.php (рядом со страницей) не найден в локальных данных: в текущей реализации настройки страницы читаются из ветки page[...] внутри /.property.php каталога страницы.

SIMAI\Main\Configuration\Property — базовый слой свойствlink

Этот класс — не “файловая конфигурация”, а сессионное runtime-хранилище. Его основная роль в проектах SF4 — хранить итоговый набор свойств, собранный из нескольких уровней (site/section/page/user/глобальные overrides), чтобы дальше в шаблонах и компонентах обращаться к ним быстро и единообразно.

Внутри данные живут в $_SESSION['site_property'][<storageId>], где storageId — строковый идентификатор хранилища (часто используют SF_SITE_DIR для “итоговых свойств сайта” и отдельный user для пользовательских оверраидов).

Контракты методов (по фактической реализации):

  • Property::getArray(string $storageId): ?array — получить весь массив значений из сессии.
  • Property::setArray(string $storageId, array $array): void — заменить массив значений и сохранить в сессию.
  • Property::getValue(string $storageId, string $name): mixed|null — получить одно значение по ключу.
  • Property::setValue(string $storageId, string $name, mixed $value): void — записать одно значение и сохранить в сессию.
  • Property::delValue(string $storageId, string $name): void — удалить ключ и сохранить в сессию.
  • Property::clear(string $storageId): void — очистка хранилища (в текущей реализации очищается общий массив данных; использовать аккуратно).
  • Property::getConfigProperty(string $fileConfig, string $name): mixed|false — чтение “описания свойства” из конфигурационного файла, где ожидается структура с ключом property (это другой формат, не как у /.property.php).

Нюанс по ключам с “вложенностью”: setValue() поддерживает запись ключей вида a/b/c и формирует вложенные массивы. При этом getValue() ищет только по верхнему ключу, поэтому такой приём полезен именно для построения итогового массива, но не для чтения “вложенного пути” строкой.

Показательный пример из реального сценария SF4: собрать свойства и сохранить итог в Property (в сессию), чтобы дальше в шаблоне обращаться к ним через единый API.

<?php

use SIMAI\Main\Configuration\Property;
use SIMAI\Main\Configuration\Section;
use SIMAI\Main\Configuration\Page;

global $APPLICATION;

$dir = $APPLICATION->GetCurDir(); // например: /ru/catalog/
$pagePath = $_SERVER['REAL_FILE_PATH'] ?: $_SERVER['SCRIPT_NAME']; // например: /ru/catalog/index.php

$siteProps = [];
if (file_exists(SF_DATA_PATH . '/.site.property.php')) {
    $siteProps = require SF_DATA_PATH . '/.site.property.php';
}

$sectionProps = Section::getRecursionArray($dir);
$pageProps = Page::getArray($pagePath);

if (is_array($sectionProps)) {
    $siteProps = array_merge($siteProps, $sectionProps);
}
if (is_array($pageProps)) {
    $siteProps = array_merge($siteProps, $pageProps);
}

// user overrides (если применяются)
$userProps = Property::getArray('user');
if (is_array($userProps)) {
    $siteProps = array_merge($siteProps, $userProps);
}

Property::setArray(SF_SITE_DIR, $siteProps);

SIMAI\Main\Configuration\Framework — конфиг ядра (framework config)link

Framework — это файловая конфигурация общих настроек SF4, которая хранится в системном слое: /simai/config/.framework.config.php. Её особенность — поддержка режима “только чтение” на уровне отдельных ключей (readonly).

Структура записи для каждого параметра фиксирована:

  • value — само значение
  • readonly — признак запрета изменения (логический)

По факту при записи SF4 сохраняет readonly как true/false (не строкой), поэтому в документации корректнее воспринимать это именно как булево значение.

Методы:

  • Framework::getValue(string $name): mixed|null — получить значение (возвращается именно value).
  • Framework::setValue(string $name, mixed $value): void — записать значение, если ключ не readonly.
  • Framework::addReadonly(string $name, mixed $value): void — записать значение как readonly (использовать только осознанно).

Как трактовать readonly в UI: в локальных данных нет прямого подтверждения, что SF4 автоматически блокирует поля формы по readonly. Но на уровне конфигурационной логики запрет работает: метод записи не перезапишет ключ, если он помечен readonly.

SIMAI\Main\Configuration\Site — настройки “сайта” и группы настроекlink

Site — это файловый слой настроек проекта, который хранится в {site_dir}/simai.data/.site.property.php. В отличие от Framework, здесь значения записываются “плоско” (код → значение), без обёртки ["value" => ...].

Методы:

  • Site::getValue(string $siteId, string $name): mixed|null
  • Site::setValue(string $siteId, string $name, mixed $value): void

Смысл параметра $siteId в текущей реализации — идентификатор хранилища в памяти класса; путь файла при этом фиксирован и берётся из SF_SITE_DIR. На практике обычно передают текущий SF_SITE_DIR (или любой стабильный ключ, если вы сознательно разделяете хранилища).

Группы настроек: getGroup($name) читает описание групп из конфигурации решения (ожидается файл config/.site.config.php в контексте решения) и возвращает список кодов свойств, входящих в группу. Это используется как “мета-слой” для построения форм настроек: сначала получить список кодов, потом отрисовать поля по этим кодам.

Практический сценарий: получить флаг режима на уровне сайта (например, development_mode) — обычно его сначала читают с диска (site), потом перекрывают section/page/user, а итог кладут в Property (сессию), чтобы в шаблоне обращаться единообразно.

SIMAI\Main\Configuration\Section — наследование по разделамlink

Section работает с файлом /.property.php в директории раздела и ожидает там ветку section. Это слой “раздел → настройки”, который умеет собирать значения по цепочке родительских директорий.

Ключевая логика наследования реализована в getRecursionArray($dirProperty): метод проходит путь сегмент за сегментом от корня к текущей директории, читает section-ветку из каждого /.property.php и делает array_merge() по мере продвижения. Практическое следствие: настройки из более глубокого раздела перекрывают родительские, если код свойства совпадает.

Методы:

  • Section::getArray(string $dirProperty): ?array — получить массив ветки section из /.property.php.
  • Section::setArray(string $dirProperty, array $array): void — записать ветку section и сохранить файл.
  • Section::getValue(string $dirProperty, string $name): mixed|null
  • Section::setValue(string $dirProperty, string $name, mixed $value): void
  • Section::getRecursionArray(string $dirProperty, string $dirCurrent = "/"): array — собрать итог раздела с наследованием.

Мини-пример: “дособрать” конфиг раздела на странице по текущему каталогу:

<?php

use SIMAI\Main\Configuration\Section;

global $APPLICATION;

$dir = $APPLICATION->GetCurDir();      // /ru/catalog/
$props = Section::getRecursionArray($dir);

// $props содержит уже объединённые section-настройки: родительские + текущие

SIMAI\Main\Configuration\Page — настройки страницы и сочетание с настройками разделаlink

Page тоже работает с /.property.php, но читает/пишет ветку page, где данные сгруппированы по имени файла страницы. В качестве аргумента передаётся путь к реальному файлу страницы (например, /ru/catalog/index.php). Класс сам вычисляет:

  • $dirProperty = dirname($fileProperty) — каталог страницы
  • $fileName = basename($fileProperty) — имя файла (например, index.php)

Дальше читает из /.property.php массива:

$data[$dirProperty]['page'][$fileName]

Методы:

  • Page::getArray(string $fileProperty): ?array — вернуть массив настроек страницы.
  • Page::setArray(string $fileProperty, array $array): void — записать массив страницы и сохранить /.property.php.
  • Page::getValue(string $fileProperty, string $name): mixed|null

Нюанс записи: статический метод Page::setValue(...) в текущей реализации вызывает сохранение без вычисления директории, поэтому для надёжной записи ориентируйтесь на setArray() (или на запись через setArray + правку массива).

Мини-диаграмма “что перекрывает что” на практике (как это обычно собирают в проекте):

  • .site.property.php (сайт, база)
  • Section::getRecursionArray($dir) (разделы, наследование)
  • Page::getArray($pagePath) (страница, самый конкретный файловый уровень)
  • Property::getArray('user') (пользовательские оверрайды в сессии)
  • $GLOBALS['SF_PROPERTY'] (глобальные оверрайды, если применяются)

Итог обычно сохраняют в Property::setArray(SF_SITE_DIR, ...) для дальнейшего чтения в шаблонах.


SIMAI\Main\Configuration\Transfer — временное хранилище в рамках запросаlink

Transfer — самый лёгкий конфигурационный класс: это временное in-memory хранилище на время выполнения PHP-запроса. Оно не пишет на диск и не использует сессию, поэтому подходит для передачи значения между частями кода без побочных эффектов.

Методы:

  • Transfer::getValue(string $name): mixed|null
  • Transfer::setValue(string $name, mixed $value): void
  • Transfer::getInstance()->delete(string $name): void (удаление есть как метод экземпляра)

Практический пример: компонент рассчитал значение, шаблон позже его подхватил.

<?php

use SIMAI\Main\Configuration\Transfer;

// где-то в коде компонента
Transfer::setValue('calculated_layout', 'wide-fixed');

// позже, в шаблоне
$layout = Transfer::getValue('calculated_layout');

SIMAI\Main\Configuration\Iblock — кеширование ID инфоблоков по коду в сессииlink

Iblock — сессионный кеш соответствия CODE → ID для инфоблоков. Данные хранятся в $_SESSION['iblock_property'].

Логика getValue($name) такая:

  1. если в сессионном кеше уже есть ID — вернуть его;
  2. если нет — попытаться найти инфоблок по CODE = $name через API инфоблоков;
  3. найденный ID положить в кеш и вернуть.

Методы:

  • Iblock::getValue(string $code): ?string — получить ID по коду (или null, если не найден).
  • Iblock::setValue(string $code, mixed $value): void — вручную записать в кеш.

Ограничения и последствия:

  • кеш привязан к сессии, то есть на другом пользователе/в другой сессии он пустой;
  • если на проекте изменится CODE инфоблока или появятся коллизии, в кеше может оказаться устаревшее соответствие до конца сессии (лечится очисткой сессии);
  • на старте “холодной” сессии getValue() делает запрос к инфоблокам, но дальше ускоряет повторные обращения.

SIMAI\Main\Configuration\Block — настройки блоков в property-файлахlink

По структуре данных SF4 предусмотрена ветка block внутри /.property.php, где настройки группируются:

  • по имени файла страницы (index.php)
  • по коду блока (hero, sidebar, …)
  • по имени параметра (theme, variant, …)

Ожидаемый формат:

<?php
return [
    'block' => [
        'index.php' => [
            'hero' => [
                'theme' => 'dark',
            ],
        ],
    ],
];

По идее методы должны выглядеть как:

  • getArray($fileProperty, $block)
  • setArray($fileProperty, $block, $array)
  • getValue($fileProperty, $block, $name)

Однако в текущей поставке класс Block реализован нестандартно (часть методов объявлена статическими, но использует контекст экземпляра). Поэтому использование этого класса для записи требует уточнения на практике. При этом сама структура ветки block[...] в /.property.php полезна как “контракт данных”: даже если вы не используете Configuration\Block, вы можете хранить и читать эти данные через общий require файла и работу с массивом.

Правила безопасной записи настроекlink

Запись конфигов SF4 — это запись PHP-файлов на диск, поэтому основные риски здесь всегда одинаковые: права доступа, конкурентная запись и валидность данных.

Что важно учитывать:

  • Права на запись. Перед записью SF4 пытается сделать файл доступным на запись (chmod 0644). Если директория/файл недоступны — сохранение не произойдёт корректно.

  • Атомарность. В текущей реализации запись идёт через file_put_contents() напрямую в целевой файл. Если два процесса пишут одновременно, можно получить “гонку” (редко, но возможно в админских сценариях). Если для проекта это критично — стоит проектно вводить атомарную запись через временный файл + rename (в ядре SF4 этого нет).

  • Сериализация через var_export(). Это значит, что:

    • в файле должны жить только значения, которые корректно экспортируются в PHP-код (строки/числа/массивы/булевы/null);
    • нельзя хранить в таких массивах “живые” объекты/ресурсы;
    • для строк важно следить за ожидаемой кодировкой/экранированием, так как это напрямую станет PHP-кодом.

Page API: ассеты, шрифты, meta, SVG/цветаlink

SIMAI\Main\Page\Asset — подключение комплектов ассетовlink

Что такое “пакет/комплект ассетов” (имя + версия)link

В SF4 “комплект ассетов” — это именованный набор подключаемых ресурсов (CSS/JS/строки), описанный в конфигурации. Комплект идентифицируется:

  • именем (например, условно bootstrap, jquery, simai.framework);
  • версией (обычно ключ default, но может быть и другой).

Внутри описания комплекта есть:

  • базовая папка комплекта (dir),
  • набор версий в ветке asset,
  • у каждой версии — своя подпапка (dir) и список файлов file[],
  • каждый файл имеет type (style, script, string) и path.

type=string — это не путь к файлу, а строка, которая будет добавлена напрямую в head через addString().

Где лежит конфиг ассетов в текущей реализацииlink

В текущей реализации реестр комплектов читается из системного слоя SF4:

  • конфиг: /simai/config/.asset.config.php
  • физические файлы: /simai/asset/...

Отдельно фиксируем вашу практику: проектный {site_dir}/simai.data/config/.asset.config.php не используется (SF4 его не читает при подключении ассетов), поэтому “добавить комплект через simai.data/config” в этой поставке не является рабочей точкой расширения.

Методыlink

Asset сделан как singleton, основной рабочий вход — через getInstance().

  • Asset::getValue($name) / Asset::setValue($name, $value) Это служебная часть, которая работает с конфигом как с хранилищем “простых” ключей ["value" => ...]. Важно: она не управляет описаниями комплектов (где dir/asset/file). То есть это не инструмент “создать новый бандл”, а скорее механизм доп. параметров в конфиге. Использовать аккуратно, потому что запись идёт в системный файл.

  • Asset::getInstance()->load(string $name, string $version = 'default') Подключает комплект в текущую страницу через \Bitrix\Main\Page\Asset::getInstance():

    • styleaddCss()
    • scriptaddJs()
    • stringaddString()

    Для CSS/JS есть поддержка минифицированных файлов: если в Bitrix включена опция main.use_minified_assets = Y, то SF4 попытается подключить *.min.css / *.min.js, но только если такой файл реально существует рядом.

Сценарииlink

Подключить комплект в шаблоне/на странице

<?php

declare(strict_types=1);

use SIMAI\Main\Page\Asset;

Asset::getInstance()->load('simai.framework', 'default');
Asset::getInstance()->load('sf-icon', 'default');

Версионирование и “если версии нет”

load($name, $version) ориентируется на конфигурацию. Если версия указана, но папка версии на диске отсутствует, SF4 делает попытку подобрать “какую-то” версию автоматически, сканируя каталог комплекта и выбирая один из найденных каталогов. Практически это спасает при рассинхроне “конфиг → файлы”, но лучше воспринимать как аварийный режим: для предсказуемости держите конфиг и структуру /simai/asset/... синхронными.

SIMAI\Main\Page\Font — шрифты как конфигурация + вставка <link>link

Методыlink

  • Font::getInstance()->load($name) Если в описании шрифта есть поле link, добавляет его в head как строку через \Bitrix\Main\Page\Asset::addString(). Возвращает массив описания шрифта.

  • Font::getInstance()->getFontFamily($name) Если задан family — вернёт его. Иначе соберёт "<name>", <type> (например "Roboto", sans-serif).

  • Font::getInstance()->getFontList() Возвращает список доступных шрифтов как “ключ → человекочитаемое имя”, отсортированный естественной сортировкой.

Где лежит конфиг шрифтовlink

Шрифты читаются из системного конфига:

  • /simai/config/.font.config.php

В нём для каждого шрифта обычно есть поля вроде name, type, link (иногда family).

Пример: выбрать шрифт из списка и применить в шаблонеlink

<?php

declare(strict_types=1);

use SIMAI\Main\Page\Font;

// 1) Подключили <link ...> (если он задан в конфиге)
Font::getInstance()->load('roboto');

// 2) Получили font-family для CSS
$fontFamily = Font::getInstance()->getFontFamily('roboto');
?>
<style>
    body {
        font-family: <?= htmlspecialchars((string) $fontFamily, ENT_QUOTES) ?>;
    }
</style>

SIMAI\Main\Page\Meta — runtime-хранилище мета-значенийlink

Назначениеlink

Meta — очень лёгкое хранилище “ключ → значение” в памяти (singleton), живёт только в рамках текущего запроса. Его удобно использовать, когда одно место кода должно “передать” значение другому месту без записи в сессию и без файлов.

Методыlink

  • Meta::setValue($name, $value) — записать значение
  • Meta::getValue($name) — прочитать значение Внутри значение хранится как ["value" => ...], но наружу возвращается само значение.

SIMAI\Main\Page\SVG — безопасная вставка SVG (очистка width/height + классы)link

Методlink

  • SVG::get(string $path, bool $clearStyle = true): string|false Принимает путь от document root (например, SF_DATA_DIR . '/template/image/icon.svg'), читает файл и возвращает SVG как строку (или false, если файла нет).

Что “чистится” при $clearStyle = truelink

В текущей реализации выполняются замены прямо по тексту SVG:

  • удаляются атрибуты width="..." и height="..." (регэкспом);
  • любой fill="..." заменяется на class="icon-svg-fill";
  • вхождение stroke-width= превращается в class="icon-svg-stroke" stroke-width=...;
  • удаляется точное stroke="#000".

Практический смысл: “вынести” окраску и толщину линий в CSS-классы.

Важный нюанс: такие замены не объединяют классы. Если в SVG-элементах уже есть class="...", после замены можно получить дубли class="...". Поэтому для “сложных” SVG иногда удобнее вызывать SVG::get(..., false) и стилизовать без автоматических правок.

Пример вставки SVG из simai.datalink

<?php

declare(strict_types=1);

use SIMAI\Main\Page\SVG;

echo SVG::get(SF_DATA_DIR . '/template/image/icons/arrow-right.svg', true) ?: '';

SIMAI\Main\Page\Color — конвертер и утилиты цветаlink

Для чего нуженlink

Color — объект для конвертации и сравнений цветов: RGB ↔ HEX, HSV, XYZ, Lab (CIE), плюс утилиты “приблизить к ближайшему из набора” и простое осветление/затемнение HEX-цвета.

Основные входы/выходыlink

  • Вход:

    • fromHex('ff00aa') (без #)
    • fromRgbInt($r, $g, $b)
    • fromRgbHex('ff', '00', 'aa')
    • fromInt($intColor)
  • Выход:

    • toHex() → строка ff00aa (без #)
    • toRgbInt()['red' => ..., 'green' => ..., 'blue' => ...]
    • toHsvFloat() → hue в градусах, sat в долях, val в диапазоне 0..255
    • toHsvInt() → hue/sat/val в диапазоне 0..255
    • toXyz(), toLabCie() — для цветовых расстояний
  • Сравнение:

    • getDistanceRgbFrom(Color $color)
    • getDistanceLabFrom(Color $color) (использует Lab)
  • Утилиты:

    • isGrayscale($threshold = 16) — проверка “почти серый” по разбросу каналов
    • lighter($color, $percent) / darker($color, $percent) — принимает #rrggbb, возвращает #rrggbb (процент внутри переводится в шаг int(2.5 * percent) по каждому каналу)

Пример: конвертация и осветлениеlink

<?php

declare(strict_types=1);

use SIMAI\Main\Page\Color;

$color = (new Color())->fromHex('336699');

$rgb = $color->toRgbInt();      // ['red' => 51, 'green' => 102, 'blue' => 153]
$hsv = $color->toHsvInt();      // hue/sat/val в 0..255

$lighter = $color->lighter('#336699', 10); // вернёт HEX c '#', посветлее

Нюанс getClosestMatch()link

В реализации getClosestMatch(array $colors) внутри ожидается массив цветов, но корректная работа требует осторожности: если передавать элементы уже как объекты Color, в текущем коде есть риск обращения к неинициализированной переменной при расчёте дистанции. Надёжнее передавать массив “сырых” значений (которые конструктор Color сможет принять) или предварительно проверить поведение на вашем наборе данных.

IO API: include-области и файловые настройкиlink

SIMAI\Main\IO\IncludeArea — подключение областей (section/file/template)link

IncludeArea — это простой, “шаблонный” механизм подключаемых областей, который работает на уровне файлов и путей. Все методы устроены одинаково: пытаются найти файл по соглашению об имени, и если файл существует — подключают его через require. Если файл не найден — возвращают false.

Практически это означает две вещи:

  1. область “подмешивает” на страницу ровно тот PHP/HTML, который лежит в файле (без дополнительных обёрток);
  2. ошибки PHP в файле области будут фатальными (потому что используется require).

includeSectionArea($filePrefix) — ищет section.<prefix>.php вверх по деревуlink

Этот метод предназначен для “секционных” областей: вы кладёте файл вида section.<prefix>.php в каталог раздела, и SF4 будет искать его в текущем каталоге и выше, пока не дойдёт до корня сайта.

Как это работает по смыслу:

  • стартовая точка поиска берётся из текущего URL (REQUEST_URI без query string);
  • дальше SF4 поднимается вверх по каталогам и ищет ближайший файл section.<prefix>.php;
  • как только файл найден — он подключается, поиск прекращается;
  • если до корня сайта файл так и не найден — метод вернёт false.

Пример: вы хотите, чтобы в разделе /ru/catalog/ и всех его вложенных страницах показывался один и тот же “баннер”.

Файл:

  • /ru/catalog/section.banner.php

В шаблоне/области:

<?php

use SIMAI\Main\IO\IncludeArea;

IncludeArea::includeSectionArea('banner');

Если в более глубоких разделах вы добавите свой section.banner.php, он автоматически “перекроет” родительский, потому что будет найден раньше (ближе к текущему пути).

includeFileArea($filePrefix) — подключает “локальный файл страницы” с суффиксомlink

Этот метод предназначен для “точечных” областей на конкретной странице: он берёт текущий исполняемый PHP-файл и подставляет в имя суффикс .<prefix>.

Соглашение такое:

  • страница: index.php
  • область: index.<prefix>.php

Пример: для страницы /ru/about/index.php вы хотите отдельный блок “подвал страницы”, который живёт рядом с самой страницей.

Файл:

  • /ru/about/index.footer.php

Подключение:

<?php

use SIMAI\Main\IO\IncludeArea;

IncludeArea::includeFileArea('footer');

Если файла нет — метод вернёт false (и ничего не выведет).

includeTemplateArea($dirName) — область из {site_dir}/simai.data/template/area/<dirName>/template.phplink

Это основной “проектный” механизм областей SF4: область живёт в слое данных сайта, внутри simai.data, и подключается по относительному пути внутри area/.

Соглашение такое:

  • путь области: {site_dir}/simai.data/template/area/<dirName>/template.php
  • строка подключения: <dirName>

Ваш пример полностью соответствует реализации:

  • область: /ru/simai.data/template/area/sidebar/right/template.php
  • подключение:
<?php

use SIMAI\Main\IO\IncludeArea;

IncludeArea::includeTemplateArea('sidebar/right');

То же самое можно делать для любых новых областей: создаёте папку, кладёте template.php, добавляете вызов includeTemplateArea() в ваш проектный template.php или в нужную проектную область.

Практический нюанс, который помогает избежать “мистических не найдено”: метод не нормализует dirName. Поэтому:

  • не добавляйте лишние слеши в начале (/sidebar/right не нужно),
  • придерживайтесь единых “чистых” путей (sidebar/right, sidebar/left и т.д.).

SIMAI\Main\IO\Setting — сохранить массив в файл и прочитать из файлаlink

Setting — это утилита для работы с “конфигами как файлами”. Важно: в текущей реализации это два разных формата файлов, и методы не являются зеркальными друг другу.

saveToFile(array $array, string $filename) — пишет PHP-файлlink

saveToFile() сохраняет массив как PHP-файл в формате “файл возвращает массив”:

  • внутри используется var_export($array, true);
  • содержимое записывается через file_put_contents();
  • файл создаётся с коротким открывающим тегом <? return ... ?>.

Практические последствия:

  • директория под $filename должна существовать (метод не создаёт папки);
  • сервер должен поддерживать short_open_tag, иначе файл может не выполниться корректно.

Как читать такой файл правильно:

<?php

use SIMAI\Main\IO\Setting;

$filename = $_SERVER['DOCUMENT_ROOT'] . '/ru/simai.data/config/example.php';

Setting::saveToFile(['a' => 1, 'b' => 2], $filename);

// ВАЖНО: читать через require с получением return-значения
$data = require $filename;
// $data === ['a' => 1, 'b' => 2]

getFromFile(string $filename) — ожидает переменную вида ar<ИмяФайла> внутри подключаемого файлаlink

getFromFile() работает иначе: он делает require $filename;, а потом пытается вернуть переменную, имя которой вычисляет из имени файла.

Правило формирования имени переменной:

  • берётся basename($filename, '.php'),
  • из него удаляются точки .,
  • к результату добавляется префикс ar,
  • первая буква делается заглавной (ucfirst).

Пример для файла .demo.config.php:

  • basename без расширения: .demo.config
  • убираем точки: democonfig
  • делаем ucfirst: Democonfig
  • итоговая переменная: $arDemoconfig

То есть, чтобы getFromFile() вернул массив, файл должен выглядеть примерно так:

<?php

$arDemoconfig = [
    'enabled' => true,
    'title' => 'Demo',
];

Тогда:

<?php

use SIMAI\Main\IO\Setting;

$data = Setting::getFromFile($_SERVER['DOCUMENT_ROOT'] . '/path/.demo.config.php');
// $data === ['enabled' => true, 'title' => 'Demo']

Если же файл написан в формате return [...], то getFromFile() вернёт null/undefined variable-сценарий (в зависимости от настроек PHP), потому что переменная $ar... не будет определена. В таком случае читать нужно через обычный require, как показано выше для saveToFile().

Iblock helpers: доступ к данным и “источники”link

SIMAI\Main\Iblock\Element — кеширование полей/свойств элемента + извлечение “source”link

Методыlink

Element — это singleton-хелпер, который по ID элемента инфоблока один раз загружает данные из Bitrix и дальше отдаёт их из внутреннего кеша в рамках запроса. Загрузка выполняется через API инфоблоков: выбираются поля (GetFields()) и свойства (GetProperties()), после чего кешируется структура вида:

  • FIELD — массив полей элемента,
  • PROPERTY — массив свойств элемента.

Основные точки входа:

  • Element::getArray($idElement) — возвращает “битриксовый” массив элемента: поля + ключ PROPERTIES, в который подставляются свойства из кеша.
  • Element::getField($idElement, $field) — возвращает одно поле из FIELD.
  • Element::getProperty($idElement, $property) — возвращает одно свойство из PROPERTY (как массив свойства Bitrix).
  • Element::getSource($arItem, $arSource) — “распаковывает” источник из уже готового массива элемента (без загрузки по ID) и возвращает нормализованные данные по правилам SF4.

Важно различать: первые методы работают от ID и берут всё из кеша Element, а getSource() работает от массива $arItem, который вы сами передали.

Как устроен $arSource и что возвращаетсяlink

$arSource — это массив-описание, который минимум содержит:

  • TYPE — тип источника: "IBLOCK" или "PROPERTY",
  • CODE — код поля/свойства (например, "NAME", "PREVIEW_PICTURE", "LINK", "PRICE").

Возвращаемая структура — массив “распакованных данных” (состав зависит от типа):

  • Для TYPE = IBLOCK возвращается структура, где:

    • для PREVIEW_PICTURE / DETAIL_PICTURE — заполняются SRC и ID (ожидается, что в $arItem[$code] уже лежит массив картинки с SRC и ID),
    • для остальных полей — возвращается VALUE = $arItem[$code].
  • Для TYPE = PROPERTY возвращается структура, где:

    • NAME — имя свойства,
    • PROPERTY_TYPE — тип свойства (S/F/G/E),
    • далее формируются VALUE и ~VALUE (для строк/HTML/ссылок) и дополнительные поля (для файлов/связей).

Практически $arItem для getSource() должен быть “похож” на то, что возвращает Bitrix в GetFields()/GetProperties() (или на то, что возвращает Element::getArray(), где есть PROPERTIES).

Поддерживаемые типы property и особенности USER_TYPElink

В текущей реализации обработка ориентируется на PROPERTY_TYPE и местами — на USER_TYPE:

  • S (строка):

    • поддерживается одиночное и множественное значение,
    • для HTML-значений (когда Bitrix отдаёт структуру с TEXT) предполагается выбор текстового содержимого,
    • для USER_TYPE = simai_link формируется HTML-ссылка в ~VALUE (с учётом настройки открытия в новом окне через _BLANK).
  • F (файл):

    • для значений (одиночных/множественных) дополнительно подтягиваются данные файла через CFile::GetFileArray() и формируется SRC, SIZE, а для одиночного — ещё и FILE.
  • G (привязка к разделам):

    • для каждого ID раздела подтягиваются NAME, SECTION_PAGE_URL (как URL), а также некоторые поля инфоблока/кода.
  • E (привязка к элементам):

    • для каждого ID элемента подтягиваются NAME, SECTION_PAGE_URL (как URL) и сопутствующие поля.

Нюанс реализации, который стоит учитывать: внутри getSourceData() встречаются обращения к структурам вида $arItem["SOURCE"]["PROPERTY"]... и переменной $keyProperty, которые в этой выгрузке не определены. То есть ветки, где используются эти обращения, требуют проверки на вашем проекте (по факту входного массива $arItem), иначе можно получить некорректное заполнение данных.

SIMAI\Main\Iblock\Section — кеширование данных разделаlink

Методыlink

Section устроен так же, как Element, но для разделов: по ID раздела один раз запрашивает данные через CIBlockSection::GetByID() и кеширует результат в рамках запроса.

  • Section::getArray($idSection) — возвращает массив полей раздела (как отдаёт Bitrix).
  • Section::getField($idSection, $field) — возвращает одно поле раздела по ключу.

Этот хелпер удобен, когда вы в нескольких местах шаблона/компонентов повторно обращаетесь к одному и тому же разделу: SF4 не делает повторный запрос к Bitrix API для одного и того же ID.

SIMAI\Main\Iblock\Source — унификация “поле/свойство → данные”link

Методыlink

Source — это более “прикладной” слой поверх Element/Section/File, который возвращает унифицированную структуру источника и, когда это возможно, добавляет “распакованные” данные в ключ DATA.

Основные входы:

  • Source::getArray($idElement, $arSource) — общий метод: по $arSource["TYPE"] выбирает, обрабатывать поле или свойство.
  • Source::getFieldArray($idElement, $code) — получить структуру источника для поля элемента.
  • Source::getPropertyArray($idElement, $code) — получить структуру источника для свойства элемента.

В отличие от Element::getSource($arItem, $arSource), этот класс работает от ID элемента и сам достаёт поля/свойства через SIMAI\Main\Iblock\Element.

Что добавляется в DATA и когдаlink

Возвращаемая структура строится по принципу: VALUE — “как в Bitrix”, DATA — “распакованное”.

Для полей (TYPE = IBLOCK) логика такая:

  • PREVIEW_PICTURE / DETAIL_PICTURE:

    • VALUE — исходное значение поля (обычно ID файла),
    • DATA — результат SIMAI\Main\File::getArray($fileId) (метаданные файла).
  • IBLOCK_SECTION_ID:

    • DATA — результат SIMAI\Main\Iblock\Section::getArray($sectionId).

Для IBLOCK_ID в текущей реализации вызывается SIMAI\Main\Iblock::getArray($iblockId). В этой выгрузке класс SIMAI\Main\Iblock содержит неконсистентность в статических методах (использование $this в статическом контексте), поэтому поведение ветки IBLOCK_ID требует уточнения на рабочей сборке.

Для свойств (TYPE = PROPERTY) логика зависит от PROPERTY_TYPE и USER_TYPE:

  • S (строка):

    • USER_TYPE = simai_link — формируется HTML-ссылка в ~VALUE (для одиночного или массива значений).
    • USER_TYPE = simai_ib_elementDATA заполняется массивом элементов (для одиночного ID или массива ID) через Element::getArray().
    • USER_TYPE = simai_ib_sectionDATA заполняется массивом разделов (для одиночного ID или массива ID) через Section::getArray().
    • USER_TYPE = HTMLVALUE/~VALUE приводятся к тексту (TEXT), а в DATA добавляется TYPE (например, html/text), причём предусмотрен и множественный формат.
  • F (файл):

    • DATA — метаданные файла/файлов через File::getArray().
  • G (разделы):

    • DATA — раздел/разделы через Section::getArray().
  • E (элементы):

    • DATA — элемент/элементы через Element::getArray().

Мини-пример “поле/свойство → единый формат источника”:

<?php

declare(strict_types=1);

use SIMAI\Main\Iblock\Source;

$elementId = 123;

// Поле элемента
$preview = Source::getArray($elementId, [
    'TYPE' => 'IBLOCK',
    'CODE' => 'PREVIEW_PICTURE',
]);

// Свойство элемента
$link = Source::getArray($elementId, [
    'TYPE' => 'PROPERTY',
    'CODE' => 'LINK',
]);

// $preview['VALUE'] — как в Bitrix, $preview['DATA'] — файл
// $link['VALUE'] / $link['~VALUE'] — как в Bitrix (для simai_link будет HTML в ~VALUE)

SIMAI\Main\File — обёртки для файловlink

SIMAI\Main\File::getArray($idFile) — это простой singleton-кеш вокруг CFile::GetFileArray($idFile). Он нужен, чтобы при разборе нескольких источников (картинки, файл-свойства, галереи) не делать повторные запросы за метаданными одного и того же файла в рамках запроса.

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

<?php

declare(strict_types=1);

use SIMAI\Main\File;

$fileId = 456;
$file = File::getArray($fileId);

// Например: $file['SRC'], $file['FILE_SIZE'], $file['CONTENT_TYPE'] и т.д.

Block API: структура блоков, списки, редактирование на сайтеlink

SIMAI\Main\Block\Section — списки разделов блоков/вьюх и merge “ядро + данные сайта”link

Section — это вспомогательный класс, который решает две практические задачи:

  1. Найти “правильную” директорию секции (с приоритетом слоя сайта), когда один и тот же путь существует и в системном каталоге SF4, и в {site_dir}/simai.data.

  2. Собрать “каталог секций” (список подпапок) с метаданными из .description.php, чтобы использовать его в админ-инструментах/выборе шаблонов (блоков, вьюх, их вариантов и т.п.).

Ключевая идея “два корня” и как они строятсяlink

Класс оперирует двумя базовыми корнями:

  • SF_DATA_DIR{site_dir}/simai.data
  • SF_DIR → системный каталог SF4 (в текущей инициализации это /simai)

Метод getDirArray($dir) принимает путь $dir и строит пару кандидатов (всегда в таком порядке):

  1. путь в {site_dir}/simai.data/...
  2. соответствующий путь в системном каталоге /simai/...

Дальше эта пара используется в разных сценариях:

  • getDir($dir) — возвращает первый реально существующий каталог из пары (то есть приоритет у слоя {site_dir}/simai.data).
  • getList($dir) — собирает список подпапок в обоих каталогах и объединяет.

.description.php как мета-описание секцииlink

При сборе списка каталогов (внутренне) происходит сканирование подпапок и попытка подключить файл:

  • <секция>/.description.php

Если файл найден — он должен вернуть массив с метаданными. В текущем поведении реально используются как минимум:

  • NAME — имя секции (в интерфейсе дополнительно префиксуется как "[<CODE>] <NAME>")
  • SORT — сортировка
  • PREVIEW — превью/картинка (используется для “картинок списка”)

Если .description.php отсутствует, секция всё равно попадёт в список с дефолтами:

  • CODE = <имя_папки>
  • NAME = <имя_папки>
  • SORT = 500

Также при сканировании игнорируются служебные элементы: ., .. и папка .config.

Методы и что они возвращаютlink

  • getDirArray($dir) Возвращает массив из двух путей-кандидатов (слой сайта + системный слой). Если $dir не относится ни к SF_DATA_DIR, ни к SF_DIR, вернёт false.

  • getDir($dir) Возвращает первый существующий каталог из getDirArray(). Это основной метод, если вам нужен “реальный” путь секции (с приоритетом кастомизаций в {site_dir}/simai.data).

  • getList($dir, $sortFlag = true) Возвращает список секций (подпапок) как ассоциативный массив вида:

    • ключ: CODE секции (имя подпапки)
    • значение: массив метаданных (из .description.php или дефолты)

    Список строится по обоим корням и затем объединяется. Сортировка (если включена) идёт по SORT, затем по NAME (естественное сравнение строк).

    Важно: объединение выполняется через array_merge(...) двух списков. При совпадении кодов (одинаковые ключи) приоритет метаданных будет у того списка, который идёт вторым в merge. При этом реальный путь секции всё равно определяется через getDir() (и там приоритет у {site_dir}/simai.data).

  • getNameList($dir, $sortFlag = true) Упрощённая форма списка: CODE => NAME.

  • getImageList($dir, $sortFlag = true) Упрощённая форма списка: CODE => PREVIEW.

  • getConfigList($dir) Ищет директорию /.config внутри выбранного $dir и возвращает список конфигов как:

    • ключ: имя файла без .php
    • значение: то же имя

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

  • saveConfig($arParams, $path) Сохраняет конфиг в файл (через SIMAI\Main\IO\Setting::saveToFile). Перед сохранением:

    • отбрасывает ключи, начинающиеся с ~
    • если для ключа X существует ~X, то сохраняет значение из ~X (это удобно, когда форма отдаёт и “сырой” вариант, и преобразованный)

Мини-пример: получить список блоков и сохранить конфигlink

<?php

declare(strict_types=1);

use SIMAI\Main\Block\Section;

$blockDir = SF_DATA_DIR . '/grid/block';

// 1) Список доступных блоков (по подпапкам)
$blocks = Section::getList($blockDir);

if (is_array($blocks))
{
    // например: вывести имена
    foreach ($blocks as $code => $meta)
    {
        $name = $meta['NAME'] ?? $code;
        // ...
    }
}

// 2) Список конфигов конкретного блока (если есть папка .config)
$configList = Section::getConfigList($blockDir . '/hero');

// 3) Сохранение выбранного конфига (путь на диск обычно задаётся вашим кодом/админ-логикой)
$savePath = $_SERVER['DOCUMENT_ROOT'] . $blockDir . '/hero/.config/default.php';

Section::saveConfig($_POST, $savePath);

SIMAI\Main\Block\Edit — оверлеи “добавить/редактировать” на публичной частиlink

Edit — это UI-хелпер, который возвращает HTML-оверлей (или false), чтобы на публичной странице появлялись кнопки:

  • добавить элемент,
  • редактировать элемент,
  • удалить элемент,
  • (отдельно) открыть настройки блока.

Кнопки появляются только при включённых режимах и (для операций с инфоблоком) при достаточных правах.

Режимы и права, от которых зависит отображениеlink

  • development_mode = "Y" Включает оверлей “настройки блока” (иконка шестерёнки).

  • edit_mode = "Y" и права на инфоблок CIBlock::GetPermission($idIblock) >= 'W' Включает оверлеи “добавить/редактировать/удалить” для элементов/разделов инфоблока.

  • Дополнительный флаг: iblock_public_editor = "Y" Переключает поведение кнопок на “правый модальный редактор” через скрипты в {site_dir}/simai.data/admin/.... Если флаг не включён — используются стандартные попап-формы Bitrix для добавления/редактирования (удаление остаётся через модалку).

Методы и сценарии использованияlink

  • addConfigurationArea($codeBlock) Возвращает HTML-оверлей с шестерёнкой, который открывает /bitrix/admin/simai/block_property.php в модальном окне (SF4-модалка). Отображается только при development_mode = "Y". Параметр $codeBlock в текущей разметке не используется (может быть оставлен “на будущее”/совместимость).

  • addNewItemArea($idIblock) Кнопка “добавить элемент” для инфоблока. Условия: edit_mode = "Y" и права на инфоблок >= 'W'.

    Поведение:

    • если iblock_public_editor = "Y" → открывает {site_dir}/simai.data/admin/iblock.element.edit.php?iblock=<ID>
    • иначе → открывает стандартную форму Bitrix iblock_element_edit.php в popup (через $APPLICATION->getPopupLink(...))
  • addEditItemArea($idItem, $section = false) Оверлей “управления элементом”:

    • + добавить новый элемент
    • редактировать текущий
    • 🗑 удалить

    Условия: edit_mode = "Y" и права на инфоблок >= 'W'.

    Нюанс: если $idItem пустой, класс пытается получить инфоблок по $section (как ID раздела). Это удобно, когда вы хотите показывать “добавить” на уровне раздела, даже без конкретного элемента.

    Поведение кнопок:

    • при iblock_public_editor = "Y":

      • +{site_dir}/simai.data/admin/iblock.element.edit.php?iblock=<ID>&section=<SECTION_ID>
      • {site_dir}/simai.data/admin/iblock.element.edit.php?iblock=<ID>&id=<ELEMENT_ID>
      • 🗑{site_dir}/simai.data/admin/iblock.element.delete.php?id=<ELEMENT_ID>
    • иначе:

      • + / → стандартные попап-формы Bitrix (iblock_element_edit.php в popup)
      • 🗑/bitrix/admin/simai/element_delete.php?id=<ELEMENT_ID> (через SF4-модалку)
  • addEditSectionArea($idItem) Оверлей управления разделом инфоблока (аналогично элементу), но всегда использует скрипты из {site_dir}/simai.data/admin/:

    • +{site_dir}/simai.data/admin/iblock.section.edit.php?iblock=<ID>
    • {site_dir}/simai.data/admin/iblock.section.edit.php?iblock=<ID>&id=<SECTION_ID>
    • 🗑{site_dir}/simai.data/admin/iblock.section.delete.php?id=<SECTION_ID>

Практический пример: добавить оверлей редактирования в шаблон блокаlink

Оверлей позиционируется абсолютно и рассчитан на то, что родительский контейнер “блока” — позиционируемый (обычно position: relative).

<?php

declare(strict_types=1);

use SIMAI\Main\Block\Edit;

// где-то в шаблоне блока
$elementId = (int)($arResult['ID'] ?? 0);
$sectionId = (int)($arResult['IBLOCK_SECTION_ID'] ?? 0);

echo '<div class="position-relative">';

// Оверлей “редактировать элемент” (вернёт HTML или false)
$overlay = Edit::addEditItemArea($elementId, $sectionId);
if ($overlay !== false)
{
    echo $overlay;
}

// ... дальше обычная разметка блока
echo '<div>...</div>';

echo '</div>';

Search API: корректные ссылки в результатах поискаlink

SIMAI\Main\Search\Iblock::OnSearchGetURL($arFields)link

Назначение: переопределить URL результата поиска для сущностей инфоблокаlink

OnSearchGetURL() — это утилитарный метод, который возвращает строку URL для результата поиска. По логике внутри он работает только для результатов модуля инфоблоков:

  • если $arFields['MODULE_ID'] !== 'iblock' — URL не меняется;
  • если $arFields['ITEM_ID'] не является “чистым” числом (проверка через сравнение с intval(...)) — URL не меняется.

Когда результат относится к инфоблоку и ITEM_ID — числовой, метод:

  1. подключает модуль инфоблоков;
  2. берёт инфоблок по $arFields['PARAM2'] (в обработчике это трактуется как ID инфоблока) и, если инфоблок найден, берёт из его настроек шаблон DETAIL_PAGE_URL;
  3. дальше выполняет точечную подстановку (см. следующий подраздел) — только если в URL встречается специальный маркер.

Возврат всегда один: строка $url.

Особый случай: подстановка #UF_SECTION_URL# из пользовательского поля разделаlink

Ключевая “фишка” метода — поддержка пользовательского маркера #UF_SECTION_URL# внутри URL.

Если в обрабатываемом URL обнаруживается подстрока #UF_SECTION_URL#, метод делает так:

  1. загружает элемент инфоблока по $arFields['ITEM_ID'];

  2. берёт у элемента:

    • IBLOCK_ID
    • IBLOCK_SECTION_ID (важно: используется основной раздел, а не список всех привязок);
  3. загружает раздел по фильтру IBLOCK_ID + ID, выбирая UF_* (то есть все пользовательские поля);

  4. берёт значение $arSection['UF_SECTION_URL'] и делает:

$url = str_replace('#UF_SECTION_URL#', $arSection['UF_SECTION_URL'], $arFields['URL']);

Именно так формируется “правильный” URL для поисковой выдачи в проектах, где раздел хранит “канонический URL” в пользовательском поле UF_SECTION_URL.

Практическое следствие: метод заменяет только #UF_SECTION_URL#. Если в URL есть другие макросы/плейсхолдеры, их разворачивание зависит от того, в каком виде URL приходит в $arFields['URL'] и какие преобразования делает механизм поиска Bitrix.

Что ожидать в $arFields и что возвращает методlink

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

  • MODULE_ID — должен быть равен iblock, иначе обработчик ничего не делает.

  • ITEM_ID — ID элемента инфоблока. Обработчик применяет логику только если это число.

  • PARAM2 — используется как ID инфоблока (по нему запрашивается инфоблок, чтобы взять DETAIL_PAGE_URL).

  • URL — исходный URL результата поиска (и/или шаблон URL), по которому:

    • проверяется наличие #UF_SECTION_URL#,
    • выполняется замена маркера.

Что возвращается:

  • строка URL (string), либо исходная $arFields['URL'], либо URL после подстановки UF_SECTION_URL.

Нюансы, которые полезно понимать при отладке:

  • Если у элемента не найден раздел (или UF_SECTION_URL пустой), замена может дать “дырку” в URL (маркер заменится на пустую строку) или метод вернёт исходный URL — зависит от того, на каком шаге не удалось получить данные.
  • Используется именно IBLOCK_SECTION_ID элемента — то есть поведение предсказуемо только при корректно заполненном “основном разделе”.

Мини-пример применения (как должно выглядеть со стороны проекта)link

Идея использования обычно такая:

  • в настройках инфоблока DETAIL_PAGE_URL (или в формируемом URL результата поиска) присутствует маркер #UF_SECTION_URL#;
  • у раздела элемента заполнено пользовательское поле UF_SECTION_URL;
  • обработчик в поиске возвращает URL, где маркер заменён реальным значением.

Пример “шаблонной” ссылки (как задумка):

##UF_SECTION_URL#/detail/

Пример включения в коде (если вы где-то вручную прогоняете через метод, например при тестировании):

<?php

declare(strict_types=1);

use SIMAI\Main\Search\Iblock;

$url = Iblock::OnSearchGetURL([
    'MODULE_ID' => 'iblock',
    'ITEM_ID' => 123,
    'PARAM2' => 7,
    'URL' => '#UF_SECTION_URL#/detail/',
]);

Utility: прикладные утилиты ядраlink

SIMAI\Main\Utility\Pathlink

Path — это набор статических функций для приведения путей к предсказуемому виду и для перехода между “локальным” путём сайта и физическим путём на диске. Класс небольшой, но его удобно использовать как “единый стандарт”, чтобы не собирать пути вручную (и не ловить двойные слэши, пустые пути и т.п.).

  • getCorrect($path) нормализует разделители: любые последовательности \ и / заменяются на один /. Полезно, когда путь собирается из разных частей и может получиться // или \/.
  • getRoot($path) превращает локальный путь в физический: добавляет $_SERVER["DOCUMENT_ROOT"] и сразу нормализует.
  • getLocal($path) делает обратную операцию: убирает $_SERVER["DOCUMENT_ROOT"] из физического пути и возвращает локальный. Если после замены получается пустая строка — возвращается /.
  • getStatic($path) ищет “ближайшую существующую директорию” для переданного пути. Алгоритм простой: берёт getRoot($path), и если это не директория — поднимается dirname() вверх, пока не найдёт директорию, и возвращает её в локальном виде. Если не нашёл — false. Это удобно, когда у вас есть путь “как будто к странице/ресурсу”, но реально нужно понять, какая часть пути существует на диске.
  • getFileName($path) возвращает имя файла/последний сегмент пути (после нормализации). Если последний сегмент пустой (например, путь заканчивается на /) — вернёт false.
  • getDirName($path) возвращает директорию для пути через SplFileInfo::getPath(). Если директория пустая — вернёт false.

Пример нормализации и перехода “локальный ⇄ физический”:

<?php

declare(strict_types=1);

use SIMAI\Main\Utility\Path;

$local = '/ru//simai.data\\template/area/sidebar/right/template.php';

$correct = Path::getCorrect($local);   // /ru/simai.data/template/area/sidebar/right/template.php
$root    = Path::getRoot($correct);    // {DOCUMENT_ROOT}/ru/simai.data/template/area/sidebar/right/template.php
$back    = Path::getLocal($root);      // /ru/simai.data/template/area/sidebar/right/template.php

SIMAI\Main\Utility\Filelink

File — утилиты для получения списков файлов в директории. Оба метода работают одинаково по “сигналам”:

  • принимают локальный путь директории (например, /ru/simai.data/template/style),
  • переводят его в физический (Path::getRoot()),
  • сканируют scandir(),
  • игнорируют ., .., .config и поддиректории (то есть возвращают только файлы),
  • сортируют natcasesort().

Разница в формате результата:

  • getList($dir) возвращает массив вида localPath => localPath. При этом дополнительно гарантирует, что путь начинается со /.
  • getFileList($dir) возвращает массив вида fileName => localPath.

Это удобно в UI/формах, когда вам либо нужен просто “список путей”, либо нужна пара “читаемый ключ (имя файла) → путь”.

Пример:

<?php

declare(strict_types=1);

use SIMAI\Main\Utility\File;

$list = File::getList('/ru/simai.data/template/style');
// [
//   '/ru/simai.data/template/style/app.css' => '/ru/simai.data/template/style/app.css',
//   ...
// ]

$fileList = File::getFileList('/ru/simai.data/template/style');
// [
//   'app.css' => '/ru/simai.data/template/style/app.css',
//   ...
// ]

SIMAI\Main\Utility\Textlink

Text закрывает несколько “прикладных” задач: генерация коротких строк, короткий хеш для кеш-ключей/идентификаторов и преобразование HTML → текст.

  • getRandomLine($length = 8, $chars = '...') — генерирует случайную строку заданной длины из переданного набора символов. Используется rand() + substr(), поэтому строка подходит как “технический идентификатор/суффикс”, но не как криптографически стойкий токен.
  • getHash($params, $len = 10) — делает md5() от строки (если передан массив — сначала json_encode()), после чего возвращает “короткий хеш” длиной $len: это первая половина + последняя половина md5-строки (длины ceil($len/2) каждая).
  • html2Text($html) — обёртка над SIMAI\Main\Utility\Html2Text: создаёт объект и возвращает результат getText().

Отдельный нюанс про “UTF-8/legacy”: внутри класса есть собственная truncateText() (используется для обрезки строки), где длина сравнивается через strlen(utf8_decode($string)), а обрезка — через регулярку smarty_utf8_substr() и preg_replace(.../u). По смыслу это ориентировано на UTF-8, но оценка длины идёт через utf8_decode(), поэтому на строках с нестандартной кодировкой поведение может быть неожиданным.

Пример короткого хеша (удобно для кеш-ключей):

<?php

declare(strict_types=1);

use SIMAI\Main\Utility\Text;

$key = Text::getHash(['iblock' => 7, 'id' => 123, 'lang' => 'ru'], 12);
// вернёт строку длиной 12: первые 6 + последние 6 символов md5

SIMAI\Main\Utility\Datalink

Data решает узкую, но частую задачу: привести регистр ключей массива (включая вложенные массивы).

  • setArrayKeyUp($array) — приводит ключи к верхнему регистру (CASE_UPPER) рекурсивно. Если на вход пришло не array, возвращает false.
  • setArrayKeyLow($array) — приводит ключи к нижнему регистру (CASE_LOWER) рекурсивно. Если на вход пришло не array, возвращает false.

Внутри используется array_change_key_case() + рекурсивный array_map(), то есть меняются только ключи, значения остаются как есть.

SIMAI\Main\Utility\Menulink

Menu предназначен для работы с меню в формате, близком к стандартному меню Bitrix: элементы представлены массивами, где критично важны:

  • ссылка ($item[1]),
  • параметры ($item[3]), где используется DEPTH_LEVEL, IS_PARENT, PERMISSION, SELECTED, а также могут быть ваши PARAMS (например, ICON).

В классе два смысловых блока: “слить меню” и “подготовить/отрисовать навигацию”.

Слияние меню

  • arrayMerge($aMenuLinks, $aMenuLinksExt, $dir) — объединяет базовое меню и расширение:

    • сначала строит “коды” по ссылкам базового меню (с заменой ./ на $dir);

    • затем проходит по расширенному меню и пытается:

      • если ссылка расширенного пункта совпала с одним из базовых — считает это “родителем” и запоминает его DEPTH_LEVEL;
      • пока идут элементы с большей глубиной — складывает их в “подразделы” этого родителя;
      • всё, что не удалось привязать — уходит в “остаток” и добавляется в конец результата.
    • в результате возвращается “плоский массив”, где сразу после родителя вставлены его подпункты из расширения.

Подготовка дерева и вывод

  • initItemNavigation(&$arResult, &$arParams) — берёт плоский список пунктов меню и строит вложенное дерево (по DEPTH_LEVEL, реализованы ветки как минимум для уровней 1–4). Дополнительно “примешивает” в каждый пункт вычисленные поля-модификаторы из $arParams (строки вида SF_NAVBAR_FULL_MODIFIER_*_<DEPTH_LEVEL> и т.п.), чтобы далее рендерить меню без дополнительных вычислений.
  • showNavigation(&$item, &$arParams, $Hash) — рекурсивно генерирует HTML. Важная деталь: для мобильного уровня (когда DEPTH_LEVEL > 0) добавляется пункт “назад” с кнопкой и текстом из $arParams.

Для параметров ($arParams) используются группы настроек, часть из них зависит от глубины (..._" . $item["DEPTH_LEVEL"]), например:

  • модификаторы контейнеров/элементов/ссылок/текста: SF_NAVBAR_*_MODIFIER_ITEMS_, SF_NAVBAR_*_MODIFIER_ITEM_, SF_NAVBAR_*_MODIFIER_LINK_, SF_NAVBAR_*_MODIFIER_TEXT_
  • иконки до/после и их модификаторы: SF_NAVBAR_ITEM_ICON_BEFORE_*, SF_NAVBAR_ITEM_ICON_AFTER_*, SF_NAVBAR_*_MODIFIER_ICON_BEFORE_, SF_NAVBAR_*_MODIFIER_ICON_AFTER_
  • мобильная кнопка “назад”: SF_NAVBAR_MOBILE_BTNBACK_ICON, SF_NAVBAR_MOBILE_BTNBACK_TEXT, SF_NAVBAR_MOBILE_MODIFIER_BTNBACK, SF_NAVBAR_MOBILE_MODIFIER_BTNBACK_TEXT

Ещё один практический штрих: при формировании ссылки (внутренний метод addLink) добавляется “замок”, если PERMISSION <= "D".

SIMAI\Main\Utility\Html2Textlink

Html2Text — встроенная библиотека конвертации HTML в обычный текст. Она полезна в ситуациях, где HTML хранится как “богатое” содержимое, но вам нужен текст:

  • для сниппетов,
  • для превью/описаний,
  • для простых текстовых уведомлений (например, email),
  • для “текстовой версии” описаний.

Интерфейс “по делу” сводится к нескольким методам:

  • __construct($html = '', $options = []) — принимает HTML и опции.
  • setHtml($html) / getHtml() — установить/получить исходный HTML.
  • getText() — вернуть текст; при первом вызове запускается преобразование и результат кешируется в объекте.

В SF4 чаще удобно использовать обёртку Text::html2Text($html), если вам не нужно управлять опциями.

SIMAI\Main\Utility\VideoFramelink

VideoFrame::get($link) — быстрый генератор iframe для встраивания видео.

Поддерживаемые сценарии распознавания ссылки:

  • Rutube: если в ссылке есть rutube.ru, выполняется замена /video//play/embed/.
  • VK / vkvideo: если ссылка содержит vk.com или vkvideo.ru и внутри есть фрагмент video<oid>_<id>, формируется embed-ссылка вида https://vk.com/video_ext.php?oid=...&id=...&hd=2&autoplay=1.
  • YouTube: если в ссылке есть youtu, пытается извлечь 11-символьный ID через регулярку и строит https://www.youtube.com/embed/<id>?autoplay=1.
  • Если ссылка не похожа на URL, метод считает, что ему передали уже ID YouTube и тоже строит embed.

Результат — строка <iframe ...> с width="100%", height="420", allow="autoplay; encrypted-media; fullscreen; picture-in-picture;" и allowfullscreen.

Пример:

<?php

declare(strict_types=1);

use SIMAI\Main\Utility\VideoFrame;

echo VideoFrame::get('https://www.youtube.com/watch?v=dQw4w9WgXcQ');

Error API: накопление и вывод ошибокlink

SIMAI\Main\Errorlink

SIMAI\Main\Error — это простая подсистема для накопления ошибок в памяти текущего запроса и их вывода в виде HTML. Она удобна там, где вы хотите “собрать” ошибки в разных местах (компонент, обработчик, шаблон), а вывести одним блоком в нужной точке страницы.

Важно понимать контракт: ошибки не пишутся в файлы и не сохраняются в сессию — это singleton-хранилище, живущее до окончания выполнения PHP.

addError($code, $message, $value = false)link

Метод добавляет (или перезаписывает) ошибку по коду. Внутри ошибки хранятся как ассоциативный массив:

  • ключ — $code,
  • значения — message и value.

Особенности поведения:

  • если вы дважды вызываете addError() с одним и тем же $code, предыдущие message/value будут перезаписаны;
  • $value выводится только если он “истинный” (условие вида if ($error["value"])), то есть 0, "", null и false не будут показаны как “Error value”.

isError($class = false)link

isError() проверяет, накоплены ли ошибки, и одновременно выполняет вывод.

Поведение по шагам:

  1. Если ошибок нет — возвращает false и ничего не выводит.

  2. Если ошибки есть:

    • выбирает CSS-класс для оформления (если $class не передан, берётся "c-red"),
    • собирает HTML-строку из <p class="...">...</p> по каждой ошибке,
    • делает echo этой строки,
    • возвращает true.

Формат вывода по каждой ошибке примерно такой:

  • Error code: <code>
  • (опционально) Error value: <value>
  • Error description: <message>

Практическое правило: из-за побочного эффекта echo этот метод лучше вызывать один раз в “общем” месте (например, в layout/шаблоне страницы), чтобы избежать повторного вывода.

Мини-пример использования в шаблонеlink

<?php

declare(strict_types=1);

use SIMAI\Main\Error;

// где-то в логике (например, перед рендером)
Error::addError('IBLOCK_NOT_FOUND', 'Не найден инфоблок по коду', 'catalog');

// в месте вывода (layout)
Error::isError('c-red'); // вернёт true/false и выведет HTML при наличии ошибок

“Кулинарная книга” (cookbook): 8–12 рабочих сценариевlink

Ниже — набор мини-сценариев, которые логично включить в документацию SF4 как отдельные короткие подразделы. Я пишу их так, чтобы их можно было почти без правок вставить в Markdown: каждый сценарий объясняет “когда применять”, “как устроено”, “пример кода”, “типичные ошибки”.

1) Прочитать настройку с нужного уровня (site → section → page → user)link

В SF4 важно разделять: где настройка хранится на диске (site/section/page), а где она используется на странице (итоговый runtime-слой, чаще всего в Configuration\Property).

Осознанный выбор уровня выглядит так:

  • site — настройка проекта по умолчанию (база, редко меняется).
  • section — логика раздела, которая должна наследоваться вниз по дереву (каталог/подкаталог).
  • page — точечная настройка конкретной страницы (перекрывает section/site).
  • user — “настройка пользователя” (сессия), обычно для режимов или персональных предпочтений.

Пример: прочитать значение “как видит его страница” (то есть уже итоговое, из runtime):

<?php

declare(strict_types=1);

use SIMAI\Main\Configuration\Property;

// Итоговое значение для текущего сайта (обычно заранее собрано и положено в Property)
$layoutType = Property::getValue(SF_SITE_DIR, 'layout_type');

Пример: прочитать напрямую с уровня section (для отладки или специальных сценариев):

<?php

declare(strict_types=1);

use SIMAI\Main\Configuration\Section;

global $APPLICATION;

$dir = $APPLICATION->GetCurDir();          // /ru/catalog/
$sectionProps = Section::getRecursionArray($dir); // наследование по дереву

$layoutType = $sectionProps['layout_type'] ?? null;

Пример: прочитать напрямую с уровня page (из /.property.php, ветка page[index.php]):

<?php

declare(strict_types=1);

use SIMAI\Main\Configuration\Page;

$pagePath = $_SERVER['REAL_FILE_PATH'] ?: $_SERVER['SCRIPT_NAME']; // /ru/catalog/index.php
$pageProps = Page::getArray($pagePath);

$title = $pageProps['title'] ?? null;

Типичная ошибка: пытаться читать “page” настройку по ключу title через Section::... — она лежит в другой ветке.

2) Создать include-область и подключить её в шаблоне (ваш пример sidebar)link

Шаблонные include-области SF4 (проектные) живут в слое {site_dir}/simai.data/template/area.

Шаги:

  1. Создайте файл области:

/ru/simai.data/template/area/sidebar/right/template.php

  1. Добавьте в нём разметку:
<?php

declare(strict_types=1);

?>
<div class="sidebar sidebar--right">
    <!-- ваш контент -->
</div>
  1. Подключите область в проектном шаблоне:
<?php

declare(strict_types=1);

use SIMAI\Main\IO\IncludeArea;

IncludeArea::includeTemplateArea('sidebar/right');

Типичная ошибка: указать путь с начальным слешом (/sidebar/right) или забыть template.php внутри папки области.

3) Подключить комплект ассетов по имени и версии (и что будет при отсутствии версии)link

В текущей реализации SF4 реестр ассетов находится в системном слое (/simai/config/.asset.config.php), а проектный .asset.config.php в simai.data не используется.

Подключение комплекта:

<?php

declare(strict_types=1);

use SIMAI\Main\Page\Asset;

Asset::getInstance()->load('simai.framework', 'default');

Если версия 'default' (или любая другая) в конфиге указана, но соответствующая директория версии на диске отсутствует, SF4 попытается подобрать версию автоматически, просканировав папку комплекта и выбрав одну из имеющихся. Это “спасательный режим”, но для стабильности лучше держать конфиг и структуру файлов синхронными.

4) Получить данные элемента инфоблока вместе со “Source” (поле/свойство → DATA)link

Когда нужно быстро получить “нормализованные данные” для поля/свойства (картинка, файл, связанный элемент/раздел), используйте SIMAI\Main\Iblock\Source.

Пример: получить картинку превью как файл-структуру:

<?php

declare(strict_types=1);

use SIMAI\Main\Iblock\Source;

$elementId = 123;

$preview = Source::getArray($elementId, [
    'TYPE' => 'IBLOCK',
    'CODE' => 'PREVIEW_PICTURE',
]);

// $preview['VALUE'] — исходное значение поля (обычно ID файла)
// $preview['DATA']  — массив CFile::GetFileArray(...)

Пример: получить свойство-ссылку simai_link с HTML в ~VALUE:

<?php

declare(strict_types=1);

use SIMAI\Main\Iblock\Source;

$link = Source::getArray(123, [
    'TYPE' => 'PROPERTY',
    'CODE' => 'LINK',
]);

// $link['VALUE']  — исходное значение
// $link['~VALUE'] — HTML-ссылка (если USER_TYPE=simai_link)

Типичная ошибка: ожидать DATA у обычной строки (PROPERTY_TYPE = S без спец. USER_TYPE) — там обычно достаточно VALUE.

5) Включить публичное редактирование элемента: какие флаги/права должны бытьlink

Публичные оверлеи редактирования (Block\Edit) включаются по сочетанию режимов и прав:

  • development_mode = "Y" — показывает оверлей “настройки блока” (шестерёнка).
  • edit_mode = "Y" + права на инфоблок >= "W" — показывает оверлей “добавить/редактировать/удалить”.

Дополнительно:

  • iblock_public_editor = "Y" — переключает поведение на ваш публичный редактор в {site_dir}/simai.data/admin/....
  • если флаг не включён — + и откроют стандартные формы Bitrix (popup).

Пример вставки оверлея для элемента в шаблон блока:

<?php

declare(strict_types=1);

use SIMAI\Main\Block\Edit;

$elementId = (int)($arResult['ID'] ?? 0);
$sectionId = (int)($arResult['IBLOCK_SECTION_ID'] ?? 0);

$overlay = Edit::addEditItemArea($elementId, $sectionId);
if ($overlay !== false)
{
    echo $overlay;
}

Типичная ошибка: включить edit_mode, но забыть про права на инфоблок — оверлей не появится, даже если режим включён.

6) Исправить URL в поиске, если используется #UF_SECTION_URL#link

Если в результате поиска URL содержит маркер #UF_SECTION_URL#, SF4 может заменить его значением пользовательского поля раздела UF_SECTION_URL основного раздела элемента.

Что важно:

  • обработчик работает только для MODULE_ID = iblock и числового ITEM_ID;
  • используется IBLOCK_SECTION_ID элемента (основной раздел);
  • значение берётся из раздела: UF_SECTION_URL.

Мини-тест (ручной вызов):

<?php

declare(strict_types=1);

use SIMAI\Main\Search\Iblock;

$url = Iblock::OnSearchGetURL([
    'MODULE_ID' => 'iblock',
    'ITEM_ID' => 123,
    'PARAM2' => 7,
    'URL' => '#UF_SECTION_URL#/detail/',
]);

Если подстановка не происходит, чаще всего причина одна из трёх:

  • в URL нет маркера #UF_SECTION_URL#,
  • у элемента не заполнен основной раздел,
  • у раздела пустой UF_SECTION_URL.

7) Собрать “итоговые свойства страницы” и сохранить в runtime-хранилищеlink

Это базовый паттерн SF4: собрать site/section/page (+user overrides) и положить в Configuration\Property, чтобы дальше работать с настройками единообразно.

<?php

declare(strict_types=1);

use SIMAI\Main\Configuration\Property;
use SIMAI\Main\Configuration\Section;
use SIMAI\Main\Configuration\Page;

global $APPLICATION;

$siteProps = [];
if (file_exists(SF_DATA_PATH . '/.site.property.php'))
{
    $siteProps = require SF_DATA_PATH . '/.site.property.php';
}

$dir = $APPLICATION->GetCurDir();
$sectionProps = Section::getRecursionArray($dir);

$pagePath = $_SERVER['REAL_FILE_PATH'] ?: $_SERVER['SCRIPT_NAME'];
$pageProps = Page::getArray($pagePath);

if (is_array($sectionProps))
{
    $siteProps = array_merge($siteProps, $sectionProps);
}

if (is_array($pageProps))
{
    $siteProps = array_merge($siteProps, $pageProps);
}

$userProps = Property::getArray('user');
if (is_array($userProps))
{
    $siteProps = array_merge($siteProps, $userProps);
}

Property::setArray(SF_SITE_DIR, $siteProps);

8) Создать “секционную область” и переопределять её в дочерних разделахlink

Это лучший способ сделать “общую” область для ветки разделов и иметь возможность точечно переопределять её глубже.

  1. В корневом разделе ветки создайте:

/ru/catalog/section.banner.php

  1. В шаблоне вставьте:
<?php

declare(strict_types=1);

use SIMAI\Main\IO\IncludeArea;

IncludeArea::includeSectionArea('banner');
  1. Если нужно другой баннер в /ru/catalog/sale/, создайте файл:

/ru/catalog/sale/section.banner.php

SF4 автоматически возьмёт ближайший к текущему пути.

9) Получить список блоков/вьюх с учётом кастомизаций в simai.datalink

Когда нужно показать выбор “какие блоки доступны”, используйте Block\Section и читайте список из SF_DATA_DIR (при этом SF4 подтянет и системные каталоги).

<?php

declare(strict_types=1);

use SIMAI\Main\Block\Section;

$blocks = Section::getList(SF_DATA_DIR . '/grid/block');

foreach ($blocks as $code => $meta)
{
    $name = $meta['NAME'] ?? $code;
    $preview = $meta['PREVIEW'] ?? null;
}

10) Нормализовать путь и найти “существующую директорию” для виртуального URLlink

Если вам приходит путь, который может ссылаться на “виртуальную” страницу или несуществующий ресурс, используйте Path::getStatic() — он вернёт ближайшую существующую директорию.

<?php

declare(strict_types=1);

use SIMAI\Main\Utility\Path;

$dir = Path::getStatic('/ru/catalog/some/virtual/page.php');

if ($dir !== false)
{
    // $dir — существующая директория (локальным путём)
}

Границы “публичного API” и “внутренностей”link

Что вы называете публичными точками расширения (и что можно использовать в решениях без страха обновлений)link

В контексте SIMAI Framework 4 (SF4) “публичными” разумно считать те точки, которые изначально рассчитаны на то, чтобы вы их дополняли/переопределяли, и которые живут в проектном слое, а не в системном коде.

Самая надёжная (и фактически основная) публичная точка расширения — это слой данных сайта:

  • {site_dir}/simai.data/template/** — ваша кастомизация шаблона (области, панели, property, meta/style/js и т.д.).
  • {site_dir}/simai.data/template/area/<код>/template.php — подключаемые области через SIMAI\Main\IO\IncludeArea::includeTemplateArea(). Это “каноничный” способ добавлять новые области (как в вашем примере sidebar/right).
  • {site_dir}/simai.data/grid/** — пользовательские блоки/вьюхи, которые SF4 подхватывает вместе с системными и умеет объединять (ключевой принцип “ядро + данные сайта”).
  • {site_dir}/simai.data/admin/** — проектный слой публичного редактора (в вашем проекте он используется для редактирования элементов/разделов инфоблоков, когда включён соответствующий режим).

Вторая “публичная” зона — конфигурационные файлы проекта, которые являются частью решения и должны переживать обновления ядра:

  • {site_dir}/simai.data/.site.property.php — базовые настройки сайта.
  • /.property.php в каталогах сайта — секционные/страничные настройки (ветки section, page, а при необходимости и block).

Третья зона — высокоуровневые фасады SF4, которые “предназначены для вызова” из шаблонов/компонентов и скрывают внутренние детали:

  • SIMAI\Main\IO\IncludeArea — подключение областей.
  • SIMAI\Main\Page\Asset/Font/SVG/Meta — подключение ресурсов и runtime-значений.
  • SIMAI\Main\Block\Section — получение списков секций (блоков/вьюх) с учётом слоя simai.data.
  • SIMAI\Main\Block\Edit — публичные оверлеи редактирования.
  • SIMAI\Main\Iblock\Source/Element/Section — получение данных элемента/раздела и “источников”.

Практическое правило: если вы расширяете SF4 через добавление файлов/папок и настроек в {site_dir}/simai.data и подключаете это через фасады (IncludeArea, Block\Section, Block\Edit и т.п.), то такие решения обычно наименее чувствительны к обновлениям.

Что считать служебным/внутренним, где лучше не завязываться на детали реализацииlink

“Внутренности” в SF4 — это всё, что рассчитано на обновление как часть ядра и не предполагает прямой правки или прямой зависимости от формата/разметки.

К этой зоне стоит относить:

  • Системный код и системные конфиги в /simai/**. Любые изменения там рискованны: при обновлении они почти гарантированно будут перезаписаны или начнут конфликтовать с новой логикой.
  • Системный шаблон /bitrix/templates/simai.framework как место правок. В вашей архитектуре он выступает “загрузчиком”, а кастомизация должна жить в {site_dir}/simai.data/template/**.
  • Детали внутреннего хранения (например, конкретные ключи в $_SESSION для runtime-свойств и кешей). Надёжная опора — это методы классов (Configuration\Property, Configuration\Iblock), а не прямое чтение/запись в $_SESSION[...].
  • Методы, которые пишут в системные файлы. Например, запись в системные конфиги через Framework::setValue() или “редактирование” значений в конфиге ассетов через Asset::setValue() — это технически возможно, но с точки зрения решения почти всегда относится к внутренностям (сложно контролировать обновления и воспроизводимость).
  • Разметка и HTML-детали служебных оверлеев (классы, структура DOM, конкретные URL админ-страниц), если вы пытаетесь на них “привязаться” в своём JS/CSS. Здесь лучше опираться на включение режима и использование готового метода (Block\Edit::addEditItemArea()), чем на копирование/парсинг внутренней верстки.

Полезное практическое деление для документации и для команды:

  • Стабильный контракт решения: структура и файлы в {site_dir}/simai.data, форматы .site.property.php и /.property.php, подключение областей/блоков через SF4.
  • Условно-публичный API: фасады SIMAI\Main\..., которые вызываются из шаблонов/компонентов (ими пользоваться можно, но лучше не зависеть от внутренних структур данных, которые они “под капотом” собирают).
  • Внутреннее: всё системное (/simai/**), “инсталляционное/мастерское” и детали реализации (сессии, служебные HTML, внутренние фолбэки).