Основной модуль: simai.framework — ядро фреймворка (компоненты, actions, утилиты, классы Configuration/Setting/Iblock/Page/Utility/IO/Block/Search)
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 — универсальные свойства и пользовательские свойства
Семейство 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) — фиксировать наличие и назначение
В экосистеме SF4 предусмотрены и встречаются вспомогательные модули, которые не являются обязательными для базовой работы “ядра”, но закрывают инфраструктурные задачи:
simai.update— модуль центра обновления (инфраструктура обновлений экосистемы SF4).simai.backup— модуль резервирования данных.simai.iblockcopy— модуль операций копирования/переноса структур и данных, связанных с инфоблоками.simai.bxeditor— модуль расширения функционала HTML-редактора.
Такие модули обычно подключают “сервисные” сценарии: обслуживание, миграции, сопровождение, удобные инструменты администрирования. При этом отключение/удаление сервисного модуля корректнее планировать от задач проекта: ядро SF4 продолжит работать, но соответствующие сервисные возможности станут недоступны.
Картина мира ядра SF4: как читать и использовать API
Пространства имён и “стили” кода
В ядре SIMAI Framework 4 (SF4) классы сгруппированы по функциональным областям и в основном живут в неймспейсе SIMAI\Main\.... Это “прикладной” слой SF4, которым пользуются и шаблоны/компоненты, и админ-инструменты.
На практике в API SF4 встречаются три характерных “стиля” использования Bitrix:
-
D7-подход (Bitrix\Main...) — там, где удобно опираться на современные классы ядра Bitrix. Типичные примеры: загрузка модулей (
\Bitrix\Main\Loader), управление ассетами страницы (\Bitrix\Main\Page\Asset), работа с путями (Loader::getDocumentRoot()). -
Legacy-подход (глобальные классы/функции Bitrix) — там, где SF4 взаимодействует с “классическим” API Bitrix (например,
CSite::GetList(),CIBlock::...) или где это проще/исторически сложилось. -
Собственный слой SF4 (SIMAI\Main...) — это фасады/хелперы, которые берут на себя “склейку” инфраструктуры: конфигурации, файловые слои, блоки, include-области, обработчики поиска. Именно этот слой чаще всего стоит рассматривать как API SF4 для проекта.
Кроме SIMAI\Main\... в составе ядра SF4 присутствуют отдельные пространства для инфраструктурных задач:
SIMAI\Install\...— импорт/экспорт (обычно вокруг установочных сценариев и миграций).SIMAI\Wizard\...— утилиты мастера (поддержка пошаговых сценариев).
Хорошее практическое правило для чтения API такое: если вы пишете проектный код (шаблон, область, блок, view), то почти всегда начинаете с SIMAI\Main\... и только при необходимости уходите в Bitrix API.
Общий паттерн: singleton + статические методы
Во многих подсистемах 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 можно выделить три основных “среды хранения”, и от этого зависит, как вы должны воспринимать метод.
-
Только память (на время текущего запроса) Типичный пример — “временные переносчики” и лёгкие runtime-объекты, которым не нужно сохранять состояние между запросами. Здесь изменения действуют только до конца исполнения PHP.
-
Сессия пользователя В SF4 есть как минимум два выраженных сессионных хранилища:
- итоговые свойства сайта/страницы живут в
$_SESSION['site_property'][<storageId>](это используетSIMAI\Main\Configuration\Property); - кеш соответствий для инфоблоков по коду живёт в
$_SESSION['iblock_property'](это используетSIMAI\Main\Configuration\Iblock).
Сессионное хранение удобно тем, что ускоряет повторные обращения в рамках сессии и позволяет держать “итоговые” свойства рядом с пользователем, но это не “истина на диске”. Если вам нужно сохранить настройку как часть проекта — нужен файловый уровень.
- Файлы (персистентное хранение на диске) Это настройки, которые должны переживать сессию и быть частью проекта/поставки. В 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).
Константы окружения SF4
Окружение 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_NAMESF_SOLUTION_LINKSF_SOLUTION_PATHSF_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: настройки, уровни и хранение
Идея “property-файлов” и формата данных
В 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 — базовый слой свойств
Этот класс — не “файловая конфигурация”, а сессионное 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)
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 — настройки “сайта” и группы настроек
Site — это файловый слой настроек проекта, который хранится в {site_dir}/simai.data/.site.property.php. В отличие от Framework, здесь значения записываются “плоско” (код → значение), без обёртки ["value" => ...].
Методы:
Site::getValue(string $siteId, string $name): mixed|nullSite::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 — наследование по разделам
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|nullSection::setValue(string $dirProperty, string $name, mixed $value): voidSection::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 — настройки страницы и сочетание с настройками раздела
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 — временное хранилище в рамках запроса
Transfer — самый лёгкий конфигурационный класс: это временное in-memory хранилище на время выполнения PHP-запроса. Оно не пишет на диск и не использует сессию, поэтому подходит для передачи значения между частями кода без побочных эффектов.
Методы:
Transfer::getValue(string $name): mixed|nullTransfer::setValue(string $name, mixed $value): voidTransfer::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 инфоблоков по коду в сессии
Iblock — сессионный кеш соответствия CODE → ID для инфоблоков. Данные хранятся в $_SESSION['iblock_property'].
Логика getValue($name) такая:
- если в сессионном кеше уже есть ID — вернуть его;
- если нет — попытаться найти инфоблок по
CODE = $nameчерез API инфоблоков; - найденный ID положить в кеш и вернуть.
Методы:
Iblock::getValue(string $code): ?string— получить ID по коду (илиnull, если не найден).Iblock::setValue(string $code, mixed $value): void— вручную записать в кеш.
Ограничения и последствия:
- кеш привязан к сессии, то есть на другом пользователе/в другой сессии он пустой;
- если на проекте изменится
CODEинфоблока или появятся коллизии, в кеше может оказаться устаревшее соответствие до конца сессии (лечится очисткой сессии); - на старте “холодной” сессии
getValue()делает запрос к инфоблокам, но дальше ускоряет повторные обращения.
SIMAI\Main\Configuration\Block — настройки блоков в property-файлах
По структуре данных 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 файла и работу с массивом.
Правила безопасной записи настроек
Запись конфигов SF4 — это запись PHP-файлов на диск, поэтому основные риски здесь всегда одинаковые: права доступа, конкурентная запись и валидность данных.
Что важно учитывать:
-
Права на запись. Перед записью SF4 пытается сделать файл доступным на запись (
chmod 0644). Если директория/файл недоступны — сохранение не произойдёт корректно. -
Атомарность. В текущей реализации запись идёт через
file_put_contents()напрямую в целевой файл. Если два процесса пишут одновременно, можно получить “гонку” (редко, но возможно в админских сценариях). Если для проекта это критично — стоит проектно вводить атомарную запись через временный файл + rename (в ядре SF4 этого нет). -
Сериализация через
var_export(). Это значит, что:- в файле должны жить только значения, которые корректно экспортируются в PHP-код (строки/числа/массивы/булевы/
null); - нельзя хранить в таких массивах “живые” объекты/ресурсы;
- для строк важно следить за ожидаемой кодировкой/экранированием, так как это напрямую станет PHP-кодом.
- в файле должны жить только значения, которые корректно экспортируются в PHP-код (строки/числа/массивы/булевы/
Page API: ассеты, шрифты, meta, SVG/цвета
SIMAI\Main\Page\Asset — подключение комплектов ассетов
Что такое “пакет/комплект ассетов” (имя + версия)
В SF4 “комплект ассетов” — это именованный набор подключаемых ресурсов (CSS/JS/строки), описанный в конфигурации. Комплект идентифицируется:
- именем (например, условно
bootstrap,jquery,simai.framework); - версией (обычно ключ
default, но может быть и другой).
Внутри описания комплекта есть:
- базовая папка комплекта (
dir), - набор версий в ветке
asset, - у каждой версии — своя подпапка (
dir) и список файловfile[], - каждый файл имеет
type(style,script,string) иpath.
type=string — это не путь к файлу, а строка, которая будет добавлена напрямую в head через addString().
Где лежит конфиг ассетов в текущей реализации
В текущей реализации реестр комплектов читается из системного слоя SF4:
- конфиг:
/simai/config/.asset.config.php - физические файлы:
/simai/asset/...
Отдельно фиксируем вашу практику: проектный {site_dir}/simai.data/config/.asset.config.php не используется (SF4 его не читает при подключении ассетов), поэтому “добавить комплект через simai.data/config” в этой поставке не является рабочей точкой расширения.
Методы
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():style→addCss()script→addJs()string→addString()
Для CSS/JS есть поддержка минифицированных файлов: если в Bitrix включена опция
main.use_minified_assets = Y, то SF4 попытается подключить*.min.css/*.min.js, но только если такой файл реально существует рядом.
Сценарии
Подключить комплект в шаблоне/на странице
<?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>
Методы
-
Font::getInstance()->load($name)Если в описании шрифта есть полеlink, добавляет его в head как строку через\Bitrix\Main\Page\Asset::addString(). Возвращает массив описания шрифта. -
Font::getInstance()->getFontFamily($name)Если заданfamily— вернёт его. Иначе соберёт"<name>", <type>(например"Roboto", sans-serif). -
Font::getInstance()->getFontList()Возвращает список доступных шрифтов как “ключ → человекочитаемое имя”, отсортированный естественной сортировкой.
Где лежит конфиг шрифтов
Шрифты читаются из системного конфига:
/simai/config/.font.config.php
В нём для каждого шрифта обычно есть поля вроде name, type, link (иногда family).
Пример: выбрать шрифт из списка и применить в шаблоне
<?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-хранилище мета-значений
Назначение
Meta — очень лёгкое хранилище “ключ → значение” в памяти (singleton), живёт только в рамках текущего запроса. Его удобно использовать, когда одно место кода должно “передать” значение другому месту без записи в сессию и без файлов.
Методы
Meta::setValue($name, $value)— записать значениеMeta::getValue($name)— прочитать значение Внутри значение хранится как["value" => ...], но наружу возвращается само значение.
SIMAI\Main\Page\SVG — безопасная вставка SVG (очистка width/height + классы)
Метод
SVG::get(string $path, bool $clearStyle = true): string|falseПринимает путь от document root (например,SF_DATA_DIR . '/template/image/icon.svg'), читает файл и возвращает SVG как строку (илиfalse, если файла нет).
Что “чистится” при $clearStyle = true
В текущей реализации выполняются замены прямо по тексту 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.data
<?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 — конвертер и утилиты цвета
Для чего нужен
Color — объект для конвертации и сравнений цветов: RGB ↔ HEX, HSV, XYZ, Lab (CIE), плюс утилиты “приблизить к ближайшему из набора” и простое осветление/затемнение HEX-цвета.
Основные входы/выходы
-
Вход:
fromHex('ff00aa')(без#)fromRgbInt($r, $g, $b)fromRgbHex('ff', '00', 'aa')fromInt($intColor)
-
Выход:
toHex()→ строкаff00aa(без#)toRgbInt()→['red' => ..., 'green' => ..., 'blue' => ...]toHsvFloat()→ hue в градусах, sat в долях, val в диапазоне 0..255toHsvInt()→ hue/sat/val в диапазоне 0..255toXyz(),toLabCie()— для цветовых расстояний
-
Сравнение:
getDistanceRgbFrom(Color $color)getDistanceLabFrom(Color $color)(использует Lab)
-
Утилиты:
isGrayscale($threshold = 16)— проверка “почти серый” по разбросу каналовlighter($color, $percent)/darker($color, $percent)— принимает#rrggbb, возвращает#rrggbb(процент внутри переводится в шагint(2.5 * percent)по каждому каналу)
Пример: конвертация и осветление
<?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()
В реализации getClosestMatch(array $colors) внутри ожидается массив цветов, но корректная работа требует осторожности: если передавать элементы уже как объекты Color, в текущем коде есть риск обращения к неинициализированной переменной при расчёте дистанции. Надёжнее передавать массив “сырых” значений (которые конструктор Color сможет принять) или предварительно проверить поведение на вашем наборе данных.
IO API: include-области и файловые настройки
SIMAI\Main\IO\IncludeArea — подключение областей (section/file/template)
IncludeArea — это простой, “шаблонный” механизм подключаемых областей, который работает на уровне файлов и путей. Все методы устроены одинаково: пытаются найти файл по соглашению об имени, и если файл существует — подключают его через require. Если файл не найден — возвращают false.
Практически это означает две вещи:
- область “подмешивает” на страницу ровно тот PHP/HTML, который лежит в файле (без дополнительных обёрток);
- ошибки PHP в файле области будут фатальными (потому что используется
require).
includeSectionArea($filePrefix) — ищет section.<prefix>.php вверх по дереву
Этот метод предназначен для “секционных” областей: вы кладёте файл вида 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) — подключает “локальный файл страницы” с суффиксом
Этот метод предназначен для “точечных” областей на конкретной странице: он берёт текущий исполняемый 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.php
Это основной “проектный” механизм областей 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 — сохранить массив в файл и прочитать из файла
Setting — это утилита для работы с “конфигами как файлами”. Важно: в текущей реализации это два разных формата файлов, и методы не являются зеркальными друг другу.
saveToFile(array $array, string $filename) — пишет PHP-файл
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<ИмяФайла> внутри подключаемого файла
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: доступ к данным и “источники”
SIMAI\Main\Iblock\Element — кеширование полей/свойств элемента + извлечение “source”
Методы
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 и что возвращается
$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_TYPE
В текущей реализации обработка ориентируется на 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), а также некоторые поля инфоблока/кода.
- для каждого ID раздела подтягиваются
-
E(привязка к элементам):- для каждого ID элемента подтягиваются
NAME,SECTION_PAGE_URL(какURL) и сопутствующие поля.
- для каждого ID элемента подтягиваются
Нюанс реализации, который стоит учитывать: внутри getSourceData() встречаются обращения к структурам вида $arItem["SOURCE"]["PROPERTY"]... и переменной $keyProperty, которые в этой выгрузке не определены. То есть ветки, где используются эти обращения, требуют проверки на вашем проекте (по факту входного массива $arItem), иначе можно получить некорректное заполнение данных.
SIMAI\Main\Iblock\Section — кеширование данных раздела
Методы
Section устроен так же, как Element, но для разделов: по ID раздела один раз запрашивает данные через CIBlockSection::GetByID() и кеширует результат в рамках запроса.
Section::getArray($idSection)— возвращает массив полей раздела (как отдаёт Bitrix).Section::getField($idSection, $field)— возвращает одно поле раздела по ключу.
Этот хелпер удобен, когда вы в нескольких местах шаблона/компонентов повторно обращаетесь к одному и тому же разделу: SF4 не делает повторный запрос к Bitrix API для одного и того же ID.
SIMAI\Main\Iblock\Source — унификация “поле/свойство → данные”
Методы
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 и когда
Возвращаемая структура строится по принципу: 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_element—DATAзаполняется массивом элементов (для одиночного ID или массива ID) черезElement::getArray().USER_TYPE = simai_ib_section—DATAзаполняется массивом разделов (для одиночного ID или массива ID) черезSection::getArray().USER_TYPE = HTML—VALUE/~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 — обёртки для файлов
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: структура блоков, списки, редактирование на сайте
SIMAI\Main\Block\Section — списки разделов блоков/вьюх и merge “ядро + данные сайта”
Section — это вспомогательный класс, который решает две практические задачи:
-
Найти “правильную” директорию секции (с приоритетом слоя сайта), когда один и тот же путь существует и в системном каталоге SF4, и в
{site_dir}/simai.data. -
Собрать “каталог секций” (список подпапок) с метаданными из
.description.php, чтобы использовать его в админ-инструментах/выборе шаблонов (блоков, вьюх, их вариантов и т.п.).
Ключевая идея “два корня” и как они строятся
Класс оперирует двумя базовыми корнями:
SF_DATA_DIR→{site_dir}/simai.dataSF_DIR→ системный каталог SF4 (в текущей инициализации это/simai)
Метод getDirArray($dir) принимает путь $dir и строит пару кандидатов (всегда в таком порядке):
- путь в
{site_dir}/simai.data/... - соответствующий путь в системном каталоге
/simai/...
Дальше эта пара используется в разных сценариях:
getDir($dir)— возвращает первый реально существующий каталог из пары (то есть приоритет у слоя{site_dir}/simai.data).getList($dir)— собирает список подпапок в обоих каталогах и объединяет.
.description.php как мета-описание секции
При сборе списка каталогов (внутренне) происходит сканирование подпапок и попытка подключить файл:
<секция>/.description.php
Если файл найден — он должен вернуть массив с метаданными. В текущем поведении реально используются как минимум:
NAME— имя секции (в интерфейсе дополнительно префиксуется как"[<CODE>] <NAME>")SORT— сортировкаPREVIEW— превью/картинка (используется для “картинок списка”)
Если .description.php отсутствует, секция всё равно попадёт в список с дефолтами:
CODE = <имя_папки>NAME = <имя_папки>SORT = 500
Также при сканировании игнорируются служебные элементы: ., .. и папка .config.
Методы и что они возвращают
-
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(это удобно, когда форма отдаёт и “сырой” вариант, и преобразованный)
- отбрасывает ключи, начинающиеся с
Мини-пример: получить список блоков и сохранить конфиг
<?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 — оверлеи “добавить/редактировать” на публичной части
Edit — это UI-хелпер, который возвращает HTML-оверлей (или false), чтобы на публичной странице появлялись кнопки:
- добавить элемент,
- редактировать элемент,
- удалить элемент,
- (отдельно) открыть настройки блока.
Кнопки появляются только при включённых режимах и (для операций с инфоблоком) при достаточных правах.
Режимы и права, от которых зависит отображение
-
development_mode = "Y"Включает оверлей “настройки блока” (иконка шестерёнки). -
edit_mode = "Y"и права на инфоблокCIBlock::GetPermission($idIblock) >= 'W'Включает оверлеи “добавить/редактировать/удалить” для элементов/разделов инфоблока. -
Дополнительный флаг:
iblock_public_editor = "Y"Переключает поведение кнопок на “правый модальный редактор” через скрипты в{site_dir}/simai.data/admin/.... Если флаг не включён — используются стандартные попап-формы Bitrix для добавления/редактирования (удаление остаётся через модалку).
Методы и сценарии использования
-
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>§ion=<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>
Практический пример: добавить оверлей редактирования в шаблон блока
Оверлей позиционируется абсолютно и рассчитан на то, что родительский контейнер “блока” — позиционируемый (обычно 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: корректные ссылки в результатах поиска
SIMAI\Main\Search\Iblock::OnSearchGetURL($arFields)
Назначение: переопределить URL результата поиска для сущностей инфоблока
OnSearchGetURL() — это утилитарный метод, который возвращает строку URL для результата поиска. По логике внутри он работает только для результатов модуля инфоблоков:
- если
$arFields['MODULE_ID'] !== 'iblock'— URL не меняется; - если
$arFields['ITEM_ID']не является “чистым” числом (проверка через сравнение сintval(...)) — URL не меняется.
Когда результат относится к инфоблоку и ITEM_ID — числовой, метод:
- подключает модуль инфоблоков;
- берёт инфоблок по
$arFields['PARAM2'](в обработчике это трактуется как ID инфоблока) и, если инфоблок найден, берёт из его настроек шаблонDETAIL_PAGE_URL; - дальше выполняет точечную подстановку (см. следующий подраздел) — только если в URL встречается специальный маркер.
Возврат всегда один: строка $url.
Особый случай: подстановка #UF_SECTION_URL# из пользовательского поля раздела
Ключевая “фишка” метода — поддержка пользовательского маркера #UF_SECTION_URL# внутри URL.
Если в обрабатываемом URL обнаруживается подстрока #UF_SECTION_URL#, метод делает так:
-
загружает элемент инфоблока по
$arFields['ITEM_ID']; -
берёт у элемента:
IBLOCK_IDIBLOCK_SECTION_ID(важно: используется основной раздел, а не список всех привязок);
-
загружает раздел по фильтру
IBLOCK_ID + ID, выбираяUF_*(то есть все пользовательские поля); -
берёт значение
$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 и что возвращает метод
Минимальный набор ключей, который реально используется в методе:
-
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элемента — то есть поведение предсказуемо только при корректно заполненном “основном разделе”.
Мини-пример применения (как должно выглядеть со стороны проекта)
Идея использования обычно такая:
- в настройках инфоблока
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: прикладные утилиты ядра
SIMAI\Main\Utility\Path
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\File
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\Text
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\Data
Data решает узкую, но частую задачу: привести регистр ключей массива (включая вложенные массивы).
setArrayKeyUp($array)— приводит ключи к верхнему регистру (CASE_UPPER) рекурсивно. Если на вход пришло неarray, возвращаетfalse.setArrayKeyLow($array)— приводит ключи к нижнему регистру (CASE_LOWER) рекурсивно. Если на вход пришло неarray, возвращаетfalse.
Внутри используется array_change_key_case() + рекурсивный array_map(), то есть меняются только ключи, значения остаются как есть.
SIMAI\Main\Utility\Menu
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\Html2Text
Html2Text — встроенная библиотека конвертации HTML в обычный текст. Она полезна в ситуациях, где HTML хранится как “богатое” содержимое, но вам нужен текст:
- для сниппетов,
- для превью/описаний,
- для простых текстовых уведомлений (например, email),
- для “текстовой версии” описаний.
Интерфейс “по делу” сводится к нескольким методам:
__construct($html = '', $options = [])— принимает HTML и опции.setHtml($html)/getHtml()— установить/получить исходный HTML.getText()— вернуть текст; при первом вызове запускается преобразование и результат кешируется в объекте.
В SF4 чаще удобно использовать обёртку Text::html2Text($html), если вам не нужно управлять опциями.
SIMAI\Main\Utility\VideoFrame
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: накопление и вывод ошибок
SIMAI\Main\Error
SIMAI\Main\Error — это простая подсистема для накопления ошибок в памяти текущего запроса и их вывода в виде HTML. Она удобна там, где вы хотите “собрать” ошибки в разных местах (компонент, обработчик, шаблон), а вывести одним блоком в нужной точке страницы.
Важно понимать контракт: ошибки не пишутся в файлы и не сохраняются в сессию — это singleton-хранилище, живущее до окончания выполнения PHP.
addError($code, $message, $value = false)
Метод добавляет (или перезаписывает) ошибку по коду. Внутри ошибки хранятся как ассоциативный массив:
- ключ —
$code, - значения —
messageиvalue.
Особенности поведения:
- если вы дважды вызываете
addError()с одним и тем же$code, предыдущиеmessage/valueбудут перезаписаны; $valueвыводится только если он “истинный” (условие видаif ($error["value"])), то есть0,"",nullиfalseне будут показаны как “Error value”.
isError($class = false)
isError() проверяет, накоплены ли ошибки, и одновременно выполняет вывод.
Поведение по шагам:
-
Если ошибок нет — возвращает
falseи ничего не выводит. -
Если ошибки есть:
- выбирает CSS-класс для оформления (если
$classне передан, берётся"c-red"), - собирает HTML-строку из
<p class="...">...</p>по каждой ошибке, - делает
echoэтой строки, - возвращает
true.
- выбирает CSS-класс для оформления (если
Формат вывода по каждой ошибке примерно такой:
Error code: <code>- (опционально)
Error value: <value> Error description: <message>
Практическое правило: из-за побочного эффекта echo этот метод лучше вызывать один раз в “общем” месте (например, в layout/шаблоне страницы), чтобы избежать повторного вывода.
Мини-пример использования в шаблоне
<?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 рабочих сценариев
Ниже — набор мини-сценариев, которые логично включить в документацию SF4 как отдельные короткие подразделы. Я пишу их так, чтобы их можно было почти без правок вставить в Markdown: каждый сценарий объясняет “когда применять”, “как устроено”, “пример кода”, “типичные ошибки”.
1) Прочитать настройку с нужного уровня (site → section → page → user)
В 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)
Шаблонные include-области SF4 (проектные) живут в слое {site_dir}/simai.data/template/area.
Шаги:
- Создайте файл области:
/ru/simai.data/template/area/sidebar/right/template.php
- Добавьте в нём разметку:
<?php
declare(strict_types=1);
?>
<div class="sidebar sidebar--right">
<!-- ваш контент -->
</div>
- Подключите область в проектном шаблоне:
<?php
declare(strict_types=1);
use SIMAI\Main\IO\IncludeArea;
IncludeArea::includeTemplateArea('sidebar/right');
Типичная ошибка: указать путь с начальным слешом (/sidebar/right) или забыть template.php внутри папки области.
3) Подключить комплект ассетов по имени и версии (и что будет при отсутствии версии)
В текущей реализации 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)
Когда нужно быстро получить “нормализованные данные” для поля/свойства (картинка, файл, связанный элемент/раздел), используйте 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) Включить публичное редактирование элемента: какие флаги/права должны быть
Публичные оверлеи редактирования (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#
Если в результате поиска 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-хранилище
Это базовый паттерн 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) Создать “секционную область” и переопределять её в дочерних разделах
Это лучший способ сделать “общую” область для ветки разделов и иметь возможность точечно переопределять её глубже.
- В корневом разделе ветки создайте:
/ru/catalog/section.banner.php
- В шаблоне вставьте:
<?php
declare(strict_types=1);
use SIMAI\Main\IO\IncludeArea;
IncludeArea::includeSectionArea('banner');
- Если нужно другой баннер в
/ru/catalog/sale/, создайте файл:
/ru/catalog/sale/section.banner.php
SF4 автоматически возьмёт ближайший к текущему пути.
9) Получить список блоков/вьюх с учётом кастомизаций в simai.data
Когда нужно показать выбор “какие блоки доступны”, используйте 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) Нормализовать путь и найти “существующую директорию” для виртуального URL
Если вам приходит путь, который может ссылаться на “виртуальную” страницу или несуществующий ресурс, используйте 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” и “внутренностей”
Что вы называете публичными точками расширения (и что можно использовать в решениях без страха обновлений)
В контексте 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 и т.п.), то такие решения обычно наименее чувствительны к обновлениям.
Что считать служебным/внутренним, где лучше не завязываться на детали реализации
“Внутренности” в 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, внутренние фолбэки).